Merge branch 'develop' into develop

This commit is contained in:
snipe 2024-07-19 19:00:56 +01:00 committed by GitHub
commit c50ab1af67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2618 changed files with 76780 additions and 131922 deletions

View file

@ -3018,6 +3018,151 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "koiakoia",
"name": "koiakoia",
"avatar_url": "https://avatars.githubusercontent.com/u/60405354?v=4",
"profile": "https://github.com/koiakoia",
"contributions": [
"code"
]
},
{
"login": "mustafa-online",
"name": "Mustafa Online",
"avatar_url": "https://avatars.githubusercontent.com/u/5323832?v=4",
"profile": "https://github.com/mustafa-online",
"contributions": [
"code"
]
},
{
"login": "franceslui",
"name": "franceslui",
"avatar_url": "https://avatars.githubusercontent.com/u/104601439?v=4",
"profile": "https://github.com/franceslui",
"contributions": [
"code"
]
},
{
"login": "Q4kK",
"name": "Q4kK",
"avatar_url": "https://avatars.githubusercontent.com/u/125313163?v=4",
"profile": "https://github.com/Q4kK",
"contributions": [
"code"
]
},
{
"login": "squintfox",
"name": "squintfox",
"avatar_url": "https://avatars.githubusercontent.com/u/55590532?v=4",
"profile": "https://github.com/squintfox",
"contributions": [
"code"
]
},
{
"login": "jeffclay",
"name": "Jeff Clay",
"avatar_url": "https://avatars.githubusercontent.com/u/1380084?v=4",
"profile": "https://github.com/jeffclay",
"contributions": [
"code"
]
},
{
"login": "PP-JN-RL",
"name": "Phil J R",
"avatar_url": "https://avatars.githubusercontent.com/u/52716446?v=4",
"profile": "https://github.com/PP-JN-RL",
"contributions": [
"code"
]
},
{
"login": "chandanchowdhury",
"name": "i_virus",
"avatar_url": "https://avatars.githubusercontent.com/u/1496725?v=4",
"profile": "https://www.corelight.com/",
"contributions": [
"code"
]
},
{
"login": "gitgrimbo",
"name": "Paul Grime",
"avatar_url": "https://avatars.githubusercontent.com/u/1020541?v=4",
"profile": "https://github.com/gitgrimbo",
"contributions": [
"code"
]
},
{
"login": "LeePorte",
"name": "Lee Porte",
"avatar_url": "https://avatars.githubusercontent.com/u/922815?v=4",
"profile": "https://leeporte.co.uk",
"contributions": [
"code"
]
},
{
"login": "bryanlopezinc",
"name": "BRYAN ",
"avatar_url": "https://avatars.githubusercontent.com/u/23613427?v=4",
"profile": "https://github.com/bryanlopezinc",
"contributions": [
"code",
"test"
]
},
{
"login": "U-H-T",
"name": "U-H-T",
"avatar_url": "https://avatars.githubusercontent.com/u/64061710?v=4",
"profile": "https://github.com/U-H-T",
"contributions": [
"code"
]
},
{
"login": "Tyree",
"name": "Matt Tyree",
"avatar_url": "https://avatars.githubusercontent.com/u/5395363?v=4",
"profile": "https://github.com/Tyree",
"contributions": [
"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"
]
} }
] ]
} }

166
.env.dev.docker Normal file
View file

@ -0,0 +1,166 @@
# --------------------------------------------
# REQUIRED: DB SETUP
# --------------------------------------------
MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit
MYSQL_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
# --------------------------------------------
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
APP_ENV=develop
APP_DEBUG=false
# please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
APP_LOCALE=en
MAX_RESULTS=500
# --------------------------------------------
# REQUIRED: UPLOADED FILE STORAGE SETTINGS
# --------------------------------------------
PRIVATE_FILESYSTEM_DISK=local
PUBLIC_FILESYSTEM_DISK=local_public
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=mariadb
DB_DATABASE=snipeit
DB_USERNAME=snipeit
DB_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS
# --------------------------------------------
DB_SSL=false
DB_SSL_IS_PAAS=false
DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null
DB_SSL_VERIFY_SERVER=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
MAIL_DRIVER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_AUTO_EMBED_METHOD='attachment'
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
# This should be gd or imagick
# --------------------------------------------
IMAGE_LIB=gd
# --------------------------------------------
# OPTIONAL: BACKUP SETTINGS
# --------------------------------------------
MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------
SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false
ENCRYPT=false
COOKIE_NAME=snipeit_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=40
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
# --------------------------------------------
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
CORS_ALLOWED_ORIGINS=null
ENABLE_HSTS=false
# --------------------------------------------
# OPTIONAL: CACHE SETTINGS
# --------------------------------------------
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
CACHE_PREFIX=snipeit
# --------------------------------------------
# OPTIONAL: REDIS SETTINGS
# --------------------------------------------
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
# --------------------------------------------
# OPTIONAL: MEMCACHED SETTINGS
# --------------------------------------------
MEMCACHED_HOST=null
MEMCACHED_PORT=null
# --------------------------------------------
# OPTIONAL: PUBLIC S3 Settings
# --------------------------------------------
PUBLIC_AWS_SECRET_ACCESS_KEY=null
PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
# OPTIONAL: PRIVATE S3 Settings
# --------------------------------------------
PRIVATE_AWS_ACCESS_KEY_ID=null
PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------
# OPTIONAL: AWS Settings
# --------------------------------------------
AWS_ACCESS_KEY_ID=null
AWS_SECRET_ACCESS_KEY=null
AWS_DEFAULT_REGION=null
# --------------------------------------------
# OPTIONAL: LOGIN THROTTLING
# --------------------------------------------
LOGIN_MAX_ATTEMPTS=5
LOGIN_LOCKOUT_DURATION=60
RESET_PASSWORD_LINK_EXPIRES=900
# --------------------------------------------
# OPTIONAL: MISC
# --------------------------------------------
LOG_CHANNEL=stderr
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC
APP_FORCE_TLS=false
GOOGLE_MAPS_API=
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600

View file

@ -1,18 +1,18 @@
# -------------------------------------------- # --------------------------------------------
# REQUIRED: DB SETUP # REQUIRED: DOCKER SPECIFIC SETTINGS
# -------------------------------------------- # --------------------------------------------
MYSQL_DATABASE=snipeit APP_VERSION=v6.4.1
MYSQL_USER=snipeit APP_PORT=8000
MYSQL_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
# -------------------------------------------- # --------------------------------------------
# REQUIRED: BASIC APP SETTINGS # REQUIRED: BASIC APP SETTINGS
# -------------------------------------------- # --------------------------------------------
APP_ENV=develop APP_ENV=production
APP_DEBUG=false APP_DEBUG=false
# please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here # Please regenerate the APP_KEY value by calling `docker compose run --rm snipeit php artisan key:generate --show`. Copy paste the value here
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ= 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
APP_TIMEZONE='UTC' APP_TIMEZONE='UTC'
APP_LOCALE=en APP_LOCALE=en
MAX_RESULTS=500 MAX_RESULTS=500
@ -27,10 +27,12 @@ PUBLIC_FILESYSTEM_DISK=local_public
# REQUIRED: DATABASE SETTINGS # REQUIRED: DATABASE SETTINGS
# -------------------------------------------- # --------------------------------------------
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=mariadb DB_HOST=db
DB_PORT='3306'
DB_DATABASE=snipeit DB_DATABASE=snipeit
DB_USERNAME=snipeit DB_USERNAME=snipeit
DB_PASSWORD=changeme1234 DB_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
DB_PREFIX=null DB_PREFIX=null
DB_DUMP_PATH='/usr/bin' DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4 DB_CHARSET=utf8mb4
@ -45,29 +47,35 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null DB_SSL_CIPHER=null
DB_SSL_VERIFY_SERVER=null
# -------------------------------------------- # --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS # REQUIRED: OUTGOING MAIL SERVER SETTINGS
# -------------------------------------------- # --------------------------------------------
MAIL_DRIVER=smtp MAIL_MAILER=smtp
MAIL_HOST=mailhog MAIL_HOST=mailhog
MAIL_PORT=1025 MAIL_PORT=1025
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_TLS_VERIFY_PEER=true
MAIL_FROM_ADDR=you@example.com MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT' MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT' MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_AUTO_EMBED_METHOD='attachment' MAIL_AUTO_EMBED_METHOD='attachment'
# --------------------------------------------
# REQUIRED: DATA PROTECTION
# --------------------------------------------
ALLOW_BACKUP_DELETE=false
ALLOW_DATA_PURGE=false
# -------------------------------------------- # --------------------------------------------
# REQUIRED: IMAGE LIBRARY # REQUIRED: IMAGE LIBRARY
# This should be gd or imagick # This should be gd or imagick
# -------------------------------------------- # --------------------------------------------
IMAGE_LIB=gd IMAGE_LIB=gd
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: BACKUP SETTINGS # OPTIONAL: BACKUP SETTINGS
# -------------------------------------------- # --------------------------------------------
@ -75,7 +83,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true BACKUP_ENV=true
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: SESSION SETTINGS # OPTIONAL: SESSION SETTINGS
# -------------------------------------------- # --------------------------------------------
@ -90,7 +97,7 @@ API_TOKEN_EXPIRATION_YEARS=40
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS # OPTIONAL: SECURITY HEADER SETTINGS
# -------------------------------------------- # --------------------------------------------
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1 APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.0.0.0/8
ALLOW_IFRAMING=false ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin REFERRER_POLICY=same-origin
ENABLE_CSP=false ENABLE_CSP=false
@ -108,7 +115,7 @@ CACHE_PREFIX=snipeit
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: REDIS SETTINGS # OPTIONAL: REDIS SETTINGS
# -------------------------------------------- # --------------------------------------------
REDIS_HOST=redis REDIS_HOST=null
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379

View file

@ -36,11 +36,12 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null DB_SSL_CIPHER=null
DB_SSL_VERIFY_SERVER=null
# -------------------------------------------- # --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS # REQUIRED: OUTGOING MAIL SERVER SETTINGS
# -------------------------------------------- # --------------------------------------------
MAIL_DRIVER="log" MAIL_MAILER="log"
# -------------------------------------------- # --------------------------------------------

View file

@ -42,21 +42,26 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null DB_SSL_CIPHER=null
DB_SSL_VERIFY_SERVER=null
# -------------------------------------------- # --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS # REQUIRED: OUTGOING MAIL SERVER SETTINGS
# -------------------------------------------- # --------------------------------------------
MAIL_DRIVER=smtp MAIL_MAILER=smtp
MAIL_HOST=email-smtp.us-west-2.amazonaws.com MAIL_HOST=email-smtp.us-west-2.amazonaws.com
MAIL_PORT=587 MAIL_PORT=587
MAIL_USERNAME=YOURUSERNAME MAIL_USERNAME=YOURUSERNAME
MAIL_PASSWORD=YOURPASSWORD MAIL_PASSWORD=YOURPASSWORD
MAIL_ENCRYPTION=null
MAIL_FROM_ADDR=you@example.com MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT' MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT' MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_AUTO_EMBED_METHOD='attachment' MAIL_AUTO_EMBED_METHOD='attachment'
MAIL_TLS_VERIFY_PEER=true
# MAIL_ENCRYPTION is no longer supported. SymfonyMailer will use tls if it's
# advertised, and won't if it's not. If you want to use your mail server's IP but it's failing
# because of certificate errors, set MAIL_TLS_VERIFY_PEER-true
# -------------------------------------------- # --------------------------------------------
# REQUIRED: IMAGE LIBRARY # REQUIRED: IMAGE LIBRARY
@ -95,6 +100,7 @@ APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin REFERRER_POLICY=same-origin
ENABLE_CSP=false ENABLE_CSP=false
ADDITIONAL_CSP_URLS=null
CORS_ALLOWED_ORIGINS=null CORS_ALLOWED_ORIGINS=null
ENABLE_HSTS=false ENABLE_HSTS=false
@ -177,6 +183,7 @@ 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
@ -191,4 +198,4 @@ ARGON_TIME=2
# OPTIONAL: SCIM # OPTIONAL: SCIM
# -------------------------------------------- # --------------------------------------------
SCIM_TRACE=false SCIM_TRACE=false
SCIM_STANDARDS_COMPLIANCE=false SCIM_STANDARDS_COMPLIANCE=false

View file

@ -22,7 +22,7 @@ DB_PASSWORD=null
# -------------------------------------------- # --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS # REQUIRED: OUTGOING MAIL SERVER SETTINGS
# -------------------------------------------- # --------------------------------------------
MAIL_DRIVER=log MAIL_MAILER=log
# -------------------------------------------- # --------------------------------------------

View file

@ -18,6 +18,6 @@ APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
LOGIN_MAX_ATTEMPTS=1000000 LOGIN_MAX_ATTEMPTS=1000000
LOGIN_LOCKOUT_DURATION=100000000 LOGIN_LOCKOUT_DURATION=100000000
MAIL_DRIVER=log MAIL_MAILER=log
MAIL_FROM_ADDR=you@example.com MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT MAIL_FROM_NAME=Snipe-IT

View file

@ -15,6 +15,6 @@ APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
LOGIN_MAX_ATTEMPTS=1000000 LOGIN_MAX_ATTEMPTS=1000000
LOGIN_LOCKOUT_DURATION=100000000 LOGIN_LOCKOUT_DURATION=100000000
MAIL_DRIVER=log MAIL_MAILER=log
MAIL_FROM_ADDR=you@example.com MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT MAIL_FROM_NAME=Snipe-IT

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.0 uses: codacy/codacy-analysis-cli-action@v4.4.5
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

View file

@ -12,7 +12,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Crowdin push - name: Crowdin push
uses: crowdin/github-action@v1 uses: crowdin/github-action@v2
with: with:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false

View file

@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image - name: Build and push 'snipe-it' image
id: docker_build id: docker_build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./Dockerfile.alpine file: ./Dockerfile.alpine

View file

@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image - name: Build and push 'snipe-it' image
id: docker_build id: docker_build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

View file

@ -25,9 +25,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
php-version: php-version:
- "7.4" - "8.1"
- "8.0" - "8.2"
- "8.1.1" - "8.3"
name: PHP ${{ matrix.php-version }} name: PHP ${{ matrix.php-version }}
@ -58,11 +58,17 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key - name: Setup Laravel
run: php artisan key:generate env:
DB_CONNECTION: mysql
- name: Directory Permissions DB_DATABASE: snipeit
run: chmod -R 777 storage bootstrap/cache DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit - name: Execute tests (Unit and Feature tests) via PHPUnit
env: env:

77
.github/workflows/tests-postgres.yml vendored Normal file
View file

@ -0,0 +1,77 @@
name: Tests in Postgres
on: workflow_dispatch
jobs:
tests:
runs-on: ubuntu-latest
services:
postgresql:
image: postgres
env:
POSTGRES_DB: snipeit
POSTGRES_USER: snipeit
POSTGRES_PASSWORD: password
ports:
- 5432:5432
options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
name: PHP ${{ matrix.php-version }}
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v4
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Copy .env
run: |
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Setup Laravel
env:
DB_CONNECTION: pgsql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
DB_USERNAME: snipeit
DB_PASSWORD: password
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: pgsql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
DB_USERNAME: snipeit
DB_PASSWORD: password
run: php artisan test --parallel

View file

@ -49,6 +49,9 @@ jobs:
- name: Generate key - name: Generate key
run: php artisan key:generate run: php artisan key:generate
- name: Setup Passport
run: php artisan passport:keys
- name: Directory Permissions - name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache run: chmod -R 777 storage bootstrap/cache

2
.nvmrc
View file

@ -1 +1 @@
v12.22.1 v18.16.0

View file

@ -3,8 +3,8 @@
"DOC2": "In other words, what you see locally are the requirements for your _current_ install", "DOC2": "In other words, what you see locally are the requirements for your _current_ install",
"DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version", "DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version",
"DOC4": "You should really just ignore it and run upgrade.php. Really", "DOC4": "You should really just ignore it and run upgrade.php. Really",
"php_min_version": "7.4.0", "php_min_version": "8.1.0",
"php_max_major_minor": "8.1", "php_max_major_minor": "8.3",
"php_max_wontwork": "8.2.0", "php_max_wontwork": "8.4.0",
"current_snipeit_version": "6.3" "current_snipeit_version": "7.0"
} }

View file

@ -1,444 +1,57 @@
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 -->
<!-- prettier-ignore-start --> | [<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") |
<!-- markdownlint-disable --> | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
<table> | [<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)") |
<tbody> | [<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") |
<tr> | [<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") |
<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://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.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://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="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://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="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://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="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://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/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/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/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/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") |
</tr> | [<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") |
<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://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="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://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="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://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/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/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://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://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://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://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/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://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") |
</tr> | [<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") |
<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://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/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://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/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://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="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://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="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://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="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://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://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/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") |
</tr> | [<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") |
<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/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/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/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/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/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/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/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/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/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/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/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/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/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") |
</tr> | [<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") |
<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/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/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/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://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/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://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/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="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/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="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/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/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/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") |
</tr> | [<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") |
<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/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") |
<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>
</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!

View file

@ -105,7 +105,7 @@ RUN \
&& ln -fs "/var/lib/snipeit/keys/ldap_client_tls.cert" "/var/www/html/storage/ldap_client_tls.cert" \ && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.cert" "/var/www/html/storage/ldap_client_tls.cert" \
&& ln -fs "/var/lib/snipeit/keys/ldap_client_tls.key" "/var/www/html/storage/ldap_client_tls.key" \ && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.key" "/var/www/html/storage/ldap_client_tls.key" \
&& chown docker "/var/lib/snipeit/keys/" \ && chown docker "/var/lib/snipeit/keys/" \
&& chown -h docker "/var/www/html/storage/" \ && chown -Rh docker "/var/www/html/storage/" \
&& chmod +x /var/www/html/artisan \ && chmod +x /var/www/html/artisan \
&& echo "Finished setting up application in /var/www/html" && echo "Finished setting up application in /var/www/html"

View file

@ -1,18 +1,18 @@
![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) ![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System ## Snipe-IT - Open Source Asset Management System
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc. This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
It is built on [Laravel 8](http://laravel.com). It is built on [Laravel 10](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
> [!TIP] > [!TIP]
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. > __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
----- -----

View file

@ -38,7 +38,7 @@
"description": "The maximum number of search results that can be returned at one time.", "description": "The maximum number of search results that can be returned at one time.",
"value": "500" "value": "500"
}, },
"MAIL_DRIVER": { "MAIL_MAILER": {
"description": "Mail driver - Generally SMTP on Heroku - https://snipe-it.readme.io/docs/configuration#required-outgoing-mail-settings", "description": "Mail driver - Generally SMTP on Heroku - https://snipe-it.readme.io/docs/configuration#required-outgoing-mail-settings",
"value": "smtp" "value": "smtp"
}, },
@ -58,9 +58,9 @@
"description": "SMTP Server Password", "description": "SMTP Server Password",
"value": "YOURPASSWORD" "value": "YOURPASSWORD"
}, },
"MAIL_ENCRYPTION": { "MAIL_TLS_VERIFY_PEER": {
"description": "Encryption protocol for email sending.", "description": "Ensure validity of TLS certificate on remote mail server",
"value": "null" "value": true
}, },
"MAIL_FROM_ADDR": { "MAIL_FROM_ADDR": {
"description": "Email from address", "description": "Email from address",

View file

@ -7,7 +7,7 @@ use Illuminate\Console\Command;
use App\Models\User; use App\Models\User;
use Laravel\Passport\TokenRepository; use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use DB; use Illuminate\Support\Facades\DB;
class GeneratePersonalAccessToken extends Command class GeneratePersonalAccessToken extends Command
{ {

View file

@ -9,7 +9,7 @@ use App\Models\Setting;
use App\Models\Ldap; use App\Models\Ldap;
use App\Models\User; use App\Models\User;
use App\Models\Location; use App\Models\Location;
use Log; use Illuminate\Support\Facades\Log;
class LdapSync extends Command class LdapSync extends Command
{ {
@ -251,6 +251,7 @@ 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';
} }
@ -298,7 +299,7 @@ class LdapSync extends Command
try { try {
$ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter')); $ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter'));
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup"); Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
// Hail-mary for Okta manager 'shortnames' - will only work if // Hail-mary for Okta manager 'shortnames' - will only work if
// Okta configuration is using full email-address-style usernames // Okta configuration is using full email-address-style usernames
$ldap_manager = [ $ldap_manager = [
@ -390,7 +391,7 @@ class LdapSync extends Command
$user->location_id = $location->id; $user->location_id = $location->id;
} }
} }
$location = null;
$user->ldap_import = 1; $user->ldap_import = 1;
$errors = ''; $errors = '';

View file

@ -5,7 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\Setting; use App\Models\Setting;
use Exception; use Exception;
use Crypt; use Illuminate\Support\Facades\Crypt;
/** /**
* Check if a given ip is in a network * Check if a given ip is in a network
@ -160,7 +160,7 @@ class LdapTroubleshooter extends Command
$output[] = "-x"; $output[] = "-x";
$output[] = "-b ".escapeshellarg($settings->ldap_basedn); $output[] = "-b ".escapeshellarg($settings->ldap_basedn);
$output[] = "-D ".escapeshellarg($settings->ldap_uname); $output[] = "-D ".escapeshellarg($settings->ldap_uname);
$output[] = "-w ".escapeshellarg(\Crypt::Decrypt($settings->ldap_pword)); $output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword));
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter)); $output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
if($settings->ldap_tls) { if($settings->ldap_tls) {
$this->line("# adding STARTTLS option"); $this->line("# adding STARTTLS option");

View file

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Events\UserMerged;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -51,7 +52,7 @@ class MergeUsersByUsername extends Command
$bad_users = User::where('username', '=', trim($parts[0])) $bad_users = User::where('username', '=', trim($parts[0]))
->whereNull('deleted_at') ->whereNull('deleted_at')
->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations') ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')
->get(); ->get();
@ -105,10 +106,26 @@ class MergeUsersByUsername extends Command
$managedLocation->save(); $managedLocation->save();
} }
foreach ($bad_user->uploads as $upload) {
$this->info('Updating upload log record '.$upload->id.' to user '.$user->id);
$upload->item_id = $user->id;
$upload->save();
}
foreach ($bad_user->acceptances as $acceptance) {
$this->info('Updating acceptance log record '.$acceptance->id.' to user '.$user->id);
$acceptance->item_id = $user->id;
$acceptance->save();
}
// Mark the user as deleted // Mark the user as deleted
$this->info('Marking the user as deleted'); $this->info('Marking the user as deleted');
$bad_user->deleted_at = Carbon::now()->timestamp; $bad_user->deleted_at = Carbon::now()->timestamp;
$bad_user->save(); $bad_user->save();
event(new UserMerged($bad_user, $user, null));
} }
} }
} }

View file

@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
class MoveUploadsToNewDisk extends Command class MoveUploadsToNewDisk extends Command
{ {
@ -74,7 +75,7 @@ class MoveUploadsToNewDisk extends Command
$new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename); $new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename);
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url); $this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
$this->error($e); $this->error($e);
} }
} }
@ -116,7 +117,7 @@ class MoveUploadsToNewDisk extends Command
$new_url = Storage::url($private_type . '/' . $filename, $filename); $new_url = Storage::url($private_type . '/' . $filename, $filename);
$this->info($type_count . '. PRIVATE: ' . $filename . ' was copied to ' . $new_url); $this->info($type_count . '. PRIVATE: ' . $filename . ' was copied to ' . $new_url);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
$this->error($e); $this->error($e);
} }
} }
@ -140,7 +141,7 @@ class MoveUploadsToNewDisk extends Command
unlink($filename); unlink($filename);
$public_delete_count++; $public_delete_count++;
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
$this->error($e); $this->error($e);
} }
} }
@ -153,7 +154,7 @@ class MoveUploadsToNewDisk extends Command
unlink($filename); unlink($filename);
$private_delete_count++; $private_delete_count++;
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
$this->error($e); $this->error($e);
} }
} }

View file

@ -5,6 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M')); ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
@ -59,7 +60,7 @@ class ObjectImportCommand extends Command
// This $logFile/useFiles() bit is currently broken, so commenting it out for now // This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile'); // $logFile = $this->option('logfile');
// \Log::useFiles($logFile); // Log::useFiles($logFile);
$this->comment('======= Importing Items from '.$filename.' ========='); $this->comment('======= Importing Items from '.$filename.' =========');
$importer->import(); $importer->import();
@ -112,10 +113,10 @@ class ObjectImportCommand extends Command
public function log($string, $level = 'info') public function log($string, $level = 'info')
{ {
if ($level === 'warning') { if ($level === 'warning') {
\Log::warning($string); Log::warning($string);
$this->comment($string); $this->comment($string);
} else { } else {
\Log::Info($string); Log::Info($string);
if ($this->option('verbose')) { if ($this->option('verbose')) {
$this->comment($string); $this->comment($string);
} }

View file

@ -5,7 +5,7 @@ namespace App\Console\Commands;
use App\Models\Asset; use App\Models\Asset;
use App\Models\CustomField; use App\Models\CustomField;
use Schema; use Schema;
use DB; use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class PaveIt extends Command class PaveIt extends Command

View file

@ -103,7 +103,7 @@ class RecryptFromMcrypt extends Command
$this->comment('INFO: No LDAP password found. Skipping... '); $this->comment('INFO: No LDAP password found. Skipping... ');
} else { } else {
$decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword); $decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword);
$settings->ldap_pword = \Crypt::encrypt($decrypted_ldap_pword); $settings->ldap_pword = Crypt::encrypt($decrypted_ldap_pword);
$settings->save(); $settings->save();
} }
/** @var CustomField[] $custom_fields */ /** @var CustomField[] $custom_fields */
@ -132,7 +132,7 @@ class RecryptFromMcrypt extends Command
// Try to decrypt the payload using the legacy app key // Try to decrypt the payload using the legacy app key
try { try {
$decrypted_field = $mcrypter->decrypt($asset->{$columnName}); $decrypted_field = $mcrypter->decrypt($asset->{$columnName});
$asset->{$columnName} = \Crypt::encrypt($decrypted_field); $asset->{$columnName} = Crypt::encrypt($decrypted_field);
$this->comment($decrypted_field); $this->comment($decrypted_field);
} catch (\Exception $e) { } catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage(); $errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();

View file

@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Setting; use App\Models\Setting;
use Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class RegenerateAssetTags extends Command class RegenerateAssetTags extends Command

View file

@ -6,8 +6,8 @@ use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\License; use App\Models\License;
use App\Models\User; use App\Models\User;
use Artisan; use Illuminate\Support\Facades\Artisan;
use DB; use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class RestoreDeletedUsers extends Command class RestoreDeletedUsers extends Command

View file

@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use ZipArchive; use ZipArchive;
use Illuminate\Support\Facades\Log;
class SQLStreamer { class SQLStreamer {
private $input; private $input;
@ -125,7 +126,7 @@ class SQLStreamer {
while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) { while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) {
$bytes_read += strlen($buffer); $bytes_read += strlen($buffer);
if ($this->reading_beginning_of_line) { if ($this->reading_beginning_of_line) {
// \Log::debug("Buffer is: '$buffer'"); // Log::debug("Buffer is: '$buffer'");
$cleaned_buffer = $this->parse_sql($buffer); $cleaned_buffer = $this->parse_sql($buffer);
if ($this->output) { if ($this->output) {
$bytes_written = fwrite($this->output, $cleaned_buffer); $bytes_written = fwrite($this->output, $cleaned_buffer);
@ -191,7 +192,7 @@ class RestoreFromBackup extends Command
{ {
$dir = getcwd(); $dir = getcwd();
if( $dir != base_path() ) { // usually only the case when running via webserver, not via command-line if( $dir != base_path() ) { // usually only the case when running via webserver, not via command-line
\Log::debug("Current working directory is: $dir, changing directory to: ".base_path()); Log::debug("Current working directory is: $dir, changing directory to: ".base_path());
chdir(base_path()); // TODO - is this *safe* to change on a running script?! chdir(base_path()); // TODO - is this *safe* to change on a running script?!
} }
// //
@ -297,7 +298,7 @@ class RestoreFromBackup extends Command
continue; continue;
} }
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') { if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
\Log::debug("Found a sql file!"); Log::debug("Found a sql file!");
$sqlfiles[] = $raw_path; $sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i; $sqlfile_indices[] = $i;
continue; continue;
@ -413,7 +414,7 @@ class RestoreFromBackup extends Command
$bytes_read = 0; $bytes_read = 0;
while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) { while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) {
$bytes_read += strlen($buffer); $bytes_read += strlen($buffer);
// \Log::debug("Buffer is: '$buffer'"); // Log::debug("Buffer is: '$buffer'");
$bytes_written = fwrite($pipes[0], $buffer); $bytes_written = fwrite($pipes[0], $buffer);
if ($bytes_written === false) { if ($bytes_written === false) {
@ -425,13 +426,13 @@ class RestoreFromBackup extends Command
$bytes_read = $sql_importer->line_aware_piping(); $bytes_read = $sql_importer->line_aware_piping();
} }
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::error("Error during restore!!!! ".$e->getMessage()); Log::error("Error during restore!!!! ".$e->getMessage());
// FIXME - put these back and/or put them in the right places?! // FIXME - put these back and/or put them in the right places?!
$err_out = fgets($pipes[1]); $err_out = fgets($pipes[1]);
$err_err = fgets($pipes[2]); $err_err = fgets($pipes[2]);
\Log::error("Error OUTPUT: ".$err_out); Log::error("Error OUTPUT: ".$err_out);
$this->info($err_out); $this->info($err_out);
\Log::error("Error ERROR : ".$err_err); Log::error("Error ERROR : ".$err_err);
$this->error($err_err); $this->error($err_err);
throw $e; throw $e;
} }

View file

@ -5,7 +5,7 @@ namespace App\Console\Commands;
use App\Models\Asset; use App\Models\Asset;
use App\Models\CustomField; use App\Models\CustomField;
use App\Models\Setting; use App\Models\Setting;
use Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Encryption\Encrypter; use Illuminate\Encryption\Encrypter;

View file

@ -0,0 +1,105 @@
<?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('archived', 0);
})
->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.');
}
}

View file

@ -43,7 +43,7 @@ class SendCurrentInventoryToUsers extends Command
$count = 0; $count = 0;
foreach ($users as $user) { foreach ($users as $user) {
if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0)) { if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0) || ($user->consumables->count() > 0)) {
$count++; $count++;
$user->notify((new CurrentInventory($user))); $user->notify((new CurrentInventory($user)));
} }

View file

@ -9,7 +9,6 @@ use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification; use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class SendExpectedCheckinAlerts extends Command class SendExpectedCheckinAlerts extends Command
{ {
@ -43,25 +42,31 @@ class SendExpectedCheckinAlerts extends Command
public function handle() public function handle()
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$whenNotify = Carbon::now(); $interval = $settings->audit_warning_days ?? 0;
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get(); $today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
$this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
foreach ($assets as $asset) { foreach ($assets as $asset) {
if ($asset->assigned && $asset->checkedOutToUser()) { if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email); $this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
$asset->assigned->notify((new ExpectedCheckinNotification($asset))); $asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
} }
} }
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate // Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item); return new AlertRecipient($item);
}); });
$this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets)); \Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
} }
} }
} }

View file

@ -3,13 +3,11 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Asset; use App\Models\Asset;
use App\Models\License; use App\Models\Recipients\AlertRecipient;
use App\Models\Recipients;
use App\Models\Setting; use App\Models\Setting;
use App\Notifications\ExpiringAssetsNotification;
use App\Notifications\SendUpcomingAuditNotification; use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class SendUpcomingAuditReport extends Command class SendUpcomingAuditReport extends Command
@ -46,39 +44,24 @@ class SendUpcomingAuditReport extends Command
public function handle() public function handle()
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$interval = $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) { $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate // Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new \App\Models\Recipients\AlertRecipient($item); return new AlertRecipient($item);
}); });
// Assets due for auditing $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$assets = Asset::whereNotNull('next_audit_date')
->DueOrOverdueForAudit($settings)
->orderBy('last_audit_date', 'asc')->get();
if ($assets->count() > 0) {
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Audit report sent to '.$settings->alert_email);
} else {
$this->info('No assets to be audited. No report sent.');
}
} elseif ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (! $settings->audit_warning_days) {
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
} elseif ($settings->alerts_enabled != 1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
} else {
$this->error('Something went wrong. :( ');
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
} }
} }
} }

View file

@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class SyncAssetCounters extends Command class SyncAssetCounters extends Command
{ {
@ -58,7 +59,7 @@ class SyncAssetCounters extends Command
$asset->save(); $asset->save();
$bar->advance(); $bar->advance();
\Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests'); Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests');
} }

View file

@ -15,7 +15,7 @@ class UserMerged
* *
* @return void * @return void
*/ */
public function __construct(User $from_user, User $to_user, User $admin) public function __construct(User $from_user, User $to_user, ?User $admin)
{ {
$this->merged_from = $from_user; $this->merged_from = $from_user;
$this->merged_to = $to_user; $this->merged_to = $to_user;

View file

@ -7,7 +7,7 @@ use App\Helpers\Helper;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException; use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
use Log; use Illuminate\Support\Facades\Log;
use Throwable; use Throwable;
use JsonException; use JsonException;
use Carbon\Exceptions\InvalidFormatException; use Carbon\Exceptions\InvalidFormatException;
@ -44,8 +44,8 @@ class Handler extends ExceptionHandler
public function report(Throwable $exception) public function report(Throwable $exception)
{ {
if ($this->shouldReport($exception)) { if ($this->shouldReport($exception)) {
if (class_exists(\Log::class)) { if (class_exists(Log::class)) {
\Log::error($exception); Log::error($exception);
} }
return parent::report($exception); return parent::report($exception);
} }

View file

@ -12,10 +12,12 @@ use App\Models\Depreciation;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use App\Models\License; use App\Models\License;
use Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Image;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Intervention\Image\ImageManagerStatic as Image;
use Illuminate\Support\Facades\Session;
class Helper class Helper
{ {
@ -62,6 +64,7 @@ class Helper
'nl' => 'nl-NL', // Dutch 'nl' => 'nl-NL', // Dutch
'no' => 'no-NO', // Norwegian '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
@ -412,7 +415,7 @@ class Helper
if ($index >= $total_colors) { if ($index >= $total_colors) {
\Log::info('Status label count is '.$index.' and exceeds the allowed count of 266.'); Log::info('Status label count is '.$index.' and exceeds the allowed count of 266.');
//patch fix for array key overflow (color count starts at 1, array starts at 0) //patch fix for array key overflow (color count starts at 1, array starts at 0)
$index = $index - $total_colors - 1; $index = $index - $total_colors - 1;
@ -875,12 +878,15 @@ class Helper
$permission_name = $permission[$x]['permission']; $permission_name = $permission[$x]['permission'];
if ($permission[$x]['display'] === true) { if ($permission[$x]['display'] === true) {
if ($selected_arr) {
if (is_array($selected_arr)) {
if (array_key_exists($permission_name, $selected_arr)) { if (array_key_exists($permission_name, $selected_arr)) {
$permissions_arr[$permission_name] = $selected_arr[$permission_name]; $permissions_arr[$permission_name] = $selected_arr[$permission_name];
} else { } else {
$permissions_arr[$permission_name] = '0'; $permissions_arr[$permission_name] = '0';
} }
} else { } else {
$permissions_arr[$permission_name] = '0'; $permissions_arr[$permission_name] = '0';
} }
@ -1012,7 +1018,7 @@ class Helper
try { try {
$tmp_date = new \Carbon($date); $tmp_date = new Carbon($date);
if ($type == 'datetime') { if ($type == 'datetime') {
$dt['datetime'] = $tmp_date->format('Y-m-d H:i:s'); $dt['datetime'] = $tmp_date->format('Y-m-d H:i:s');
@ -1029,7 +1035,7 @@ class Helper
return $dt['formatted']; return $dt['formatted'];
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::warning($e); Log::warning($e);
return $date.' (Invalid '.$type.' value.)'; return $date.' (Invalid '.$type.' value.)';
} }
@ -1342,7 +1348,7 @@ class Helper
public static function isDemoMode() { public static function isDemoMode() {
if (config('app.lock_passwords') === true) { if (config('app.lock_passwords') === true) {
return true; return true;
\Log::debug('app locked!'); Log::debug('app locked!');
} }
return false; return false;
@ -1435,7 +1441,6 @@ 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;
} }
} }
@ -1446,6 +1451,7 @@ 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
} }
@ -1453,10 +1459,53 @@ 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)
{
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
//return to index
if ($redirect_option == '0') {
switch ($table) {
case "Assets":
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
}
}
//return to thing being assigned
if ($redirect_option == '1') {
switch ($table) {
case "Assets":
return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success'));
}
}
//return to thing being assigned to
if ($redirect_option == '2') {
switch ($checkout_to_type) {
case 'user':
return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success'));
case 'location':
return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success'));
case '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'));
}
} }

View file

@ -3,10 +3,13 @@
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') public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
{ {
if ($disk == 'default') { if ($disk == 'default') {
$disk = config('filesystems.default'); $disk = config('filesystems.default');

View file

@ -7,10 +7,11 @@ 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 Redirect; use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
/** This controller handles all actions related to Accessories for /** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application. * the Snipe-IT Asset Management application.
@ -26,13 +27,10 @@ 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() public function index() : View
{ {
$this->authorize('index', Accessory::class); $this->authorize('index', Accessory::class);
return view('accessories/index'); return view('accessories/index');
} }
@ -40,10 +38,8 @@ 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() public function create() : View
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
$category_type = 'accessory'; $category_type = 'accessory';
@ -57,10 +53,8 @@ class AccessoriesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) public function store(ImageUploadRequest $request) : RedirectResponse
{ {
$this->authorize(Accessory::class); $this->authorize(Accessory::class);
@ -79,7 +73,7 @@ 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->user_id = Auth::user()->id; $accessory->user_id = auth()->id();
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes'); $accessory->notes = request('notes');
@ -99,15 +93,12 @@ 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) public function edit($accessoryId = null) : View | RedirectResponse
{ {
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');
} }
@ -121,9 +112,8 @@ 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) public function getClone($accessoryId = null) : View | RedirectResponse
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
@ -150,10 +140,8 @@ 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 Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(ImageUploadRequest $request, $accessoryId = null) public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
{ {
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) { if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
@ -204,10 +192,8 @@ class AccessoriesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId * @param int $accessoryId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($accessoryId) public function destroy($accessoryId) : RedirectResponse
{ {
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'));
@ -224,7 +210,7 @@ class AccessoriesController extends Controller
try { try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image); Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
@ -242,10 +228,8 @@ 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) public function show($accessoryID = null) : View | RedirectResponse
{ {
$accessory = Accessory::withCount('users as users_count')->find($accessoryID); $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$this->authorize('view', $accessory); $this->authorize('view', $accessory);

View file

@ -7,9 +7,13 @@ 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\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Accessory\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class AccessoriesFilesController extends Controller class AccessoriesFilesController extends Controller
{ {
@ -18,20 +22,17 @@ class AccessoriesFilesController extends Controller
* *
* @param UploadFileRequest $request * @param UploadFileRequest $request
* @param int $accessoryId * @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse * @author [A. Gianotto] [<snipe@snipe.net>]
* @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) public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse
{ {
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)) {
@ -68,10 +69,8 @@ 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) public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
{ {
$accessory = Accessory::find($accessoryId); $accessory = Accessory::find($accessoryId);
@ -85,7 +84,7 @@ class AccessoriesFilesController extends Controller
try { try {
Storage::delete('accessories/'.$log->filename); Storage::delete('accessories/'.$log->filename);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
@ -106,13 +105,11 @@ 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) public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{ {
\Log::debug('Private filesystem is: '.config('filesystems.default')); Log::debug('Private filesystem is: '.config('filesystems.default'));
$accessory = Accessory::find($accessoryId); $accessory = Accessory::find($accessoryId);
@ -129,8 +126,8 @@ class AccessoriesFilesController extends Controller
$file = 'private_uploads/accessories/'.$log->filename; $file = 'private_uploads/accessories/'.$log->filename;
if (Storage::missing($file)) { if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file); Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file)); Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain'); ->header('Content-Type', 'text/plain');

View file

@ -7,8 +7,9 @@ use App\Http\Controllers\Controller;
use App\Models\Accessory; use App\Models\Accessory;
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
{ {
@ -19,15 +20,10 @@ 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) public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
{ {
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { 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'));
} }
@ -44,15 +40,10 @@ class AccessoryCheckinController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param null $accessoryUserId * @param null $accessoryUserId
* @param string $backto * @param string $backto
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param int $accessoryId
*/ */
public function store(Request $request, $accessoryUserId = null, $backto = null) public function store(Request $request, $accessoryUserId = null, $backto = null) : RedirectResponse
{ {
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { 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'));
} }
@ -70,7 +61,7 @@ class AccessoryCheckinController extends Controller
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
$return_to = e($accessory_user->assigned_to); $return_to = e($accessory_user->assigned_to);
event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at)); event(new CheckoutableCheckedIn($accessory, User::find($return_to), auth()->user(), $request->input('note'), $checkin_at));
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success')); return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
} }

View file

@ -4,13 +4,14 @@ namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
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\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\Support\Facades\DB; use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Input; use \Illuminate\Http\RedirectResponse;
class AccessoryCheckoutController extends Controller class AccessoryCheckoutController extends Controller
{ {
@ -19,10 +20,8 @@ class AccessoryCheckoutController extends Controller
* *
* @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) public function create($id) : View | RedirectResponse
{ {
if ($accessory = Accessory::withCount('users as users_count')->find($id)) { if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
@ -58,46 +57,29 @@ class AccessoryCheckoutController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request * @param Request $request
* @param int $accessoryId * @param int $accessory
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(Request $request, $accessoryId) public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
{ {
// 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);
$accessory->assigned_to = $request->input('assigned_to');
$user = User::find($request->input('assigned_to'));
$accessory->checkout_qty = $request->input('checkout_qty', 1);
if (!$user = User::find($request->input('assigned_to'))) { for ($i = 0; $i < $accessory->checkout_qty; $i++) {
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist')); $accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->input('assigned_to'),
'note' => $request->input('note'),
]);
} }
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// 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()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success')); return redirect()->route('accessories.index')
->with('success', trans('admin/accessories/message.checkout.success'));
} }
} }

View file

@ -23,25 +23,23 @@ use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification; use App\Notifications\AcceptanceAssetDeclinedNotification;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; 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 phpDocumentor\Reflection\Types\Compound; use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
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() public function index() : View
{ {
$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'));
} }
@ -49,9 +47,8 @@ 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) public function create($id) : View | RedirectResponse
{ {
$acceptance = CheckoutAcceptance::find($id); $acceptance = CheckoutAcceptance::find($id);
@ -64,7 +61,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'));
} }
@ -80,9 +77,8 @@ class AcceptanceController extends Controller
* *
* @param Request $request * @param Request $request
* @param int $id * @param int $id
* @return Redirect
*/ */
public function store(Request $request, $id) public function store(Request $request, $id) : RedirectResponse
{ {
$acceptance = CheckoutAcceptance::find($id); $acceptance = CheckoutAcceptance::find($id);
@ -94,7 +90,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'));
} }
@ -223,6 +219,7 @@ class AcceptanceController extends Controller
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'eula' => $item->getEula(), 'eula' => $item->getEula(),
'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'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'), 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
@ -233,12 +230,12 @@ class AcceptanceController extends Controller
]; ];
if ($pdf_view_route!='') { if ($pdf_view_route!='') {
\Log::debug($pdf_filename.' is the filename, and the route was specified.'); Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data); $pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
} }
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename); $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); $acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
event(new CheckoutAccepted($acceptance)); event(new CheckoutAccepted($acceptance));
@ -306,10 +303,12 @@ class AcceptanceController extends Controller
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break; break;
} }
$data = [ $data = [
'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,
'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,
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
@ -318,12 +317,12 @@ class AcceptanceController extends Controller
]; ];
if ($pdf_view_route!='') { if ($pdf_view_route!='') {
\Log::debug($pdf_filename.' is the filename, and the route was specified.'); Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data); $pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
} }
$acceptance->decline($sig_filename); $acceptance->decline($sig_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data)); $acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
event(new CheckoutDeclined($acceptance)); event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined'); $return_msg = trans('admin/users/message.declined');

View file

@ -3,34 +3,44 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Actionlog; use Illuminate\Http\RedirectResponse;
use Response; use Illuminate\Support\Facades\Log;
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) public function displaySig($filename) : RedirectResponse | Response | bool
{ {
// 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
error_reporting(0); error_reporting(0);
$this->authorize('view', \App\Models\Asset::class); $disk = config('filesystems.default');
$file = config('app.private_uploads').'/signatures/'.$filename; switch (config("filesystems.disks.$disk.driver")) {
$filetype = Helper::checkUploadIsImage($file);
$contents = file_get_contents($file, false, stream_context_create(['http' => ['ignore_errors' => true]])); case 's3':
if ($contents === false) { $file = 'private_uploads/signatures/'.$filename;
\Log::warn('File '.$file.' not found'); return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5)));
return false; default:
} else { $this->authorize('view', \App\Models\Asset::class);
return Response::make($contents)->header('Content-Type', $filetype); $file = config('app.private_uploads').'/signatures/'.$filename;
$filetype = Helper::checkUploadIsImage($file);
$contents = file_get_contents($file, false, stream_context_create(['http' => ['ignore_errors' => true]]));
if ($contents === false) {
Log::warning('File '.$file.' not found');
return false;
} else {
return response()->make($contents)->header('Content-Type', $filetype);
}
} }
} }
public function getStoredEula($filename){
public function getStoredEula($filename) : Response | BinaryFileResponse
{
$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);
} }
} }

View file

@ -5,17 +5,20 @@ 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\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;
use App\Models\Company; use App\Models\Company;
use App\Models\User; use App\Models\User;
use Auth; use Illuminate\Support\Facades\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
class AccessoriesController extends Controller class AccessoriesController extends Controller
{ {
/** /**
@ -120,12 +123,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(ImageUploadRequest $request) public function store(StoreAccessoryRequest $request)
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
$accessory = new Accessory; $accessory = new Accessory;
@ -143,10 +146,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 show($id) public function show($id)
{ {
@ -160,10 +163,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)
{ {
@ -272,43 +275,31 @@ 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.
* *
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId * @param int $accessoryId
* @return Redirect * @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] [<snipe@snipe.net>]
*/ */
public function checkout(Request $request, $accessoryId) public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
{ {
// Check if the accessory exists
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
$accessory->assigned_to = $request->input('assigned_to');
$user = User::find($request->input('assigned_to'));
$accessory->checkout_qty = $request->input('checkout_qty', 1);
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
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->users()->attach($accessory->id, [
'accessory_id' => $accessory->id, 'accessory_id' => $accessory->id,
'created_at' => Carbon::now(), 'created_at' => Carbon::now(),
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to'), 'assigned_to' => $request->input('assigned_to'),
'note' => $request->get('note'), 'note' => $request->input('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')); // Set this value to be able to pass the qty through to the event
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
} }
@ -320,7 +311,7 @@ class AccessoriesController extends Controller
* @param Request $request * @param Request $request
* @param int $accessoryUserId * @param int $accessoryUserId
* @param string $backto * @param string $backto
* @return Redirect * @return \Illuminate\Http\RedirectResponse
* @internal param int $accessoryId * @internal param int $accessoryId
*/ */
public function checkin(Request $request, $accessoryUserId = null) public function checkin(Request $request, $accessoryUserId = null)

View file

@ -0,0 +1,200 @@
<?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\Asset;
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 AssetFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/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/hardware/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function list($assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that there are some uploads on this asset that can be listed
if ($asset->uploads->count() > 0) {
$files = array();
foreach ($asset->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/hardware/message.upload.success')));
}
// There are no files.
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
}
/**
* Check for permissions and display the file.
*
* @param int $assetId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assets/'.$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/hardware/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/hardware/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetId
* @param int $fileId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function destroy($assetId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assets';
// the asset is valid
if (isset($asset->id)) {
$this->authorize('update', $asset);
// 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/hardware/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/hardware/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
}

View file

@ -8,10 +8,9 @@ use App\Http\Transformers\AssetMaintenancesTransformer;
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 Auth; use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Input; use Illuminate\Http\JsonResponse;
/** /**
* This controller handles all actions related to Asset Maintenance for * This controller handles all actions related to Asset Maintenance for
@ -22,7 +21,6 @@ use Illuminate\Support\Facades\Input;
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.
* *
@ -30,9 +28,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
@ -120,9 +117,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// create a new model instance // create a new model instance
@ -148,9 +144,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
@ -186,9 +181,8 @@ 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) public function destroy($assetMaintenanceId) : JsonResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -212,9 +206,8 @@ 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) public function show($assetMaintenanceId) : JsonResponse
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);

View file

@ -4,14 +4,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\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\Http\JsonResponse;
/** /**
* This class controls all actions related to asset models for * This class controls all actions related to asset models for
@ -27,9 +29,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$allowed_columns = $allowed_columns =
@ -115,10 +116,9 @@ 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\ImageUploadRequest $request * @param \App\Http\Requests\StoreAssetModelRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) public function store(StoreAssetModelRequest $request) : JsonResponse
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
$assetmodel = new AssetModel; $assetmodel = new AssetModel;
@ -139,9 +139,8 @@ 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) public function show($id) : array
{ {
$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);
@ -155,9 +154,8 @@ 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) public function assets($id) : array
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$assets = Asset::where('model_id', '=', $id)->get(); $assets = Asset::where('model_id', '=', $id)->get();
@ -175,7 +173,7 @@ class AssetModelsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) public function update(StoreAssetModelRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id); $assetmodel = AssetModel::findOrFail($id);
@ -208,9 +206,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id); $assetmodel = AssetModel::findOrFail($id);
@ -224,7 +221,7 @@ class AssetModelsController extends Controller
try { try {
Storage::disk('public')->delete('assetmodels/'.$assetmodel->image); Storage::disk('public')->delete('assetmodels/'.$assetmodel->image);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::info($e); Log::info($e);
} }
} }
@ -240,7 +237,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');

View file

@ -25,19 +25,13 @@ 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 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 Input; use Illuminate\Support\Facades\Route;
use Paginator;
use Slack;
use Str;
use TCPDF;
use Validator;
use Route;
/** /**
@ -57,9 +51,8 @@ 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, $audit = null) public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
{ {
$filter_non_deprecable_assets = false; $filter_non_deprecable_assets = false;
@ -155,17 +148,44 @@ class AssetsController extends Controller
$assets->TextSearch($request->input('search')); $assets->TextSearch($request->input('search'));
} }
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) { /**
switch ($audit) { * Handle due and overdue audits and checkin dates
case 'due': */
$assets->DueOrOverdueForAudit($settings); switch ($action) {
break; case 'audits':
case 'overdue':
$assets->overdueForAudit($settings); switch ($upcoming_status) {
break; case 'due':
$assets->DueForAudit($settings);
break;
case 'overdue':
$assets->OverdueForAudit();
break;
case 'due-or-overdue':
$assets->DueOrOverdueForAudit($settings);
break;
}
break;
case 'checkins':
switch ($upcoming_status) {
case 'due':
$assets->DueForCheckin($settings);
break;
case 'overdue':
$assets->OverdueForCheckin();
break;
case 'due-or-overdue':
$assets->DueOrOverdueForCheckin($settings);
break;
}
break;
} }
}
/**
* End handling due and overdue audits and checkin dates
*/
// This is used by the sidenav, mostly // This is used by the sidenav, mostly
@ -389,9 +409,8 @@ 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) public function showByTag(Request $request, $tag) : JsonResponse | array
{ {
$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');
@ -429,7 +448,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) public function showBySerial(Request $request, $serial) : JsonResponse | array
{ {
$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');
@ -456,19 +475,20 @@ class AssetsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function show(Request $request, $id) public function show(Request $request, $id) : JsonResponse | array
{ {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed() if ($asset = Asset::with('assetstatus')
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) { ->with('assignedTo')->withTrashed()
->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) public function licenses(Request $request, $id) : array
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
@ -485,9 +505,8 @@ 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) public function selectlist(Request $request) : array
{ {
$assets = Asset::select([ $assets = Asset::select([
@ -612,7 +631,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) {
@ -634,9 +653,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function update(ImageUploadRequest $request, $id) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
@ -665,25 +683,26 @@ class AssetsController extends Controller
$model = AssetModel::find($asset->model_id); $model = AssetModel::find($asset->model_id);
// Update custom fields // Update custom fields
$problems_updating_encrypted_custom_fields = false;
if (($model) && (isset($model->fieldset))) { if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) { foreach ($model->fieldset->fields as $field) {
$field_val = $request->input($field->db_column, null); $field_val = $request->input($field->db_column, null);
if ($request->has($field->db_column)) { if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
$asset->{$field->db_column} = Crypt::encrypt($field_val);
}
}
if ($field->element == 'checkbox') { if ($field->element == 'checkbox') {
if(is_array($field_val)) { if(is_array($field_val)) {
$field_val = implode(',', $field_val); $field_val = implode(',', $field_val);
$asset->{$field->db_column} = $field_val;
} }
} }
else { if ($field->field_encrypted == '1') {
$asset->{$field->db_column} = $field_val; if (Gate::allows('admin')) {
$field_val = Crypt::encrypt($field_val);
} else {
$problems_updating_encrypted_custom_fields = true;
continue;
}
} }
$asset->{$field->db_column} = $field_val;
} }
} }
} }
@ -702,15 +721,18 @@ class AssetsController extends Controller
} }
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();
} }
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); if ($problems_updating_encrypted_custom_fields) {
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
} else {
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);
@ -726,9 +748,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Asset::class); $this->authorize('delete', Asset::class);
@ -755,9 +776,8 @@ 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) public function restore(Request $request, $assetId = null) : JsonResponse
{ {
if ($asset = Asset::withTrashed()->find($assetId)) { if ($asset = Asset::withTrashed()->find($assetId)) {
@ -785,9 +805,8 @@ 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) public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonResponse
{ {
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);
@ -801,9 +820,8 @@ 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) public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonResponse
{ {
$this->authorize('checkout', Asset::class); $this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
@ -820,7 +838,6 @@ class AssetsController extends Controller
'asset_tag' => $asset->asset_tag, 'asset_tag' => $asset->asset_tag,
]; ];
// This item is checked out to a location // This item is checked out to a location
if (request('checkout_to_type') == 'location') { if (request('checkout_to_type') == 'location') {
$target = Location::find(request('assigned_location')); $target = Location::find(request('assigned_location'));
@ -847,13 +864,10 @@ class AssetsController extends Controller
$asset->status_id = $request->get('status_id'); $asset->status_id = $request->get('status_id');
} }
if (! isset($target)) { if (! isset($target)) {
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.')); return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
} }
$checkout_at = request('checkout_at', date('Y-m-d H:i:s')); $checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null); $expected_checkin = request('expected_checkin', null);
$note = request('note', null); $note = request('note', null);
@ -869,9 +883,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')));
} }
@ -885,9 +897,8 @@ 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) public function checkin(Request $request, $asset_id) : JsonResponse
{ {
$asset = Asset::with('model')->findOrFail($asset_id); $asset = Asset::with('model')->findOrFail($asset_id);
$this->authorize('checkin', $asset); $this->authorize('checkin', $asset);
@ -952,7 +963,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),
@ -969,9 +980,8 @@ 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) public function checkinByTag(Request $request, $tag = null) : JsonResponse
{ {
$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'))) {
@ -995,31 +1005,44 @@ 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) public function audit(Request $request) : JsonResponse
{ {
$this->authorize('audit', Asset::class); $this->authorize('audit', Asset::class);
$rules = [
'asset_tag' => 'required',
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable',
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
// No tag passed - return an error
if (!$request->filled('asset_tag')) {
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag'=> '',
'error'=> trans('admin/hardware/message.no_tag'),
], trans('admin/hardware/message.no_tag')), 200);
}
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
if ($asset) { if ($asset) {
// We don't want to log this as a normal update, so let's bypass that
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
* the audit log entry we're creating through this controller.
*
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
* will bypass normal model-level validation that's usually handled at the observer )
*
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
*/
$asset->unsetEventDispatcher(); $asset->unsetEventDispatcher();
$asset->next_audit_date = $dt; $asset->next_audit_date = $dt;
@ -1035,8 +1058,12 @@ class AssetsController extends Controller
$asset->last_audit_date = date('Y-m-d H:i:s'); $asset->last_audit_date = date('Y-m-d H:i:s');
if ($asset->save()) { /**
$log = $asset->logAudit(request('note'), request('location_id')); * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
* We have to invoke this manually because of the unsetEventDispatcher() above.)
*/
if ($asset->isValid() && $asset->save()) {
$asset->logAudit(request('note'), request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [ return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag), 'asset_tag'=> e($asset->asset_tag),
@ -1044,9 +1071,23 @@ class AssetsController extends Controller
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
], trans('admin/hardware/message.audit.success'))); ], trans('admin/hardware/message.audit.success')));
} }
// Asset failed validation or was not able to be saved
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag'=> e($asset->asset_tag),
'error'=> $asset->getErrors()->first(),
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
} }
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
// No matching asset for the asset tag that was passed.
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag'=> e($request->input('asset_tag')),
'error'=> trans('admin/hardware/message.audit.error'),
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
} }
@ -1056,9 +1097,8 @@ 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) public function requestable(Request $request) : JsonResponse | array
{ {
$this->authorize('viewRequestable', Asset::class); $this->authorize('viewRequestable', Asset::class);

View file

@ -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) public function index(Request $request) : array
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
$allowed_columns = [ $allowed_columns = [
@ -115,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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
$category = new Category; $category = new Category;
@ -136,9 +136,8 @@ 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) public function show($id) : array
{ {
$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);
@ -156,7 +155,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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
$category = Category::findOrFail($id); $category = Category::findOrFail($id);
@ -164,7 +163,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, trans('admin/categories/message.update.cannot_change_category_type')) Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422)
); );
} }
$category->fill($request->all()); $category->fill($request->all());
@ -185,7 +184,7 @@ class CategoriesController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function destroy($id) public function destroy($id) : JsonResponse
{ {
$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);
@ -208,7 +207,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') public function selectlist(Request $request, $category_type = 'asset') : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
$categories = Category::select([ $categories = Category::select([

View file

@ -10,6 +10,7 @@ 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
{ {
@ -18,9 +19,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
@ -79,9 +79,8 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
$company = new Company; $company = new Company;
@ -102,9 +101,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@ -120,9 +118,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Company::class); $this->authorize('update', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@ -144,9 +141,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Company::class); $this->authorize('delete', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@ -169,7 +165,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
$companies = Company::select([ $companies = Company::select([

View file

@ -5,15 +5,17 @@ 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\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class ComponentsController extends Controller class ComponentsController extends Controller
{ {
@ -23,9 +25,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Component::class); $this->authorize('view', Component::class);
@ -115,9 +116,8 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Component::class); $this->authorize('create', Component::class);
$component = new Component; $component = new Component;
@ -136,9 +136,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', Component::class); $this->authorize('view', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@ -155,9 +154,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Component::class); $this->authorize('update', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@ -178,9 +176,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Component::class); $this->authorize('delete', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@ -197,9 +194,8 @@ 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) public function getAssets(Request $request, $id) : array
{ {
$this->authorize('view', \App\Models\Asset::class); $this->authorize('view', \App\Models\Asset::class);
@ -240,10 +236,8 @@ 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) public function checkout(Request $request, $componentId) : JsonResponse
{ {
// Check if the component exists // Check if the component exists
if (!$component = Component::find($componentId)) { if (!$component = Component::find($componentId)) {
@ -274,9 +268,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),
'user_id' => \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'),
]); ]);
@ -296,12 +290,10 @@ 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) public function checkin(Request $request, $component_asset_id) : JsonResponse
{ {
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))) {
@ -314,7 +306,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"
]); ]);
@ -331,21 +323,21 @@ class ComponentsController extends Controller
// actually checked out. // actually checked out.
$component_assets->assigned_qty = $qty_remaining_in_checkout; $component_assets->assigned_qty = $qty_remaining_in_checkout;
\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')));

View file

@ -5,6 +5,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\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;
@ -12,7 +13,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\Http\JsonResponse;
class ConsumablesController extends Controller class ConsumablesController extends Controller
{ {
@ -21,34 +23,13 @@ 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) public function index(Request $request) : array
{ {
$this->authorize('index', Consumable::class); $this->authorize('index', Consumable::class);
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations $consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
// Relations will be handled in query scopes a little further down. ->withCount('users as consumables_users_count');
$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')));
@ -90,15 +71,9 @@ 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';
$sort_override = $request->input('sort'); switch ($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;
@ -112,10 +87,30 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderCompany($order); $consumables = $consumables->OrderCompany($order);
break; break;
case 'supplier': case 'supplier':
$components = $consumables->OrderSupplier($order); $consumables = $consumables->OrderSupplier($order);
break; break;
default: default:
$consumables = $consumables->orderBy($column_sort, $order); // This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image'
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$consumables = $consumables->orderBy($sort, $order);
break; break;
} }
@ -131,9 +126,8 @@ 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(ImageUploadRequest $request) public function store(StoreConsumableRequest $request) : JsonResponse
{ {
$this->authorize('create', Consumable::class); $this->authorize('create', Consumable::class);
$consumable = new Consumable; $consumable = new Consumable;
@ -152,9 +146,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', Consumable::class); $this->authorize('view', Consumable::class);
$consumable = Consumable::with('users')->findOrFail($id); $consumable = Consumable::with('users')->findOrFail($id);
@ -169,9 +162,8 @@ 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(ImageUploadRequest $request, $id) public function update(StoreConsumableRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Consumable::class); $this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id); $consumable = Consumable::findOrFail($id);
@ -191,9 +183,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Consumable::class); $this->authorize('delete', Consumable::class);
$consumable = Consumable::findOrFail($id); $consumable = Consumable::findOrFail($id);
@ -210,9 +201,8 @@ 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) public function getDataView($consumableId) : array
{ {
$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');
@ -251,9 +241,8 @@ 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) public function checkout(Request $request, $id) : JsonResponse
{ {
// Check if the consumable exists // Check if the consumable exists
if (!$consumable = Consumable::with('users')->find($id)) { if (!$consumable = Consumable::with('users')->find($id)) {
@ -277,7 +266,6 @@ 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
@ -292,7 +280,7 @@ class ConsumablesController extends Controller
] ]
); );
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')));
@ -303,7 +291,7 @@ class ConsumablesController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) public function selectlist(Request $request) : array
{ {
$consumables = Consumable::select([ $consumables = Consumable::select([
'consumables.id', 'consumables.id',

View file

@ -8,7 +8,8 @@ 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 Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Http\JsonResponse;
class CustomFieldsController extends Controller class CustomFieldsController extends Controller
{ {
@ -20,7 +21,7 @@ class CustomFieldsController extends Controller
* @since [v3.0] * @since [v3.0]
* @return array * @return array
*/ */
public function index() public function index() : array
{ {
$this->authorize('index', CustomField::class); $this->authorize('index', CustomField::class);
$fields = CustomField::get(); $fields = CustomField::get();
@ -33,9 +34,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) { if ($field = CustomField::find($id)) {
@ -52,9 +52,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id); $field = CustomField::findOrFail($id);
@ -86,9 +85,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$field = new CustomField; $field = new CustomField;
@ -136,7 +134,7 @@ class CustomFieldsController extends Controller
return $fieldset->fields()->sync($fields); return $fieldset->fields()->sync($fields);
} }
public function associate(Request $request, $field_id) public function associate(Request $request, $field_id) : JsonResponse
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomFieldset::class);
@ -155,10 +153,9 @@ 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) public function disassociate(Request $request, $field_id) : JsonResponse
{ {
$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');
@ -179,9 +176,8 @@ class CustomFieldsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return Redirect
*/ */
public function destroy($field_id) public function destroy($field_id) : JsonResponse
{ {
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);

View file

@ -9,8 +9,7 @@ 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 Redirect; use Illuminate\Http\JsonResponse;
use View;
/** /**
* This controller handles all actions related to Custom Asset Fieldsets for * This controller handles all actions related to Custom Asset Fieldsets for
@ -30,9 +29,8 @@ 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() public function index() : array
{ {
$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();
@ -46,9 +44,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) { if ($fieldset = CustomFieldset::find($id)) {
@ -65,9 +62,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
@ -86,9 +82,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset; $fieldset = new CustomFieldset;
@ -118,9 +113,8 @@ class CustomFieldsetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return Redirect
*/ */
public function destroy($id) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', CustomField::class); $this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
@ -147,7 +141,7 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId * @param $fieldsetId
* @return string JSON * @return string JSON
*/ */
public function fields($id) public function fields($id) : array
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id); $set = CustomFieldset::findOrFail($id);
@ -164,14 +158,11 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId * @param $fieldsetId
* @return string JSON * @return string JSON
*/ */
public function fieldsWithDefaultValues($fieldsetId, $modelId) public function fieldsWithDefaultValues($fieldsetId, $modelId) : array
{ {
$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());
} }
} }

View file

@ -6,12 +6,11 @@ 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 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
{ {
@ -20,9 +19,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Department::class); $this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count']; $allowed_columns = ['id', 'name', 'image', 'users_count'];
@ -91,16 +89,15 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$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->user_id = Auth::user()->id; $department->user_id = auth()->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()) {
@ -116,13 +113,11 @@ 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) public function show($id) : array
{ {
$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);
} }
@ -133,9 +128,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Department::class); $this->authorize('update', Department::class);
$department = Department::findOrFail($id); $department = Department::findOrFail($id);
@ -156,9 +150,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$department = Department::findOrFail($id); $department = Department::findOrFail($id);
@ -180,7 +173,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');

View file

@ -7,6 +7,7 @@ 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
{ {
@ -15,9 +16,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Depreciation::class); $this->authorize('view', Depreciation::class);
$allowed_columns = ['id','name','months','depreciation_min','created_at']; $allowed_columns = ['id','name','months','depreciation_min','created_at'];
@ -48,9 +48,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('create', Depreciation::class); $this->authorize('create', Depreciation::class);
$depreciation = new Depreciation; $depreciation = new Depreciation;
@ -69,9 +68,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', Depreciation::class); $this->authorize('view', Depreciation::class);
$depreciation = Depreciation::findOrFail($id); $depreciation = Depreciation::findOrFail($id);
@ -86,9 +84,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', Depreciation::class); $this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id); $depreciation = Depreciation::findOrFail($id);
@ -107,9 +104,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$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);

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 Auth; use Illuminate\Http\JsonResponse;
class GroupsController extends Controller class GroupsController extends Controller
@ -17,9 +17,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
@ -56,19 +55,21 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = new Group; $group = new Group;
// Get all the available permissions
$permissions = config('permissions');
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$group->name = $request->input('name'); $group->name = $request->input('name');
$group->created_by = Auth::user()->id; $group->created_by = auth()->id();
$group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here $group->permissions = json_encode($request->input('permissions', $groupPermissions));
if ($group->save()) { if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', (new GroupsTransformer)->transformGroup($group), trans('admin/groups/message.success.create')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors()));
@ -80,13 +81,11 @@ 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) public function show($id) : array
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
return (new GroupsTransformer)->transformGroup($group); return (new GroupsTransformer)->transformGroup($group);
} }
@ -97,9 +96,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
@ -108,7 +106,7 @@ class GroupsController extends Controller
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here $group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
if ($group->save()) { if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', (new GroupsTransformer)->transformGroup($group), trans('admin/groups/message.success.update')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors()));
@ -120,9 +118,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);

View file

@ -9,22 +9,23 @@ use App\Http\Transformers\ImportsTransformer;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Company; use App\Models\Company;
use App\Models\Import; use App\Models\Import;
use Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage; 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\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() public function index() : JsonResponse | array
{ {
$this->authorize('import'); $this->authorize('import');
$imports = Import::latest()->get(); $imports = Import::latest()->get();
@ -36,9 +37,8 @@ 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() public function store() : JsonResponse
{ {
$this->authorize('import'); $this->authorize('import');
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
@ -151,18 +151,17 @@ 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) public function process(ItemImportRequest $request, $import_id) : JsonResponse
{ {
$this->authorize('import'); $this->authorize('import');
// Run a backup immediately before processing // Run a backup immediately before processing
if ($request->get('run-backup')) { if ($request->get('run-backup')) {
\Log::debug('Backup manually requested via importer'); Log::debug('Backup manually requested via importer');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]); Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
} else { } else {
\Log::debug('NO BACKUP requested via importer'); Log::debug('NO BACKUP requested via importer');
} }
$import = Import::find($import_id); $import = Import::find($import_id);
@ -211,9 +210,8 @@ 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) public function destroy($import_id) : JsonResponse
{ {
$this->authorize('create', Asset::class); $this->authorize('create', Asset::class);
@ -230,6 +228,8 @@ 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')));
} }
} }

View file

@ -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 Auth; use Illuminate\Http\JsonResponse;
class LabelsController extends Controller class LabelsController extends Controller
{ {
@ -16,9 +16,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Label::class); $this->authorize('view', Label::class);
@ -50,9 +49,8 @@ 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) public function show(string $labelName) : JsonResponse | array
{ {
$labelName = str_replace('/', '\\', $labelName); $labelName = str_replace('/', '\\', $labelName);
try { try {

View file

@ -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 Auth; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class LicenseSeatsController extends Controller class LicenseSeatsController extends Controller
@ -19,11 +19,10 @@ 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) public function index(Request $request, $licenseId) : JsonResponse | array
{ {
//
if ($license = License::find($licenseId)) { if ($license = License::find($licenseId)) {
$this->authorize('view', $license); $this->authorize('view', $license);
@ -64,11 +63,10 @@ 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) public function show($licenseId, $seatId) : JsonResponse | array
{ {
//
$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?
@ -89,19 +87,18 @@ 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) public function update(Request $request, $licenseId, $seatId) : JsonResponse | array
{ {
$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?
if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) { $license = $licenseSeat->license()->first();
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'));
} }
@ -110,7 +107,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->user_id = Auth::user()->id; $licenseSeat->user_id = auth()->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?

View file

@ -4,14 +4,12 @@ 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
{ {
@ -21,9 +19,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', License::class); $this->authorize('view', License::class);
@ -156,11 +153,9 @@ 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) public function store(Request $request) : JsonResponse
{ {
//
$this->authorize('create', License::class); $this->authorize('create', License::class);
$license = new License; $license = new License;
$license->fill($request->all()); $license->fill($request->all());
@ -177,9 +172,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', License::class); $this->authorize('view', License::class);
$license = License::withCount('freeSeats')->findOrFail($id); $license = License::withCount('freeSeats')->findOrFail($id);
@ -195,9 +189,8 @@ 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) public function update(Request $request, $id) : JsonResponse | array
{ {
// //
$this->authorize('update', License::class); $this->authorize('update', License::class);
@ -218,9 +211,8 @@ 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) public function destroy($id) : JsonResponse
{ {
// //
$license = License::findOrFail($id); $license = License::findOrFail($id);
@ -248,7 +240,7 @@ class LicensesController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) public function selectlist(Request $request) : array
{ {
$licenses = License::select([ $licenses = License::select([
'licenses.id', 'licenses.id',

View file

@ -11,6 +11,7 @@ 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
{ {
@ -21,7 +22,7 @@ class LocationsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Location::class); $this->authorize('view', Location::class);
$allowed_columns = [ $allowed_columns = [
@ -138,9 +139,8 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Location::class); $this->authorize('create', Location::class);
$location = new Location; $location = new Location;
@ -160,9 +160,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', Location::class); $this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children') $location = Location::with('parent', 'manager', 'children')
@ -199,9 +198,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Location::class); $this->authorize('update', Location::class);
$location = Location::findOrFail($id); $location = Location::findOrFail($id);
@ -230,9 +228,8 @@ 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 destroy($id) public function destroy($id) : JsonResponse
{ {
$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')
@ -244,7 +241,7 @@ class LocationsController extends Controller
if (! $location->isDeletable()) { if (! $location->isDeletable()) {
return response() return response()
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users'))); ->json(Helper::formatStandardApiResponse('error', null, trans('admin/locations/message.assoc_users')));
} }
$this->authorize('delete', $location); $this->authorize('delete', $location);
$location->delete(); $location->delete();
@ -280,7 +277,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) public function selectlist(Request $request) : array
{ {
// 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.
@ -325,7 +322,6 @@ 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);
} }
} }

View file

@ -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,7 +22,7 @@ class ManufacturersController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', Manufacturer::class); $this->authorize('view', Manufacturer::class);
$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']; $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'];
@ -83,9 +83,8 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Manufacturer::class); $this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer; $manufacturer = new Manufacturer;
@ -105,9 +104,8 @@ 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) public function show($id) : JsonResponse | array
{ {
$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);
@ -122,9 +120,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Manufacturer::class); $this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id); $manufacturer = Manufacturer::findOrFail($id);
@ -144,9 +141,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id); $manufacturer = Manufacturer::findOrFail($id);
@ -167,10 +163,9 @@ 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) public function restore($id) : JsonResponse
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
@ -186,7 +181,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->user_id = Auth::user()->id; $logaction->user_id = auth()->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);
@ -206,7 +201,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');

View file

@ -7,6 +7,8 @@ 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>]
@ -18,7 +20,7 @@ class PredefinedKitsController extends Controller
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$allowed_columns = ['id', 'name']; $allowed_columns = ['id', 'name'];
@ -47,9 +49,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('create', PredefinedKit::class); $this->authorize('create', PredefinedKit::class);
$kit = new PredefinedKit; $kit = new PredefinedKit;
@ -66,9 +67,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@ -81,9 +81,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@ -100,9 +99,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', PredefinedKit::class); $this->authorize('delete', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@ -123,7 +121,7 @@ class PredefinedKitsController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) public function selectlist(Request $request) : array
{ {
$kits = PredefinedKit::select([ $kits = PredefinedKit::select([
'id', 'id',
@ -145,7 +143,7 @@ class PredefinedKitsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function indexLicenses($kit_id) public function indexLicenses($kit_id) : array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -160,7 +158,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) public function storeLicense(Request $request, $kit_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@ -186,9 +184,8 @@ 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) public function updateLicense(Request $request, $kit_id, $license_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -205,9 +202,8 @@ 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) public function detachLicense($kit_id, $license_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -221,9 +217,8 @@ 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) public function indexModels($kit_id) : array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -236,9 +231,8 @@ 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) public function storeModel(Request $request, $kit_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@ -264,9 +258,8 @@ 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) public function updateModel(Request $request, $kit_id, $model_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -283,9 +276,8 @@ 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) public function detachModel($kit_id, $model_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -299,9 +291,8 @@ 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) public function indexConsumables($kit_id) : array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -314,9 +305,8 @@ 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) public function storeConsumable(Request $request, $kit_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@ -342,9 +332,8 @@ 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) public function updateConsumable(Request $request, $kit_id, $consumable_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -361,9 +350,8 @@ 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) public function detachConsumable($kit_id, $consumable_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -377,9 +365,8 @@ 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) public function indexAccessories($kit_id) : array
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -392,9 +379,8 @@ 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) public function storeAccessory(Request $request, $kit_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@ -420,9 +406,8 @@ 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) public function updateAccessory(Request $request, $kit_id, $accessory_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@ -439,9 +424,8 @@ 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) public function detachAccessory($kit_id, $accessory_id) : JsonResponse
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);

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 DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
class ProfileController extends Controller class ProfileController extends Controller
{ {
@ -42,12 +42,10 @@ 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() public function requestedAssets() : array
{ {
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get(); $checkoutRequests = CheckoutRequest::where('user_id', '=', auth()->id())->get();
$results = array(); $results = array();
$show_field = array(); $show_field = array();
@ -95,10 +93,9 @@ 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) { public function createApiToken(Request $request) : JsonResponse
{
if (!Gate::allows('self.api')) { if (!Gate::allows('self.api')) {
abort(403); abort(403);
@ -106,14 +103,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::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first(); $token = DB::table('oauth_access_tokens')->where('user_id', '=', auth()->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, 'Personal access token '.$accessTokenName.' created successfully')); return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, trans('account/general.personal_api_keys_success', ['key' => $accessTokenName])));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.'));
@ -125,17 +122,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 deleteApiToken($tokenId) { public function deleteApiToken($tokenId) : Response
{
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)) {
@ -154,16 +150,15 @@ 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(Request $request) { public function showApiTokens() : JsonResponse
{
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();

View file

@ -6,6 +6,7 @@ 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
{ {
@ -14,9 +15,8 @@ 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) public function index(Request $request) : JsonResponse | array
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -78,13 +78,14 @@ class ReportsController extends Controller
]; ];
$total = $actionlogs->count();
// 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') > $actionlogs->count()) ? $actionlogs->count() : 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'; $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';
$total = $actionlogs->count();
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get(); $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();

View file

@ -9,42 +9,38 @@ 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() public function ldaptest() : JsonResponse
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
if ($settings->ldap_enabled!='1') { if ($settings->ldap_enabled!='1') {
\Log::debug('LDAP is not enabled cannot test.'); Log::debug('LDAP is not enabled cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400); return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
} }
\Log::debug('Preparing to test LDAP connection'); Log::debug('Preparing to test LDAP connection');
$message = []; //where we collect together test messages $message = []; //where we collect together test messages
try { try {
$connection = Ldap::connectToLdap(); $connection = Ldap::connectToLdap();
try { try {
$message['bind'] = ['message' => 'Successfully bound to LDAP server.']; $message['bind'] = ['message' => 'Successfully bound to LDAP server.'];
\Log::debug('attempting to bind to LDAP for LDAP test'); Log::debug('attempting to bind to LDAP for LDAP test');
Ldap::bindAdminToLdap($connection); Ldap::bindAdminToLdap($connection);
$message['login'] = [ $message['login'] = [
'message' => 'Successfully connected to LDAP server.', 'message' => 'Successfully connected to LDAP server.',
@ -75,24 +71,24 @@ class SettingsController extends Controller
return response()->json($message, 200); return response()->json($message, 200);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Bind failed'); Log::debug('Bind failed');
\Log::debug("Exception was: ".$e->getMessage()); Log::debug("Exception was: ".$e->getMessage());
return response()->json(['message' => $e->getMessage()], 400); return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500); //return response()->json(['message' => $e->getMessage()], 500);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Connection failed but we cannot debug it any further on our end.'); Log::debug('Connection failed but we cannot debug it any further on our end.');
return response()->json(['message' => $e->getMessage()], 500); return response()->json(['message' => $e->getMessage()], 500);
} }
} }
public function ldaptestlogin(Request $request) public function ldaptestlogin(Request $request) : JsonResponse
{ {
if (Setting::getSettings()->ldap_enabled != '1') { if (Setting::getSettings()->ldap_enabled != '1') {
\Log::debug('LDAP is not enabled. Cannot test.'); Log::debug('LDAP is not enabled. Cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400); return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
} }
@ -104,39 +100,39 @@ class SettingsController extends Controller
$validator = Validator::make($request->all(), $rules); $validator = Validator::make($request->all(), $rules);
if ($validator->fails()) { if ($validator->fails()) {
\Log::debug('LDAP Validation test failed.'); Log::debug('LDAP Validation test failed.');
$validation_errors = implode(' ',$validator->errors()->all()); $validation_errors = implode(' ',$validator->errors()->all());
return response()->json(['message' => $validator->errors()->all()], 400); return response()->json(['message' => $validator->errors()->all()], 400);
} }
\Log::debug('Preparing to test LDAP login'); Log::debug('Preparing to test LDAP login');
try { try {
$connection = Ldap::connectToLdap(); $connection = Ldap::connectToLdap();
try { try {
Ldap::bindAdminToLdap($connection); Ldap::bindAdminToLdap($connection);
\Log::debug('Attempting to bind to LDAP for LDAP test'); Log::debug('Attempting to bind to LDAP for LDAP test');
try { try {
$ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password')); $ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password'));
if ($ldap_user) { if ($ldap_user) {
\Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'); Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200); return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
} }
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400); return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('LDAP login failed'); Log::debug('LDAP login failed');
return response()->json(['message' => $e->getMessage()], 400); return response()->json(['message' => $e->getMessage()], 400);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Bind failed'); Log::debug('Bind failed');
return response()->json(['message' => $e->getMessage()], 400); return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500); //return response()->json(['message' => $e->getMessage()], 500);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Connection failed'); Log::debug('Connection failed');
return response()->json(['message' => $e->getMessage()], 500); return response()->json(['message' => $e->getMessage()], 500);
} }
@ -148,9 +144,8 @@ 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() public function ajaxTestEmail() : JsonResponse
{ {
if (!config('app.lock_passwords')) { if (!config('app.lock_passwords')) {
try { try {
@ -170,9 +165,8 @@ 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() public function purgeBarcodes() : JsonResponse
{ {
$file_count = 0; $file_count = 0;
$files = Storage::disk('public')->files('barcodes'); $files = Storage::disk('public')->files('barcodes');
@ -181,19 +175,19 @@ class SettingsController extends Controller
$file_parts = explode('.', $file); $file_parts = explode('.', $file);
$extension = end($file_parts); $extension = end($file_parts);
\Log::debug($extension); Log::debug($extension);
// Only generated barcodes would have a .png file extension // Only generated barcodes would have a .png file extension
if ($extension == 'png') { if ($extension == 'png') {
\Log::debug('Deleting: '.$file); Log::debug('Deleting: '.$file);
try { try {
Storage::disk('public')->delete($file); Storage::disk('public')->delete($file);
\Log::debug('Deleting: '.$file); Log::debug('Deleting: '.$file);
$file_count++; $file_count++;
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
} }
@ -211,9 +205,8 @@ 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) public function showLoginAttempts(Request $request) : array
{ {
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at']; $allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at'];
@ -233,9 +226,9 @@ class SettingsController extends Controller
* Lists backup files * Lists backup files
* *
* @author [A. Gianotto] * @author [A. Gianotto]
* @return array | JsonResponse
*/ */
public function listBackups() { public function listBackups() : array
{
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$path = 'app/backups'; $path = 'app/backups';
$backup_files = Storage::files($path); $backup_files = Storage::files($path);
@ -276,9 +269,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) { public function downloadBackup($file) : JsonResponse | BinaryFileResponse
{
$path = storage_path('app/backups'); $path = storage_path('app/backups');
@ -296,9 +289,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() { public function downloadLatestBackup() : JsonResponse | BinaryFileResponse
{
$fileData = collect(); $fileData = collect();
foreach (Storage::files('app/backups') as $file) { foreach (Storage::files('app/backups') as $file) {

View file

@ -11,7 +11,7 @@ use App\Models\Asset;
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\Support\Arr; use Illuminate\Http\JsonResponse;
class StatuslabelsController extends Controller class StatuslabelsController extends Controller
{ {
@ -20,9 +20,8 @@ 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) public function index(Request $request) : array
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label']; $allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label'];
@ -72,9 +71,8 @@ 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) public function store(Request $request) : JsonResponse
{ {
$this->authorize('create', Statuslabel::class); $this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending', 'archived'); $request->except('deployable', 'pending', 'archived');
@ -108,9 +106,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@ -126,9 +123,8 @@ 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) public function update(Request $request, $id) : JsonResponse
{ {
$this->authorize('update', Statuslabel::class); $this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@ -163,9 +159,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$this->authorize('delete', Statuslabel::class); $this->authorize('delete', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@ -188,9 +183,8 @@ 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() public function getAssetCountByStatuslabel() : array
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::withCount('assets')->get(); $statuslabels = Statuslabel::withCount('assets')->get();
@ -215,9 +209,8 @@ 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() public function getAssetCountByMetaStatus() : array
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
@ -245,9 +238,8 @@ 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) public function assets(Request $request, $id) : array
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
@ -281,9 +273,8 @@ 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) public function checkIfDeployable($id) : string
{ {
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') { if ($statuslabel->getStatuslabelType() == 'deployable') {
@ -300,7 +291,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');

View file

@ -10,6 +10,7 @@ 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
{ {
@ -20,7 +21,7 @@ class SuppliersController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) public function index(Request $request): array
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$allowed_columns = [' $allowed_columns = ['
@ -114,9 +115,8 @@ 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) public function store(ImageUploadRequest $request) : JsonResponse
{ {
$this->authorize('create', Supplier::class); $this->authorize('create', Supplier::class);
$supplier = new Supplier; $supplier = new Supplier;
@ -136,9 +136,8 @@ 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) public function show($id) : array
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$supplier = Supplier::findOrFail($id); $supplier = Supplier::findOrFail($id);
@ -154,9 +153,8 @@ 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) public function update(ImageUploadRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', Supplier::class); $this->authorize('update', Supplier::class);
$supplier = Supplier::findOrFail($id); $supplier = Supplier::findOrFail($id);
@ -176,9 +174,8 @@ 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) public function destroy($id) : JsonResponse
{ {
$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);
@ -209,7 +206,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) public function selectlist(Request $request) : array
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');

View file

@ -13,15 +13,18 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\UsersTransformer; use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Company; use App\Models\Accessory;
use App\Models\Consumable;
use App\Models\License; use App\Models\License;
use App\Models\User; use App\Models\User;
use App\Notifications\CurrentInventory; use App\Notifications\CurrentInventory;
use Auth; use Illuminate\Support\Facades\Auth;
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\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use App\Http\Requests\DeleteUserRequest;
use Illuminate\Http\JsonResponse;
class UsersController extends Controller class UsersController extends Controller
{ {
@ -31,9 +34,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* *
* @return \Illuminate\Http\Response * @return array
*/ */
public function index(Request $request) public function index(Request $request) : array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
@ -75,10 +78,14 @@ class UsersController extends Controller
'users.autoassign_licenses', 'users.autoassign_licenses',
'users.website', 'users.website',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',) ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count');
if ($request->filled('search') != '') {
$users = $users->TextSearch($request->input('search'));
}
if ($request->filled('activated')) { if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated')); $users = $users->where('users.activated', '=', $request->input('activated'));
} }
@ -187,12 +194,23 @@ class UsersController extends Controller
$users->has('accessories', '=', $request->input('accessories_count')); $users->has('accessories', '=', $request->input('accessories_count'));
} }
if ($request->filled('manages_users_count')) {
$users->has('manages_users_count', '=', $request->input('manages_users_count'));
}
if ($request->filled('manages_locations_count')) {
$users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
}
if ($request->filled('autoassign_licenses')) { if ($request->filled('autoassign_licenses')) {
$users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses'));
} }
if ($request->filled('search')) {
$users = $users->TextSearch($request->input('search')); if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
} }
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@ -230,10 +248,6 @@ class UsersController extends Controller
'jobtitle', 'jobtitle',
'username', 'username',
'employee_num', 'employee_num',
'assets',
'accessories',
'consumables',
'licenses',
'groups', 'groups',
'activated', 'activated',
'created_at', 'created_at',
@ -244,6 +258,8 @@ class UsersController extends Controller
'licenses_count', 'licenses_count',
'consumables_count', 'consumables_count',
'accessories_count', 'accessories_count',
'manages_users_count',
'manages_locations_count',
'phone', 'phone',
'address', 'address',
'city', 'city',
@ -262,18 +278,11 @@ class UsersController extends Controller
'website', 'website',
]; ];
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
$users = $users->orderBy($sort, $order); $users = $users->orderBy($sort, $order);
break; break;
} }
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
$users = Company::scopeCompanyables($users);
// 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
@ -293,7 +302,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) public function selectlist(Request $request) : array
{ {
$users = User::select( $users = User::select(
[ [
@ -308,8 +317,6 @@ class UsersController extends Controller
] ]
)->where('show_in_list', '=', '1'); )->where('show_in_list', '=', '1');
$users = Company::scopeCompanyables($users);
if ($request->filled('search')) { if ($request->filled('search')) {
$users = $users->where(function ($query) use ($request) { $users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search')) $query->SimpleNameSearch($request->get('search'))
@ -351,21 +358,20 @@ 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 \Illuminate\Http\Response
*/ */
public function store(SaveUserRequest $request) public function store(SaveUserRequest $request) : JsonResponse
{ {
$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::user()->id; $user->created_by = auth()->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;
@ -398,14 +404,18 @@ class UsersController 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) public function show($id) : JsonResponse | array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id);
return (new UsersTransformer)->transformUser($user); if ($user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count')->find($id)) {
$this->authorize('view', $user);
return (new UsersTransformer)->transformUser($user);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
} }
@ -416,89 +426,88 @@ 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\Response
*/ */
public function update(SaveUserRequest $request, $id) public function update(SaveUserRequest $request, $id) : JsonResponse
{ {
$this->authorize('update', User::class); $this->authorize('update', User::class);
$user = User::findOrFail($id); if ($user = User::find($id)) {
/**
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
*
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
*
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) { $this->authorize('update', $user);
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
} /**
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
$user->fill($request->all()); 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.'));
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
}
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
// We need to use has() instead of filled()
// here because we need to overwrite permissions
// if someone needs to null them out
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
if (! Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']);
} }
$user->permissions = $permissions_array;
}
$user->fill($request->all());
// Update the location of any assets checked out to this user if ($user->id == $request->input('manager_id')) {
Asset::where('assigned_type', User::class) return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); }
if ($request->filled('password')) {
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); $user->password = bcrypt($request->input('password'));
}
if ($user->save()) {
// Sync group memberships: // We need to use has() instead of filled()
// This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5 // here because we need to overwrite permissions
// which changes the behavior of has vs filled. // if someone needs to null them out
// The $request->has method will now return true even if the input value is an empty string or null. if ($request->has('permissions')) {
// A new $request->filled method has was added that provides the previous behavior of the has method. $permissions_array = $request->input('permissions');
// Check if the request has groups passed and has a value // Strip out the individual superuser permission if the API user isn't a superadmin
if ($request->filled('groups')) { if (!auth()->user()->isSuperUser()) {
$validator = Validator::make($request->all(), [ unset($permissions_array['superuser']);
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()){
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
} }
$user->groups()->sync($request->input('groups'));
// The groups field has been passed but it is null, so we should blank it out $user->permissions = $permissions_array;
} elseif ($request->has('groups')) {
$user->groups()->sync([]);
} }
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); // Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
if ($user->save()) {
// 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())) {
$validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
// Sync the groups since the user is a superuser and the groups pass validation
$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('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'))));
} }
/** /**
@ -507,45 +516,35 @@ 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\Response
*/ */
public function destroy($id) public function destroy(DeleteUserRequest $request, $id) : JsonResponse
{ {
$this->authorize('delete', User::class); $this->authorize('delete', User::class);
$user = User::findOrFail($id);
$this->authorize('delete', $user);
if (($user->assets) && ($user->assets->count() > 0)) { if ($user = User::withTrashed()->find($id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
}
if (($user->licenses) && ($user->licenses->count() > 0)) { $this->authorize('delete', $user);
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->licenses->count().' license(s) associated with them and cannot be deleted.'));
}
if (($user->accessories) && ($user->accessories->count() > 0)) { if ($user->delete()) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->accessories->count().' accessories associated with them.'));
}
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) { // Remove the user's avatar if they have one
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->managedLocations()->count().' locations that they manage.')); if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
} try {
Storage::disk('public')->delete('avatars/' . $user->avatar);
if ($user->delete()) { } catch (\Exception $e) {
Log::debug($e);
// Remove the user's avatar if they have one }
if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
try {
Storage::disk('public')->delete('avatars/'.$user->avatar);
} catch (\Exception $e) {
\Log::debug($e);
} }
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
} }
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')));
} }
/** /**
@ -554,34 +553,41 @@ 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 assets(Request $request, $id) public function assets(Request $request, $id) : JsonResponse | array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
if ($user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id)) {
$this->authorize('view', $user);
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
// Filter on category ID // Filter on category ID
if ($request->filled('category_id')) { if ($request->filled('category_id')) {
$assets = $assets->InCategory($request->input('category_id')); $assets = $assets->InCategory($request->input('category_id'));
}
// Filter on model ID
if ($request->filled('model_id')) {
$model_ids = $request->input('model_id');
if (!is_array($model_ids)) {
$model_ids = array($model_ids);
} }
$assets = $assets->InModelList($model_ids);
// Filter on model ID
if ($request->filled('model_id')) {
$model_ids = $request->input('model_id');
if (!is_array($model_ids)) {
$model_ids = array($model_ids);
}
$assets = $assets->InModelList($model_ids);
}
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
} }
$assets = $assets->get(); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
} }
/** /**
@ -591,19 +597,26 @@ 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) public function emailAssetList(Request $request, $id) : JsonResponse
{
$user = User::findOrFail($id);
if (empty($user->email)) { {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error'))); $this->authorize('update', User::class);
if ($user = User::find($id)) {
$this->authorize('update', $user);
if (empty($user->email)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
}
$user->notify((new CurrentInventory($user)));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
} }
$user->notify((new CurrentInventory($user))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
} }
/** /**
@ -612,13 +625,13 @@ 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 consumables(Request $request, $id) public function consumables(Request $request, $id) : array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', Consumable::class); $this->authorize('view', Consumable::class);
$user = User::findOrFail($id); $user = User::findOrFail($id);
$this->authorize('view', $user);
$consumables = $user->consumables; $consumables = $user->consumables;
return (new ConsumablesTransformer)->transformConsumables($consumables, $consumables->count(), $request); return (new ConsumablesTransformer)->transformConsumables($consumables, $consumables->count(), $request);
} }
@ -629,12 +642,12 @@ 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 string JSON
*/ */
public function accessories($id) public function accessories($id) : array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$user = User::findOrFail($id); $user = User::findOrFail($id);
$this->authorize('view', $user);
$this->authorize('view', Accessory::class); $this->authorize('view', Accessory::class);
$accessories = $user->accessories; $accessories = $user->accessories;
@ -647,14 +660,14 @@ 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 string JSON
*/ */
public function licenses($id) public function licenses($id) : JsonResponse | array
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
if ($user = User::where('id', $id)->withTrashed()->first()) { if ($user = User::where('id', $id)->withTrashed()->first()) {
$this->authorize('update', $user);
$licenses = $user->licenses()->get(); $licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count()); return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
} }
@ -669,15 +682,15 @@ 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) public function postTwoFactorReset(Request $request) : JsonResponse
{ {
$this->authorize('update', User::class); $this->authorize('update', User::class);
if ($request->filled('id')) { if ($request->filled('id')) {
try { try {
$user = User::find($request->get('id')); $user = User::find($request->get('id'));
$this->authorize('update', $user);
$user->two_factor_secret = null; $user->two_factor_secret = null;
$user->two_factor_enrolled = 0; $user->two_factor_enrolled = 0;
$user->saveQuietly(); $user->saveQuietly();
@ -689,7 +702,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->user_id = Auth::user()->id; $logaction->user_id = auth()->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);
@ -708,9 +721,8 @@ 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 \Illuminate\Http\Response
*/ */
public function getCurrentUserInfo(Request $request) public function getCurrentUserInfo(Request $request) : array
{ {
return (new UsersTransformer)->transformUser($request->user()); return (new UsersTransformer)->transformUser($request->user());
} }
@ -721,12 +733,13 @@ 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 JsonResponse
*/ */
public function restore($userId = null) public function restore($userId) : JsonResponse
{ {
$this->authorize('delete', User::class);
if ($user = User::withTrashed()->find($userId)) { if ($user = User::withTrashed()->find($userId)) {
$this->authorize('delete', $user); $this->authorize('delete', $user);
if ($user->deleted_at == '') { if ($user->deleted_at == '') {
@ -739,14 +752,12 @@ 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->user_id = Auth::user()->id; $logaction->user_id = auth()->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);
} }
// Check validation to make sure we're not restoring a user with the same username as an existing user
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);

View file

@ -2,17 +2,14 @@
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 Auth; use Illuminate\Support\Facades\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Slack; use \Illuminate\Contracts\View\View;
use Str; use \Illuminate\Http\RedirectResponse;
use TCPDF;
use View;
/** /**
* This controller handles all actions related to Asset Maintenance for * This controller handles all actions related to Asset Maintenance for
@ -29,9 +26,8 @@ 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() private static function getInsufficientPermissionsRedirect(): RedirectResponse
{ {
return redirect()->route('maintenances.index') return redirect()->route('maintenances.index')
->with('error', trans('general.insufficient_permissions')); ->with('error', trans('general.insufficient_permissions'));
@ -46,9 +42,8 @@ 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() public function index() : View
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
return view('asset_maintenances/index'); return view('asset_maintenances/index');
@ -63,7 +58,7 @@ class AssetMaintenancesController extends Controller
* @since [v1.8] * @since [v1.8]
* @return mixed * @return mixed
*/ */
public function create() public function create() : View
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$asset = null; $asset = null;
@ -92,9 +87,8 @@ 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) public function store(Request $request) : RedirectResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// create a new model instance // create a new model instance
@ -144,9 +138,8 @@ 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) public function edit($assetMaintenanceId = null) : View | RedirectResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -162,14 +155,9 @@ class AssetMaintenancesController extends Controller
return static::getInsufficientPermissionsRedirect(); return static::getInsufficientPermissionsRedirect();
} }
// Prepare Improvement Type List // Prepare Improvement Type List
$assetMaintenanceType = [ $assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions();
'' => '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)
@ -183,11 +171,10 @@ 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) public function update(Request $request, $assetMaintenanceId = null) : View | RedirectResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -255,9 +242,8 @@ 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) public function destroy($assetMaintenanceId) : RedirectResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -284,9 +270,8 @@ 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) public function show($assetMaintenanceId) : View | RedirectResponse
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);

View file

@ -4,20 +4,21 @@ 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\Input;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Redirect; use Illuminate\Http\Request;
use Request; use Illuminate\Support\Facades\Storage;
use Storage; use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\JsonResponse; 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
@ -34,10 +35,8 @@ 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() public function index() : View
{ {
$this->authorize('index', AssetModel::class); $this->authorize('index', AssetModel::class);
@ -49,10 +48,8 @@ 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() public function create() : View
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
@ -67,16 +64,12 @@ 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 Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) public function store(StoreAssetModelRequest $request) : RedirectResponse
{ {
$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');
@ -86,7 +79,7 @@ class AssetModelsController extends Controller
$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->user_id = 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') != '') {
$model->fieldset_id = $request->input('fieldset_id'); $model->fieldset_id = $request->input('fieldset_id');
@ -94,7 +87,6 @@ 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'))){
@ -114,18 +106,14 @@ 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) public function edit($modelId = null) : View | RedirectResponse
{ {
$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';
$view = View::make('models/edit', compact('item', 'category_type')); return view('models/edit', compact('item', 'category_type'))->with('depreciation_list', Helper::depreciationList());
$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'));
@ -140,15 +128,14 @@ class AssetModelsController extends Controller
* @since [v1.0] * @since [v1.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @param int $modelId * @param int $modelId
* @return Redirect * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(ImageUploadRequest $request, $modelId = null) public function update(StoreAssetModelRequest $request, $modelId) : RedirectResponse
{ {
$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'));
} }
@ -174,9 +161,6 @@ class AssetModelsController extends Controller
} }
} }
if ($model->save()) { if ($model->save()) {
if ($model->wasChanged('eol')) { if ($model->wasChanged('eol')) {
if ($model->eol > 0) { if ($model->eol > 0) {
@ -201,10 +185,8 @@ 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 Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($modelId) public function destroy($modelId) : RedirectResponse
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);
// Check if the model exists // Check if the model exists
@ -221,7 +203,7 @@ class AssetModelsController extends Controller
try { try {
Storage::disk('public')->delete('models/'.$model->image); Storage::disk('public')->delete('models/'.$model->image);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::info($e); Log::info($e);
} }
} }
@ -238,10 +220,8 @@ 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 Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function getRestore($id) public function getRestore($id) : RedirectResponse
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
@ -256,7 +236,7 @@ class AssetModelsController extends Controller
$logaction->item_type = User::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->user_id = Auth::user()->id; $logaction->user_id = auth()->id();
$logaction->logaction('restore'); $logaction->logaction('restore');
@ -283,13 +263,11 @@ 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) public function show($modelId = null) : View | RedirectResponse
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$model = AssetModel::withTrashed()->withCount('assets')->find($modelId); $model = AssetModel::withTrashed()->find($modelId);
if (isset($model->id)) { if (isset($model->id)) {
return view('models/view', compact('model')); return view('models/view', compact('model'));
@ -304,9 +282,8 @@ 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) public function getClone($modelId = null) : View | RedirectResponse
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
// Check if the model exists // Check if the model exists
@ -332,9 +309,8 @@ 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) public function getCustomFields($modelId) : View
{ {
return view('models.custom_fields_form')->with('model', AssetModel::find($modelId)); return view('models.custom_fields_form')->with('model', AssetModel::find($modelId));
} }
@ -346,9 +322,8 @@ 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) public function postBulkEdit(Request $request) : View | RedirectResponse
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@ -390,9 +365,8 @@ 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) public function postBulkEditSave(Request $request) : RedirectResponse
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
$update_array = []; $update_array = [];
@ -430,9 +404,8 @@ 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 Redirect
*/ */
public function postBulkDelete(Request $request) public function postBulkDelete(Request $request) : RedirectResponse
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@ -471,9 +444,8 @@ 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) private function shouldAddDefaultValues(array $input) : bool
{ {
return ! empty($input['add_default_values']) return ! empty($input['add_default_values'])
&& ! empty($input['default_values']) && ! empty($input['default_values'])
@ -485,9 +457,8 @@ class AssetModelsController extends Controller
* *
* @param AssetModel $model * @param AssetModel $model
* @param array $defaultValues * @param array $defaultValues
* @return void
*/ */
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, array $defaultValues): bool
{ {
$data = array(); $data = array();
foreach ($defaultValues as $customFieldId => $defaultValue) { foreach ($defaultValues as $customFieldId => $defaultValue) {
@ -496,17 +467,17 @@ class AssetModelsController extends Controller
$data[$customField->db_column] = $defaultValue; $data[$customField->db_column] = $defaultValue;
} }
$fieldsets = $model->fieldset->validation_rules(); $allRules = $model->fieldset->validation_rules();
$rules = array(); $rules = array();
foreach ($fieldsets as $fieldset => $validation){ foreach ($allRules as $field => $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[$fieldset] = $validation; $rules[$field] = $validation;
} }
$validator = Validator::make($data, $rules); $validator = Validator::make($data, $rules);
@ -528,9 +499,8 @@ class AssetModelsController extends Controller
/** /**
* Removes all default values * Removes all default values
* *
* @return void
*/ */
private function removeCustomFieldsDefaultValues(AssetModel $model) private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void
{ {
$model->defaultValues()->detach(); $model->defaultValues()->detach();
} }

View file

@ -6,8 +6,11 @@ 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\Support\Facades\Response; use Illuminate\Http\RedirectResponse;
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
{ {
@ -16,12 +19,12 @@ class AssetModelsFilesController extends Controller
* *
* @param UploadFileRequest $request * @param UploadFileRequest $request
* @param int $modelId * @param int $modelId
* @return Redirect * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*@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) public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse
{ {
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'));
@ -38,7 +41,7 @@ class AssetModelsFilesController extends Controller
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file); $file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
$model->logUpload($file_name, e($request->get('notes'))); $model->logUpload($file_name, $request->get('notes'));
} }
return redirect()->back()->with('success', trans('general.file_upload_success')); return redirect()->back()->with('success', trans('general.file_upload_success'));
@ -54,10 +57,8 @@ 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) public function show($modelId = null, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{ {
$model = AssetModel::find($modelId); $model = AssetModel::find($modelId);
// the asset is valid // the asset is valid
@ -70,8 +71,6 @@ class AssetModelsFilesController extends Controller
} }
$file = 'private_uploads/assetmodels/'.$log->filename; $file = 'private_uploads/assetmodels/'.$log->filename;
\Log::debug('Checking for '.$file);
if (! Storage::exists($file)) { if (! Storage::exists($file)) {
return response('File '.$file.' not found on server', 404) return response('File '.$file.' not found on server', 404)
@ -103,10 +102,8 @@ 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) public function destroy($modelId = null, $fileId = null) : RedirectResponse
{ {
$model = AssetModel::find($modelId); $model = AssetModel::find($modelId);
$this->authorize('update', $model); $this->authorize('update', $model);

View file

@ -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\Redirect; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View; use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AssetCheckinController extends Controller class AssetCheckinController extends Controller
{ {
@ -25,11 +26,9 @@ 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) public function create($assetId, $backto = null) : View | RedirectResponse
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@ -39,7 +38,17 @@ class AssetCheckinController extends Controller
$this->authorize('checkin', $asset); $this->authorize('checkin', $asset);
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto); // This asset is already checked in, redirect
if (is_null($asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
} }
/** /**
@ -49,11 +58,9 @@ class AssetCheckinController extends Controller
* @param AssetCheckinRequest $request * @param AssetCheckinRequest $request
* @param int $assetId * @param int $assetId
* @param null $backto * @param null $backto
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0] * @since [v1.0]
*/ */
public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) : RedirectResponse
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@ -64,6 +71,11 @@ class AssetCheckinController extends Controller
if (is_null($target = $asset->assignedTo)) { if (is_null($target = $asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
} }
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
$this->authorize('checkin', $asset); $this->authorize('checkin', $asset);
if ($asset->assignedType() == Asset::USER) { if ($asset->assignedType() == Asset::USER) {
@ -86,7 +98,7 @@ class AssetCheckinController extends Controller
$asset->location_id = $asset->rtd_location_id; $asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) { if ($request->filled('location_id')) {
\Log::debug('NEW Location ID: '.$request->get('location_id')); Log::debug('NEW Location ID: '.$request->get('location_id'));
$asset->location_id = $request->get('location_id'); $asset->location_id = $request->get('location_id');
if ($request->get('update_default_location') == 0){ if ($request->get('update_default_location') == 0){
@ -116,15 +128,12 @@ class AssetCheckinController extends Controller
$acceptance->delete(); $acceptance->delete();
}); });
Session::put('redirect_option', $request->get('redirect_option'));
// Was the asset updated? // Was the asset updated?
if ($asset->save()) { if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
if ((isset($user)) && ($backto == 'user')) { event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success')); return Helper::getRedirectOption($asset, $assetId, 'Assets');
}
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkin.success'));
} }
// 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());

View file

@ -9,7 +9,9 @@ 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\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AssetCheckoutController extends Controller class AssetCheckoutController extends Controller
{ {
@ -22,9 +24,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function create($assetId) public function create($assetId) : View | RedirectResponse
{ {
// 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)))) {
@ -33,11 +35,17 @@ class AssetCheckoutController extends Controller
$this->authorize('checkout', $asset); $this->authorize('checkout', $asset);
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
if ($asset->availableForCheckout()) { if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset')) return view('hardware/checkout', compact('asset'))
->with('statusLabel_list', Helper::deployableStatusLabelList()); ->with('statusLabel_list', Helper::deployableStatusLabelList())
->with('table_name', 'Assets');
} }
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
} }
@ -46,11 +54,9 @@ 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 Redirect
* @since [v1.0] * @since [v1.0]
*/ */
public function store(AssetCheckoutRequest $request, $assetId) public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
{ {
try { try {
// Check if the asset exists // Check if the asset exists
@ -60,9 +66,14 @@ class AssetCheckoutController extends Controller
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
} }
$this->authorize('checkout', $asset); $this->authorize('checkout', $asset);
$admin = Auth::user();
$target = $this->determineCheckoutTarget($asset); if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
$admin = auth()->user();
$target = $this->determineCheckoutTarget();
$asset = $this->updateAssetLocation($asset, $target); $asset = $this->updateAssetLocation($asset, $target);
@ -97,11 +108,12 @@ class AssetCheckoutController extends Controller
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company')); return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
} }
} }
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
}
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'))) {
return Helper::getRedirectOption($request, $assetId, 'Assets');
}
// 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());
} catch (ModelNotFoundException $e) { } catch (ModelNotFoundException $e) {

View file

@ -7,8 +7,12 @@ 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\Support\Facades\Response; use \Illuminate\Http\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
{ {
@ -17,12 +21,12 @@ class AssetFilesController extends Controller
* *
* @param UploadFileRequest $request * @param UploadFileRequest $request
* @param int $assetId * @param int $assetId
* @return Redirect * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*@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) public function store(UploadFileRequest $request, $assetId = null) : RedirectResponse
{ {
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'));
@ -38,7 +42,7 @@ class AssetFilesController extends Controller
foreach ($request->file('file') as $file) { foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file); $file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes'))); $asset->logUpload($file_name, $request->get('notes'));
} }
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success')); return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
@ -54,10 +58,8 @@ 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) public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{ {
$asset = Asset::find($assetId); $asset = Asset::find($assetId);
// the asset is valid // the asset is valid
@ -70,7 +72,6 @@ class AssetFilesController extends Controller
} }
$file = 'private_uploads/assets/'.$log->filename; $file = 'private_uploads/assets/'.$log->filename;
\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;
@ -106,10 +107,8 @@ 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) public function destroy($assetId = null, $fileId = null) : RedirectResponse
{ {
$asset = Asset::find($assetId); $asset = Asset::find($assetId);
$this->authorize('update', $asset); $this->authorize('update', $asset);
@ -132,7 +131,6 @@ 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'));
} }
} }

View file

@ -6,7 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Manufacturer; use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel; use App\Models\AssetModel;
@ -20,14 +20,16 @@ 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\Support\Facades\Redirect; use Illuminate\Http\Response;
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
@ -55,10 +57,8 @@ 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) public function index(Request $request) : View
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
$company = Company::find($request->input('company_id')); $company = Company::find($request->input('company_id'));
@ -72,13 +72,12 @@ 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) public function create(Request $request) : View
{ {
$this->authorize('create', Asset::class); $this->authorize('create', Asset::class);
$view = View::make('hardware/edit') $view = view('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,9 +95,8 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return Redirect
*/ */
public function store(ImageUploadRequest $request) public function store(ImageUploadRequest $request) : RedirectResponse
{ {
$this->authorize(Asset::class); $this->authorize(Asset::class);
@ -198,7 +196,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;
@ -207,7 +205,6 @@ class AssetsController extends Controller
} }
if ($success) { if ($success) {
\Log::debug(e($asset->asset_tag));
return redirect()->route('hardware.index') return redirect()->route('hardware.index')
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $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)]));
@ -217,11 +214,6 @@ 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.
@ -229,9 +221,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function edit($assetId = null) public function edit($assetId = null) : View | RedirectResponse
{ {
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
@ -252,9 +244,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function show($assetId = null) public function show($assetId = null) : View | RedirectResponse
{ {
$asset = Asset::withTrashed()->find($assetId); $asset = Asset::withTrashed()->find($assetId);
$this->authorize('view', $asset); $this->authorize('view', $asset);
@ -292,11 +284,10 @@ 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) public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse
{ {
// Check if the asset exists // Check if the asset exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
@ -308,7 +299,8 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null); $asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null); $asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = $request->input('purchase_cost', null); $asset->purchase_cost = $request->input('purchase_cost', null);
$asset->purchase_date = $request->input('purchase_date', null); $asset->purchase_date = $request->input('purchase_date', null);
$asset->next_audit_date = $request->input('next_audit_date', null);
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) { if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
$asset->purchase_date = $request->input('purchase_date', null); $asset->purchase_date = $request->input('purchase_date', null);
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
@ -410,9 +402,8 @@ 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 Redirect
*/ */
public function destroy($assetId) public function destroy($assetId) : RedirectResponse
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@ -444,9 +435,8 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return Redirect
*/ */
public function getAssetBySerial(Request $request) public function getAssetBySerial(Request $request) : RedirectResponse
{ {
$topsearch = ($request->get('topsearch')=="true"); $topsearch = ($request->get('topsearch')=="true");
@ -462,9 +452,9 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function getAssetByTag(Request $request, $tag=null) public function getAssetByTag(Request $request, $tag=null) : RedirectResponse
{ {
$tag = $tag ? $tag : $request->get('assetTag'); $tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true'); $topsearch = ($request->get('topsearch') == 'true');
@ -484,9 +474,8 @@ 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) public function getQrCode($assetId = null) : Response | BinaryFileResponse | string | bool
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
@ -513,6 +502,7 @@ class AssetsController extends Controller
return 'That asset is invalid'; return 'That asset is invalid';
} }
return false;
} }
/** /**
@ -560,7 +550,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function getLabel($assetId = null) public function getLabel($assetId = null)
{ {
@ -584,7 +574,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function getClone($assetId = null) public function getClone($assetId = null)
{ {
@ -613,7 +603,7 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View * @return \Illuminate\Contracts\View\View
*/ */
public function getImportHistory() public function getImportHistory()
{ {
@ -635,7 +625,7 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.3] * @since [v3.3]
* @return View * @return \Illuminate\Contracts\View\View
*/ */
public function postImportHistory(Request $request) public function postImportHistory(Request $request)
{ {
@ -729,8 +719,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,
'user_id' => Auth::user()->id, 'user_id' => auth()->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'],
@ -757,8 +747,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,
'user_id' => Auth::user()->id, 'user_id' => auth()->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',
@ -795,7 +785,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 View * @return \Illuminate\Contracts\View\View
*/ */
public function getRestore($assetId = null) public function getRestore($assetId = null)
{ {
@ -854,15 +844,15 @@ class AssetsController extends Controller
return view('hardware/audit-due'); return view('hardware/audit-due');
} }
public function overdueForAudit() public function dueForCheckin()
{ {
$this->authorize('audit', Asset::class); $this->authorize('checkin', Asset::class);
return view('hardware/audit-overdue'); return view('hardware/checkin-due');
} }
public function auditStore(Request $request, $id) public function auditStore(UploadFileRequest $request, $id)
{ {
$this->authorize('audit', Asset::class); $this->authorize('audit', Asset::class);
@ -879,7 +869,21 @@ class AssetsController extends Controller
$asset = Asset::findOrFail($id); $asset = Asset::findOrFail($id);
// We don't want to log this as a normal update, so let's bypass that /**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
* the audit log entry we're creating through this controller.
*
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
* will bypass normal model-level validation that's usually handled at the observer )
*
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
*/
$asset->unsetEventDispatcher(); $asset->unsetEventDispatcher();
$asset->next_audit_date = $request->input('next_audit_date'); $asset->next_audit_date = $request->input('next_audit_date');
@ -888,29 +892,27 @@ class AssetsController extends Controller
// Check to see if they checked the box to update the physical location, // Check to see if they checked the box to update the physical location,
// not just note it in the audit notes // not just note it in the audit notes
if ($request->input('update_location') == '1') { if ($request->input('update_location') == '1') {
Log::debug('update location in audit');
$asset->location_id = $request->input('location_id'); $asset->location_id = $request->input('location_id');
} }
/**
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
* We have to invoke this manually because of the unsetEventDispatcher() above.)
*/
if ($asset->isValid() && $asset->save()) {
if ($asset->save()) { $file_name = null;
$file_name = ''; // Create the image (if one was chosen.)
// Upload an image, if attached
if ($request->hasFile('image')) { if ($request->hasFile('image')) {
$path = 'private_uploads/audits'; $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
if (! Storage::exists($path)) {
Storage::makeDirectory($path, 775);
}
$upload = $image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = 'audit-'.str_random(18).'.'.$ext;
Storage::putFileAs($path, $upload, $file_name);
} }
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success')); return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
} }
return redirect()->back()->withInput()->withErrors($asset->getErrors());
} }
public function getRequestedIndex($user_id = null) public function getRequestedIndex($user_id = null)

View file

@ -2,7 +2,6 @@
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;
@ -12,12 +11,15 @@ use App\Models\Statuslabel;
use App\Models\Setting; use App\Models\Setting;
use App\View\Label; use App\View\Label;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
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
{ {
@ -34,12 +36,10 @@ 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) public function edit(Request $request) : View | RedirectResponse
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
@ -189,11 +189,10 @@ class BulkAssetsController extends Controller
* Save bulk edits * Save bulk edits
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @return Redirect
* @internal param array $assets * @internal param array $assets
* @since [v2.0] * @since [v2.0]
*/ */
public function update(Request $request) public function update(Request $request) : RedirectResponse
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$has_errors = 0; $has_errors = 0;
@ -214,7 +213,7 @@ class BulkAssetsController extends Controller
} }
$assets = Asset::whereIn('id', array_keys($request->input('ids')))->get(); $assets = Asset::whereIn('id', $request->input('ids'))->get();
@ -379,28 +378,30 @@ class BulkAssetsController extends Controller
foreach ($asset->model->fieldset->fields as $field) { foreach ($asset->model->fieldset->fields as $field) {
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) { if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); if (Gate::allows('admin')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
/*
* Check if the decrypted existing value is different from one we just submitted
* and if not, pull it out of the object since it shouldn't really be updating at all.
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
* different times will have different values, so it will *look* like it was updated
* but it wasn't.
*/
if ($decrypted_old != $this->update_array[$field->db_column]) {
$asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
} else {
/* /*
* Remove the encrypted custom field from the update_array, since nothing changed * Check if the decrypted existing value is different from one we just submitted
* and if not, pull it out of the object since it shouldn't really be updating at all.
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
* different times will have different values, so it will *look* like it was updated
* but it wasn't.
*/ */
unset($this->update_array[$field->db_column]); if ($decrypted_old != $this->update_array[$field->db_column]) {
unset($asset->{$field->db_column}); $asset->{$field->db_column} = Crypt::encrypt($this->update_array[$field->db_column]);
} } else {
/*
* Remove the encrypted custom field from the update_array, since nothing changed
*/
unset($this->update_array[$field->db_column]);
unset($asset->{$field->db_column});
}
/* /*
* These custom fields aren't encrypted, just carry on as usual * These custom fields aren't encrypted, just carry on as usual
*/ */
}
} else { } else {
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) { if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
@ -452,9 +453,8 @@ 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) protected function conditionallyAddItem($field) : BulkAssetsController
{ {
if (request()->filled($field)) { if (request()->filled($field)) {
$this->update_array[$field] = request()->input($field); $this->update_array[$field] = request()->input($field);
@ -468,12 +468,10 @@ 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) public function destroy(Request $request) : RedirectResponse
{ {
$this->authorize('delete', Asset::class); $this->authorize('delete', Asset::class);
@ -502,27 +500,23 @@ class BulkAssetsController extends Controller
/** /**
* Show Bulk Checkout Page * Show Bulk Checkout Page
* @return View View to checkout multiple assets
*/ */
public function showCheckout() public function showCheckout() : View
{ {
$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) public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{ {
$this->authorize('checkout', Asset::class); $this->authorize('checkout', Asset::class);
try { try {
$admin = Auth::user(); $admin = auth()->user();
$target = $this->determineCheckoutTarget(); $target = $this->determineCheckoutTarget();
@ -581,17 +575,19 @@ class BulkAssetsController extends Controller
} }
} }
public function restore(Request $request) { public function restore(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$assetIds = $request->get('ids'); $assetIds = $request->get('ids');
if (empty($assetIds)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated')); if (empty($assetIds)) {
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'));
} }
} }
} }

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ForgotPasswordController extends Controller class ForgotPasswordController extends Controller
{ {
/* /*
@ -79,16 +79,16 @@ class ForgotPasswordController extends Controller
) )
); );
} catch(\Exception $e) { } catch(\Exception $e) {
\Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e ); Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e );
} }
// Prevent timing attack to enumerate users. // Prevent timing attack to enumerate users.
usleep(500000 + random_int(0, 1500000)); usleep(500000 + random_int(0, 1500000));
if ($response === \Password::RESET_LINK_SENT) { if ($response === \Password::RESET_LINK_SENT) {
\Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent'); Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
} else { } else {
\Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive'); Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive');
} }
/** /**

View file

@ -16,7 +16,7 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Log; use Illuminate\Support\Facades\Log;
use Redirect; use Redirect;
/** /**
@ -122,12 +122,12 @@ class LoginController extends Controller
Auth::login($user); Auth::login($user);
} else { } else {
$username = $saml->getUsername(); $username = $saml->getUsername();
\Log::debug("SAML user '$username' could not be found in database."); Log::debug("SAML user '$username' could not be found in database.");
$request->session()->flash('error', trans('auth/message.signin.error')); $request->session()->flash('error', trans('auth/message.signin.error'));
$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();
} }
@ -137,7 +137,7 @@ class LoginController extends Controller
$s->save(); $s->save();
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('There was an error authenticating the SAML user: '.$e->getMessage()); Log::debug('There was an error authenticating the SAML user: '.$e->getMessage());
throw $e; throw $e;
} }
@ -146,7 +146,7 @@ class LoginController extends Controller
// Better logging // Better logging
if (empty($samlData)) { if (empty($samlData)) {
\Log::debug("SAML page requested, but samlData seems empty."); Log::debug("SAML page requested, but samlData seems empty.");
} }
} }
@ -261,19 +261,19 @@ class LoginController extends Controller
/** /**
* Account sign in form processing. * Account sign in form processing.
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function login(Request $request) public function login(Request $request)
{ {
//If the environment is set to ALWAYS require SAML, return access denied //If the environment is set to ALWAYS require SAML, return access denied
if (config('app.require_saml')) { if (config('app.require_saml')) {
\Log::debug('require SAML is enabled in the .env - return a 403'); Log::debug('require SAML is enabled in the .env - return a 403');
return view('errors.403'); return view('errors.403');
} }
if (Setting::getSettings()->login_common_disabled == '1') { if (Setting::getSettings()->login_common_disabled == '1') {
\Log::debug('login_common_disabled is set to 1 - return a 403'); Log::debug('login_common_disabled is set to 1 - return a 403');
return view('errors.403'); return view('errors.403');
} }
@ -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();
@ -339,7 +339,7 @@ class LoginController extends Controller
/** /**
* Two factor enrollment page * Two factor enrollment page
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function getTwoFactorEnroll() public function getTwoFactorEnroll()
{ {
@ -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,
@ -389,7 +389,7 @@ class LoginController extends Controller
/** /**
* Two factor code form page * Two factor code form page
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function getTwoFactorAuth() public function getTwoFactorAuth()
{ {
@ -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
@ -415,7 +415,7 @@ class LoginController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function postTwoFactorAuth(Request $request) public function postTwoFactorAuth(Request $request)
{ {
@ -427,11 +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'));
} }
if (! $request->has('two_factor_secret')) { // TODO this seems almost the same as above? $user = auth()->user();
return redirect()->route('two-factor')->with('error', 'Two-factor code is required.');
}
$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)) {
@ -439,7 +435,7 @@ class LoginController extends Controller
$user->saveQuietly(); $user->saveQuietly();
$request->session()->put('2fa_authed', $user->id); $request->session()->put('2fa_authed', $user->id);
return redirect()->route('home')->with('success', 'You are logged in!'); return redirect()->route('home')->with('success', trans('auth/message.signin.success'));
} }
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code')); return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
@ -451,7 +447,7 @@ class LoginController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @return Redirect * @return Illuminate\Http\RedirectResponse
*/ */
public function logout(Request $request) public function logout(Request $request)
{ {
@ -537,7 +533,7 @@ class LoginController extends Controller
$minutes = round($seconds / 60); $minutes = round($seconds / 60);
$message = \Lang::get('auth/message.throttle', ['minutes' => $minutes]); $message = trans('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back() return redirect()->back()
->withInput($request->only($this->username(), 'remember')) ->withInput($request->only($this->username(), 'remember'))

View file

@ -7,7 +7,7 @@ use App\Models\Setting;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ResetPasswordController extends Controller class ResetPasswordController extends Controller
{ {
@ -66,7 +66,7 @@ class ResetPasswordController extends Controller
$credentials = $request->only('email', 'token'); $credentials = $request->only('email', 'token');
if (is_null($this->broker()->getUser($credentials))) { if (is_null($this->broker()->getUser($credentials))) {
\Log::debug('Password reset form FAILED - this token is not valid.'); Log::debug('Password reset form FAILED - this token is not valid.');
return redirect()->route('password.request')->with('error', trans('passwords.token')); return redirect()->route('password.request')->with('error', trans('passwords.token'));
} }
@ -89,10 +89,10 @@ class ResetPasswordController extends Controller
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages()); $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
if ($user = User::where('username', '=', $request->input('username'))->where('activated', '1')->whereNotNull('email')->first()) { if ($user = User::where('username', '=', $request->input('username'))->where('activated', '1')->whereNotNull('email')->first()) {
\Log::debug($user->username.' exists'); Log::debug($user->username.' exists');
// handle the password validation rules set by the admin settings // handle the password validation rules set by the admin settings
@ -112,17 +112,17 @@ class ResetPasswordController extends Controller
// Check if the password reset above actually worked // Check if the password reset above actually worked
if ($response == \Password::PASSWORD_RESET) { if ($response == \Password::PASSWORD_RESET) {
\Log::debug('Password reset for '.$user->username.' worked'); Log::debug('Password reset for '.$user->username.' worked');
return redirect()->guest('login')->with('success', trans('passwords.reset')); return redirect()->guest('login')->with('success', trans('passwords.reset'));
} }
\Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid'); Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset')); return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
} }
\Log::debug('Password reset for '.$request->input('username').' FAILED - user does not exist or does not have an email address - but make it look like it succeeded'); Log::debug('Password reset for '.$request->input('username').' FAILED - user does not exist or does not have an email address - but make it look like it succeeded');
return redirect()->guest('login')->with('success', trans('passwords.reset')); return redirect()->guest('login')->with('success', trans('passwords.reset'));
} }

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Saml; use App\Services\Saml;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Log; use Illuminate\Support\Facades\Log;
/** /**
* This controller provides the endpoint for SAML communication and metadata. * This controller provides the endpoint for SAML communication and metadata.
@ -51,7 +51,7 @@ class SamlController extends Controller
$metadata = $this->saml->getSPMetadata(); $metadata = $this->saml->getSPMetadata();
if (empty($metadata)) { if (empty($metadata)) {
\Log::debug('SAML metadata is empty - return a 403'); Log::debug('SAML metadata is empty - return a 403');
return response()->view('errors.403', [], 403); return response()->view('errors.403', [], 403);
} }
@ -71,7 +71,7 @@ class SamlController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function login(Request $request) public function login(Request $request)
{ {
@ -93,7 +93,7 @@ class SamlController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function acs(Request $request) public function acs(Request $request)
{ {
@ -126,7 +126,7 @@ class SamlController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @return Redirect * @return \Illuminate\Http\RedirectResponse
*/ */
public function sls(Request $request) public function sls(Request $request)
{ {

View file

@ -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\Support\Facades\Input; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Redirect; use \Illuminate\Contracts\View\View;
class BulkAssetModelsController extends Controller class BulkAssetModelsController extends Controller
{ {
@ -16,9 +16,8 @@ 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) public function edit(Request $request) : View | RedirectResponse
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@ -61,9 +60,8 @@ 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) public function update(Request $request): View | RedirectResponse
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
@ -105,9 +103,8 @@ class BulkAssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return Redirect
*/ */
public function destroy(Request $request) public function destroy(Request $request) : RedirectResponse
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);

View file

@ -4,10 +4,11 @@ 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 as Category; use App\Models\Category;
use Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Str; use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This class controls all actions related to Categories for * This class controls all actions related to Categories for
@ -25,10 +26,8 @@ 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() public function index() : View
{ {
// Show the page // Show the page
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
@ -42,10 +41,8 @@ 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() public function create() : View
{ {
// Show the page // Show the page
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
@ -61,10 +58,8 @@ 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) public function store(ImageUploadRequest $request) : RedirectResponse
{ {
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
$category = new Category(); $category = new Category();
@ -91,10 +86,8 @@ 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) public function edit($categoryId = null) : RedirectResponse | View
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
if (is_null($item = Category::find($categoryId))) { if (is_null($item = Category::find($categoryId))) {
@ -112,23 +105,31 @@ 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) public function update(ImageUploadRequest $request, $categoryId = null) : RedirectResponse
{ {
$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()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist')); return redirect()->route('categories.index')->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');
@ -150,10 +151,8 @@ 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) public function destroy($categoryId) : RedirectResponse
{ {
$this->authorize('delete', Category::class); $this->authorize('delete', Category::class);
// Check if the category exists // Check if the category exists
@ -178,11 +177,9 @@ 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) public function show($id) : View | RedirectResponse
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
if ($category = Category::find($id)) { if ($category = Category::find($id)) {

View file

@ -11,9 +11,8 @@ trait CheckInOutRequest
{ {
/** /**
* Find target for checkout * Find target for checkout
* @return SnipeModel Target asset is being checked out to.
*/ */
protected function determineCheckoutTarget() protected function determineCheckoutTarget() : ?SnipeModel
{ {
// 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')) {
@ -34,7 +33,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) protected function updateAssetLocation($asset, $target) : Asset
{ {
switch (request('checkout_to_type')) { switch (request('checkout_to_type')) {
case 'location': case 'location':

View file

@ -6,6 +6,9 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company; 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\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
@ -20,10 +23,8 @@ 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() public function index() : View
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
@ -35,10 +36,8 @@ 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() public function create() : View
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
@ -51,10 +50,8 @@ 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) public function store(ImageUploadRequest $request) : RedirectResponse
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
@ -80,10 +77,8 @@ 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) public function edit($companyId) : View | RedirectResponse
{ {
if (is_null($item = Company::find($companyId))) { if (is_null($item = Company::find($companyId))) {
return redirect()->route('companies.index') return redirect()->route('companies.index')
@ -102,10 +97,8 @@ 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) public function update(ImageUploadRequest $request, $companyId) : RedirectResponse
{ {
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'));
@ -134,10 +127,8 @@ 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) public function destroy($companyId) : RedirectResponse
{ {
if (is_null($company = Company::find($companyId))) { if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index') return redirect()->route('companies.index')
@ -154,7 +145,7 @@ final class CompaniesController extends Controller
try { try {
Storage::disk('public')->delete('companies'.'/'.$company->image); Storage::disk('public')->delete('companies'.'/'.$company->image);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
@ -164,7 +155,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) public function show($id) : View | RedirectResponse
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);

View file

@ -95,7 +95,7 @@ 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'){ if ($backto == 'asset'){
return redirect()->route('hardware.show', $asset->id)->with('success', return redirect()->route('hardware.show', $asset->id)->with('success',
trans('admin/components/message.checkin.success')); trans('admin/components/message.checkin.success'));

View file

@ -100,14 +100,14 @@ class ComponentCheckoutController extends Controller
$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,
'user_id' => Auth::user(), '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')));
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success')); return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
} }

View file

@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
/** /**
* This class controls all actions related to Components for * This class controls all actions related to Components for
@ -188,7 +189,7 @@ class ComponentsController extends Controller
try { try {
Storage::disk('public')->delete('components/'.$component->image); Storage::disk('public')->delete('components/'.$component->image);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }

View file

@ -10,6 +10,7 @@ use App\Models\Component;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ComponentsFilesController extends Controller class ComponentsFilesController extends Controller
{ {
@ -84,7 +85,7 @@ class ComponentsFilesController extends Controller
try { try {
Storage::delete('components/'.$log->filename); Storage::delete('components/'.$log->filename);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
@ -110,7 +111,7 @@ class ComponentsFilesController extends Controller
*/ */
public function show($componentId = null, $fileId = null) public function show($componentId = null, $fileId = null)
{ {
\Log::debug('Private filesystem is: '.config('filesystems.default')); Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId); $component = Component::find($componentId);
// the component is valid // the component is valid
@ -126,8 +127,8 @@ class ComponentsFilesController extends Controller
$file = 'private_uploads/components/'.$log->filename; $file = 'private_uploads/components/'.$log->filename;
if (Storage::missing($file)) { if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file); Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file)); Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain'); ->header('Content-Type', 'text/plain');

View file

@ -4,12 +4,11 @@ namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
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\Support\Facades\Auth; use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Input; use \Illuminate\Http\RedirectResponse;
class ConsumableCheckoutController extends Controller class ConsumableCheckoutController extends Controller
{ {
@ -20,13 +19,11 @@ 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) public function create($id) : View | RedirectResponse
{ {
if ($consumable = Consumable::with('users')->find($id)) { if ($consumable = Consumable::find($id)) {
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
@ -71,12 +68,18 @@ class ConsumableCheckoutController extends Controller
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
// If the quantity is not present in the request or is not a positive integer, set it to 1
$quantity = $request->input('qty');
if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
$quantity = 1;
}
// 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 || $quantity > $consumable->numRemaining()) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable')); 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
@ -88,14 +91,15 @@ class ConsumableCheckoutController extends Controller
// Update the consumable data // Update the consumable data
$consumable->assigned_to = e($request->input('assigned_to')); $consumable->assigned_to = e($request->input('assigned_to'));
for($i = 0; $i < $quantity; $i++){
$consumable->users()->attach($consumable->id, [ $consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id, 'consumable_id' => $consumable->id,
'user_id' => $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')));
// Redirect to the new consumable page // Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success')); return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));

View file

@ -8,8 +8,10 @@ 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
@ -62,7 +64,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(ImageUploadRequest $request) public function store(StoreConsumableRequest $request)
{ {
$this->authorize('create', Consumable::class); $this->authorize('create', Consumable::class);
$consumable = new Consumable(); $consumable = new Consumable();
@ -99,10 +101,8 @@ 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) public function edit($consumableId = null) : View | RedirectResponse
{ {
if ($item = Consumable::find($consumableId)) { if ($item = Consumable::find($consumableId)) {
$this->authorize($item); $this->authorize($item);
@ -124,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(ImageUploadRequest $request, $consumableId = null) public function update(StoreConsumableRequest $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'));
@ -182,6 +182,7 @@ 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'));

View file

@ -10,7 +10,7 @@ use App\Models\Consumable;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse; use Symfony\Consumable\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ConsumablesFilesController extends Controller class ConsumablesFilesController extends Controller
{ {
/** /**
@ -83,7 +83,7 @@ class ConsumablesFilesController extends Controller
try { try {
Storage::delete('consumables/'.$log->filename); Storage::delete('consumables/'.$log->filename);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); Log::debug($e);
} }
} }
@ -124,8 +124,8 @@ class ConsumablesFilesController extends Controller
$file = 'private_uploads/consumables/'.$log->filename; $file = 'private_uploads/consumables/'.$log->filename;
if (Storage::missing($file)) { if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file); Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file)); Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain'); ->header('Content-Type', 'text/plain');

View file

@ -22,7 +22,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
@ -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());
} }
} }

View file

@ -8,7 +8,8 @@ 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 Redirect; 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
@ -26,10 +27,8 @@ 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() public function index() : View
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
@ -46,10 +45,8 @@ 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() public function show() : RedirectResponse
{ {
return redirect()->route('fields.index'); return redirect()->route('fields.index');
} }
@ -61,10 +58,8 @@ 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) public function create(Request $request) : View
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get(); $fieldsets = CustomFieldset::get();
@ -83,10 +78,8 @@ 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) public function store(CustomFieldRequest $request) : RedirectResponse
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
@ -145,10 +138,8 @@ 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) public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
{ {
$field = CustomField::find($field_id); $field = CustomField::find($field_id);
@ -177,10 +168,8 @@ 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) public function destroy($field_id) : RedirectResponse
{ {
if ($field = CustomField::find($field_id)) { if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field); $this->authorize('delete', $field);
@ -203,10 +192,8 @@ 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) public function edit(Request $request, $id) : View | RedirectResponse
{ {
if ($field = CustomField::find($id)) { if ($field = CustomField::find($id)) {
@ -242,7 +229,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) public function update(CustomFieldRequest $request, $id) : RedirectResponse
{ {
$field = CustomField::find($id); $field = CustomField::find($id);

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