mirror of
https://github.com/snipe/snipe-it.git
synced 2025-03-05 20:52:15 -08:00
Merge branch 'develop' into saving_custom_report_template
# Conflicts: # resources/lang/en/admin/reports/general.php # resources/views/reports/custom.blade.php
This commit is contained in:
commit
530ea474d1
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"projectName": "snipe-it",
|
"projectName": "snipe-it",
|
||||||
"projectOwner": "snipe",
|
"projectOwner": "snipe",
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
"files": [
|
"files": [
|
||||||
"README.md"
|
"CONTRIBUTORS.md"
|
||||||
],
|
],
|
||||||
"imageSize": 110,
|
"imageSize": 110,
|
||||||
"commit": true,
|
"commit": true,
|
||||||
|
"commitConvention": "angular",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"login": "snipe",
|
"login": "snipe",
|
||||||
|
@ -2961,6 +2964,60 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Singrity",
|
||||||
|
"name": "Bogdan",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/58479551?v=4",
|
||||||
|
"profile": "http://@singrity",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mmanjos",
|
||||||
|
"name": "mmanjos",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/3483684?v=4",
|
||||||
|
"profile": "https://github.com/mmanjos",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Azooz2014",
|
||||||
|
"name": "Abdelaziz Faki",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/7429229?v=4",
|
||||||
|
"profile": "https://azooz2014.github.io/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "bilias",
|
||||||
|
"name": "bilias",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/47315739?v=4",
|
||||||
|
"profile": "https://github.com/bilias",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "coach1988",
|
||||||
|
"name": "coach1988",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/2565989?v=4",
|
||||||
|
"profile": "https://github.com/coach1988",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mauro-miatello",
|
||||||
|
"name": "MrM",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/11910225?v=4",
|
||||||
|
"profile": "https://github.com/mauro-miatello",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
version: 1
|
|
||||||
|
|
||||||
environment:
|
|
||||||
php: 8.0
|
|
||||||
node: 12
|
|
||||||
|
|
||||||
services:
|
|
||||||
- mysql: 5.7
|
|
||||||
- dusk:
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches: .*
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
- name: Setup
|
|
||||||
cmd: |
|
|
||||||
cp -v .env.testing.example .env
|
|
||||||
cp -v .env.testing.example .env.testing
|
|
||||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
|
||||||
|
|
||||||
- name: Generate Key
|
|
||||||
cmd: |
|
|
||||||
php artisan key:generate --force
|
|
||||||
|
|
||||||
- name: Passport Keys
|
|
||||||
cmd: |
|
|
||||||
php artisan passport:keys
|
|
||||||
|
|
||||||
- name: Run Migrations
|
|
||||||
cmd: |
|
|
||||||
php artisan migrate --force
|
|
||||||
|
|
||||||
- name: PHPUnit Unit Tests
|
|
||||||
cmd: |
|
|
||||||
php artisan test --testsuite Unit
|
|
||||||
|
|
||||||
- name: PHPUnit Feature Tests
|
|
||||||
cmd: |
|
|
||||||
php artisan test --testsuite Feature
|
|
||||||
|
|
||||||
# - name: Browser Tests
|
|
||||||
# cmd: |
|
|
||||||
# cp -v .env.dusk.example .env.dusk.ci
|
|
||||||
# sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci
|
|
||||||
# sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
|
|
||||||
# #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci
|
|
||||||
# sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci
|
|
||||||
# sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci
|
|
||||||
# sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci
|
|
||||||
# sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci
|
|
||||||
#
|
|
||||||
# php -S [::0]:8000 -t public 2>server.log &
|
|
||||||
# sleep 2
|
|
||||||
# php artisan dusk:chrome-driver $CHROME_DRIVER
|
|
||||||
# php artisan dusk --env=ci
|
|
|
@ -159,6 +159,7 @@ LOG_CHANNEL=stderr
|
||||||
LOG_MAX_DAYS=10
|
LOG_MAX_DAYS=10
|
||||||
APP_LOCKED=false
|
APP_LOCKED=false
|
||||||
APP_CIPHER=AES-256-CBC
|
APP_CIPHER=AES-256-CBC
|
||||||
|
APP_FORCE_TLS=false
|
||||||
GOOGLE_MAPS_API=
|
GOOGLE_MAPS_API=
|
||||||
LDAP_MEM_LIM=500M
|
LDAP_MEM_LIM=500M
|
||||||
LDAP_TIME_LIM=600
|
LDAP_TIME_LIM=600
|
||||||
|
|
|
@ -6,7 +6,7 @@ APP_DEBUG=false
|
||||||
APP_KEY=ChangeMe
|
APP_KEY=ChangeMe
|
||||||
APP_URL=null
|
APP_URL=null
|
||||||
APP_TIMEZONE='UTC'
|
APP_TIMEZONE='UTC'
|
||||||
APP_LOCALE=en
|
APP_LOCALE='en-US'
|
||||||
MAX_RESULTS=500
|
MAX_RESULTS=500
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
|
@ -6,7 +6,7 @@ APP_DEBUG=false
|
||||||
APP_KEY='base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU='
|
APP_KEY='base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU='
|
||||||
APP_URL='http://localhost:8000'
|
APP_URL='http://localhost:8000'
|
||||||
APP_TIMEZONE='US/Pacific'
|
APP_TIMEZONE='US/Pacific'
|
||||||
APP_LOCALE=en
|
APP_LOCALE='en-US'
|
||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
|
@ -6,7 +6,7 @@ APP_DEBUG=true
|
||||||
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||||
APP_URL=http://localhost:8000
|
APP_URL=http://localhost:8000
|
||||||
APP_TIMEZONE='UTC'
|
APP_TIMEZONE='UTC'
|
||||||
APP_LOCALE=en
|
APP_LOCALE='en-US'
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# REQUIRED: DATABASE SETTINGS
|
# REQUIRED: DATABASE SETTINGS
|
||||||
|
|
2
.github/autolabeler.yml
vendored
2
.github/autolabeler.yml
vendored
|
@ -18,5 +18,5 @@ importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views
|
||||||
cli / artisan: ["/app/Console/*"]
|
cli / artisan: ["/app/Console/*"]
|
||||||
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
|
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
|
||||||
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
|
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
|
||||||
tests: ["/tests/*", "/stubs"]
|
tests: ["/tests/*", "/database/factories/*", "/stubs"]
|
||||||
config: .github
|
config: .github
|
||||||
|
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
|
@ -2,5 +2,6 @@ version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
target-branch: "develop"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
|
8
.github/workflows/SA-codeql.yml
vendored
8
.github/workflows/SA-codeql.yml
vendored
|
@ -26,14 +26,14 @@ jobs:
|
||||||
language: [ 'javascript' ]
|
language: [ 'javascript' ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
4
.github/workflows/codacy-analysis.yml
vendored
4
.github/workflows/codacy-analysis.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# Checkout the repository to the GitHub Actions runner
|
# Checkout the repository to the GitHub Actions runner
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -52,6 +52,6 @@ jobs:
|
||||||
|
|
||||||
# Upload the SARIF file generated in the previous step
|
# Upload the SARIF file generated in the previous step
|
||||||
- name: Upload SARIF results file
|
- name: Upload SARIF results file
|
||||||
uses: github/codeql-action/upload-sarif@v2
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|
2
.github/workflows/crowdin-upload.yml
vendored
2
.github/workflows/crowdin-upload.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Crowdin push
|
- name: Crowdin push
|
||||||
uses: crowdin/github-action@v1
|
uses: crowdin/github-action@v1
|
||||||
|
|
11
.github/workflows/docker-alpine.yml
vendored
11
.github/workflows/docker-alpine.yml
vendored
|
@ -32,6 +32,7 @@ jobs:
|
||||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||||
type=ref,event=tag,suffix=-alpine
|
type=ref,event=tag,suffix=-alpine
|
||||||
|
type=semver,pattern=v{{major}}-latest-alpine
|
||||||
# Define default tag "flavor" for docker/metadata-action per
|
# Define default tag "flavor" for docker/metadata-action per
|
||||||
# https://github.com/docker/metadata-action#flavor-input
|
# https://github.com/docker/metadata-action#flavor-input
|
||||||
# We turn off 'latest' tag by default.
|
# We turn off 'latest' tag by default.
|
||||||
|
@ -41,17 +42,17 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
@ -63,7 +64,7 @@ jobs:
|
||||||
# Get Metadata for docker_build step below
|
# Get Metadata for docker_build step below
|
||||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||||
id: meta_build
|
id: meta_build
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: snipe/snipe-it
|
images: snipe/snipe-it
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
tags: ${{ env.IMAGE_TAGS }}
|
||||||
|
@ -72,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@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile.alpine
|
file: ./Dockerfile.alpine
|
||||||
|
|
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
|
@ -32,6 +32,7 @@ jobs:
|
||||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
||||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
||||||
type=ref,event=tag
|
type=ref,event=tag
|
||||||
|
type=semver,pattern=v{{major}}-latest
|
||||||
# Define default tag "flavor" for docker/metadata-action per
|
# Define default tag "flavor" for docker/metadata-action per
|
||||||
# https://github.com/docker/metadata-action#flavor-input
|
# https://github.com/docker/metadata-action#flavor-input
|
||||||
# We turn off 'latest' tag by default.
|
# We turn off 'latest' tag by default.
|
||||||
|
@ -41,17 +42,17 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
@ -63,7 +64,7 @@ jobs:
|
||||||
# Get Metadata for docker_build step below
|
# Get Metadata for docker_build step below
|
||||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||||
id: meta_build
|
id: meta_build
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: snipe/snipe-it
|
images: snipe/snipe-it
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
tags: ${{ env.IMAGE_TAGS }}
|
||||||
|
@ -72,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@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|
22
.github/workflows/dockerhub-description.yml
vendored
Normal file
22
.github/workflows/dockerhub-description.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Update Docker Hub Description
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
- README.md
|
||||||
|
- .github/workflows/dockerhub-description.yml
|
||||||
|
jobs:
|
||||||
|
dockerHubDescription:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker Hub Description
|
||||||
|
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
repository: snipe/snipe-it
|
||||||
|
readme-filepath: ./README.md
|
73
.github/workflows/tests-mysql.yml
vendored
Normal file
73
.github/workflows/tests-mysql.yml
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
name: Tests in MySQL
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:5.7
|
||||||
|
env:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||||
|
MYSQL_DATABASE: snipeit
|
||||||
|
ports:
|
||||||
|
- 33306:3306
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- "7.4"
|
||||||
|
- "8.0"
|
||||||
|
- "8.1.1"
|
||||||
|
|
||||||
|
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@v3
|
||||||
|
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: Generate key
|
||||||
|
run: php artisan key:generate
|
||||||
|
|
||||||
|
- name: Directory Permissions
|
||||||
|
run: chmod -R 777 storage bootstrap/cache
|
||||||
|
|
||||||
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
|
env:
|
||||||
|
DB_CONNECTION: mysql
|
||||||
|
DB_DATABASE: snipeit
|
||||||
|
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||||
|
DB_USERNAME: root
|
||||||
|
run: php artisan test --parallel
|
58
.github/workflows/tests-sqlite.yml
vendored
Normal file
58
.github/workflows/tests-sqlite.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
name: Tests in SQLite
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- "8.1.1"
|
||||||
|
|
||||||
|
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@v3
|
||||||
|
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: Generate key
|
||||||
|
run: php artisan key:generate
|
||||||
|
|
||||||
|
- name: Directory Permissions
|
||||||
|
run: chmod -R 777 storage bootstrap/cache
|
||||||
|
|
||||||
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
|
env:
|
||||||
|
DB_CONNECTION: sqlite_testing
|
||||||
|
run: php artisan test --parallel
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,8 +1,6 @@
|
||||||
.couscous
|
.couscous
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
.env.dusk.*
|
|
||||||
!.env.dusk.example
|
|
||||||
.env.testing
|
.env.testing
|
||||||
phpstan.neon
|
phpstan.neon
|
||||||
.idea
|
.idea
|
||||||
|
|
444
CONTRIBUTORS.md
Normal file
444
CONTRIBUTORS.md
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
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 -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
<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 -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.17.3
|
FROM alpine:3.18.5
|
||||||
# Apache + PHP
|
# Apache + PHP
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
apache2 \
|
apache2 \
|
||||||
|
|
69
README.md
69
README.md
|
@ -1,5 +1,7 @@
|
||||||
 [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|

|
||||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
|
||||||
|
[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
|
||||||
|
[](#contributors) [](https://discord.gg/yZFtShAcKk)
|
||||||
|
|
||||||
## Snipe-IT - Open Source Asset Management System
|
## Snipe-IT - Open Source Asset Management System
|
||||||
|
|
||||||
|
@ -80,73 +82,14 @@ As these were created by third-parties, Snipe-IT cannot provide support for thes
|
||||||
|
|
||||||
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
||||||
|
|
||||||
|
|
||||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||||
|
|
||||||
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||||
|
|
||||||
|
[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
|
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
### Contributors
|
|
||||||
|
|
||||||
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 -->
|
|
||||||
| [<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") |
|
|
||||||
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
|
||||||
| [<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)") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
|
|
||||||
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
|
|
||||||
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
|
|
||||||
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
| [<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") |
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
|
|
||||||
|
|
65
TESTING.md
65
TESTING.md
|
@ -9,7 +9,39 @@ Before starting, follow the [instructions](README.md#installation) for installin
|
||||||
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
|
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
|
||||||
|
|
||||||
`cp .env.testing.example .env.testing`
|
`cp .env.testing.example .env.testing`
|
||||||
> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
|
|
||||||
|
The following should work for running tests in memory with sqlite:
|
||||||
|
```
|
||||||
|
# --------------------------------------------
|
||||||
|
# REQUIRED: BASIC APP SETTINGS
|
||||||
|
# --------------------------------------------
|
||||||
|
APP_ENV=testing
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||||
|
APP_URL=http://localhost:8000
|
||||||
|
APP_TIMEZONE='UTC'
|
||||||
|
APP_LOCALE=en
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# REQUIRED: DATABASE SETTINGS
|
||||||
|
# --------------------------------------------
|
||||||
|
DB_CONNECTION=sqlite_testing
|
||||||
|
#DB_HOST=127.0.0.1
|
||||||
|
#DB_PORT=3306
|
||||||
|
#DB_DATABASE=null
|
||||||
|
#DB_USERNAME=null
|
||||||
|
#DB_PASSWORD=null
|
||||||
|
```
|
||||||
|
|
||||||
|
To use MySQL you should update the `DB_` variables to match your local test database:
|
||||||
|
```
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE={}
|
||||||
|
DB_USERNAME={}
|
||||||
|
DB_PASSWORD={}
|
||||||
|
```
|
||||||
|
|
||||||
Now you are ready to run the entire test suite from your terminal:
|
Now you are ready to run the entire test suite from your terminal:
|
||||||
|
|
||||||
|
@ -18,34 +50,3 @@ Now you are ready to run the entire test suite from your terminal:
|
||||||
To run individual test files, you can pass the path to the test that you want to run:
|
To run individual test files, you can pass the path to the test that you want to run:
|
||||||
|
|
||||||
`php artisan test tests/Unit/AccessoryTest.php`
|
`php artisan test tests/Unit/AccessoryTest.php`
|
||||||
|
|
||||||
## Browser Tests
|
|
||||||
|
|
||||||
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
|
|
||||||
|
|
||||||
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
|
|
||||||
|
|
||||||
`cp .env.dusk.example .env.dusk.local`
|
|
||||||
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
|
|
||||||
|
|
||||||
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
|
|
||||||
|
|
||||||
### Running Browser Tests
|
|
||||||
|
|
||||||
Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
|
|
||||||
|
|
||||||
`php artisan serve`
|
|
||||||
|
|
||||||
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
|
|
||||||
|
|
||||||
`php artisan dusk`
|
|
||||||
|
|
||||||
To run individual test files, you can pass the path to the test that you want to run:
|
|
||||||
|
|
||||||
`php artisan dusk tests/Browser/LoginTest.php`
|
|
||||||
|
|
||||||
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
|
|
||||||
|
|
||||||
`php artisan dusk:chrome-driver --detect`
|
|
||||||
|
|
||||||
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class LdapSync extends Command
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}';
|
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=*} {--base_dn=} {--filter=} {--summary} {--json_summary}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
|
@ -66,6 +66,7 @@ class LdapSync extends Command
|
||||||
$ldap_result_dept = Setting::getSettings()->ldap_dept;
|
$ldap_result_dept = Setting::getSettings()->ldap_dept;
|
||||||
$ldap_result_manager = Setting::getSettings()->ldap_manager;
|
$ldap_result_manager = Setting::getSettings()->ldap_manager;
|
||||||
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
||||||
|
$search_base = Setting::getSettings()->ldap_base_dn;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$ldapconn = Ldap::connectToLdap();
|
$ldapconn = Ldap::connectToLdap();
|
||||||
|
@ -83,17 +84,35 @@ class LdapSync extends Command
|
||||||
$summary = [];
|
$summary = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($this->option('base_dn') != '') {
|
|
||||||
|
/**
|
||||||
|
* if a location ID has been specified, use that OU
|
||||||
|
*/
|
||||||
|
if ( $this->option('location_id') != '') {
|
||||||
|
|
||||||
|
foreach($this->option('location_id') as $location_id){
|
||||||
|
$location_ou = Location::where('id', '=', $location_id)->value('ldap_ou');
|
||||||
|
$search_base = $location_ou;
|
||||||
|
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otherwise if a manual base DN has been specified, use that
|
||||||
|
*/
|
||||||
|
} elseif ($this->option('base_dn') != '') {
|
||||||
$search_base = $this->option('base_dn');
|
$search_base = $this->option('base_dn');
|
||||||
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
||||||
} else {
|
|
||||||
$search_base = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a filter has been specified, use that
|
||||||
|
*/
|
||||||
if ($this->option('filter') != '') {
|
if ($this->option('filter') != '') {
|
||||||
$results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
|
$results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
|
||||||
} else {
|
} else {
|
||||||
$results = Ldap::findLdapUsers($search_base);
|
$results = Ldap::findLdapUsers($search_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
if ($this->option('json_summary')) {
|
if ($this->option('json_summary')) {
|
||||||
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
|
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
|
||||||
|
@ -106,17 +125,21 @@ class LdapSync extends Command
|
||||||
|
|
||||||
/* Determine which location to assign users to by default. */
|
/* Determine which location to assign users to by default. */
|
||||||
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
|
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
|
||||||
|
if ($this->option('location') != '') {
|
||||||
|
if ($location = Location::where('name', '=', $this->option('location'))->first()) {
|
||||||
|
Log::debug('Location name ' . $this->option('location') . ' passed');
|
||||||
|
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->option('location') != '') {
|
} elseif ($this->option('location_id') != '') {
|
||||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
foreach($this->option('location_id') as $location_id) {
|
||||||
Log::debug('Location name '.$this->option('location').' passed');
|
if ($location = Location::where('id', '=', $location_id)->first()) {
|
||||||
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
|
Log::debug('Location ID ' . $location_id . ' passed');
|
||||||
} elseif ($this->option('location_id') != '') {
|
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
|
||||||
$location = Location::where('id', '=', $this->option('location_id'))->first();
|
}
|
||||||
Log::debug('Location ID '.$this->option('location_id').' passed');
|
|
||||||
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isset($location)) {
|
if (! isset($location)) {
|
||||||
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
||||||
}
|
}
|
||||||
|
@ -180,10 +203,6 @@ class LdapSync extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create user account entries in Snipe-IT */
|
|
||||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
|
|
||||||
$pass = bcrypt($tmp_pass);
|
|
||||||
|
|
||||||
$manager_cache = [];
|
$manager_cache = [];
|
||||||
|
|
||||||
if($ldap_default_group != null) {
|
if($ldap_default_group != null) {
|
||||||
|
@ -229,22 +248,44 @@ class LdapSync extends Command
|
||||||
} else {
|
} else {
|
||||||
// Creating a new user.
|
// Creating a new user.
|
||||||
$user = new User;
|
$user = new User;
|
||||||
$user->password = $pass;
|
$user->password = $user->noPassword();
|
||||||
$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';
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->first_name = $item['firstname'];
|
//If a sync option is not filled in on the LDAP settings don't populate the user field
|
||||||
$user->last_name = $item['lastname'];
|
if($ldap_result_username != null){
|
||||||
$user->username = $item['username'];
|
$user->username = $item['username'];
|
||||||
$user->email = $item['email'];
|
}
|
||||||
|
if($ldap_result_last_name != null){
|
||||||
|
$user->last_name = $item['lastname'];
|
||||||
|
}
|
||||||
|
if($ldap_result_first_name != null){
|
||||||
|
$user->first_name = $item['firstname'];
|
||||||
|
}
|
||||||
|
if($ldap_result_emp_num != null){
|
||||||
$user->employee_num = e($item['employee_number']);
|
$user->employee_num = e($item['employee_number']);
|
||||||
|
}
|
||||||
|
if($ldap_result_email != null){
|
||||||
|
$user->email = $item['email'];
|
||||||
|
}
|
||||||
|
if($ldap_result_phone != null){
|
||||||
$user->phone = $item['telephone'];
|
$user->phone = $item['telephone'];
|
||||||
|
}
|
||||||
|
if($ldap_result_jobtitle != null){
|
||||||
$user->jobtitle = $item['jobtitle'];
|
$user->jobtitle = $item['jobtitle'];
|
||||||
|
}
|
||||||
|
if($ldap_result_country != null){
|
||||||
$user->country = $item['country'];
|
$user->country = $item['country'];
|
||||||
|
}
|
||||||
|
if($ldap_result_dept != null){
|
||||||
$user->department_id = $department->id;
|
$user->department_id = $department->id;
|
||||||
|
}
|
||||||
|
if($ldap_result_location != null){
|
||||||
$user->location_id = $location ? $location->id : null;
|
$user->location_id = $location ? $location->id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($ldap_result_manager != null){
|
||||||
if($item['manager'] != null) {
|
if($item['manager'] != null) {
|
||||||
// Check Cache first
|
// Check Cache first
|
||||||
if (isset($manager_cache[$item['manager']])) {
|
if (isset($manager_cache[$item['manager']])) {
|
||||||
|
@ -284,6 +325,7 @@ class LdapSync extends Command
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sync activated state for Active Directory.
|
// Sync activated state for Active Directory.
|
||||||
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
|
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
|
||||||
|
|
|
@ -63,7 +63,7 @@ class ResetDemoSettings extends Command
|
||||||
$settings->date_display_format = 'D M d, Y';
|
$settings->date_display_format = 'D M d, Y';
|
||||||
$settings->time_display_format = 'g:iA';
|
$settings->time_display_format = 'g:iA';
|
||||||
$settings->thumbnail_max_h = '30';
|
$settings->thumbnail_max_h = '30';
|
||||||
$settings->locale = 'en';
|
$settings->locale = 'en-US';
|
||||||
$settings->version_footer = 'on';
|
$settings->version_footer = 'on';
|
||||||
$settings->support_footer = null;
|
$settings->support_footer = null;
|
||||||
$settings->saml_enabled = '0';
|
$settings->saml_enabled = '0';
|
||||||
|
@ -78,7 +78,7 @@ class ResetDemoSettings extends Command
|
||||||
$settings->save();
|
$settings->save();
|
||||||
|
|
||||||
if ($user = User::where('username', '=', 'admin')->first()) {
|
if ($user = User::where('username', '=', 'admin')->first()) {
|
||||||
$user->locale = 'en';
|
$user->locale = 'en-US';
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,35 +84,36 @@ class RestoreFromBackup extends Command
|
||||||
|
|
||||||
|
|
||||||
$private_dirs = [
|
$private_dirs = [
|
||||||
|
'storage/private_uploads/accessories',
|
||||||
|
'storage/private_uploads/assetmodels',
|
||||||
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
||||||
'storage/private_uploads/audits',
|
'storage/private_uploads/audits',
|
||||||
|
'storage/private_uploads/components',
|
||||||
|
'storage/private_uploads/consumables',
|
||||||
|
'storage/private_uploads/eula-pdfs',
|
||||||
'storage/private_uploads/imports',
|
'storage/private_uploads/imports',
|
||||||
'storage/private_uploads/assetmodels',
|
|
||||||
'storage/private_uploads/users',
|
|
||||||
'storage/private_uploads/licenses',
|
'storage/private_uploads/licenses',
|
||||||
'storage/private_uploads/signatures',
|
'storage/private_uploads/signatures',
|
||||||
|
'storage/private_uploads/users',
|
||||||
];
|
];
|
||||||
$private_files = [
|
$private_files = [
|
||||||
'storage/oauth-private.key',
|
'storage/oauth-private.key',
|
||||||
'storage/oauth-public.key',
|
'storage/oauth-public.key',
|
||||||
];
|
];
|
||||||
$public_dirs = [
|
$public_dirs = [
|
||||||
|
'public/uploads/accessories',
|
||||||
|
'public/uploads/assets', // these are asset _pictures_, not asset files
|
||||||
|
'public/uploads/avatars',
|
||||||
|
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
|
||||||
|
'public/uploads/categories',
|
||||||
'public/uploads/companies',
|
'public/uploads/companies',
|
||||||
'public/uploads/components',
|
'public/uploads/components',
|
||||||
'public/uploads/categories',
|
|
||||||
'public/uploads/manufacturers',
|
|
||||||
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
|
|
||||||
'public/uploads/consumables',
|
'public/uploads/consumables',
|
||||||
'public/uploads/departments',
|
'public/uploads/departments',
|
||||||
'public/uploads/avatars',
|
|
||||||
'public/uploads/suppliers',
|
|
||||||
'public/uploads/assets', // these are asset _pictures_, not asset files
|
|
||||||
'public/uploads/locations',
|
'public/uploads/locations',
|
||||||
'public/uploads/accessories',
|
|
||||||
'public/uploads/models',
|
|
||||||
'public/uploads/categories',
|
|
||||||
'public/uploads/avatars',
|
|
||||||
'public/uploads/manufacturers',
|
'public/uploads/manufacturers',
|
||||||
|
'public/uploads/models',
|
||||||
|
'public/uploads/suppliers',
|
||||||
];
|
];
|
||||||
|
|
||||||
$public_files = [
|
$public_files = [
|
||||||
|
|
|
@ -7,6 +7,7 @@ use App\Models\CustomField;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Artisan;
|
use Artisan;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Encryption\Encrypter;
|
use Illuminate\Encryption\Encrypter;
|
||||||
|
|
||||||
class RotateAppKey extends Command
|
class RotateAppKey extends Command
|
||||||
|
@ -16,14 +17,17 @@ class RotateAppKey extends Command
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'snipeit:rotate-key';
|
protected $signature = 'snipeit:rotate-key
|
||||||
|
{previous_key? : The previous key to rotate from}
|
||||||
|
{--emergency : Emergency mode - rotate from .env APP_KEY to newly-generated one, modifying .env}
|
||||||
|
{--force : Skip interactive confirmation}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Command description';
|
protected $description = 'Rotates APP_KEY to a new value, optionally taking the previous key as an argument';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Create a new command instance.
|
||||||
|
@ -42,26 +46,42 @@ class RotateAppKey extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->confirm("\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ")) {
|
//make sure they specify only exactly one of --emergency, or a filename. Not neither, and not both.
|
||||||
|
if ( (!$this->option('emergency') && !$this->argument('previous_key')) || ( $this->option('emergency') && $this->argument('previous_key'))) {
|
||||||
|
$this->error("Specify only one of --emergency, or an app key value, in order to rotate keys");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ( $this->option('emergency') ) {
|
||||||
|
$msg = "\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ";
|
||||||
|
} else {
|
||||||
|
$msg = "\n****************************************************\nTHIS WILL DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND RE-ENCRYPT THEM WITH YOUR\nAPP_KEY.\n\nThere is NO undo. \n\nMake SURE you have a database backup BEFORE running this command. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup? ";
|
||||||
|
}
|
||||||
|
if ($this->option('force') || $this->confirm($msg)) {
|
||||||
|
|
||||||
// Get the existing app_key and ciphers
|
// Get the existing app_key and ciphers
|
||||||
// We put them in a variable since we clear the cache partway through here.
|
// We put them in a variable since we clear the cache partway through here.
|
||||||
$old_app_key = config('app.key');
|
if ($this->option('emergency')) {
|
||||||
$cipher = config('app.cipher');
|
$old_app_key = config('app.key');
|
||||||
|
$cipher = config('app.cipher');
|
||||||
|
|
||||||
// Generate a new one
|
// Generate a new one
|
||||||
Artisan::call('key:generate', ['--show' => true]);
|
Artisan::call('key:generate', ['--show' => true]);
|
||||||
$new_app_key = Artisan::output();
|
$new_app_key = trim(Artisan::output());
|
||||||
|
|
||||||
// Clear the config cache
|
// Clear the config cache
|
||||||
Artisan::call('config:clear');
|
Artisan::call('config:clear');
|
||||||
|
|
||||||
$this->warn('Your app cipher is: '.$cipher);
|
// Write the new app key to the .env file
|
||||||
$this->warn('Your old APP_KEY is: '.$old_app_key);
|
$this->writeNewEnvironmentFileWith($new_app_key);
|
||||||
$this->warn('Your new APP_KEY is: '.$new_app_key);
|
} elseif ($this->argument('previous_key')) {
|
||||||
|
$old_app_key = $this->argument('previous_key');
|
||||||
|
$cipher = config('app.cipher'); // just a guess?
|
||||||
|
$new_app_key = config('app.key');
|
||||||
|
}
|
||||||
|
|
||||||
// Write the new app key to the .env file
|
$this->warn('Your app cipher is: ' . $cipher);
|
||||||
$this->writeNewEnvironmentFileWith($new_app_key);
|
$this->warn('Your old APP_KEY is: ' . $old_app_key);
|
||||||
|
$this->warn('Your new APP_KEY is: ' . $new_app_key);
|
||||||
|
|
||||||
// Manually create an old encrypter instance using the old app key
|
// Manually create an old encrypter instance using the old app key
|
||||||
// and also create a new encrypter instance so we can re-crypt the field
|
// and also create a new encrypter instance so we can re-crypt the field
|
||||||
|
@ -75,8 +95,16 @@ class RotateAppKey extends Command
|
||||||
$assets = Asset::whereNotNull($field->db_column)->get();
|
$assets = Asset::whereNotNull($field->db_column)->get();
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
|
try {
|
||||||
$this->line('DECRYPTED: '.$field->db_column);
|
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
|
||||||
|
$this->line('DECRYPTED: ' . $field->db_column);
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
$this->line('Could not decrypt '. $field->db_column.' using "old key" - skipping...');
|
||||||
|
continue;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error("Error decrypting ".$field->db_column.", reason: ".$e->getMessage().". Aborting key rotation");
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
|
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
|
||||||
$this->line('ENCRYPTED: '.$field->db_column);
|
$this->line('ENCRYPTED: '.$field->db_column);
|
||||||
$asset->save();
|
$asset->save();
|
||||||
|
@ -86,10 +114,14 @@ class RotateAppKey extends Command
|
||||||
// Handle the LDAP password if one is provided
|
// Handle the LDAP password if one is provided
|
||||||
$setting = Setting::first();
|
$setting = Setting::first();
|
||||||
if ($setting->ldap_pword != '') {
|
if ($setting->ldap_pword != '') {
|
||||||
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
|
try {
|
||||||
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
|
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
|
||||||
$setting->save();
|
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
|
||||||
$this->warn('LDAP password has been re-encrypted.');
|
$setting->save();
|
||||||
|
$this->warn('LDAP password has been re-encrypted.');
|
||||||
|
} catch(DecryptException $e) {
|
||||||
|
$this->warn("Unable to decrypt old LDAP password; skipping");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->info('This operation has been canceled. No changes have been made.');
|
$this->info('This operation has been canceled. No changes have been made.');
|
||||||
|
@ -106,7 +138,7 @@ class RotateAppKey extends Command
|
||||||
{
|
{
|
||||||
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
|
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
|
||||||
$this->keyReplacementPattern(),
|
$this->keyReplacementPattern(),
|
||||||
'APP_KEY='.$key,
|
'APP_KEY="'.$key.'"',
|
||||||
file_get_contents($this->laravel->environmentFilePath())
|
file_get_contents($this->laravel->environmentFilePath())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -118,7 +150,7 @@ class RotateAppKey extends Command
|
||||||
*/
|
*/
|
||||||
protected function keyReplacementPattern()
|
protected function keyReplacementPattern()
|
||||||
{
|
{
|
||||||
$escaped = preg_quote('='.$this->laravel['config']['app.key'], '/');
|
$escaped = '="?'.preg_quote($this->laravel['config']['app.key'], '/').'"?';
|
||||||
|
|
||||||
return "/^APP_KEY{$escaped}/m";
|
return "/^APP_KEY{$escaped}/m";
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -42,7 +43,7 @@ class SendExpectedCheckinAlerts extends Command
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$settings = Setting::getSettings();
|
$settings = Setting::getSettings();
|
||||||
$whenNotify = Carbon::now()->addDays(7);
|
$whenNotify = Carbon::now();
|
||||||
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
|
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
|
||||||
|
|
||||||
$this->info($whenNotify.' is deadline');
|
$this->info($whenNotify.' is deadline');
|
||||||
|
@ -50,6 +51,7 @@ class SendExpectedCheckinAlerts extends Command
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
if ($asset->assigned && $asset->checkedOutToUser()) {
|
if ($asset->assigned && $asset->checkedOutToUser()) {
|
||||||
|
Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email);
|
||||||
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
|
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,20 @@ class CheckoutableCheckedIn
|
||||||
public $checkedInBy;
|
public $checkedInBy;
|
||||||
public $note;
|
public $note;
|
||||||
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
|
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
|
||||||
|
public $originalValues;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null)
|
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null, $originalValues = [])
|
||||||
{
|
{
|
||||||
$this->checkoutable = $checkoutable;
|
$this->checkoutable = $checkoutable;
|
||||||
$this->checkedOutTo = $checkedOutTo;
|
$this->checkedOutTo = $checkedOutTo;
|
||||||
$this->checkedInBy = $checkedInBy;
|
$this->checkedInBy = $checkedInBy;
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
$this->action_date = $action_date ?? date('Y-m-d');
|
$this->action_date = $action_date ?? date('Y-m-d');
|
||||||
|
$this->originalValues = $originalValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,19 @@ class CheckoutableCheckedOut
|
||||||
public $checkedOutTo;
|
public $checkedOutTo;
|
||||||
public $checkedOutBy;
|
public $checkedOutBy;
|
||||||
public $note;
|
public $note;
|
||||||
|
public $originalValues;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
|
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [])
|
||||||
{
|
{
|
||||||
$this->checkoutable = $checkoutable;
|
$this->checkoutable = $checkoutable;
|
||||||
$this->checkedOutTo = $checkedOutTo;
|
$this->checkedOutTo = $checkedOutTo;
|
||||||
$this->checkedOutBy = $checkedOutBy;
|
$this->checkedOutBy = $checkedOutBy;
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
|
$this->originalValues = $originalValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,11 @@ class Handler extends ExceptionHandler
|
||||||
return redirect()->guest('login');
|
return redirect()->guest('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function invalidJson($request, ValidationException $exception)
|
||||||
|
{
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of the inputs that are never flashed for validation exceptions.
|
* A list of the inputs that are never flashed for validation exceptions.
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\AssetModel;
|
||||||
use App\Models\Component;
|
use App\Models\Component;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use App\Models\CustomField;
|
use App\Models\CustomField;
|
||||||
|
@ -16,6 +18,63 @@ use Carbon\Carbon;
|
||||||
|
|
||||||
class Helper
|
class Helper
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is only used for reversing the migration that updates the locale to the 5-6 letter codes from two
|
||||||
|
* letter codes. The normal dropdowns use the autoglossonyms in the language files located
|
||||||
|
* in resources/en-US/localizations.php.
|
||||||
|
*/
|
||||||
|
public static $language_map = [
|
||||||
|
'af' => 'af-ZA', // Afrikaans
|
||||||
|
'am' => 'am-ET', // Amharic
|
||||||
|
'ar' => 'ar-SA', // Arabic
|
||||||
|
'bg' => 'bg-BG', // Bulgarian
|
||||||
|
'ca' => 'ca-ES', // Catalan
|
||||||
|
'cs' => 'cs-CZ', // Czech
|
||||||
|
'cy' => 'cy-GB', // Welsh
|
||||||
|
'da' => 'da-DK', // Danish
|
||||||
|
'de-i' => 'de-if', // German informal
|
||||||
|
'de' => 'de-DE', // German
|
||||||
|
'el' => 'el-GR', // Greek
|
||||||
|
'en' => 'en-US', // English
|
||||||
|
'et' => 'et-EE', // Estonian
|
||||||
|
'fa' => 'fa-IR', // Persian
|
||||||
|
'fi' => 'fi-FI', // Finnish
|
||||||
|
'fil' => 'fil-PH', // Filipino
|
||||||
|
'fr' => 'fr-FR', // French
|
||||||
|
'he' => 'he-IL', // Hebrew
|
||||||
|
'hr' => 'hr-HR', // Croatian
|
||||||
|
'hu' => 'hu-HU', // Hungarian
|
||||||
|
'id' => 'id-ID', // Indonesian
|
||||||
|
'is' => 'is-IS', // Icelandic
|
||||||
|
'it' => 'it-IT', // Italian
|
||||||
|
'iu' => 'iu-NU', // Inuktitut
|
||||||
|
'ja' => 'ja-JP', // Japanese
|
||||||
|
'ko' => 'ko-KR', // Korean
|
||||||
|
'lt' => 'lt-LT', // Lithuanian
|
||||||
|
'lv' => 'lv-LV', // Latvian
|
||||||
|
'mi' => 'mi-NZ', // Maori
|
||||||
|
'mk' => 'mk-MK', // Macedonian
|
||||||
|
'mn' => 'mn-MN', // Mongolian
|
||||||
|
'ms' => 'ms-MY', // Malay
|
||||||
|
'nl' => 'nl-NL', // Dutch
|
||||||
|
'no' => 'no-NO', // Norwegian
|
||||||
|
'pl' => 'pl-PL', // Polish
|
||||||
|
'ro' => 'ro-RO', // Romanian
|
||||||
|
'ru' => 'ru-RU', // Russian
|
||||||
|
'sk' => 'sk-SK', // Slovak
|
||||||
|
'sl' => 'sl-SI', // Slovenian
|
||||||
|
'so' => 'so-SO', // Somali
|
||||||
|
'ta' => 'ta-IN', // Tamil
|
||||||
|
'th' => 'th-TH', // Thai
|
||||||
|
'tl' => 'tl-PH', // Tagalog
|
||||||
|
'tr' => 'tr-TR', // Turkish
|
||||||
|
'uk' => 'uk-UA', // Ukrainian
|
||||||
|
'vi' => 'vi-VN', // Vietnamese
|
||||||
|
'zu' => 'zu-ZA', // Zulu
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple helper to invoke the markdown parser
|
* Simple helper to invoke the markdown parser
|
||||||
*
|
*
|
||||||
|
@ -71,10 +130,14 @@ class Helper
|
||||||
*
|
*
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v3.3]
|
* @since [v3.3]
|
||||||
* @return array
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function defaultChartColors($index = 0)
|
public static function defaultChartColors(int $index = 0)
|
||||||
{
|
{
|
||||||
|
if ($index < 0) {
|
||||||
|
$index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$colors = [
|
$colors = [
|
||||||
'#008941',
|
'#008941',
|
||||||
'#FF4A46',
|
'#FF4A46',
|
||||||
|
@ -347,7 +410,19 @@ class Helper
|
||||||
$total_colors = count($colors);
|
$total_colors = count($colors);
|
||||||
|
|
||||||
if ($index >= $total_colors) {
|
if ($index >= $total_colors) {
|
||||||
$index = $index - $total_colors;
|
|
||||||
|
\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)
|
||||||
|
$index = $index - $total_colors - 1;
|
||||||
|
|
||||||
|
//constraints to keep result in 0-265 range. This should never be needed, but if something happens
|
||||||
|
//to create this many status labels and it DOES happen, this will keep it from failing at least.
|
||||||
|
if($index < 0) {
|
||||||
|
$index = 0;
|
||||||
|
}
|
||||||
|
elseif($index >($total_colors - 1)) {
|
||||||
|
$index = $total_colors - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $colors[$index];
|
return $colors[$index];
|
||||||
|
@ -643,6 +718,7 @@ class Helper
|
||||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||||
$components = Component::whereNotNull('min_amt')->get();
|
$components = Component::whereNotNull('min_amt')->get();
|
||||||
|
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
||||||
|
|
||||||
$avail_consumables = 0;
|
$avail_consumables = 0;
|
||||||
$items_array = [];
|
$items_array = [];
|
||||||
|
@ -705,6 +781,28 @@ class Helper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($asset_models as $asset_model){
|
||||||
|
|
||||||
|
$asset = new Asset();
|
||||||
|
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
|
||||||
|
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
|
||||||
|
|
||||||
|
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
|
||||||
|
if ($avail > 0) {
|
||||||
|
$percent = number_format((($avail / $total_owned) * 100), 0);
|
||||||
|
} else {
|
||||||
|
$percent = 100;
|
||||||
|
}
|
||||||
|
$items_array[$all_count]['id'] = $asset_model->id;
|
||||||
|
$items_array[$all_count]['name'] = $asset_model->name;
|
||||||
|
$items_array[$all_count]['type'] = 'models';
|
||||||
|
$items_array[$all_count]['percent'] = $percent;
|
||||||
|
$items_array[$all_count]['remaining'] = $avail;
|
||||||
|
$items_array[$all_count]['min_amt'] = $asset_model->min_amt;
|
||||||
|
$all_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $items_array;
|
return $items_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1276,7 +1374,7 @@ class Helper
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
|
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
|
||||||
* it seemed pretty safe to do here. Don't you judge me.
|
* it seemed pretty safe to do here. Don't you judge me.
|
||||||
*/
|
*/
|
||||||
public static function showDemoModeFieldWarning() {
|
public static function showDemoModeFieldWarning() {
|
||||||
|
@ -1284,4 +1382,55 @@ class Helper
|
||||||
return "<p class=\"text-warning\"><i class=\"fas fa-lock\"></i>" . trans('general.feature_disabled') . "</p>";
|
return "<p class=\"text-warning\"><i class=\"fas fa-lock\"></i>" . trans('general.feature_disabled') . "</p>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ah, legacy code.
|
||||||
|
*
|
||||||
|
* This corrects the original mistakes from 2013 where we used the wrong locale codes. Hopefully we
|
||||||
|
* can get rid of this in a future version, but this should at least give us the belt and suspenders we need
|
||||||
|
* to be sure this change is not too disruptive.
|
||||||
|
*
|
||||||
|
* In this array, we ONLY include the older languages where we weren't using the correct locale codes.
|
||||||
|
*
|
||||||
|
* @see public static $language_map in this file
|
||||||
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
|
* @since 6.3.0
|
||||||
|
*
|
||||||
|
* @param $language_code
|
||||||
|
* @return string []
|
||||||
|
*/
|
||||||
|
public static function mapLegacyLocale($language_code = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (strlen($language_code) > 4) {
|
||||||
|
return $language_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::$language_map as $legacy => $new) {
|
||||||
|
if ($language_code == $legacy) {
|
||||||
|
\Log::debug('Current language is '.$legacy.', using '.$new.' instead');
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return US english if we don't have a match
|
||||||
|
return 'en-US';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mapBackToLegacyLocale($new_locale = null)
|
||||||
|
{
|
||||||
|
if (strlen($new_locale) <= 4) {
|
||||||
|
return $new_locale; //"new locale" apparently wasn't quite so new
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if($legacy_locale !== false) {
|
||||||
|
return $legacy_locale;
|
||||||
|
}
|
||||||
|
return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,9 +146,8 @@ class AccessoriesFilesController extends Controller
|
||||||
$this->authorize('view', $accessory);
|
$this->authorize('view', $accessory);
|
||||||
$this->authorize('accessories.files', $accessory);
|
$this->authorize('accessories.files', $accessory);
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
|
||||||
->header('Content-Type', 'text/plain');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = 'private_uploads/accessories/'.$log->filename;
|
$file = 'private_uploads/accessories/'.$log->filename;
|
||||||
|
@ -161,22 +160,19 @@ class AccessoriesFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Display the file inline
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||||
// won't work, as they're not accessible via the web
|
// won't work, as they're not accessible via the web
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
} else {
|
|
||||||
if ($download != 'true') {
|
|
||||||
\Log::debug('display the file');
|
|
||||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,9 +60,10 @@ class AccessoryCheckinController extends Controller
|
||||||
|
|
||||||
$this->authorize('checkin', $accessory);
|
$this->authorize('checkin', $accessory);
|
||||||
|
|
||||||
$checkin_at = date('Y-m-d');
|
$checkin_hours = date('H:i:s');
|
||||||
|
$checkin_at = date('Y-m-d H:i:s');
|
||||||
if ($request->filled('checkin_at')) {
|
if ($request->filled('checkin_at')) {
|
||||||
$checkin_at = $request->input('checkin_at');
|
$checkin_at = $request->input('checkin_at').' '.$checkin_hours;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Was the accessory updated?
|
// Was the accessory updated?
|
||||||
|
|
|
@ -18,31 +18,36 @@ class AccessoryCheckoutController extends Controller
|
||||||
* Return the form to checkout an Accessory to a user.
|
* Return the form to checkout an Accessory to a user.
|
||||||
*
|
*
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @param int $accessoryId
|
* @param int $id
|
||||||
* @return View
|
* @return View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create($accessoryId)
|
public function create($id)
|
||||||
{
|
{
|
||||||
// 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.not_found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure there is at least one available to checkout
|
if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
|
||||||
if ($accessory->numRemaining() <= 0){
|
|
||||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($accessory->category) {
|
|
||||||
$this->authorize('checkout', $accessory);
|
$this->authorize('checkout', $accessory);
|
||||||
|
|
||||||
// Get the dropdown of users and then pass it to the checkout view
|
if ($accessory->category) {
|
||||||
return view('accessories/checkout', compact('accessory'));
|
// 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'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the checkout view
|
||||||
|
return view('accessories/checkout', compact('accessory'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid category
|
||||||
|
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
|
||||||
|
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
|
// Not found
|
||||||
|
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -69,7 +69,7 @@ class AcceptanceController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
return redirect()->route('account.accept')->with('error', trans('general.error_user_company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('account/accept.create', compact('acceptance'));
|
return view('account/accept.create', compact('acceptance'));
|
||||||
|
@ -245,6 +245,36 @@ class AcceptanceController extends Controller
|
||||||
$return_msg = trans('admin/users/message.accepted');
|
$return_msg = trans('admin/users/message.accepted');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for the eula-pdfs directory
|
||||||
|
*/
|
||||||
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||||
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
||||||
|
|
||||||
|
// Check if the signature directory exists, if not create it
|
||||||
|
if (!Storage::exists('private_uploads/signatures')) {
|
||||||
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The item was accepted, check for a signature
|
||||||
|
if ($request->filled('signature_output')) {
|
||||||
|
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
||||||
|
$data_uri = $request->input('signature_output');
|
||||||
|
$encoded_image = explode(',', $data_uri);
|
||||||
|
$decoded_image = base64_decode($encoded_image[1]);
|
||||||
|
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
||||||
|
|
||||||
|
// No image data is present, kick them back.
|
||||||
|
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
||||||
|
} else {
|
||||||
|
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Format the data to send the declined notification
|
// Format the data to send the declined notification
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
$branding_settings = SettingsController::getPDFBranding();
|
||||||
|
|
||||||
|
@ -281,11 +311,18 @@ class AcceptanceController extends Controller
|
||||||
'item_model' => $display_model,
|
'item_model' => $display_model,
|
||||||
'item_serial' => $item->serial,
|
'item_serial' => $item->serial,
|
||||||
'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,
|
||||||
'assigned_to' => $assigned_to,
|
'assigned_to' => $assigned_to,
|
||||||
'company_name' => $branding_settings->site_name,
|
'company_name' => $branding_settings->site_name,
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
'date_settings' => $branding_settings->date_display_format,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($pdf_view_route!='') {
|
||||||
|
\Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
||||||
|
$pdf = Pdf::loadView($pdf_view_route, $data);
|
||||||
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
||||||
|
}
|
||||||
|
|
||||||
$acceptance->decline($sig_filename);
|
$acceptance->decline($sig_filename);
|
||||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||||
event(new CheckoutDeclined($acceptance));
|
event(new CheckoutDeclined($acceptance));
|
||||||
|
|
|
@ -331,7 +331,7 @@ class AccessoriesController extends Controller
|
||||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||||
$this->authorize('checkin', $accessory);
|
$this->authorize('checkin', $accessory);
|
||||||
|
|
||||||
$logaction = $accessory->logCheckin(User::find($accessory_user->user_id), $request->input('note'));
|
$logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
|
||||||
|
|
||||||
// Was the accessory updated?
|
// Was the accessory updated?
|
||||||
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
||||||
|
|
|
@ -116,41 +116,17 @@ class AssetMaintenancesController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
// create a new model instance
|
// create a new model instance
|
||||||
$assetMaintenance = new AssetMaintenance();
|
$maintenance = new AssetMaintenance();
|
||||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
$maintenance->fill($request->all());
|
||||||
$assetMaintenance->is_warranty = $request->input('is_warranty');
|
$maintenance->user_id = Auth::id();
|
||||||
$assetMaintenance->cost = $request->input('cost');
|
|
||||||
$assetMaintenance->notes = e($request->input('notes'));
|
|
||||||
$asset = Asset::find(e($request->input('asset_id')));
|
|
||||||
|
|
||||||
if (! Company::isCurrentUserHasAccess($asset)) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot add a maintenance for that asset'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the asset maintenance data
|
|
||||||
$assetMaintenance->asset_id = $request->input('asset_id');
|
|
||||||
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
|
||||||
$assetMaintenance->title = $request->input('title');
|
|
||||||
$assetMaintenance->start_date = $request->input('start_date');
|
|
||||||
$assetMaintenance->completion_date = $request->input('completion_date');
|
|
||||||
$assetMaintenance->user_id = Auth::id();
|
|
||||||
|
|
||||||
if (($assetMaintenance->completion_date !== null)
|
|
||||||
&& ($assetMaintenance->start_date !== '')
|
|
||||||
&& ($assetMaintenance->start_date !== '0000-00-00')
|
|
||||||
) {
|
|
||||||
$startDate = Carbon::parse($assetMaintenance->start_date);
|
|
||||||
$completionDate = Carbon::parse($assetMaintenance->completion_date);
|
|
||||||
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was the asset maintenance created?
|
// Was the asset maintenance created?
|
||||||
if ($assetMaintenance->save()) {
|
if ($maintenance->save()) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.create.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.create.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
|
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,65 +134,39 @@ class AssetMaintenancesController extends Controller
|
||||||
* Validates and stores an update to an asset maintenance
|
* Validates and stores an update to an asset maintenance
|
||||||
*
|
*
|
||||||
* @author A. Gianotto <snipe@snipe.net>
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
* @param int $assetMaintenanceId
|
* @param int $id
|
||||||
* @param int $request
|
* @param int $request
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @return string JSON
|
* @return string JSON
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, $assetMaintenanceId = null)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
// Check if the asset maintenance exists
|
|
||||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
|
||||||
|
|
||||||
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
if ($maintenance = AssetMaintenance::with('asset')->find($id)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot edit a maintenance for that asset'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$assetMaintenance->supplier_id = e($request->input('supplier_id'));
|
// Can this user manage this asset?
|
||||||
$assetMaintenance->is_warranty = e($request->input('is_warranty'));
|
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||||
$assetMaintenance->cost = $request->input('cost');
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
|
||||||
$assetMaintenance->notes = e($request->input('notes'));
|
|
||||||
|
|
||||||
$asset = Asset::find(request('asset_id'));
|
|
||||||
|
|
||||||
if (! Company::isCurrentUserHasAccess($asset)) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot edit a maintenance for that asset'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the asset maintenance data
|
|
||||||
$assetMaintenance->asset_id = $request->input('asset_id');
|
|
||||||
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
|
||||||
$assetMaintenance->title = $request->input('title');
|
|
||||||
$assetMaintenance->start_date = $request->input('start_date');
|
|
||||||
$assetMaintenance->completion_date = $request->input('completion_date');
|
|
||||||
|
|
||||||
if (($assetMaintenance->completion_date == null)
|
|
||||||
) {
|
|
||||||
if (($assetMaintenance->asset_maintenance_time !== 0)
|
|
||||||
|| (! is_null($assetMaintenance->asset_maintenance_time))
|
|
||||||
) {
|
|
||||||
$assetMaintenance->asset_maintenance_time = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The asset this miantenance is attached to is not valid or has been deleted
|
||||||
|
if (!$maintenance->asset) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('general.asset'), 'id' => $id])));
|
||||||
|
}
|
||||||
|
|
||||||
|
$maintenance->fill($request->all());
|
||||||
|
|
||||||
|
if ($maintenance->save()) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.edit.success')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($assetMaintenance->completion_date !== null)
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id])));
|
||||||
&& ($assetMaintenance->start_date !== '')
|
|
||||||
&& ($assetMaintenance->start_date !== '0000-00-00')
|
|
||||||
) {
|
|
||||||
$startDate = Carbon::parse($assetMaintenance->start_date);
|
|
||||||
$completionDate = Carbon::parse($assetMaintenance->completion_date);
|
|
||||||
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was the asset maintenance created?
|
|
||||||
if ($assetMaintenance->save()) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.edit.success')));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,7 @@ class AssetModelsController extends Controller
|
||||||
'image',
|
'image',
|
||||||
'name',
|
'name',
|
||||||
'model_number',
|
'model_number',
|
||||||
|
'min_amt',
|
||||||
'eol',
|
'eol',
|
||||||
'notes',
|
'notes',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -45,6 +46,7 @@ class AssetModelsController extends Controller
|
||||||
'requestable',
|
'requestable',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
'category',
|
'category',
|
||||||
|
'fieldset',
|
||||||
];
|
];
|
||||||
|
|
||||||
$assetmodels = AssetModel::select([
|
$assetmodels = AssetModel::select([
|
||||||
|
@ -52,6 +54,7 @@ class AssetModelsController extends Controller
|
||||||
'models.image',
|
'models.image',
|
||||||
'models.name',
|
'models.name',
|
||||||
'model_number',
|
'model_number',
|
||||||
|
'min_amt',
|
||||||
'eol',
|
'eol',
|
||||||
'requestable',
|
'requestable',
|
||||||
'models.notes',
|
'models.notes',
|
||||||
|
@ -92,6 +95,9 @@ class AssetModelsController extends Controller
|
||||||
case 'category':
|
case 'category':
|
||||||
$assetmodels->OrderCategory($order);
|
$assetmodels->OrderCategory($order);
|
||||||
break;
|
break;
|
||||||
|
case 'fieldset':
|
||||||
|
$assetmodels->OrderFieldset($order);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$assetmodels->orderBy($sort, $order);
|
$assetmodels->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3,15 +3,16 @@
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Events\CheckoutableCheckedIn;
|
use App\Events\CheckoutableCheckedIn;
|
||||||
|
use App\Http\Requests\StoreAssetRequest;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\AssetCheckoutRequest;
|
use App\Http\Requests\AssetCheckoutRequest;
|
||||||
use App\Http\Transformers\AssetsTransformer;
|
use App\Http\Transformers\AssetsTransformer;
|
||||||
use App\Http\Transformers\DepreciationReportTransformer;
|
|
||||||
use App\Http\Transformers\LicensesTransformer;
|
use App\Http\Transformers\LicensesTransformer;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetModel;
|
use App\Models\AssetModel;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
|
@ -20,11 +21,12 @@ 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 Auth;
|
use \Illuminate\Support\Facades\Auth;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use DB;
|
use 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 Input;
|
use Input;
|
||||||
use Paginator;
|
use Paginator;
|
||||||
use Slack;
|
use Slack;
|
||||||
|
@ -33,6 +35,7 @@ use TCPDF;
|
||||||
use Validator;
|
use Validator;
|
||||||
use Route;
|
use Route;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class controls all actions related to assets for
|
* This class controls all actions related to assets for
|
||||||
* the Snipe-IT Asset Management application.
|
* the Snipe-IT Asset Management application.
|
||||||
|
@ -48,7 +51,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function index(Request $request, $audit = null)
|
public function index(Request $request, $audit = null)
|
||||||
{
|
{
|
||||||
|
@ -133,7 +136,7 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
// Search custom fields by column name
|
// Search custom fields by column name
|
||||||
foreach ($all_custom_fields as $field) {
|
foreach ($all_custom_fields as $field) {
|
||||||
if ($request->filled($field->db_column_name())) {
|
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
|
||||||
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,7 +298,7 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('order_number')) {
|
if ($request->filled('order_number')) {
|
||||||
$assets->where('assets.order_number', '=', $request->get('order_number'));
|
$assets->where('assets.order_number', '=', strval($request->get('order_number')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is kinda gross, but we need to do this because the Bootstrap Tables
|
// This is kinda gross, but we need to do this because the Bootstrap Tables
|
||||||
|
@ -346,7 +349,7 @@ class AssetsController 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') > $assets->count()) ? $assets->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$total = $assets->count();
|
$total = $assets->count();
|
||||||
|
@ -443,7 +446,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, $id)
|
public function show(Request $request, $id)
|
||||||
{
|
{
|
||||||
|
@ -474,7 +477,7 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -530,38 +533,14 @@ 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 JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function store(ImageUploadRequest $request)
|
public function store(StoreAssetRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('create', Asset::class);
|
|
||||||
|
|
||||||
$asset = new Asset();
|
$asset = new Asset();
|
||||||
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
|
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
|
||||||
|
|
||||||
$asset->name = $request->get('name');
|
$asset->fill($request->validated());
|
||||||
$asset->serial = $request->get('serial');
|
$asset->user_id = Auth::id();
|
||||||
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id'));
|
|
||||||
$asset->model_id = $request->get('model_id');
|
|
||||||
$asset->order_number = $request->get('order_number');
|
|
||||||
$asset->notes = $request->get('notes');
|
|
||||||
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset()); //yup, problem :/
|
|
||||||
// NO IT IS NOT!!! This is never firing; we SHOW the asset_tag you're going to get, so it *will* be filled in!
|
|
||||||
$asset->user_id = Auth::id();
|
|
||||||
$asset->archived = '0';
|
|
||||||
$asset->physical = '1';
|
|
||||||
$asset->depreciate = '0';
|
|
||||||
$asset->status_id = $request->get('status_id', 0);
|
|
||||||
$asset->warranty_months = $request->get('warranty_months', null);
|
|
||||||
$asset->purchase_cost = $request->get('purchase_cost');
|
|
||||||
$asset->asset_eol_date = $request->get('asset_eol_date', $asset->present()->eol_date());
|
|
||||||
$asset->purchase_date = $request->get('purchase_date', null);
|
|
||||||
$asset->assigned_to = $request->get('assigned_to', null);
|
|
||||||
$asset->supplier_id = $request->get('supplier_id');
|
|
||||||
$asset->requestable = $request->get('requestable', 0);
|
|
||||||
$asset->rtd_location_id = $request->get('rtd_location_id', null);
|
|
||||||
$asset->location_id = $request->get('rtd_location_id', null);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is here just legacy reasons. Api\AssetController
|
* this is here just legacy reasons. Api\AssetController
|
||||||
|
@ -574,10 +553,11 @@ class AssetsController extends Controller
|
||||||
$asset = $request->handleImages($asset);
|
$asset = $request->handleImages($asset);
|
||||||
|
|
||||||
// Update custom fields in the database.
|
// Update custom fields in the database.
|
||||||
// Validation for these fields is handled through the AssetRequest form request
|
$model = AssetModel::find($request->input('model_id'));
|
||||||
$model = AssetModel::find($request->get('model_id'));
|
|
||||||
|
|
||||||
if (($model) && ($model->fieldset)) {
|
// Check that it's an object and not a collection
|
||||||
|
// (Sometimes people send arrays here and they shouldn't
|
||||||
|
if (($model) && ($model instanceof AssetModel) && ($model->fieldset)) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
foreach ($model->fieldset->fields as $field) {
|
||||||
|
|
||||||
// Set the field value based on what was sent in the request
|
// Set the field value based on what was sent in the request
|
||||||
|
@ -585,22 +565,22 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
// If input value is null, use custom field's default value
|
// If input value is null, use custom field's default value
|
||||||
if ($field_val == null) {
|
if ($field_val == null) {
|
||||||
\Log::debug('Field value for '.$field->db_column.' is null');
|
Log::debug('Field value for '.$field->db_column.' is null');
|
||||||
$field_val = $field->defaultValue($request->get('model_id'));
|
$field_val = $field->defaultValue($request->get('model_id'));
|
||||||
\Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
|
Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the field is set to encrypted, make sure we encrypt the value
|
// if the field is set to encrypted, make sure we encrypt the value
|
||||||
if ($field->field_encrypted == '1') {
|
if ($field->field_encrypted == '1') {
|
||||||
\Log::debug('This model field is encrypted in this fieldset.');
|
Log::debug('This model field is encrypted in this fieldset.');
|
||||||
|
|
||||||
if (Gate::allows('admin')) {
|
if (Gate::allows('admin')) {
|
||||||
|
|
||||||
// If input value is null, use custom field's default value
|
// If input value is null, use custom field's default value
|
||||||
if (($field_val == null) && ($request->has('model_id') != '')) {
|
if (($field_val == null) && ($request->has('model_id') != '')) {
|
||||||
$field_val = \Crypt::encrypt($field->defaultValue($request->get('model_id')));
|
$field_val = Crypt::encrypt($field->defaultValue($request->get('model_id')));
|
||||||
} else {
|
} else {
|
||||||
$field_val = \Crypt::encrypt($request->input($field->db_column));
|
$field_val = Crypt::encrypt($request->input($field->db_column));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -639,7 +619,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function update(ImageUploadRequest $request, $id)
|
public function update(ImageUploadRequest $request, $id)
|
||||||
{
|
{
|
||||||
|
@ -667,9 +647,10 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset = $request->handleImages($asset);
|
$asset = $request->handleImages($asset);
|
||||||
|
$model = AssetModel::find($asset->model_id);
|
||||||
|
|
||||||
// Update custom fields
|
// Update custom fields
|
||||||
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
|
if (($model) && (isset($model->fieldset))) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
foreach ($model->fieldset->fields as $field) {
|
||||||
if ($request->has($field->db_column)) {
|
if ($request->has($field->db_column)) {
|
||||||
if ($field->field_encrypted == '1') {
|
if ($field->field_encrypted == '1') {
|
||||||
|
@ -720,7 +701,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
|
@ -749,38 +730,28 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function restore(Request $request, $assetId = null)
|
public function restore(Request $request, $assetId = null)
|
||||||
{
|
{
|
||||||
// Get asset information
|
|
||||||
$asset = Asset::withTrashed()->find($assetId);
|
|
||||||
$this->authorize('delete', $asset);
|
|
||||||
|
|
||||||
if (isset($asset->id)) {
|
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||||
|
$this->authorize('delete', $asset);
|
||||||
|
|
||||||
if ($asset->deleted_at=='') {
|
if ($asset->deleted_at == '') {
|
||||||
$message = 'Asset was not deleted. No data was changed.';
|
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.asset')])), 200);
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
$message = trans('admin/hardware/message.restore.success');
|
|
||||||
// Restore the asset
|
|
||||||
Asset::withTrashed()->where('id', $assetId)->restore();
|
|
||||||
|
|
||||||
$logaction = new Actionlog();
|
|
||||||
$logaction->item_type = Asset::class;
|
|
||||||
$logaction->item_id = $asset->id;
|
|
||||||
$logaction->created_at = date("Y-m-d H:i:s");
|
|
||||||
$logaction->user_id = Auth::user()->id;
|
|
||||||
$logaction->logaction('restored');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset, $request), $message));
|
if ($asset->restore()) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/hardware/message.restore.success')), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()])), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -789,7 +760,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
|
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
|
||||||
{
|
{
|
||||||
|
@ -805,7 +776,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function checkout(AssetCheckoutRequest $request, $asset_id)
|
public function checkout(AssetCheckoutRequest $request, $asset_id)
|
||||||
{
|
{
|
||||||
|
@ -889,22 +860,27 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function checkin(Request $request, $asset_id)
|
public function checkin(Request $request, $asset_id)
|
||||||
{
|
{
|
||||||
$this->authorize('checkin', Asset::class);
|
$this->authorize('checkin', Asset::class);
|
||||||
$asset = Asset::findOrFail($asset_id);
|
$asset = Asset::with('model')->findOrFail($asset_id);
|
||||||
$this->authorize('checkin', $asset);
|
$this->authorize('checkin', $asset);
|
||||||
|
|
||||||
|
|
||||||
$target = $asset->assignedTo;
|
$target = $asset->assignedTo;
|
||||||
if (is_null($target)) {
|
if (is_null($target)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.already_checked_in')));
|
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||||
|
'asset_tag'=> e($asset->asset_tag),
|
||||||
|
'model' => e($asset->model->name),
|
||||||
|
'model_number' => e($asset->model->model_number)
|
||||||
|
], trans('admin/hardware/message.checkin.already_checked_in')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$asset->expected_checkin = null;
|
$asset->expected_checkin = null;
|
||||||
$asset->last_checkout = null;
|
$asset->last_checkout = null;
|
||||||
|
$asset->last_checkin = now();
|
||||||
$asset->assigned_to = null;
|
$asset->assigned_to = null;
|
||||||
$asset->assignedTo()->disassociate($asset);
|
$asset->assignedTo()->disassociate($asset);
|
||||||
$asset->accepted = null;
|
$asset->accepted = null;
|
||||||
|
@ -924,12 +900,20 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
||||||
|
$originalValues = $asset->getRawOriginal();
|
||||||
|
|
||||||
|
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||||
|
$originalValues['action_date'] = $checkin_at;
|
||||||
|
}
|
||||||
|
|
||||||
if ($asset->save()) {
|
if ($asset->save()) {
|
||||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||||
|
'asset_tag'=> e($asset->asset_tag),
|
||||||
|
'model' => e($asset->model->name),
|
||||||
|
'model_number' => e($asset->model->model_number)
|
||||||
|
], trans('admin/hardware/message.checkin.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
||||||
|
@ -940,7 +924,7 @@ class AssetsController extends Controller
|
||||||
*
|
*
|
||||||
* @author [A. Janes] [<ajanes@adagiohealth.org>]
|
* @author [A. Janes] [<ajanes@adagiohealth.org>]
|
||||||
* @since [v6.0]
|
* @since [v6.0]
|
||||||
* @return JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function checkinByTag(Request $request, $tag = null)
|
public function checkinByTag(Request $request, $tag = null)
|
||||||
{
|
{
|
||||||
|
@ -966,7 +950,7 @@ 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 JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function audit(Request $request)
|
public function audit(Request $request)
|
||||||
|
|
||||||
|
@ -1027,24 +1011,54 @@ class AssetsController extends Controller
|
||||||
*
|
*
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @return JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function requestable(Request $request)
|
public function requestable(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('viewRequestable', Asset::class);
|
$this->authorize('viewRequestable', Asset::class);
|
||||||
|
|
||||||
|
$allowed_columns = [
|
||||||
|
'name',
|
||||||
|
'asset_tag',
|
||||||
|
'serial',
|
||||||
|
'model_number',
|
||||||
|
'image',
|
||||||
|
'purchase_cost',
|
||||||
|
'expected_checkin',
|
||||||
|
];
|
||||||
|
|
||||||
|
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||||
|
|
||||||
|
foreach ($all_custom_fields as $field) {
|
||||||
|
$allowed_columns[] = $field->db_column_name();
|
||||||
|
}
|
||||||
|
|
||||||
$assets = Asset::select('assets.*')
|
$assets = Asset::select('assets.*')
|
||||||
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
|
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
|
||||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')
|
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
|
||||||
->requestableAssets();
|
->requestableAssets();
|
||||||
|
|
||||||
$offset = request('offset', 0);
|
|
||||||
$limit = $request->input('limit', 50);
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$assets->TextSearch($request->input('search'));
|
$assets->TextSearch($request->input('search'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search custom fields by column name
|
||||||
|
foreach ($all_custom_fields as $field) {
|
||||||
|
if ($request->filled($field->db_column_name())) {
|
||||||
|
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
$sort_override = str_replace('custom_fields.', '', $request->input('sort'));
|
||||||
|
|
||||||
|
// This handles all the pivot sorting (versus the assets.* fields
|
||||||
|
// in the allowed_columns array)
|
||||||
|
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
|
||||||
|
|
||||||
switch ($request->input('sort')) {
|
switch ($request->input('sort')) {
|
||||||
case 'model':
|
case 'model':
|
||||||
$assets->OrderModels($order);
|
$assets->OrderModels($order);
|
||||||
|
@ -1052,17 +1066,19 @@ class AssetsController extends Controller
|
||||||
case 'model_number':
|
case 'model_number':
|
||||||
$assets->OrderModelNumber($order);
|
$assets->OrderModelNumber($order);
|
||||||
break;
|
break;
|
||||||
case 'category':
|
case 'location':
|
||||||
$assets->OrderCategory($order);
|
$assets->OrderLocation($order);
|
||||||
break;
|
|
||||||
case 'manufacturer':
|
|
||||||
$assets->OrderManufacturer($order);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$assets->orderBy('assets.created_at', $order);
|
$assets->orderBy($column_sort, $order);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
|
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
|
||||||
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$total = $assets->count();
|
$total = $assets->count();
|
||||||
$assets = $assets->skip($offset)->take($limit)->get();
|
$assets = $assets->skip($offset)->take($limit)->get();
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ class CategoriesController 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') > $categories->count()) ? $categories->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -56,7 +56,7 @@ class CompaniesController 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') > $companies->count()) ? $companies->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ use App\Events\CheckoutableCheckedIn;
|
||||||
use App\Events\ComponentCheckedIn;
|
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;
|
||||||
|
|
||||||
class ComponentsController extends Controller
|
class ComponentsController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -76,7 +77,7 @@ class ComponentsController 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') > $components->count()) ? $components->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $components->count()) ? $components->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
@ -203,12 +204,29 @@ class ComponentsController extends Controller
|
||||||
$this->authorize('view', \App\Models\Asset::class);
|
$this->authorize('view', \App\Models\Asset::class);
|
||||||
|
|
||||||
$component = Component::findOrFail($id);
|
$component = Component::findOrFail($id);
|
||||||
$assets = $component->assets();
|
|
||||||
|
|
||||||
$offset = request('offset', 0);
|
$offset = request('offset', 0);
|
||||||
$limit = $request->input('limit', 50);
|
$limit = $request->input('limit', 50);
|
||||||
$total = $assets->count();
|
|
||||||
$assets = $assets->skip($offset)->take($limit)->get();
|
if ($request->filled('search')) {
|
||||||
|
$assets = $component->assets()
|
||||||
|
->where(function ($query) use ($request) {
|
||||||
|
$search_str = '%' . $request->input('search') . '%';
|
||||||
|
$query->where('name', 'like', $search_str)
|
||||||
|
->orWhereIn('model_id', function (Builder $query) use ($request) {
|
||||||
|
$search_str = '%' . $request->input('search') . '%';
|
||||||
|
$query->selectRaw('id')->from('models')->where('name', 'like', $search_str);
|
||||||
|
})
|
||||||
|
->orWhere('asset_tag', 'like', $search_str);
|
||||||
|
})
|
||||||
|
->get();
|
||||||
|
$total = $assets->count();
|
||||||
|
} else {
|
||||||
|
$assets = $component->assets();
|
||||||
|
|
||||||
|
$total = $assets->count();
|
||||||
|
$assets = $assets->skip($offset)->take($limit)->get();
|
||||||
|
}
|
||||||
|
|
||||||
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
|
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
|
||||||
}
|
}
|
||||||
|
@ -245,7 +263,7 @@ class ComponentsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there is at least one available to checkout
|
// Make sure there is at least one available to checkout
|
||||||
if ($component->numRemaining() <= $request->get('assigned_qty')) {
|
if ($component->numRemaining() < $request->get('assigned_qty')) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ 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() : abs($request->input('offset'));
|
$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'];
|
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
|
||||||
|
@ -263,9 +263,14 @@ class ConsumablesController extends Controller
|
||||||
// Make sure there is at least one available to checkout
|
// Make sure there is at least one available to checkout
|
||||||
if ($consumable->numRemaining() <= 0) {
|
if ($consumable->numRemaining() <= 0) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
|
||||||
\Log::debug('No enough remaining');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure there is a valid category
|
||||||
|
if (!$consumable->category){
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
|
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
|
||||||
if (!$user = User::find($request->input('assigned_to'))) {
|
if (!$user = User::find($request->input('assigned_to'))) {
|
||||||
// Return error message
|
// Return error message
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DepartmentsController extends Controller
|
||||||
$this->authorize('view', Department::class);
|
$this->authorize('view', Department::class);
|
||||||
$allowed_columns = ['id', 'name', 'image', 'users_count'];
|
$allowed_columns = ['id', 'name', 'image', 'users_count'];
|
||||||
|
|
||||||
$departments = Company::scopeCompanyables(Department::select(
|
$departments = Department::select(
|
||||||
'departments.id',
|
'departments.id',
|
||||||
'departments.name',
|
'departments.name',
|
||||||
'departments.phone',
|
'departments.phone',
|
||||||
|
@ -37,8 +37,8 @@ class DepartmentsController extends Controller
|
||||||
'departments.manager_id',
|
'departments.manager_id',
|
||||||
'departments.created_at',
|
'departments.created_at',
|
||||||
'departments.updated_at',
|
'departments.updated_at',
|
||||||
'departments.image'),
|
'departments.image'
|
||||||
"company_id", "departments")->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$departments = $departments->TextSearch($request->input('search'));
|
$departments = $departments->TextSearch($request->input('search'));
|
||||||
|
@ -61,7 +61,7 @@ class DepartmentsController 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') > $departments->count()) ? $departments->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -29,7 +29,7 @@ class DepreciationsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -36,7 +36,7 @@ class GroupsController 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') > $groups->count()) ? $groups->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
@ -63,7 +63,7 @@ class GroupsController extends Controller
|
||||||
$group = new Group;
|
$group = new Group;
|
||||||
|
|
||||||
$group->name = $request->input('name');
|
$group->name = $request->input('name');
|
||||||
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
|
$group->permissions = json_encode($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.create.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));
|
||||||
|
|
|
@ -41,7 +41,7 @@ class LicenseSeatsController extends Controller
|
||||||
$total = $seats->count();
|
$total = $seats->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') > $seats->count()) ? $seats->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $seats->count()) ? $seats->count() : app('api_offset_value');
|
||||||
|
|
||||||
if ($offset >= $total ){
|
if ($offset >= $total ){
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
|
|
|
@ -95,7 +95,7 @@ class LicensesController 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') > $licenses->count()) ? $licenses->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -81,7 +81,7 @@ class LocationsController 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') > $locations->count()) ? $locations->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -6,9 +6,11 @@ use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\ManufacturersTransformer;
|
use App\Http\Transformers\ManufacturersTransformer;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
|
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;
|
||||||
|
|
||||||
class ManufacturersController extends Controller
|
class ManufacturersController extends Controller
|
||||||
|
@ -62,7 +64,7 @@ class ManufacturersController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
@ -159,6 +161,44 @@ class ManufacturersController extends Controller
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a given Manufacturer (mark as un-deleted)
|
||||||
|
*
|
||||||
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
|
* @since [v6.3.4]
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
|
*/
|
||||||
|
public function restore($id)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Manufacturer::class);
|
||||||
|
|
||||||
|
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
|
||||||
|
|
||||||
|
if ($manufacturer->deleted_at == '') {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')])), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manufacturer->restore()) {
|
||||||
|
|
||||||
|
$logaction = new Actionlog();
|
||||||
|
$logaction->item_type = Manufacturer::class;
|
||||||
|
$logaction->item_id = $manufacturer->id;
|
||||||
|
$logaction->created_at = date('Y-m-d H:i:s');
|
||||||
|
$logaction->user_id = Auth::user()->id;
|
||||||
|
$logaction->logaction('restore');
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation to make sure we're not restoring an item with the same unique attributes as a non-deleted one
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()])), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a paginated collection for the select2 menus
|
* Gets a paginated collection for the select2 menus
|
||||||
*
|
*
|
||||||
|
|
|
@ -30,7 +30,7 @@ class PredefinedKitsController 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') > $kits->count()) ? $kits->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $kits->count()) ? $kits->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
|
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
|
||||||
|
|
|
@ -11,6 +11,7 @@ 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 DB;
|
use DB;
|
||||||
|
|
||||||
class ProfileController extends Controller
|
class ProfileController extends Controller
|
||||||
|
@ -48,14 +49,23 @@ class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
|
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
|
||||||
|
|
||||||
$results = [];
|
$results = array();
|
||||||
|
$show_field = array();
|
||||||
|
$showable_fields = array();
|
||||||
$results['total'] = $checkoutRequests->count();
|
$results['total'] = $checkoutRequests->count();
|
||||||
|
|
||||||
|
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||||
|
foreach ($all_custom_fields as $field) {
|
||||||
|
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
|
||||||
|
$showable_fields[] = $field->db_column_name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($checkoutRequests as $checkoutRequest) {
|
foreach ($checkoutRequests as $checkoutRequest) {
|
||||||
|
|
||||||
// Make sure the asset and request still exist
|
// Make sure the asset and request still exist
|
||||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||||
$results['rows'][] = [
|
$assets = [
|
||||||
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
||||||
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
||||||
'type' => e($checkoutRequest->itemType()),
|
'type' => e($checkoutRequest->itemType()),
|
||||||
|
@ -64,7 +74,16 @@ class ProfileController extends Controller
|
||||||
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
|
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
|
||||||
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
|
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
foreach ($showable_fields as $showable_field_name) {
|
||||||
|
$show_field['custom_fields.'.$showable_field_name] = $checkoutRequest->itemRequested()->{$showable_field_name};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the plain asset data and the custom fields data
|
||||||
|
$results['rows'][] = array_merge($assets, $show_field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
|
|
|
@ -40,6 +40,14 @@ class ReportsController extends Controller
|
||||||
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
|
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('action_source')) {
|
||||||
|
$actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('remote_ip')) {
|
||||||
|
$actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('uploads')) {
|
if ($request->filled('uploads')) {
|
||||||
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
|
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
@ -52,11 +60,14 @@ class ReportsController extends Controller
|
||||||
'accept_signature',
|
'accept_signature',
|
||||||
'action_type',
|
'action_type',
|
||||||
'note',
|
'note',
|
||||||
|
'remote_ip',
|
||||||
|
'user_agent',
|
||||||
|
'action_source',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// 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() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : 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';
|
||||||
|
|
|
@ -52,7 +52,7 @@ class StatuslabelsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SuppliersController extends Controller
|
||||||
];
|
];
|
||||||
|
|
||||||
$suppliers = Supplier::select(
|
$suppliers = Supplier::select(
|
||||||
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes'])
|
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url'])
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
->withCount('licenses as licenses_count')
|
->withCount('licenses as licenses_count')
|
||||||
->withCount('accessories as accessories_count')
|
->withCount('accessories as accessories_count')
|
||||||
|
@ -94,7 +94,7 @@ class SuppliersController 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') > $suppliers->count()) ? $suppliers->count() : abs($request->input('offset'));
|
$offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Http\Transformers\ConsumablesTransformer;
|
||||||
use App\Http\Transformers\LicensesTransformer;
|
use App\Http\Transformers\LicensesTransformer;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
use App\Http\Transformers\UsersTransformer;
|
use App\Http\Transformers\UsersTransformer;
|
||||||
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
|
@ -75,7 +76,6 @@ class UsersController extends Controller
|
||||||
|
|
||||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
|
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
|
||||||
->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');
|
||||||
$users = Company::scopeCompanyables($users);
|
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('activated')) {
|
if ($request->filled('activated')) {
|
||||||
|
@ -192,11 +192,6 @@ class UsersController extends Controller
|
||||||
|
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
|
||||||
$offset = ($request->input('offset') > $users->count()) ? $users->count() : abs($request->input('offset'));
|
|
||||||
$limit = app('api_limit_value');
|
|
||||||
|
|
||||||
|
|
||||||
switch ($request->input('sort')) {
|
switch ($request->input('sort')) {
|
||||||
case 'manager':
|
case 'manager':
|
||||||
$users = $users->OrderManager($order);
|
$users = $users->OrderManager($order);
|
||||||
|
@ -272,6 +267,18 @@ class UsersController extends Controller
|
||||||
$users = $users->withTrashed();
|
$users = $users->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$users = Company::scopeCompanyables($users);
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
|
$offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
|
||||||
|
$limit = app('api_limit_value');
|
||||||
|
|
||||||
|
\Log::debug('Requested offset: '. $request->input('offset'));
|
||||||
|
\Log::debug('App offset: '. app('api_offset_value'));
|
||||||
|
\Log::debug('Actual offset: '. $offset);
|
||||||
|
\Log::debug('Limit: '. $limit);
|
||||||
|
|
||||||
$total = $users->count();
|
$total = $users->count();
|
||||||
$users = $users->skip($offset)->take($limit)->get();
|
$users = $users->skip($offset)->take($limit)->get();
|
||||||
|
|
||||||
|
@ -351,6 +358,7 @@ class UsersController extends Controller
|
||||||
|
|
||||||
$user = new User;
|
$user = new User;
|
||||||
$user->fill($request->all());
|
$user->fill($request->all());
|
||||||
|
$user->created_by = Auth::user()->id;
|
||||||
|
|
||||||
if ($request->has('permissions')) {
|
if ($request->has('permissions')) {
|
||||||
$permissions_array = $request->input('permissions');
|
$permissions_array = $request->input('permissions');
|
||||||
|
@ -362,8 +370,12 @@ class UsersController extends Controller
|
||||||
$user->permissions = $permissions_array;
|
$user->permissions = $permissions_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40);
|
//
|
||||||
$user->password = bcrypt($request->get('password', $tmp_pass));
|
if ($request->filled('password')) {
|
||||||
|
$user->password = bcrypt($request->get('password'));
|
||||||
|
} else {
|
||||||
|
$user->password = $user->noPassword();
|
||||||
|
}
|
||||||
|
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||||
|
|
||||||
|
@ -683,17 +695,31 @@ class UsersController extends Controller
|
||||||
*/
|
*/
|
||||||
public function restore($userId = null)
|
public function restore($userId = null)
|
||||||
{
|
{
|
||||||
// Get asset information
|
|
||||||
$user = User::withTrashed()->find($userId);
|
|
||||||
$this->authorize('delete', $user);
|
|
||||||
if (isset($user->id)) {
|
|
||||||
// Restore the user
|
|
||||||
User::withTrashed()->where('id', $userId)->restore();
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
|
if ($user = User::withTrashed()->find($userId)) {
|
||||||
|
$this->authorize('delete', $user);
|
||||||
|
|
||||||
|
if ($user->deleted_at == '') {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.user')])), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->restore()) {
|
||||||
|
|
||||||
|
$logaction = new Actionlog();
|
||||||
|
$logaction->item_type = User::class;
|
||||||
|
$logaction->item_id = $user->id;
|
||||||
|
$logaction->created_at = date('Y-m-d H:i:s');
|
||||||
|
$logaction->user_id = Auth::user()->id;
|
||||||
|
$logaction->logaction('restore');
|
||||||
|
|
||||||
|
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $userId;
|
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', compact('id'))), 200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,12 @@ 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\Actionlog;
|
||||||
|
use App\Models\Asset;
|
||||||
use App\Models\AssetModel;
|
use App\Models\AssetModel;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
@ -76,6 +80,7 @@ class AssetModelsController extends Controller
|
||||||
$model->depreciation_id = $request->input('depreciation_id');
|
$model->depreciation_id = $request->input('depreciation_id');
|
||||||
$model->name = $request->input('name');
|
$model->name = $request->input('name');
|
||||||
$model->model_number = $request->input('model_number');
|
$model->model_number = $request->input('model_number');
|
||||||
|
$model->min_amt = $request->input('min_amt');
|
||||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
|
@ -83,7 +88,7 @@ class AssetModelsController extends Controller
|
||||||
$model->requestable = Request::has('requestable');
|
$model->requestable = Request::has('requestable');
|
||||||
|
|
||||||
if ($request->input('fieldset_id') != '') {
|
if ($request->input('fieldset_id') != '') {
|
||||||
$model->fieldset_id = e($request->input('fieldset_id'));
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$model = $request->handleImages($model);
|
$model = $request->handleImages($model);
|
||||||
|
@ -96,7 +101,6 @@ class AssetModelsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to the new model page
|
|
||||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.create.success'));
|
return redirect()->route('models.index')->with('success', trans('admin/models/message.create.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +157,7 @@ class AssetModelsController extends Controller
|
||||||
$model->eol = $request->input('eol');
|
$model->eol = $request->input('eol');
|
||||||
$model->name = $request->input('name');
|
$model->name = $request->input('name');
|
||||||
$model->model_number = $request->input('model_number');
|
$model->model_number = $request->input('model_number');
|
||||||
|
$model->min_amt = $request->input('min_amt');
|
||||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
|
@ -160,19 +165,28 @@ class AssetModelsController extends Controller
|
||||||
|
|
||||||
$this->removeCustomFieldsDefaultValues($model);
|
$this->removeCustomFieldsDefaultValues($model);
|
||||||
|
|
||||||
if ($request->input('fieldset_id') == '') {
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
$model->fieldset_id = null;
|
|
||||||
} else {
|
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
|
||||||
|
|
||||||
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'))){
|
||||||
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($model->save()) {
|
if ($model->save()) {
|
||||||
|
if ($model->wasChanged('eol')) {
|
||||||
|
if ($model->eol > 0) {
|
||||||
|
$newEol = $model->eol;
|
||||||
|
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
|
||||||
|
->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
|
||||||
|
} elseif ($model->eol == 0) {
|
||||||
|
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
|
||||||
|
->update(['asset_eol_date' => DB::raw('null')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
|
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +208,7 @@ class AssetModelsController extends Controller
|
||||||
$this->authorize('delete', AssetModel::class);
|
$this->authorize('delete', AssetModel::class);
|
||||||
// Check if the model exists
|
// Check if the model exists
|
||||||
if (is_null($model = AssetModel::find($modelId))) {
|
if (is_null($model = AssetModel::find($modelId))) {
|
||||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.not_found'));
|
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($model->assets()->count() > 0) {
|
if ($model->assets()->count() > 0) {
|
||||||
|
@ -222,22 +236,42 @@ 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 $id
|
||||||
* @return Redirect
|
* @return Redirect
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function getRestore($modelId = null)
|
public function getRestore($id)
|
||||||
{
|
{
|
||||||
$this->authorize('create', AssetModel::class);
|
$this->authorize('create', AssetModel::class);
|
||||||
// Get user information
|
|
||||||
$model = AssetModel::withTrashed()->find($modelId);
|
|
||||||
|
|
||||||
if (isset($model->id)) {
|
if ($model = AssetModel::withTrashed()->find($id)) {
|
||||||
$model->restore();
|
|
||||||
|
|
||||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
if ($model->deleted_at == '') {
|
||||||
|
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset_model')]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($model->restore()) {
|
||||||
|
$logaction = new Actionlog();
|
||||||
|
$logaction->item_type = User::class;
|
||||||
|
$logaction->item_id = $model->id;
|
||||||
|
$logaction->created_at = date('Y-m-d H:i:s');
|
||||||
|
$logaction->user_id = Auth::user()->id;
|
||||||
|
$logaction->logaction('restore');
|
||||||
|
|
||||||
|
|
||||||
|
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||||
|
$deleted_models = AssetModel::onlyTrashed()->count();
|
||||||
|
if ($deleted_models > 0) {
|
||||||
|
return redirect()->back()->with('success', trans('admin/models/message.restore.success'));
|
||||||
|
}
|
||||||
|
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation
|
||||||
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset_model'), 'error' => $model->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
|
|
||||||
|
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +320,7 @@ class AssetModelsController extends Controller
|
||||||
return view('models/edit')
|
return view('models/edit')
|
||||||
->with('depreciation_list', Helper::depreciationList())
|
->with('depreciation_list', Helper::depreciationList())
|
||||||
->with('item', $model)
|
->with('item', $model)
|
||||||
|
->with('model_id', $model_to_clone->id)
|
||||||
->with('clone_model', $model_to_clone);
|
->with('clone_model', $model_to_clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ class AssetModelsFilesController extends Controller
|
||||||
* @return View
|
* @return View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function show($modelId = null, $fileId = null, $download = true)
|
public function show($modelId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
$model = AssetModel::find($modelId);
|
$model = AssetModel::find($modelId);
|
||||||
// the asset is valid
|
// the asset is valid
|
||||||
|
@ -99,12 +99,13 @@ class AssetModelsFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($download != 'true') {
|
if (request('inline') == 'true') {
|
||||||
if ($contents = file_get_contents(Storage::url($file))) {
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
|
|
|
@ -68,6 +68,7 @@ class AssetCheckinController extends Controller
|
||||||
|
|
||||||
$asset->expected_checkin = null;
|
$asset->expected_checkin = null;
|
||||||
$asset->last_checkout = null;
|
$asset->last_checkout = null;
|
||||||
|
$asset->last_checkin = now();
|
||||||
$asset->assigned_to = null;
|
$asset->assigned_to = null;
|
||||||
$asset->assignedTo()->disassociate($asset);
|
$asset->assignedTo()->disassociate($asset);
|
||||||
$asset->assigned_type = null;
|
$asset->assigned_type = null;
|
||||||
|
@ -108,8 +109,11 @@ class AssetCheckinController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$originalValues = $asset->getRawOriginal();
|
||||||
|
|
||||||
$checkin_at = date('Y-m-d H:i:s');
|
$checkin_at = date('Y-m-d H:i:s');
|
||||||
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||||
|
$originalValues['action_date'] = $checkin_at;
|
||||||
$checkin_at = $request->get('checkin_at');
|
$checkin_at = $request->get('checkin_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +136,7 @@ class AssetCheckinController extends Controller
|
||||||
|
|
||||||
// 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));
|
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
|
||||||
|
|
||||||
if ((isset($user)) && ($backto == 'user')) {
|
if ((isset($user)) && ($backto == 'user')) {
|
||||||
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
|
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
|
||||||
|
|
|
@ -89,7 +89,16 @@ class AssetCheckoutController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
|
$settings = \App\Models\Setting::getSettings();
|
||||||
|
|
||||||
|
// We have to check whether $target->company_id is null here since locations don't have a company yet
|
||||||
|
if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
|
||||||
|
if ($target->company_id != $asset->company_id){
|
||||||
|
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'));
|
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,14 +79,14 @@ class AssetFilesController extends Controller
|
||||||
* @return View
|
* @return View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function show($assetId = null, $fileId = null, $download = true)
|
public function show($assetId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
$asset = Asset::find($assetId);
|
$asset = Asset::find($assetId);
|
||||||
// the asset is valid
|
// the asset is valid
|
||||||
if (isset($asset->id)) {
|
if (isset($asset->id)) {
|
||||||
$this->authorize('view', $asset);
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
return response('No matching record for that asset/file', 500)
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
@ -103,12 +103,13 @@ class AssetFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($download != 'true') {
|
if (request('inline') == 'true') {
|
||||||
if ($contents = file_get_contents(Storage::url($file))) {
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
|
|
|
@ -6,6 +6,8 @@ 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 Illuminate\Support\Facades\Log;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetModel;
|
use App\Models\AssetModel;
|
||||||
use App\Models\CheckoutRequest;
|
use App\Models\CheckoutRequest;
|
||||||
|
@ -14,26 +16,18 @@ use App\Models\Location;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\Statuslabel;
|
use App\Models\Statuslabel;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use App\View\Label;
|
use App\View\Label;
|
||||||
use Auth;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use 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\Cache;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Cookie;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Input;
|
|
||||||
use Intervention\Image\Facades\Image;
|
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
use League\Csv\Statement;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
use Paginator;
|
|
||||||
use Redirect;
|
|
||||||
use Response;
|
|
||||||
use Slack;
|
|
||||||
use Str;
|
|
||||||
use TCPDF;
|
|
||||||
use View;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class controls all actions related to assets for
|
* This class controls all actions related to assets for
|
||||||
|
@ -137,14 +131,11 @@ class AssetsController extends Controller
|
||||||
$asset->order_number = $request->input('order_number');
|
$asset->order_number = $request->input('order_number');
|
||||||
$asset->notes = $request->input('notes');
|
$asset->notes = $request->input('notes');
|
||||||
$asset->user_id = Auth::id();
|
$asset->user_id = Auth::id();
|
||||||
$asset->archived = '0';
|
|
||||||
$asset->physical = '1';
|
|
||||||
$asset->depreciate = '0';
|
|
||||||
$asset->status_id = request('status_id');
|
$asset->status_id = request('status_id');
|
||||||
$asset->warranty_months = request('warranty_months', null);
|
$asset->warranty_months = request('warranty_months', null);
|
||||||
$asset->purchase_cost = request('purchase_cost');
|
$asset->purchase_cost = request('purchase_cost');
|
||||||
$asset->purchase_date = request('purchase_date', null);
|
$asset->purchase_date = request('purchase_date', null);
|
||||||
$asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date());
|
$asset->asset_eol_date = request('asset_eol_date', null);
|
||||||
$asset->assigned_to = request('assigned_to', null);
|
$asset->assigned_to = request('assigned_to', null);
|
||||||
$asset->supplier_id = request('supplier_id', null);
|
$asset->supplier_id = request('supplier_id', null);
|
||||||
$asset->requestable = request('requestable', 0);
|
$asset->requestable = request('requestable', 0);
|
||||||
|
@ -173,9 +164,9 @@ class AssetsController extends Controller
|
||||||
if ($field->field_encrypted == '1') {
|
if ($field->field_encrypted == '1') {
|
||||||
if (Gate::allows('admin')) {
|
if (Gate::allows('admin')) {
|
||||||
if (is_array($request->input($field->db_column))) {
|
if (is_array($request->input($field->db_column))) {
|
||||||
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||||
} else {
|
} else {
|
||||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -211,12 +202,9 @@ class AssetsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
// Redirect to the asset listing page
|
\Log::debug(e($asset->asset_tag));
|
||||||
$minutes = 518400;
|
|
||||||
// dd( $_POST['options']);
|
|
||||||
// Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes));
|
|
||||||
return redirect()->route('hardware.index')
|
return redirect()->route('hardware.index')
|
||||||
->with('success', trans('admin/hardware/message.create.success'));
|
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -298,10 +286,10 @@ class AssetsController extends Controller
|
||||||
/**
|
/**
|
||||||
* Validate and process asset edit form.
|
* Validate and process asset edit form.
|
||||||
*
|
*
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @param int $assetId
|
* @param int $assetId
|
||||||
* @since [v1.0]
|
* @return \Illuminate\Http\RedirectResponse|Redirect
|
||||||
* @return Redirect
|
*@since [v1.0]
|
||||||
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
*/
|
*/
|
||||||
public function update(ImageUploadRequest $request, $assetId = null)
|
public function update(ImageUploadRequest $request, $assetId = null)
|
||||||
{
|
{
|
||||||
|
@ -315,9 +303,27 @@ 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->asset_eol_date = request('asset_eol_date', null);
|
|
||||||
|
|
||||||
$asset->purchase_date = $request->input('purchase_date', null);
|
$asset->purchase_date = $request->input('purchase_date', null);
|
||||||
|
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
|
||||||
|
$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->eol_explicit = false;
|
||||||
|
} elseif ($request->filled('asset_eol_date')) {
|
||||||
|
$asset->asset_eol_date = $request->input('asset_eol_date', null);
|
||||||
|
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
|
||||||
|
if($asset->model->eol) {
|
||||||
|
if($months != $asset->model->eol > 0) {
|
||||||
|
$asset->eol_explicit = true;
|
||||||
|
} else {
|
||||||
|
$asset->eol_explicit = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$asset->eol_explicit = true;
|
||||||
|
}
|
||||||
|
} elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
|
||||||
|
$asset->asset_eol_date = null;
|
||||||
|
$asset->eol_explicit = false;
|
||||||
|
}
|
||||||
$asset->supplier_id = $request->input('supplier_id', null);
|
$asset->supplier_id = $request->input('supplier_id', null);
|
||||||
$asset->expected_checkin = $request->input('expected_checkin', null);
|
$asset->expected_checkin = $request->input('expected_checkin', null);
|
||||||
|
|
||||||
|
@ -342,7 +348,7 @@ class AssetsController extends Controller
|
||||||
unlink(public_path().'/uploads/assets/'.$asset->image);
|
unlink(public_path().'/uploads/assets/'.$asset->image);
|
||||||
$asset->image = '';
|
$asset->image = '';
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::info($e);
|
Log::info($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +362,6 @@ class AssetsController extends Controller
|
||||||
$asset->order_number = $request->input('order_number');
|
$asset->order_number = $request->input('order_number');
|
||||||
$asset->asset_tag = $asset_tag[1];
|
$asset->asset_tag = $asset_tag[1];
|
||||||
$asset->notes = $request->input('notes');
|
$asset->notes = $request->input('notes');
|
||||||
$asset->physical = '1';
|
|
||||||
|
|
||||||
$asset = $request->handleImages($asset);
|
$asset = $request->handleImages($asset);
|
||||||
|
|
||||||
|
@ -370,9 +375,9 @@ class AssetsController extends Controller
|
||||||
if ($field->field_encrypted == '1') {
|
if ($field->field_encrypted == '1') {
|
||||||
if (Gate::allows('admin')) {
|
if (Gate::allows('admin')) {
|
||||||
if (is_array($request->input($field->db_column))) {
|
if (is_array($request->input($field->db_column))) {
|
||||||
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||||
} else {
|
} else {
|
||||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -420,7 +425,7 @@ class AssetsController extends Controller
|
||||||
try {
|
try {
|
||||||
Storage::disk('public')->delete('assets'.'/'.$asset->image);
|
Storage::disk('public')->delete('assets'.'/'.$asset->image);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::debug($e);
|
Log::debug($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,7 +540,7 @@ class AssetsController extends Controller
|
||||||
|
|
||||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::debug('The barcode format is invalid.');
|
Log::debug('The barcode format is invalid.');
|
||||||
|
|
||||||
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
|
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
|
||||||
}
|
}
|
||||||
|
@ -787,21 +792,24 @@ class AssetsController extends Controller
|
||||||
*/
|
*/
|
||||||
public function getRestore($assetId = null)
|
public function getRestore($assetId = null)
|
||||||
{
|
{
|
||||||
// Get asset information
|
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||||
$asset = Asset::withTrashed()->find($assetId);
|
$this->authorize('delete', $asset);
|
||||||
$this->authorize('delete', $asset);
|
|
||||||
if (isset($asset->id)) {
|
|
||||||
// Restore the asset
|
|
||||||
Asset::withTrashed()->where('id', $assetId)->restore();
|
|
||||||
|
|
||||||
$logaction = new Actionlog();
|
if ($asset->deleted_at == '') {
|
||||||
$logaction->item_type = Asset::class;
|
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset')]));
|
||||||
$logaction->item_id = $asset->id;
|
}
|
||||||
$logaction->created_at = date('Y-m-d H:i:s');
|
|
||||||
$logaction->user_id = Auth::user()->id;
|
|
||||||
$logaction->logaction('restored');
|
|
||||||
|
|
||||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
if ($asset->restore()) {
|
||||||
|
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||||
|
$deleted_assets = Asset::onlyTrashed()->count();
|
||||||
|
if ($deleted_assets > 0) {
|
||||||
|
return redirect()->back()->with('success', trans('admin/hardware/message.restore.success'));
|
||||||
|
}
|
||||||
|
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
|
||||||
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
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'));
|
||||||
|
@ -856,7 +864,7 @@ class AssetsController extends Controller
|
||||||
'next_audit_date' => 'date|nullable',
|
'next_audit_date' => 'date|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
$validator = \Validator::make($request->all(), $rules);
|
$validator = Validator::make($request->all(), $rules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||||
|
@ -873,7 +881,7 @@ 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');
|
Log::debug('update location in audit');
|
||||||
$asset->location_id = $request->input('location_id');
|
$asset->location_id = $request->input('location_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\CheckInOutRequest;
|
use App\Http\Controllers\CheckInOutRequest;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
|
use App\Models\AssetModel;
|
||||||
|
use App\Models\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;
|
||||||
|
@ -14,6 +16,7 @@ use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
use App\Http\Requests\AssetCheckoutRequest;
|
use App\Http\Requests\AssetCheckoutRequest;
|
||||||
|
use App\Models\CustomField;
|
||||||
|
|
||||||
class BulkAssetsController extends Controller
|
class BulkAssetsController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -22,6 +25,13 @@ class BulkAssetsController extends Controller
|
||||||
/**
|
/**
|
||||||
* Display the bulk edit page.
|
* Display the bulk edit page.
|
||||||
*
|
*
|
||||||
|
* This method is super weird because it's kinda of like a controller within a controller.
|
||||||
|
* It's main function is to determine what the bulk action in, and then return a view with
|
||||||
|
* the information that view needs, be it bulk delete, bulk edit, restore, etc.
|
||||||
|
*
|
||||||
|
* This is something that made sense at the time, but sort of doesn't make sense now. A JS front-end to determine form
|
||||||
|
* 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
|
* @return View
|
||||||
* @internal param int $assetId
|
* @internal param int $assetId
|
||||||
|
@ -32,6 +42,9 @@ class BulkAssetsController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('view', Asset::class);
|
$this->authorize('view', Asset::class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No asset IDs were passed
|
||||||
|
*/
|
||||||
if (! $request->filled('ids')) {
|
if (! $request->filled('ids')) {
|
||||||
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||||
}
|
}
|
||||||
|
@ -40,23 +53,35 @@ class BulkAssetsController extends Controller
|
||||||
$bulk_back_url = request()->headers->get('referer');
|
$bulk_back_url = request()->headers->get('referer');
|
||||||
session(['bulk_back_url' => $bulk_back_url]);
|
session(['bulk_back_url' => $bulk_back_url]);
|
||||||
|
|
||||||
$asset_ids = array_values(array_unique($request->input('ids')));
|
|
||||||
|
$asset_ids = $request->input('ids');
|
||||||
|
// Using the 'short-ternary' A/K/A "Elvis operator" '?:' here because ->input() might return an empty string
|
||||||
|
list($sortname,$sortdir) = explode(" ",$request->input('sort') ?: 'id ASC');
|
||||||
|
$assets = Asset::with('assignedTo', 'location', 'model')->whereIn('id', $asset_ids)->orderBy($sortname, $sortdir)->get();
|
||||||
|
|
||||||
|
$models = $assets->unique('model_id');
|
||||||
|
$modelNames = [];
|
||||||
|
foreach($models as $model) {
|
||||||
|
$modelNames[] = $model->model->name;
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('bulk_actions')) {
|
if ($request->filled('bulk_actions')) {
|
||||||
|
|
||||||
|
|
||||||
switch ($request->input('bulk_actions')) {
|
switch ($request->input('bulk_actions')) {
|
||||||
case 'labels':
|
case 'labels':
|
||||||
$this->authorize('view', Asset::class);
|
$this->authorize('view', Asset::class);
|
||||||
|
|
||||||
return (new Label)
|
return (new Label)
|
||||||
->with('assets', Asset::find($asset_ids))
|
->with('assets', $assets)
|
||||||
->with('settings', Setting::getSettings())
|
->with('settings', Setting::getSettings())
|
||||||
->with('bulkedit', true)
|
->with('bulkedit', true)
|
||||||
->with('count', 0);
|
->with('count', 0);
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
$this->authorize('delete', Asset::class);
|
$this->authorize('delete', Asset::class);
|
||||||
$assets = Asset::with('assignedTo', 'location')->find($asset_ids);
|
$assets->each(function ($assets) {
|
||||||
$assets->each(function ($asset) {
|
$this->authorize('delete', $assets);
|
||||||
$this->authorize('delete', $asset);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view('hardware/bulk-delete')->with('assets', $assets);
|
return view('hardware/bulk-delete')->with('assets', $assets);
|
||||||
|
@ -67,14 +92,16 @@ class BulkAssetsController extends Controller
|
||||||
$assets->each(function ($asset) {
|
$assets->each(function ($asset) {
|
||||||
$this->authorize('delete', $asset);
|
$this->authorize('delete', $asset);
|
||||||
});
|
});
|
||||||
|
|
||||||
return view('hardware/bulk-restore')->with('assets', $assets);
|
return view('hardware/bulk-restore')->with('assets', $assets);
|
||||||
|
|
||||||
case 'edit':
|
case 'edit':
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
|
||||||
return view('hardware/bulk')
|
return view('hardware/bulk')
|
||||||
->with('assets', $asset_ids)
|
->with('assets', $asset_ids)
|
||||||
->with('statuslabel_list', Helper::statusLabelList());
|
->with('statuslabel_list', Helper::statusLabelList())
|
||||||
|
->with('models', $models->pluck(['model']))
|
||||||
|
->with('modelNames', $modelNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,19 +119,35 @@ class BulkAssetsController extends Controller
|
||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
$has_errors = 0;
|
||||||
|
$error_array = array();
|
||||||
|
|
||||||
// Get the back url from the session and then destroy the session
|
// Get the back url from the session and then destroy the session
|
||||||
$bulk_back_url = route('hardware.index');
|
$bulk_back_url = route('hardware.index');
|
||||||
|
|
||||||
if ($request->session()->has('bulk_back_url')) {
|
if ($request->session()->has('bulk_back_url')) {
|
||||||
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||||
|
|
||||||
if (! $request->filled('ids') || count($request->input('ids')) <= 0) {
|
|
||||||
|
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
||||||
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$assets = array_keys($request->input('ids'));
|
|
||||||
|
$assets = Asset::whereIn('id', array_keys($request->input('ids')))->get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If ANY of these are filled, prepare to update the values on the assets.
|
||||||
|
*
|
||||||
|
* Additional checks will be needed for some of them to make sure the values
|
||||||
|
* make sense (for example, changing the status ID to something incompatible with
|
||||||
|
* its checkout status.
|
||||||
|
*/
|
||||||
|
|
||||||
if (($request->filled('purchase_date'))
|
if (($request->filled('purchase_date'))
|
||||||
|| ($request->filled('expected_checkin'))
|
|| ($request->filled('expected_checkin'))
|
||||||
|
@ -121,22 +164,35 @@ class BulkAssetsController extends Controller
|
||||||
|| ($request->filled('null_purchase_date'))
|
|| ($request->filled('null_purchase_date'))
|
||||||
|| ($request->filled('null_expected_checkin_date'))
|
|| ($request->filled('null_expected_checkin_date'))
|
||||||
|| ($request->filled('null_next_audit_date'))
|
|| ($request->filled('null_next_audit_date'))
|
||||||
|
|| ($request->anyFilled($custom_field_columns))
|
||||||
|
|
||||||
) {
|
) {
|
||||||
foreach ($assets as $assetId) {
|
// Let's loop through those assets and build an update array
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
|
||||||
$this->update_array = [];
|
$this->update_array = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leave out model_id and status here because we do math on that later. We have to do some extra
|
||||||
|
* validation and checks on those two.
|
||||||
|
*
|
||||||
|
* It's tempting to make these match the request check above, but some of these values require
|
||||||
|
* extra work to make sure the data makes sense.
|
||||||
|
*/
|
||||||
$this->conditionallyAddItem('purchase_date')
|
$this->conditionallyAddItem('purchase_date')
|
||||||
->conditionallyAddItem('expected_checkin')
|
->conditionallyAddItem('expected_checkin')
|
||||||
->conditionallyAddItem('model_id')
|
|
||||||
->conditionallyAddItem('order_number')
|
->conditionallyAddItem('order_number')
|
||||||
->conditionallyAddItem('requestable')
|
->conditionallyAddItem('requestable')
|
||||||
->conditionallyAddItem('status_id')
|
|
||||||
->conditionallyAddItem('supplier_id')
|
->conditionallyAddItem('supplier_id')
|
||||||
->conditionallyAddItem('warranty_months')
|
->conditionallyAddItem('warranty_months')
|
||||||
->conditionallyAddItem('next_audit_date');
|
->conditionallyAddItem('next_audit_date');
|
||||||
|
foreach ($custom_field_columns as $key => $custom_field_column) {
|
||||||
|
$this->conditionallyAddItem($custom_field_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blank out fields that were requested to be blanked out via checkbox
|
||||||
|
*/
|
||||||
if ($request->input('null_purchase_date')=='1') {
|
if ($request->input('null_purchase_date')=='1') {
|
||||||
$this->update_array['purchase_date'] = null;
|
$this->update_array['purchase_date'] = null;
|
||||||
}
|
}
|
||||||
|
@ -160,41 +216,152 @@ class BulkAssetsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're trying to change the model ID - we need to do some extra checks here to make sure
|
||||||
|
* the custom field values work for the custom fieldset rules around this asset. Uniqueness
|
||||||
|
* and requiredness across the fieldset is particularly important, since those are
|
||||||
|
* fieldset-specific attributes.
|
||||||
|
*/
|
||||||
|
if ($request->filled('model_id')) {
|
||||||
|
$this->update_array['model_id'] = AssetModel::find($request->input('model_id'))->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're trying to change the status ID - we need to do some extra checks here to
|
||||||
|
* make sure the status label type is one that makes sense for the state of the asset,
|
||||||
|
* for example, we shouldn't be able to make an asset archived if it's currently assigned
|
||||||
|
* to someone/something.
|
||||||
|
*/
|
||||||
|
if ($request->filled('status_id')) {
|
||||||
|
$updated_status = Statuslabel::find($request->input('status_id'));
|
||||||
|
|
||||||
|
// We cannot assign a non-deployable status type if the asset is already assigned.
|
||||||
|
// This could probably be added to a form request.
|
||||||
|
// If the asset isn't assigned, we don't care what the status is.
|
||||||
|
// Otherwise we need to make sure the status type is still a deployable one.
|
||||||
|
if (
|
||||||
|
($asset->assigned_to == '')
|
||||||
|
|| ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
|
||||||
|
) {
|
||||||
|
$this->update_array['status_id'] = $updated_status->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're changing the location ID - figure out which location we should apply
|
||||||
|
* this change to:
|
||||||
|
*
|
||||||
|
* 0 - RTD location only
|
||||||
|
* 1 - location ID and RTD location ID
|
||||||
|
* 2 - location ID only
|
||||||
|
*
|
||||||
|
* Note: this is kinda dumb and we should just use human-readable values IMHO. - snipe
|
||||||
|
*/
|
||||||
if ($request->filled('rtd_location_id')) {
|
if ($request->filled('rtd_location_id')) {
|
||||||
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
|
|
||||||
|
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) {
|
||||||
|
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
|
||||||
|
}
|
||||||
|
|
||||||
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
|
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
|
||||||
$this->update_array['location_id'] = $request->input('rtd_location_id');
|
$this->update_array['location_id'] = $request->input('rtd_location_id');
|
||||||
|
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) {
|
||||||
|
$this->update_array['location_id'] = $request->input('rtd_location_id');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ------------------------------------------------------------------------------
|
||||||
|
* ANYTHING that happens past this foreach
|
||||||
|
* WILL NOT BE logged in the edit log_meta data
|
||||||
|
* ------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
$changed = [];
|
$changed = [];
|
||||||
$asset = Asset::where('id' ,$assetId)->get();
|
|
||||||
|
|
||||||
foreach ($this->update_array as $key => $value) {
|
foreach ($this->update_array as $key => $value) {
|
||||||
if ($this->update_array[$key] != $asset->toArray()[0][$key]) {
|
|
||||||
$changed[$key]['old'] = $asset->toArray()[0][$key];
|
if ($this->update_array[$key] != $asset->{$key}) {
|
||||||
|
$changed[$key]['old'] = $asset->{$key};
|
||||||
$changed[$key]['new'] = $this->update_array[$key];
|
$changed[$key]['new'] = $this->update_array[$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$logAction = new Actionlog();
|
/**
|
||||||
$logAction->item_type = Asset::class;
|
* Start all the custom fields shenanigans
|
||||||
$logAction->item_id = $assetId;
|
*/
|
||||||
$logAction->created_at = date("Y-m-d H:i:s");
|
|
||||||
$logAction->user_id = Auth::id();
|
|
||||||
$logAction->log_meta = json_encode($changed);
|
|
||||||
$logAction->logaction('update');
|
|
||||||
|
|
||||||
DB::table('assets')
|
// Does the model have a fieldset?
|
||||||
->where('id', $assetId)
|
if ($asset->model->fieldset) {
|
||||||
->update($this->update_array);
|
foreach ($asset->model->fieldset->fields as $field) {
|
||||||
} // endforeach
|
|
||||||
|
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
|
||||||
|
$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
|
||||||
|
*/
|
||||||
|
unset($this->update_array[$field->db_column]);
|
||||||
|
unset($asset->{$field->db_column});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These custom fields aren't encrypted, just carry on as usual
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
|
||||||
|
|
||||||
|
// Check if this is an array, and if so, flatten it
|
||||||
|
if (is_array($this->update_array[$field->db_column])) {
|
||||||
|
$asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]);
|
||||||
|
} else {
|
||||||
|
$asset->{$field->db_column} = $this->update_array[$field->db_column];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // endforeach
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if it passes validation, and then try to save
|
||||||
|
if (!$asset->update($this->update_array)) {
|
||||||
|
|
||||||
|
// Build the error array
|
||||||
|
foreach ($asset->getErrors()->toArray() as $key => $message) {
|
||||||
|
for ($x = 0; $x < count($message); $x++) {
|
||||||
|
$error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x];
|
||||||
|
$has_errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end if saved
|
||||||
|
|
||||||
|
} // end asset foreach
|
||||||
|
|
||||||
|
if ($has_errors > 0) {
|
||||||
|
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no values given, nothing to update
|
// no values given, nothing to update
|
||||||
return redirect($bulk_back_url)->with('warning', trans('admin/hardware/message.update.nothing_updated'));
|
return redirect($bulk_back_url)->with('warning', trans('admin/hardware/message.update.nothing_updated'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@ class LoginController extends Controller
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
|
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
|
||||||
Session::put('backUrl', \URL::previous());
|
Session::put('backUrl', \URL::previous());
|
||||||
// $this->ldap = $ldap;
|
|
||||||
$this->saml = $saml;
|
$this->saml = $saml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +81,6 @@ class LoginController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
|
||||||
return view('errors.403');
|
return view('errors.403');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +121,7 @@ class LoginController extends Controller
|
||||||
|
|
||||||
if ($user = Auth::user()) {
|
if ($user = Auth::user()) {
|
||||||
$user->last_login = \Carbon::now();
|
$user->last_login = \Carbon::now();
|
||||||
$user->save();
|
$user->saveQuietly();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -191,13 +189,15 @@ class LoginController extends Controller
|
||||||
|
|
||||||
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
||||||
|
|
||||||
|
$user->password = $user->noPassword();
|
||||||
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
||||||
$user->password = bcrypt($request->input('password'));
|
$user->password = bcrypt($request->input('password'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->email = $ldap_attr['email'];
|
$user->email = $ldap_attr['email'];
|
||||||
$user->first_name = $ldap_attr['firstname'];
|
$user->first_name = $ldap_attr['firstname'];
|
||||||
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
||||||
$user->save();
|
$user->saveQuietly();
|
||||||
} // End if(!user)
|
} // End if(!user)
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +317,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->save();
|
$user->saveQuietly();
|
||||||
}
|
}
|
||||||
// Redirect to the users page
|
// Redirect to the users page
|
||||||
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
|
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
|
||||||
|
@ -369,7 +369,7 @@ class LoginController extends Controller
|
||||||
[-2, -2, -2, -2]
|
[-2, -2, -2, -2]
|
||||||
);
|
);
|
||||||
|
|
||||||
$user->save(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
|
$user->saveQuietly(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
|
||||||
|
|
||||||
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
|
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
|
||||||
}
|
}
|
||||||
|
@ -424,7 +424,7 @@ class LoginController extends Controller
|
||||||
|
|
||||||
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
||||||
$user->two_factor_enrolled = 1;
|
$user->two_factor_enrolled = 1;
|
||||||
$user->save();
|
$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', 'You are logged in!');
|
||||||
|
|
|
@ -56,10 +56,11 @@ class ComponentCheckinController extends Controller
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request, $component_asset_id)
|
public function store(Request $request, $component_asset_id, $backto = null)
|
||||||
{
|
{
|
||||||
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))) {
|
||||||
|
|
||||||
return redirect()->route('components.index')->with('error',
|
return redirect()->route('components.index')->with('error',
|
||||||
trans('admin/components/message.not_found'));
|
trans('admin/components/message.not_found'));
|
||||||
}
|
}
|
||||||
|
@ -95,6 +96,10 @@ class ComponentCheckinController extends Controller
|
||||||
$asset = Asset::find($component_assets->asset_id);
|
$asset = Asset::find($component_assets->asset_id);
|
||||||
|
|
||||||
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
|
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
|
||||||
|
if ($backto == 'asset'){
|
||||||
|
return redirect()->route('hardware.show', $asset->id)->with('success',
|
||||||
|
trans('admin/components/message.checkin.success'));
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('components.index')->with('success',
|
return redirect()->route('components.index')->with('success',
|
||||||
trans('admin/components/message.checkin.success'));
|
trans('admin/components/message.checkin.success'));
|
||||||
|
|
|
@ -20,25 +20,38 @@ class ComponentCheckoutController extends Controller
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @see ComponentCheckoutController::store() method that stores the data.
|
* @see ComponentCheckoutController::store() method that stores the data.
|
||||||
* @since [v3.0]
|
* @since [v3.0]
|
||||||
* @param int $componentId
|
* @param int $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create($componentId)
|
public function create($id)
|
||||||
{
|
{
|
||||||
// Check if the component exists
|
|
||||||
if (is_null($component = Component::find($componentId))) {
|
|
||||||
// Redirect to the component management page with error
|
|
||||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
|
||||||
}
|
|
||||||
$this->authorize('checkout', $component);
|
|
||||||
|
|
||||||
// Make sure there is at least one available to checkout
|
if ($component = Component::find($id)) {
|
||||||
if ($component->numRemaining() <= 0){
|
|
||||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.checkout.unavailable'));
|
$this->authorize('checkout', $component);
|
||||||
|
|
||||||
|
// Make sure the category is valid
|
||||||
|
if ($component->category) {
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($component->numRemaining() <= 0){
|
||||||
|
return redirect()->route('components.index')
|
||||||
|
->with('error', trans('admin/components/message.checkout.unavailable'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the checkout view
|
||||||
|
return view('components/checkout', compact('component'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid category
|
||||||
|
return redirect()->route('components.edit', ['component' => $component->id])
|
||||||
|
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.component')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('components/checkout', compact('component'));
|
// Not found
|
||||||
|
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -132,7 +132,7 @@ class ComponentsFilesController extends Controller
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function show($componentId = null, $fileId = null, $download = true)
|
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);
|
||||||
|
@ -142,7 +142,7 @@ class ComponentsFilesController extends Controller
|
||||||
$this->authorize('view', $component);
|
$this->authorize('view', $component);
|
||||||
$this->authorize('components.files', $component);
|
$this->authorize('components.files', $component);
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
return response('No matching record for that asset/file', 500)
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
@ -157,20 +157,16 @@ class ComponentsFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Display the file inline
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
} else {
|
|
||||||
if ($download != 'true') {
|
|
||||||
\Log::debug('display the file');
|
|
||||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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;
|
||||||
|
@ -18,25 +19,38 @@ class ConsumableCheckoutController extends Controller
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @see ConsumableCheckoutController::store() method that stores the data.
|
* @see ConsumableCheckoutController::store() method that stores the data.
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param int $consumableId
|
* @param int $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create($consumableId)
|
public function create($id)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
|
if ($consumable = Consumable::with('users')->find($id)) {
|
||||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
|
||||||
|
$this->authorize('checkout', $consumable);
|
||||||
|
|
||||||
|
// Make sure the category is valid
|
||||||
|
if ($consumable->category) {
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($consumable->numRemaining() <= 0){
|
||||||
|
return redirect()->route('consumables.index')
|
||||||
|
->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the checkout view
|
||||||
|
return view('consumables/checkout', compact('consumable'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid category
|
||||||
|
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])
|
||||||
|
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there is at least one available to checkout
|
// Not found
|
||||||
if ($consumable->numRemaining() <= 0){
|
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.checkout.unavailable'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->authorize('checkout', $consumable);
|
|
||||||
|
|
||||||
return view('consumables/checkout', compact('consumable'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -131,7 +131,7 @@ class ConsumablesFilesController extends Controller
|
||||||
* @return \Symfony\Consumable\HttpFoundation\Response
|
* @return \Symfony\Consumable\HttpFoundation\Response
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function show($consumableId = null, $fileId = null, $download = true)
|
public function show($consumableId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
$consumable = Consumable::find($consumableId);
|
$consumable = Consumable::find($consumableId);
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ class ConsumablesFilesController extends Controller
|
||||||
$this->authorize('view', $consumable);
|
$this->authorize('view', $consumable);
|
||||||
$this->authorize('consumables.files', $consumable);
|
$this->authorize('consumables.files', $consumable);
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
return response('No matching record for that asset/file', 500)
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
@ -155,22 +155,19 @@ class ConsumablesFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Display the file inline
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||||
// won't work, as they're not accessible via the web
|
// won't work, as they're not accessible via the web
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
} else {
|
|
||||||
if ($download != 'true') {
|
|
||||||
\Log::debug('display the file');
|
|
||||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@ class CustomFieldsController extends Controller
|
||||||
"display_in_user_view" => $display_in_user_view,
|
"display_in_user_view" => $display_in_user_view,
|
||||||
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
|
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
|
||||||
"show_in_listview" => $request->get("show_in_listview", 0),
|
"show_in_listview" => $request->get("show_in_listview", 0),
|
||||||
|
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
|
||||||
"user_id" => Auth::id()
|
"user_id" => Auth::id()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -267,6 +268,7 @@ class CustomFieldsController extends Controller
|
||||||
$field->display_in_user_view = $display_in_user_view;
|
$field->display_in_user_view = $display_in_user_view;
|
||||||
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
|
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
|
||||||
$field->show_in_listview = $request->get("show_in_listview", 0);
|
$field->show_in_listview = $request->get("show_in_listview", 0);
|
||||||
|
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
|
||||||
|
|
||||||
if ($request->get('format') == 'CUSTOM REGEX') {
|
if ($request->get('format') == 'CUSTOM REGEX') {
|
||||||
$field->format = e($request->get('custom_format'));
|
$field->format = e($request->get('custom_format'));
|
||||||
|
|
|
@ -7,8 +7,10 @@ use App\Models\AssetModel;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Labels\Label;
|
use App\Models\Labels\Label;
|
||||||
|
use App\Models\Location;
|
||||||
use App\Models\Manufacturer;
|
use App\Models\Manufacturer;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
|
use App\Models\Supplier;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\View\Label as LabelView;
|
use App\View\Label as LabelView;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
@ -30,21 +32,23 @@ class LabelsController extends Controller
|
||||||
$exampleAsset = new Asset();
|
$exampleAsset = new Asset();
|
||||||
|
|
||||||
$exampleAsset->id = 999999;
|
$exampleAsset->id = 999999;
|
||||||
$exampleAsset->name = 'AST-AB-CD-1234';
|
$exampleAsset->name = 'JEN-867-5309';
|
||||||
$exampleAsset->asset_tag = 'TCA-00001';
|
$exampleAsset->asset_tag = '100001';
|
||||||
$exampleAsset->serial = 'SN9876543210';
|
$exampleAsset->serial = 'SN9876543210';
|
||||||
|
$exampleAsset->asset_eol_date = '2025-01-01';
|
||||||
|
$exampleAsset->order_number = '12345';
|
||||||
|
$exampleAsset->purchase_date = '2023-01-01';
|
||||||
|
$exampleAsset->status_id = 1;
|
||||||
|
|
||||||
$exampleAsset->company = new Company();
|
$exampleAsset->company = new Company([
|
||||||
$exampleAsset->company->id = 999999;
|
'name' => 'Test Company Limited',
|
||||||
$exampleAsset->company->name = 'Test Company Limited';
|
'phone' => '1-555-555-5555',
|
||||||
$exampleAsset->company->image = 'company-image-test.png';
|
'email' => 'company@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
$exampleAsset->assignedto = new User();
|
$exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker']));
|
||||||
$exampleAsset->assignedto->id = 999999;
|
$exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']);
|
||||||
$exampleAsset->assignedto->first_name = 'Test';
|
$exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']);
|
||||||
$exampleAsset->assignedto->last_name = 'Person';
|
|
||||||
$exampleAsset->assignedto->username = 'Test.Person';
|
|
||||||
$exampleAsset->assignedto->employee_num = '0123456789';
|
|
||||||
|
|
||||||
$exampleAsset->model = new AssetModel();
|
$exampleAsset->model = new AssetModel();
|
||||||
$exampleAsset->model->id = 999999;
|
$exampleAsset->model->id = 999999;
|
||||||
|
@ -53,6 +57,10 @@ class LabelsController extends Controller
|
||||||
$exampleAsset->model->manufacturer = new Manufacturer();
|
$exampleAsset->model->manufacturer = new Manufacturer();
|
||||||
$exampleAsset->model->manufacturer->id = 999999;
|
$exampleAsset->model->manufacturer->id = 999999;
|
||||||
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
|
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
|
||||||
|
$exampleAsset->model->manufacturer->support_email = 'support@test.com';
|
||||||
|
$exampleAsset->model->manufacturer->support_phone = '1-555-555-5555';
|
||||||
|
$exampleAsset->model->manufacturer->support_url = 'https://example.com';
|
||||||
|
$exampleAsset->supplier = new Supplier(['name' => 'Test Company Limited']);
|
||||||
$exampleAsset->model->category = new Category();
|
$exampleAsset->model->category = new Category();
|
||||||
$exampleAsset->model->category->id = 999999;
|
$exampleAsset->model->category->id = 999999;
|
||||||
$exampleAsset->model->category->name = 'Test Category';
|
$exampleAsset->model->category->name = 'Test Category';
|
||||||
|
|
|
@ -76,7 +76,7 @@ class LicenseCheckinController extends Controller
|
||||||
|
|
||||||
// Declare the rules for the form validation
|
// Declare the rules for the form validation
|
||||||
$rules = [
|
$rules = [
|
||||||
'note' => 'string|nullable',
|
'notes' => 'string|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create a new validator instance from our validation rules
|
// Create a new validator instance from our validation rules
|
||||||
|
@ -97,10 +97,11 @@ class LicenseCheckinController extends Controller
|
||||||
// Update the asset data
|
// Update the asset data
|
||||||
$licenseSeat->assigned_to = null;
|
$licenseSeat->assigned_to = null;
|
||||||
$licenseSeat->asset_id = null;
|
$licenseSeat->asset_id = null;
|
||||||
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
|
||||||
// Was the asset updated?
|
// Was the asset updated?
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note')));
|
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
|
||||||
|
|
||||||
if ($backTo == 'user') {
|
if ($backTo == 'user') {
|
||||||
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
|
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
|
||||||
|
@ -128,6 +129,13 @@ class LicenseCheckinController extends Controller
|
||||||
$license = License::findOrFail($licenseId);
|
$license = License::findOrFail($licenseId);
|
||||||
$this->authorize('checkin', $license);
|
$this->authorize('checkin', $license);
|
||||||
|
|
||||||
|
if (! $license->reassignable) {
|
||||||
|
// Not allowed to checkin
|
||||||
|
Session::flash('error', 'License not reassignable.');
|
||||||
|
|
||||||
|
return redirect()->back()->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
||||||
->whereNotNull('assigned_to')
|
->whereNotNull('assigned_to')
|
||||||
->with('user')
|
->with('user')
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Licenses;
|
||||||
use App\Events\CheckoutableCheckedOut;
|
use App\Events\CheckoutableCheckedOut;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\LicenseCheckoutRequest;
|
use App\Http\Requests\LicenseCheckoutRequest;
|
||||||
|
use App\Models\Accessory;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
|
@ -21,23 +22,35 @@ class LicenseCheckoutController extends Controller
|
||||||
*
|
*
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param $licenseId
|
* @param $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create($licenseId)
|
public function create($id)
|
||||||
{
|
{
|
||||||
// Check that the license is valid
|
|
||||||
if ($license = License::find($licenseId)) {
|
if ($license = License::find($id)) {
|
||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
// If the license is valid, check that there is an available seat
|
|
||||||
if ($license->avail_seats_count < 1) {
|
if ($license->category) {
|
||||||
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($license->availCount()->count() < 1){
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the checkout view
|
||||||
|
return view('licenses/checkout', compact('license'));
|
||||||
}
|
}
|
||||||
return view('licenses/checkout', compact('license'));
|
|
||||||
|
// Invalid category
|
||||||
|
return redirect()->route('licenses.edit', ['license' => $license->id])
|
||||||
|
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not found
|
||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,6 +76,7 @@ class LicenseCheckoutController extends Controller
|
||||||
|
|
||||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||||
$licenseSeat->user_id = Auth::id();
|
$licenseSeat->user_id = Auth::id();
|
||||||
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
|
||||||
|
|
||||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
||||||
|
@ -104,7 +118,7 @@ class LicenseCheckoutController extends Controller
|
||||||
$licenseSeat->assigned_to = $target->assigned_to;
|
$licenseSeat->assigned_to = $target->assigned_to;
|
||||||
}
|
}
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
|
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +135,7 @@ class LicenseCheckoutController extends Controller
|
||||||
$licenseSeat->assigned_to = request('assigned_to');
|
$licenseSeat->assigned_to = request('assigned_to');
|
||||||
|
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
|
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ class LicenseFilesController extends Controller
|
||||||
$this->authorize('view', $license);
|
$this->authorize('view', $license);
|
||||||
$this->authorize('licenses.files', $license);
|
$this->authorize('licenses.files', $license);
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
|
||||||
return response('No matching record for that asset/file', 500)
|
return response('No matching record for that asset/file', 500)
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
|
@ -152,21 +152,19 @@ class LicenseFilesController extends Controller
|
||||||
->header('Content-Type', 'text/plain');
|
->header('Content-Type', 'text/plain');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
|
||||||
|
return Storage::download($file, $log->filename, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||||
// won't work, as they're not accessible via the web
|
// won't work, as they're not accessible via the web
|
||||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||||
return StorageHelper::downloader($file);
|
return StorageHelper::downloader($file);
|
||||||
} else {
|
|
||||||
if ($download != 'true') {
|
|
||||||
\Log::debug('display the file');
|
|
||||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
|
||||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use App\Models\Asset;
|
||||||
use App\Models\Manufacturer;
|
use App\Models\Manufacturer;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
@ -218,22 +222,37 @@ class ManufacturersController extends Controller
|
||||||
* @return Redirect
|
* @return Redirect
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function restore($manufacturers_id)
|
public function restore($id)
|
||||||
{
|
{
|
||||||
$this->authorize('create', Manufacturer::class);
|
$this->authorize('delete', Manufacturer::class);
|
||||||
$manufacturer = Manufacturer::onlyTrashed()->where('id', $manufacturers_id)->first();
|
|
||||||
|
|
||||||
if ($manufacturer) {
|
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
|
||||||
|
|
||||||
|
if ($manufacturer->deleted_at == '') {
|
||||||
|
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')]));
|
||||||
|
}
|
||||||
|
|
||||||
// Not sure why this is necessary - it shouldn't fail validation here, but it fails without this, so....
|
|
||||||
$manufacturer->setValidating(false);
|
|
||||||
if ($manufacturer->restore()) {
|
if ($manufacturer->restore()) {
|
||||||
|
$logaction = new Actionlog();
|
||||||
|
$logaction->item_type = Manufacturer::class;
|
||||||
|
$logaction->item_id = $manufacturer->id;
|
||||||
|
$logaction->created_at = date('Y-m-d H:i:s');
|
||||||
|
$logaction->user_id = Auth::user()->id;
|
||||||
|
$logaction->logaction('restore');
|
||||||
|
|
||||||
|
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||||
|
$deleted_manufacturers = Manufacturer::onlyTrashed()->count();
|
||||||
|
if ($deleted_manufacturers > 0) {
|
||||||
|
return redirect()->back()->with('success', trans('admin/manufacturers/message.success.restored'));
|
||||||
|
}
|
||||||
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
|
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', 'Could not restore.');
|
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
|
||||||
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('admin/manufacturers/message.does_not_exist'));
|
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ class ProfileController extends Controller
|
||||||
$user->phone = $request->input('phone');
|
$user->phone = $request->input('phone');
|
||||||
|
|
||||||
if (! config('app.lock_passwords')) {
|
if (! config('app.lock_passwords')) {
|
||||||
$user->locale = $request->input('locale', 'en');
|
$user->locale = $request->input('locale', 'en-US');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
|
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
|
||||||
|
@ -134,6 +134,7 @@ class ProfileController extends Controller
|
||||||
];
|
];
|
||||||
|
|
||||||
$validator = \Validator::make($request->all(), $rules);
|
$validator = \Validator::make($request->all(), $rules);
|
||||||
|
|
||||||
$validator->after(function ($validator) use ($request, $user) {
|
$validator->after(function ($validator) use ($request, $user) {
|
||||||
if (! Hash::check($request->input('current_password'), $user->password)) {
|
if (! Hash::check($request->input('current_password'), $user->password)) {
|
||||||
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
|
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
|
||||||
|
@ -159,12 +160,14 @@ class ProfileController extends Controller
|
||||||
});
|
});
|
||||||
|
|
||||||
if (! $validator->fails()) {
|
if (! $validator->fails()) {
|
||||||
|
|
||||||
$user->password = Hash::make($request->input('password'));
|
$user->password = Hash::make($request->input('password'));
|
||||||
$user->save();
|
// We have to use saveQuietly here because for some reason this method was calling the User Oserver twice :(
|
||||||
|
$user->saveQuietly();
|
||||||
|
|
||||||
// Log the user out of other devices
|
// Log the user out of other devices
|
||||||
Auth::logoutOtherDevices($request->input('password'));
|
Auth::logoutOtherDevices($request->input('password'));
|
||||||
return redirect()->route('account.password.index')->with('success', 'Password updated!');
|
return redirect()->route('account')->with('success', trans('passwords.password_change'));
|
||||||
|
|
||||||
}
|
}
|
||||||
return redirect()->back()->withInput()->withErrors($validator);
|
return redirect()->back()->withInput()->withErrors($validator);
|
||||||
|
|
|
@ -24,6 +24,7 @@ use Input;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
use League\Csv\EscapeFormula;
|
use League\Csv\EscapeFormula;
|
||||||
|
use App\Http\Requests\CustomAssetReportRequest;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -247,8 +248,14 @@ class ReportsController extends Controller
|
||||||
trans('general.action'),
|
trans('general.action'),
|
||||||
trans('general.type'),
|
trans('general.type'),
|
||||||
trans('general.item'),
|
trans('general.item'),
|
||||||
|
trans('general.license_serial'),
|
||||||
|
trans('general.model_name'),
|
||||||
|
trans('general.model_no'),
|
||||||
'To',
|
'To',
|
||||||
trans('general.notes'),
|
trans('general.notes'),
|
||||||
|
trans('admin/settings/general.login_ip'),
|
||||||
|
trans('admin/settings/general.login_user_agent'),
|
||||||
|
trans('general.action_source'),
|
||||||
'Changed',
|
'Changed',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -289,9 +296,15 @@ class ReportsController extends Controller
|
||||||
$actionlog->present()->actionType(),
|
$actionlog->present()->actionType(),
|
||||||
e($actionlog->itemType()),
|
e($actionlog->itemType()),
|
||||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||||
|
($actionlog->item->serial) ? $actionlog->item->serial : null,
|
||||||
|
($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
|
||||||
|
($actionlog->item->model) ? $actionlog->item->model->model_number : null,
|
||||||
$target_name,
|
$target_name,
|
||||||
($actionlog->note) ? e($actionlog->note) : '',
|
($actionlog->note) ? e($actionlog->note) : '',
|
||||||
$actionlog->log_meta,
|
$actionlog->log_meta,
|
||||||
|
$actionlog->remote_ip,
|
||||||
|
$actionlog->user_agent,
|
||||||
|
$actionlog->action_source,
|
||||||
];
|
];
|
||||||
fputcsv($handle, $row);
|
fputcsv($handle, $row);
|
||||||
}
|
}
|
||||||
|
@ -416,11 +429,12 @@ class ReportsController extends Controller
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function postCustom(Request $request)
|
public function postCustom(CustomAssetReportRequest $request)
|
||||||
{
|
{
|
||||||
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
|
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
|
|
||||||
|
|
||||||
\Debugbar::disable();
|
\Debugbar::disable();
|
||||||
$customfields = CustomField::get();
|
$customfields = CustomField::get();
|
||||||
$response = new StreamedResponse(function () use ($customfields, $request) {
|
$response = new StreamedResponse(function () use ($customfields, $request) {
|
||||||
|
@ -539,6 +553,30 @@ class ReportsController extends Controller
|
||||||
$header[] = trans('admin/users/table.title');
|
$header[] = trans('admin/users/table.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('phone')) {
|
||||||
|
$header[] = trans('admin/users/table.phone');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_address')) {
|
||||||
|
$header[] = trans('admin/reports/general.custom_export.user_address');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_city')) {
|
||||||
|
$header[] = trans('admin/reports/general.custom_export.user_city');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_state')) {
|
||||||
|
$header[] = trans('admin/reports/general.custom_export.user_state');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_country')) {
|
||||||
|
$header[] = trans('admin/reports/general.custom_export.user_country');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_zip')) {
|
||||||
|
$header[] = trans('admin/reports/general.custom_export.user_zip');
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('status')) {
|
if ($request->filled('status')) {
|
||||||
$header[] = trans('general.status');
|
$header[] = trans('general.status');
|
||||||
}
|
}
|
||||||
|
@ -558,6 +596,10 @@ class ReportsController extends Controller
|
||||||
$header[] = trans('admin/hardware/table.checkout_date');
|
$header[] = trans('admin/hardware/table.checkout_date');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('checkin_date')) {
|
||||||
|
$header[] = trans('admin/hardware/table.last_checkin_date');
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('expected_checkin')) {
|
if ($request->filled('expected_checkin')) {
|
||||||
$header[] = trans('admin/hardware/form.expected_checkin');
|
$header[] = trans('admin/hardware/form.expected_checkin');
|
||||||
}
|
}
|
||||||
|
@ -664,15 +706,23 @@ class ReportsController extends Controller
|
||||||
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
|
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($request->filled('checkin_date_start'))) {
|
||||||
|
$assets->whereBetween('last_checkin', [
|
||||||
|
Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
|
||||||
|
// use today's date is `checkin_date_end` is not provided
|
||||||
|
Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
|
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
|
||||||
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
|
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
|
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
|
||||||
$last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
|
$last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
|
||||||
$last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
|
$last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
|
||||||
|
|
||||||
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
|
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
||||||
|
@ -743,7 +793,7 @@ class ReportsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('eol')) {
|
if ($request->filled('eol')) {
|
||||||
$row[] = ($asset->purchase_date != '') ? $asset->present()->eol_date() : '';
|
$row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('order')) {
|
if ($request->filled('order')) {
|
||||||
|
@ -827,6 +877,54 @@ class ReportsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('phone')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->phone : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_address')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->address : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_city')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->city : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_state')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->state : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_country')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->country : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('user_zip')) {
|
||||||
|
if ($asset->checkedOutToUser()) {
|
||||||
|
$row[] = ($asset->assignedto) ? $asset->assignedto->zip : '';
|
||||||
|
} else {
|
||||||
|
$row[] = ''; // Empty string if unassigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('status')) {
|
if ($request->filled('status')) {
|
||||||
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
|
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
|
||||||
}
|
}
|
||||||
|
@ -848,6 +946,12 @@ class ReportsController extends Controller
|
||||||
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
|
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('checkin_date')) {
|
||||||
|
$row[] = ($asset->last_checkin)
|
||||||
|
? Carbon::parse($asset->last_checkin)->format('Y-m-d')
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('expected_checkin')) {
|
if ($request->filled('expected_checkin')) {
|
||||||
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
|
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
|
||||||
}
|
}
|
||||||
|
@ -1016,7 +1120,12 @@ class ReportsController extends Controller
|
||||||
|
|
||||||
$assetsForReport = $acceptances
|
$assetsForReport = $acceptances
|
||||||
->filter(function ($acceptance) {
|
->filter(function ($acceptance) {
|
||||||
return $acceptance->checkoutable_type == 'App\Models\Asset';
|
$acceptance_checkoutable_flag = false;
|
||||||
|
if ($acceptance->checkoutable){
|
||||||
|
$acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag;
|
||||||
})
|
})
|
||||||
->map(function($acceptance) {
|
->map(function($acceptance) {
|
||||||
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
|
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
|
||||||
|
@ -1033,27 +1142,34 @@ class ReportsController extends Controller
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
*/
|
*/
|
||||||
public function sentAssetAcceptanceReminder($acceptanceId = null)
|
public function sentAssetAcceptanceReminder(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
|
|
||||||
if (!$acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
|
if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
|
||||||
|
\Log::debug('No pending acceptances');
|
||||||
// Redirect to the unaccepted assets report page with error
|
// Redirect to the unaccepted assets report page with error
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$assetItem = $acceptance->checkoutable;
|
$assetItem = $acceptance->checkoutable;
|
||||||
|
|
||||||
|
\Log::debug(print_r($assetItem, true));
|
||||||
|
|
||||||
if (is_null($acceptance->created_at)){
|
if (is_null($acceptance->created_at)){
|
||||||
|
\Log::debug('No acceptance created_at');
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||||
} else {
|
} else {
|
||||||
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
|
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
|
||||||
|
|
||||||
if ($logItem_res->isEmpty()){
|
if ($logItem_res->isEmpty()){
|
||||||
|
\Log::debug('Acceptance date mismatch');
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||||
}
|
}
|
||||||
$logItem = $logItem_res[0];
|
$logItem = $logItem_res[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$assetItem->assignedTo->locale){
|
if (!$assetItem->assignedTo->locale){
|
||||||
Notification::locale(Setting::getSettings()->locale)->send(
|
Notification::locale(Setting::getSettings()->locale)->send(
|
||||||
$assetItem->assignedTo,
|
$assetItem->assignedTo,
|
||||||
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
||||||
|
|
|
@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Http\Requests\SettingsSamlRequest;
|
use App\Http\Requests\SettingsSamlRequest;
|
||||||
use App\Http\Requests\SetupUserRequest;
|
use App\Http\Requests\SetupUserRequest;
|
||||||
|
use App\Models\CustomField;
|
||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
|
@ -26,7 +27,7 @@ use Response;
|
||||||
use App\Http\Requests\SlackSettingsRequest;
|
use App\Http\Requests\SlackSettingsRequest;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller handles all actions related to Settings for
|
* This controller handles all actions related to Settings for
|
||||||
|
@ -185,7 +186,7 @@ class SettingsController extends Controller
|
||||||
$settings->alerts_enabled = 1;
|
$settings->alerts_enabled = 1;
|
||||||
$settings->pwd_secure_min = 10;
|
$settings->pwd_secure_min = 10;
|
||||||
$settings->brand = 1;
|
$settings->brand = 1;
|
||||||
$settings->locale = $request->input('locale', 'en');
|
$settings->locale = $request->input('locale', 'en-US');
|
||||||
$settings->default_currency = $request->input('default_currency', 'USD');
|
$settings->default_currency = $request->input('default_currency', 'USD');
|
||||||
$settings->user_id = 1;
|
$settings->user_id = 1;
|
||||||
$settings->email_domain = $request->input('email_domain');
|
$settings->email_domain = $request->input('email_domain');
|
||||||
|
@ -584,12 +585,13 @@ class SettingsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! config('app.lock_passwords')) {
|
if (! config('app.lock_passwords')) {
|
||||||
$setting->locale = $request->input('locale', 'en');
|
$setting->locale = $request->input('locale', 'en-US');
|
||||||
}
|
}
|
||||||
$setting->default_currency = $request->input('default_currency', '$');
|
$setting->default_currency = $request->input('default_currency', '$');
|
||||||
$setting->date_display_format = $request->input('date_display_format');
|
$setting->date_display_format = $request->input('date_display_format');
|
||||||
$setting->time_display_format = $request->input('time_display_format');
|
$setting->time_display_format = $request->input('time_display_format');
|
||||||
$setting->digit_separator = $request->input('digit_separator');
|
$setting->digit_separator = $request->input('digit_separator');
|
||||||
|
$setting->name_display_format = $request->input('name_display_format');
|
||||||
|
|
||||||
if ($setting->save()) {
|
if ($setting->save()) {
|
||||||
return redirect()->route('settings.index')
|
return redirect()->route('settings.index')
|
||||||
|
@ -808,9 +810,10 @@ class SettingsController extends Controller
|
||||||
*/
|
*/
|
||||||
public function getLabels()
|
public function getLabels()
|
||||||
{
|
{
|
||||||
$setting = Setting::getSettings();
|
return view('settings.labels', [
|
||||||
|
'setting' => Setting::getSettings(),
|
||||||
return view('settings.labels', compact('setting'));
|
'customFields' => CustomField::all(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1247,13 +1250,11 @@ class SettingsController extends Controller
|
||||||
if (!$request->hasFile('file')) {
|
if (!$request->hasFile('file')) {
|
||||||
return redirect()->route('settings.backups.index')->with('error', 'No file uploaded');
|
return redirect()->route('settings.backups.index')->with('error', 'No file uploaded');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$max_file_size = Helper::file_upload_max_size();
|
$max_file_size = Helper::file_upload_max_size();
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
$rules = [
|
|
||||||
'file' => 'required|mimes:zip|max:'.$max_file_size,
|
'file' => 'required|mimes:zip|max:'.$max_file_size,
|
||||||
];
|
]);
|
||||||
|
|
||||||
$validator = \Validator::make($request->all(), $rules);
|
|
||||||
|
|
||||||
if ($validator->passes()) {
|
if ($validator->passes()) {
|
||||||
|
|
||||||
|
@ -1264,7 +1265,7 @@ class SettingsController extends Controller
|
||||||
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
|
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
|
return redirect()->route('settings.backups.index')->withErrors($validator);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,10 +125,26 @@ class BulkUsersController extends Controller
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the user wants to actually blank out the values vs skip them
|
||||||
|
*/
|
||||||
if ($request->input('null_location_id')=='1') {
|
if ($request->input('null_location_id')=='1') {
|
||||||
$this->update_array['location_id'] = null;
|
$this->update_array['location_id'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->input('null_department_id')=='1') {
|
||||||
|
$this->update_array['department_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('null_manager_id')=='1') {
|
||||||
|
$this->update_array['manager_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('null_company_id')=='1') {
|
||||||
|
$this->update_array['company_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (! $manager_conflict) {
|
if (! $manager_conflict) {
|
||||||
$this->conditionallyAddItem('manager_id');
|
$this->conditionallyAddItem('manager_id');
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,15 +49,19 @@ class LDAPImportController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('update', User::class);
|
$this->authorize('update', User::class);
|
||||||
// Call Artisan LDAP import command.
|
// Call Artisan LDAP import command.
|
||||||
$location_id = $request->input('location_id');
|
|
||||||
Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]);
|
Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]);
|
||||||
|
|
||||||
// Collect and parse JSON summary.
|
// Collect and parse JSON summary.
|
||||||
$ldap_results_json = Artisan::output();
|
$ldap_results_json = Artisan::output();
|
||||||
$ldap_results = json_decode($ldap_results_json, true);
|
$ldap_results = json_decode($ldap_results_json, true);
|
||||||
|
if (!$ldap_results) {
|
||||||
|
return redirect()->back()->withInput()->with('error', trans('general.no_results'));
|
||||||
|
}
|
||||||
|
|
||||||
// Direct user to appropriate status page.
|
// Direct user to appropriate status page.
|
||||||
if ($ldap_results['error']) {
|
if ($ldap_results['error']) {
|
||||||
|
|
||||||
return redirect()->back()->withInput()->with('error', $ldap_results['error_message']);
|
return redirect()->back()->withInput()->with('error', $ldap_results['error_message']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Users;
|
namespace App\Http\Controllers\Users;
|
||||||
|
|
||||||
|
use App\Helpers\StorageHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\AssetFileRequest;
|
use App\Http\Requests\AssetFileRequest;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
|
@ -135,22 +136,36 @@ class UserFilesController extends Controller
|
||||||
*/
|
*/
|
||||||
public function show($userId = null, $fileId = null)
|
public function show($userId = null, $fileId = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (empty($fileId)) {
|
||||||
|
return redirect()->route('users.show')->with('error', 'Invalid file request');
|
||||||
|
}
|
||||||
|
|
||||||
$user = User::find($userId);
|
$user = User::find($userId);
|
||||||
|
|
||||||
// the license is valid
|
// the license is valid
|
||||||
if (isset($user->id)) {
|
if (isset($user->id)) {
|
||||||
|
|
||||||
$this->authorize('view', $user);
|
$this->authorize('view', $user);
|
||||||
|
|
||||||
$log = Actionlog::find($fileId);
|
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
|
||||||
$file = $log->get_src('users');
|
|
||||||
|
|
||||||
return Response::download($file); //FIXME this doesn't use the new StorageHelper yet, but it's complicated...
|
// Display the file inline
|
||||||
|
if (request('inline') == 'true') {
|
||||||
|
$headers = [
|
||||||
|
'Content-Disposition' => 'inline',
|
||||||
|
];
|
||||||
|
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::download('private_uploads/users/'.$log->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found'));
|
||||||
}
|
}
|
||||||
// Prepare the error message
|
|
||||||
$error = trans('admin/users/message.user_not_found', ['id' => $userId]);
|
|
||||||
|
|
||||||
// Redirect to the licence management page
|
// Redirect to the user management page if the user doesn't exist
|
||||||
return redirect()->route('users.index')->with('error', $error);
|
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
|
||||||
use App\Http\Controllers\UserNotFoundException;
|
use App\Http\Controllers\UserNotFoundException;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Http\Requests\SaveUserRequest;
|
use App\Http\Requests\SaveUserRequest;
|
||||||
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\Ldap;
|
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\WelcomeNotification;
|
use App\Notifications\WelcomeNotification;
|
||||||
|
@ -385,18 +385,35 @@ class UsersController extends Controller
|
||||||
*/
|
*/
|
||||||
public function getRestore($id = null)
|
public function getRestore($id = null)
|
||||||
{
|
{
|
||||||
$this->authorize('update', User::class);
|
if ($user = User::withTrashed()->find($id)) {
|
||||||
// Get user information
|
$this->authorize('delete', $user);
|
||||||
if (! User::onlyTrashed()->find($id)) {
|
|
||||||
return redirect()->route('users.index')->with('error', trans('admin/users/messages.user_not_found'));
|
if ($user->deleted_at == '') {
|
||||||
|
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->restore()) {
|
||||||
|
$logaction = new Actionlog();
|
||||||
|
$logaction->item_type = User::class;
|
||||||
|
$logaction->item_id = $user->id;
|
||||||
|
$logaction->created_at = date('Y-m-d H:i:s');
|
||||||
|
$logaction->user_id = Auth::user()->id;
|
||||||
|
$logaction->logaction('restore');
|
||||||
|
|
||||||
|
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||||
|
$deleted_users = User::onlyTrashed()->count();
|
||||||
|
if ($deleted_users > 0) {
|
||||||
|
return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
|
||||||
|
}
|
||||||
|
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation to make sure we're not restoring a user with the same username as an existing user
|
||||||
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the user
|
return redirect()->route('users.index')->with('error', trans('admin/users/message.does_not_exist'));
|
||||||
if (User::withTrashed()->where('id', $id)->restore()) {
|
|
||||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->route('users.index')->with('error', 'User could not be restored.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -275,6 +275,7 @@ class Importer extends Component
|
||||||
'license_email' => trans('admin/licenses/form.to_email'),
|
'license_email' => trans('admin/licenses/form.to_email'),
|
||||||
'license_name' => trans('admin/licenses/form.to_name'),
|
'license_name' => trans('admin/licenses/form.to_name'),
|
||||||
'purchase_order' => trans('admin/licenses/form.purchase_order'),
|
'purchase_order' => trans('admin/licenses/form.purchase_order'),
|
||||||
|
'order_number' => trans('general.order_number'),
|
||||||
'reassignable' => trans('admin/licenses/form.reassignable'),
|
'reassignable' => trans('admin/licenses/form.reassignable'),
|
||||||
'seats' => trans('admin/licenses/form.seats'),
|
'seats' => trans('admin/licenses/form.seats'),
|
||||||
'notes' => trans('general.notes'),
|
'notes' => trans('general.notes'),
|
||||||
|
@ -484,8 +485,17 @@ class Importer extends Component
|
||||||
|
|
||||||
public function selectFile($id)
|
public function selectFile($id)
|
||||||
{
|
{
|
||||||
|
$this->clearMessage();
|
||||||
|
|
||||||
$this->activeFile = Import::find($id);
|
$this->activeFile = Import::find($id);
|
||||||
|
|
||||||
|
if (!$this->activeFile) {
|
||||||
|
$this->message = trans('admin/hardware/message.import.file_missing');
|
||||||
|
$this->message_type = 'danger';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->field_map = null;
|
$this->field_map = null;
|
||||||
foreach($this->activeFile->header_row as $element) {
|
foreach($this->activeFile->header_row as $element) {
|
||||||
if(isset($this->activeFile->field_map[$element])) {
|
if(isset($this->activeFile->field_map[$element])) {
|
||||||
|
@ -520,6 +530,12 @@ class Importer extends Component
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clearMessage()
|
||||||
|
{
|
||||||
|
$this->message = null;
|
||||||
|
$this->message_type = null;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
|
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
|
||||||
|
|
|
@ -12,7 +12,7 @@ class SlackSettingsForm extends Component
|
||||||
public $webhook_endpoint;
|
public $webhook_endpoint;
|
||||||
public $webhook_channel;
|
public $webhook_channel;
|
||||||
public $webhook_botname;
|
public $webhook_botname;
|
||||||
public $isDisabled ='' ;
|
public $isDisabled ='disabled' ;
|
||||||
public $webhook_name;
|
public $webhook_name;
|
||||||
public $webhook_link;
|
public $webhook_link;
|
||||||
public $webhook_placeholder;
|
public $webhook_placeholder;
|
||||||
|
@ -22,11 +22,17 @@ class SlackSettingsForm extends Component
|
||||||
|
|
||||||
public Setting $setting;
|
public Setting $setting;
|
||||||
|
|
||||||
|
public $webhook_endpoint_rules;
|
||||||
|
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
|
'webhook_endpoint' => 'required_with:webhook_channel|starts_with:http://,https://,ftp://,irc://,https://hooks.slack.com/services/|url|nullable',
|
||||||
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
||||||
'webhook_botname' => 'string|nullable',
|
'webhook_botname' => 'string|nullable',
|
||||||
];
|
];
|
||||||
|
public $messages = [
|
||||||
|
'webhook_endpoint.starts_with' => 'your webhook endpoint should begin with http://, https:// or other protocol.',
|
||||||
|
];
|
||||||
|
|
||||||
public function mount() {
|
public function mount() {
|
||||||
$this->webhook_text= [
|
$this->webhook_text= [
|
||||||
|
@ -55,9 +61,7 @@ class SlackSettingsForm extends Component
|
||||||
$this->webhook_botname = $this->setting->webhook_botname;
|
$this->webhook_botname = $this->setting->webhook_botname;
|
||||||
$this->webhook_options = $this->setting->webhook_selected;
|
$this->webhook_options = $this->setting->webhook_selected;
|
||||||
|
|
||||||
if($this->setting->webhook_selected == 'general'){
|
|
||||||
$this->isDisabled='';
|
|
||||||
}
|
|
||||||
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
||||||
$this->isDisabled= '';
|
$this->isDisabled= '';
|
||||||
}
|
}
|
||||||
|
@ -65,9 +69,8 @@ class SlackSettingsForm extends Component
|
||||||
}
|
}
|
||||||
public function updated($field) {
|
public function updated($field) {
|
||||||
|
|
||||||
if($this->webhook_selected != 'general') {
|
|
||||||
$this->validateOnly($field, $this->rules);
|
$this->validateOnly($field, $this->rules);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedWebhookSelected() {
|
public function updatedWebhookSelected() {
|
||||||
|
@ -82,7 +85,6 @@ class SlackSettingsForm extends Component
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isButtonDisabled() {
|
private function isButtonDisabled() {
|
||||||
if($this->webhook_selected == 'slack') {
|
|
||||||
if (empty($this->webhook_endpoint)) {
|
if (empty($this->webhook_endpoint)) {
|
||||||
$this->isDisabled = 'disabled';
|
$this->isDisabled = 'disabled';
|
||||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||||
|
@ -91,8 +93,6 @@ class SlackSettingsForm extends Component
|
||||||
$this->isDisabled = 'disabled';
|
$this->isDisabled = 'disabled';
|
||||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@ -108,6 +108,7 @@ class SlackSettingsForm extends Component
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'exceptions' => false,
|
'exceptions' => false,
|
||||||
],
|
],
|
||||||
|
'allow_redirects' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$payload = json_encode(
|
$payload = json_encode(
|
||||||
|
@ -116,18 +117,23 @@ class SlackSettingsForm extends Component
|
||||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||||
'username' => e($this->webhook_botname),
|
'username' => e($this->webhook_botname),
|
||||||
'icon_emoji' => ':heart:',
|
'icon_emoji' => ':heart:',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$test = $webhook->post($this->webhook_endpoint, ['body' => $payload]);
|
||||||
|
|
||||||
$webhook->post($this->webhook_endpoint, ['body' => $payload]);
|
if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){
|
||||||
|
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||||
|
}
|
||||||
$this->isDisabled='';
|
$this->isDisabled='';
|
||||||
$this->save_button = trans('general.save');
|
$this->save_button = trans('general.save');
|
||||||
return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
|
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
||||||
$this->isDisabled= 'disabled';
|
$this->isDisabled='disabled';
|
||||||
|
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +164,7 @@ class SlackSettingsForm extends Component
|
||||||
if (Helper::isDemoMode()) {
|
if (Helper::isDemoMode()) {
|
||||||
session()->flash('error',trans('general.feature_disabled'));
|
session()->flash('error',trans('general.feature_disabled'));
|
||||||
} else {
|
} else {
|
||||||
if ($this->webhook_selected != 'general') {
|
$this->validate($this->rules);
|
||||||
$this->validate($this->rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setting->webhook_selected = $this->webhook_selected;
|
$this->setting->webhook_selected = $this->webhook_selected;
|
||||||
$this->setting->webhook_endpoint = $this->webhook_endpoint;
|
$this->setting->webhook_endpoint = $this->webhook_endpoint;
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use \App\Helpers\Helper;
|
||||||
|
|
||||||
class CheckLocale
|
class CheckLocale
|
||||||
{
|
{
|
||||||
|
@ -18,22 +19,28 @@ class CheckLocale
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next, $guard = null)
|
public function handle($request, Closure $next, $guard = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Default app settings from config
|
||||||
|
$language = config('app.locale');
|
||||||
|
|
||||||
if ($settings = Setting::getSettings()) {
|
if ($settings = Setting::getSettings()) {
|
||||||
|
|
||||||
// User's preference
|
// User's preference
|
||||||
if (($request->user()) && ($request->user()->locale)) {
|
if (($request->user()) && ($request->user()->locale)) {
|
||||||
\App::setLocale($request->user()->locale);
|
$language = $request->user()->locale;
|
||||||
|
|
||||||
// App setting preference
|
// App setting preference
|
||||||
} elseif ($settings->locale != '') {
|
} elseif ($settings->locale != '') {
|
||||||
\App::setLocale($settings->locale);
|
$language = $settings->locale;
|
||||||
|
|
||||||
// Default app setting
|
|
||||||
} else {
|
|
||||||
\App::setLocale(config('app.locale'));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
\App::setLocale(config('app.locale'));
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config('app.locale') != Helper::mapLegacyLocale($language)) {
|
||||||
|
\Log::warning('Your current APP_LOCALE in your .env is set to "'.config('app.locale').'" and should be updated to be "'.Helper::mapLegacyLocale($language).'" in '.base_path().'/.env. Translations may display unexpectedly until this is updated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
\App::setLocale(Helper::mapLegacyLocale($language));
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
app/Http/Requests/CustomAssetReportRequest.php
Normal file
46
app/Http/Requests/CustomAssetReportRequest.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
class CustomAssetReportRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'purchase_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'purchase_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'created_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'created_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'checkout_date_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'expected_checkin_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'expected_checkin_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'checkin_date_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'checkin_date_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'last_audit_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'last_audit_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'next_audit_start' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
'next_audit_end' => 'date|date_format:Y-m-d|nullable',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function response(array $errors)
|
||||||
|
{
|
||||||
|
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ class SaveUserRequest extends FormRequest
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
|
'department_id' => 'nullable|exists:departments,id',
|
||||||
'manager_id' => 'nullable|exists:users,id',
|
'manager_id' => 'nullable|exists:users,id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
51
app/Http/Requests/StoreAssetRequest.php
Normal file
51
app/Http/Requests/StoreAssetRequest.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\Company;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
|
class StoreAssetRequest extends ImageUploadRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return Gate::allows('create', new Asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
// Guard against users passing in an array for company_id instead of an integer.
|
||||||
|
// If the company_id is not an integer then we simply use what was
|
||||||
|
// provided to be caught by model level validation later.
|
||||||
|
$idForCurrentUser = is_int($this->company_id)
|
||||||
|
? Company::getIdForCurrentUser($this->company_id)
|
||||||
|
: $this->company_id;
|
||||||
|
|
||||||
|
$this->merge([
|
||||||
|
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
|
||||||
|
'company_id' => $idForCurrentUser,
|
||||||
|
'assigned_to' => $assigned_to ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = array_merge(
|
||||||
|
(new Asset)->getRules(),
|
||||||
|
parent::rules(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Traits;
|
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
|
|
||||||
trait UniqueSerialTrait
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Prepare a unique_ids rule, adding a model identifier if required.
|
|
||||||
*
|
|
||||||
* @param array $parameters
|
|
||||||
* @param string $field
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function prepareUniqueSerialRule($parameters, $field)
|
|
||||||
{
|
|
||||||
if ($settings = Setting::getSettings()) {
|
|
||||||
if ($settings->unique_serial == '1') {
|
|
||||||
return 'unique_undeleted:'.$this->table.','.$this->getKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,18 @@ namespace App\Http\Transformers;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\CustomField;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
|
use App\Models\Statuslabel;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\AssetModel;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class ActionlogsTransformer
|
class ActionlogsTransformer
|
||||||
{
|
{
|
||||||
|
@ -38,21 +48,80 @@ class ActionlogsTransformer
|
||||||
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
||||||
{
|
{
|
||||||
$icon = $actionlog->present()->icon();
|
$icon = $actionlog->present()->icon();
|
||||||
|
$custom_fields = CustomField::all();
|
||||||
|
|
||||||
if ($actionlog->filename!='') {
|
if ($actionlog->filename!='') {
|
||||||
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
|
$icon = Helper::filetype_icon($actionlog->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is necessary since we can't escape special characters within a JSON object
|
// This is necessary since we can't escape special characters within a JSON object
|
||||||
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
||||||
$meta_array = json_decode($actionlog->log_meta);
|
$meta_array = json_decode($actionlog->log_meta);
|
||||||
|
|
||||||
|
$clean_meta = [];
|
||||||
|
|
||||||
if ($meta_array) {
|
if ($meta_array) {
|
||||||
|
|
||||||
foreach ($meta_array as $fieldname => $fieldata) {
|
foreach ($meta_array as $fieldname => $fieldata) {
|
||||||
|
|
||||||
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
|
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
|
||||||
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
|
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
|
||||||
|
|
||||||
|
// this is a custom field
|
||||||
|
if (str_starts_with($fieldname, '_snipeit_')) {
|
||||||
|
|
||||||
|
foreach ($custom_fields as $custom_field) {
|
||||||
|
|
||||||
|
if ($custom_field->db_column == $fieldname) {
|
||||||
|
|
||||||
|
if ($custom_field->field_encrypted == '1') {
|
||||||
|
|
||||||
|
// Unset these fields. We need to decrypt them, since even if the decrypted value
|
||||||
|
// didn't change, their value in the DB will, so we have to compare the unencrypted version
|
||||||
|
// to see if the values actually did change
|
||||||
|
unset($clean_meta[$fieldname]);
|
||||||
|
unset($clean_meta[$fieldname]);
|
||||||
|
|
||||||
|
$enc_old = '';
|
||||||
|
$enc_new = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$enc_old = \Crypt::decryptString($this->clean_field($fieldata->old));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::debug('Could not decrypt field - maybe the key changed?');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$enc_new = \Crypt::decryptString($this->clean_field($fieldata->new));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::debug('Could not decrypt field - maybe the key changed?');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($enc_old != $enc_new) {
|
||||||
|
\Log::debug('custom fields do not match');
|
||||||
|
$clean_meta[$fieldname]['old'] = "************";
|
||||||
|
$clean_meta[$fieldname]['new'] = "************";
|
||||||
|
|
||||||
|
// Display the changes if the user is an admin or superadmin
|
||||||
|
if (Gate::allows('admin')) {
|
||||||
|
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
|
||||||
|
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
$clean_meta= $this->changedInfo($clean_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_url = '';
|
$file_url = '';
|
||||||
|
@ -113,11 +182,15 @@ class ActionlogsTransformer
|
||||||
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
|
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
|
||||||
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
|
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
|
||||||
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
|
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
|
||||||
|
'remote_ip' => ($actionlog->remote_ip) ?? null,
|
||||||
|
'user_agent' => ($actionlog->user_agent) ?? null,
|
||||||
|
'action_source' => ($actionlog->action_source) ?? null,
|
||||||
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
|
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
|
||||||
];
|
];
|
||||||
//\Log::info("Clean Meta is: ".print_r($clean_meta,true));
|
|
||||||
|
|
||||||
|
// \Log::info("Clean Meta is: ".print_r($clean_meta,true));
|
||||||
//dd($array);
|
//dd($array);
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +205,112 @@ class ActionlogsTransformer
|
||||||
}
|
}
|
||||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This takes the ids of the changed attributes and returns the names instead for the history view of an Asset
|
||||||
|
*
|
||||||
|
* @param array $clean_meta
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function changedInfo(array $clean_meta)
|
||||||
|
{ $location = Location::withTrashed()->get();
|
||||||
|
$supplier = Supplier::withTrashed()->get();
|
||||||
|
$model = AssetModel::withTrashed()->get();
|
||||||
|
$status = Statuslabel::withTrashed()->get();
|
||||||
|
$company = Company::get();
|
||||||
|
|
||||||
|
|
||||||
|
if(array_key_exists('rtd_location_id',$clean_meta)) {
|
||||||
|
|
||||||
|
$oldRtd = $location->find($clean_meta['rtd_location_id']['old']);
|
||||||
|
$oldRtdName = $oldRtd ? e($oldRtd->name) : trans('general.deleted');
|
||||||
|
|
||||||
|
$newRtd = $location->find($clean_meta['rtd_location_id']['new']);
|
||||||
|
$newRtdName = $newRtd ? e($newRtd->name) : trans('general.deleted');
|
||||||
|
|
||||||
|
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : '';
|
||||||
|
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : '';
|
||||||
|
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
|
||||||
|
unset($clean_meta['rtd_location_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (array_key_exists('location_id', $clean_meta)) {
|
||||||
|
|
||||||
|
$oldLocation = $location->find($clean_meta['location_id']['old']);
|
||||||
|
$oldLocationName = $oldLocation ? e($oldLocation->name) : trans('general.deleted');
|
||||||
|
|
||||||
|
$newLocation = $location->find($clean_meta['location_id']['new']);
|
||||||
|
$newLocationName = $newLocation ? e($newLocation->name) : trans('general.deleted');
|
||||||
|
|
||||||
|
|
||||||
|
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : '';
|
||||||
|
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : '';
|
||||||
|
$clean_meta['Current Location'] = $clean_meta['location_id'];
|
||||||
|
unset($clean_meta['location_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(array_key_exists('model_id', $clean_meta)) {
|
||||||
|
|
||||||
|
$oldModel = $model->find($clean_meta['model_id']['old']);
|
||||||
|
$oldModelName = $oldModel ? e($oldModel->name) : trans('admin/models/message.deleted');
|
||||||
|
|
||||||
|
$newModel = $model->find($clean_meta['model_id']['new']);
|
||||||
|
$newModelName = $newModel ? e($newModel->name) : trans('admin/models/message.deleted');
|
||||||
|
|
||||||
|
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
|
||||||
|
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
|
||||||
|
|
||||||
|
$clean_meta['Model'] = $clean_meta['model_id'];
|
||||||
|
unset($clean_meta['model_id']);
|
||||||
|
}
|
||||||
|
if(array_key_exists('company_id', $clean_meta)) {
|
||||||
|
|
||||||
|
$oldCompany = $company->find($clean_meta['company_id']['old']);
|
||||||
|
$oldCompanyName = $oldCompany ? e($oldCompany->name) : trans('admin/company/message.deleted');
|
||||||
|
|
||||||
|
$newCompany = $company->find($clean_meta['company_id']['new']);
|
||||||
|
$newCompanyName = $newCompany ? e($newCompany->name) : trans('admin/company/message.deleted');
|
||||||
|
|
||||||
|
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
|
||||||
|
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
|
||||||
|
$clean_meta['Company'] = $clean_meta['company_id'];
|
||||||
|
unset($clean_meta['company_id']);
|
||||||
|
}
|
||||||
|
if(array_key_exists('supplier_id', $clean_meta)) {
|
||||||
|
|
||||||
|
$oldSupplier = $supplier->find($clean_meta['supplier_id']['old']);
|
||||||
|
$oldSupplierName = $oldSupplier ? e($oldSupplier->name) : trans('admin/suppliers/message.deleted');
|
||||||
|
|
||||||
|
$newSupplier = $supplier->find($clean_meta['supplier_id']['new']);
|
||||||
|
$newSupplierName = $newSupplier ? e($newSupplier->name) : trans('admin/suppliers/message.deleted');
|
||||||
|
|
||||||
|
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
|
||||||
|
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
|
||||||
|
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
|
||||||
|
unset($clean_meta['supplier_id']);
|
||||||
|
}
|
||||||
|
if(array_key_exists('status_id', $clean_meta)) {
|
||||||
|
|
||||||
|
$oldStatus = $status->find($clean_meta['status_id']['old']);
|
||||||
|
$oldStatusName = $oldStatus ? e($oldStatus->name) : trans('admin/statuslabels/message.deleted_label');
|
||||||
|
|
||||||
|
$newStatus = $status->find($clean_meta['status_id']['new']);
|
||||||
|
$newStatusName = $newStatus ? e($newStatus->name) : trans('admin/statuslabels/message.deleted_label');
|
||||||
|
|
||||||
|
$clean_meta['status_id']['old'] = $clean_meta['status_id']['old'] ? "[id: ".$clean_meta['status_id']['old']."] ". $oldStatusName : trans('general.unassigned');
|
||||||
|
$clean_meta['status_id']['new'] = $clean_meta['status_id']['new'] ? "[id: ".$clean_meta['status_id']['new']."] ". $newStatusName : trans('general.unassigned');
|
||||||
|
$clean_meta['Status'] = $clean_meta['status_id'];
|
||||||
|
unset($clean_meta['status_id']);
|
||||||
|
}
|
||||||
|
if(array_key_exists('asset_eol_date', $clean_meta)) {
|
||||||
|
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
|
||||||
|
unset($clean_meta['asset_eol_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean_meta;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class AssetMaintenancesTransformer
|
||||||
'name'=> e($assetmaintenance->asset->location->name),
|
'name'=> e($assetmaintenance->asset->location->name),
|
||||||
|
|
||||||
] : null,
|
] : null,
|
||||||
'rtd_location' => ($assetmaintenance->asset->defaultLoc) ? [
|
'rtd_location' => (($assetmaintenance->asset) && ($assetmaintenance->asset->defaultLoc)) ? [
|
||||||
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
|
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
|
||||||
'name'=> e($assetmaintenance->asset->defaultLoc->name),
|
'name'=> e($assetmaintenance->asset->defaultLoc->name),
|
||||||
] : null,
|
] : null,
|
||||||
|
|
|
@ -47,6 +47,7 @@ class AssetModelsTransformer
|
||||||
] : null,
|
] : null,
|
||||||
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
||||||
'model_number' => e($assetmodel->model_number),
|
'model_number' => e($assetmodel->model_number),
|
||||||
|
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
|
||||||
'depreciation' => ($assetmodel->depreciation) ? [
|
'depreciation' => ($assetmodel->depreciation) ? [
|
||||||
'id' => (int) $assetmodel->depreciation->id,
|
'id' => (int) $assetmodel->depreciation->id,
|
||||||
'name'=> e($assetmodel->depreciation->name),
|
'name'=> e($assetmodel->depreciation->name),
|
||||||
|
@ -72,7 +73,7 @@ class AssetModelsTransformer
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
'update' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
'update' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
||||||
'delete' => (Gate::allows('delete', AssetModel::class) && ($assetmodel->assets_count == 0)),
|
'delete' => $assetmodel->isDeletable(),
|
||||||
'clone' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
'clone' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
||||||
'restore' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at != '')),
|
'restore' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at != '')),
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,7 +7,8 @@ use App\Models\Asset;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Auth;
|
||||||
|
|
||||||
class AssetsTransformer
|
class AssetsTransformer
|
||||||
{
|
{
|
||||||
|
@ -38,7 +39,7 @@ class AssetsTransformer
|
||||||
'byod' => ($asset->byod ? true : false),
|
'byod' => ($asset->byod ? true : false),
|
||||||
|
|
||||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||||
'eol' => (($asset->model) && ($asset->model->eol != '')) ? $asset->model->eol : null,
|
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
|
||||||
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
|
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
|
||||||
'status_label' => ($asset->assetstatus) ? [
|
'status_label' => ($asset->assetstatus) ? [
|
||||||
'id' => (int) $asset->assetstatus->id,
|
'id' => (int) $asset->assetstatus->id,
|
||||||
|
@ -146,7 +147,7 @@ class AssetsTransformer
|
||||||
'clone' => Gate::allows('create', Asset::class) ? true : false,
|
'clone' => Gate::allows('create', Asset::class) ? true : false,
|
||||||
'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false,
|
'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false,
|
||||||
'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false,
|
'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false,
|
||||||
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class)) ? true : false,
|
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -231,6 +232,29 @@ class AssetsTransformer
|
||||||
'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id),
|
'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
|
||||||
|
$fields_array = [];
|
||||||
|
|
||||||
|
foreach ($asset->model->fieldset->fields as $field) {
|
||||||
|
|
||||||
|
// Only display this if it's allowed via the custom field setting
|
||||||
|
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
|
||||||
|
|
||||||
|
$value = $asset->{$field->db_column};
|
||||||
|
if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) {
|
||||||
|
$value = Helper::getFormattedDateObject($value, 'date', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields_array[$field->db_column] = e($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$array['custom_fields'] = $fields_array;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
|
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
|
||||||
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,
|
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,
|
||||||
|
|
|
@ -26,8 +26,8 @@ class LabelsTransformer
|
||||||
'name' => $label->getName(),
|
'name' => $label->getName(),
|
||||||
'unit' => $label->getUnit(),
|
'unit' => $label->getUnit(),
|
||||||
|
|
||||||
'width' => $label->getWidth(),
|
'width' => number_format($label->getWidth(), 2),
|
||||||
'height' => $label->getHeight(),
|
'height' => number_format($label->getHeight(), 2),
|
||||||
|
|
||||||
'margin_top' => $label->getMarginTop(),
|
'margin_top' => $label->getMarginTop(),
|
||||||
'margin_bottom' => $label->getMarginBottom(),
|
'margin_bottom' => $label->getMarginBottom(),
|
||||||
|
|
|
@ -45,6 +45,7 @@ class LicenseSeatsTransformer
|
||||||
'name'=> e($seat->location()->name),
|
'name'=> e($seat->location()->name),
|
||||||
] : null,
|
] : null,
|
||||||
'reassignable' => (bool) $seat->license->reassignable,
|
'reassignable' => (bool) $seat->license->reassignable,
|
||||||
|
'notes' => e($seat->notes),
|
||||||
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ class UsersTransformer
|
||||||
$array = [
|
$array = [
|
||||||
'id' => (int) $user->id,
|
'id' => (int) $user->id,
|
||||||
'avatar' => e($user->present()->gravatar),
|
'avatar' => e($user->present()->gravatar),
|
||||||
'name' => e($user->first_name).' '.e($user->last_name),
|
'name' => e($user->getFullNameAttribute()),
|
||||||
'first_name' => e($user->first_name),
|
'first_name' => e($user->first_name),
|
||||||
'last_name' => e($user->last_name),
|
'last_name' => e($user->last_name),
|
||||||
'username' => e($user->username),
|
'username' => e($user->username),
|
||||||
|
@ -79,7 +79,7 @@ class UsersTransformer
|
||||||
|
|
||||||
$permissions_array['available_actions'] = [
|
$permissions_array['available_actions'] = [
|
||||||
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
|
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
|
||||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
|
'delete' => $user->isDeletable(),
|
||||||
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
|
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
|
||||||
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
|
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
|
||||||
];
|
];
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AccessoryImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('Updating Accessory');
|
$this->log('Updating Accessory');
|
||||||
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
|
$this->item['model_number'] = trim($this->findCsvMatch($row, "model_number"));
|
||||||
$accessory->update($this->sanitizeItemForUpdating($accessory));
|
$accessory->update($this->sanitizeItemForUpdating($accessory));
|
||||||
$accessory->save();
|
$accessory->save();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,12 @@
|
||||||
namespace App\Importer;
|
namespace App\Importer;
|
||||||
|
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
|
use App\Models\AssetModel;
|
||||||
use App\Models\Statuslabel;
|
use App\Models\Statuslabel;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Events\CheckoutableCheckedIn;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class AssetImporter extends ItemImporter
|
class AssetImporter extends ItemImporter
|
||||||
{
|
{
|
||||||
|
@ -63,6 +68,7 @@ class AssetImporter extends ItemImporter
|
||||||
$asset_tag = Asset::autoincrement_asset();
|
$asset_tag = Asset::autoincrement_asset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
|
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
|
||||||
if ($asset) {
|
if ($asset) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
|
@ -77,13 +83,13 @@ class AssetImporter extends ItemImporter
|
||||||
$this->log('No Matching Asset, Creating a new one');
|
$this->log('No Matching Asset, Creating a new one');
|
||||||
$asset = new Asset;
|
$asset = new Asset;
|
||||||
}
|
}
|
||||||
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
|
$this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes'));
|
||||||
$this->item['image'] = $this->findCsvMatch($row, 'image');
|
$this->item['image'] = trim($this->findCsvMatch($row, 'image'));
|
||||||
$this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
|
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0;
|
||||||
$asset->requestable = $this->item['requestable'];
|
$asset->requestable = $this->item['requestable'];
|
||||||
$this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
|
$this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months')));
|
||||||
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
|
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
|
||||||
$this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
|
$this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0;
|
||||||
|
|
||||||
|
|
||||||
// If no status ID is found
|
// If no status ID is found
|
||||||
|
@ -117,11 +123,6 @@ class AssetImporter extends ItemImporter
|
||||||
$item['next_audit_date'] = $this->item['next_audit_date'];
|
$item['next_audit_date'] = $this->item['next_audit_date'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$item['asset_eol_date'] = null;
|
|
||||||
if (isset($this->item['asset_eol_date'])) {
|
|
||||||
$item['asset_eol_date'] = $this->item['asset_eol_date'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($editingAsset) {
|
if ($editingAsset) {
|
||||||
$asset->update($item);
|
$asset->update($item);
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,15 +136,21 @@ class AssetImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($asset->save()) {
|
if ($asset->save()) {
|
||||||
|
|
||||||
$asset->logCreate(trans('general.importer.import_note'));
|
$asset->logCreate(trans('general.importer.import_note'));
|
||||||
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
|
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
|
||||||
|
|
||||||
// If we have a target to checkout to, lets do so.
|
// If we have a target to checkout to, lets do so.
|
||||||
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
|
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
|
||||||
//-- the class that needs to use it (command importer or GUI importer inside the project).
|
//-- the class that needs to use it (command importer or GUI importer inside the project).
|
||||||
if (isset($target)) {
|
if (isset($target) && ($target !== false)) {
|
||||||
|
if (!is_null($asset->assigned_to)){
|
||||||
|
if ($asset->assigned_to != $target->id){
|
||||||
|
event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
|
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ class ComponentImporter extends ItemImporter
|
||||||
{
|
{
|
||||||
$component = null;
|
$component = null;
|
||||||
$this->log('Creating Component');
|
$this->log('Creating Component');
|
||||||
$component = Component::where('name', $this->item['name'])
|
$component = Component::where('name', trim($this->item['name']))
|
||||||
->where('serial', $this->item['serial'])
|
->where('serial', trim($this->item['serial']))
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($component) {
|
if ($component) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ConsumableImporter extends ItemImporter
|
||||||
*/
|
*/
|
||||||
public function createConsumableIfNotExists($row)
|
public function createConsumableIfNotExists($row)
|
||||||
{
|
{
|
||||||
$consumable = Consumable::where('name', $this->item['name'])->first();
|
$consumable = Consumable::where('name', trim($this->item['name']))->first();
|
||||||
if ($consumable) {
|
if ($consumable) {
|
||||||
if (! $this->updating) {
|
if (! $this->updating) {
|
||||||
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
|
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
|
||||||
|
@ -41,9 +41,9 @@ class ConsumableImporter extends ItemImporter
|
||||||
}
|
}
|
||||||
$this->log('No matching consumable, creating one');
|
$this->log('No matching consumable, creating one');
|
||||||
$consumable = new Consumable();
|
$consumable = new Consumable();
|
||||||
$this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
|
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
|
||||||
$this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
|
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
|
||||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
|
||||||
$consumable->fill($this->sanitizeItemForStoring($consumable));
|
$consumable->fill($this->sanitizeItemForStoring($consumable));
|
||||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||||
$consumable->unsetEventDispatcher();
|
$consumable->unsetEventDispatcher();
|
||||||
|
|
|
@ -19,22 +19,76 @@ abstract class Importer
|
||||||
* Id of User performing import
|
* Id of User performing import
|
||||||
* @var
|
* @var
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected $user_id;
|
protected $user_id;
|
||||||
/**
|
/**
|
||||||
* Are we updating items in the import
|
* Are we updating items in the import
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected $updating;
|
protected $updating;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Map of item fields->csv names
|
* Default Map of item fields->csv names
|
||||||
*
|
*
|
||||||
* This has been moved into Livewire/Importer.php to be more granular.
|
* This has been moved into app/Http/Livewire/Importer.php to be more granular.
|
||||||
* @todo - remove references to this property since we don't use it anymore.
|
* This private variable is ONLY used for the cli-importer.
|
||||||
*
|
*
|
||||||
|
* @todo - find a way to make this less duplicative
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $defaultFieldMap = [
|
private $defaultFieldMap = [
|
||||||
|
'asset_tag' => 'asset tag',
|
||||||
|
'activated' => 'activated',
|
||||||
|
'category' => 'category',
|
||||||
|
'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already.
|
||||||
|
'checkout_location' => 'checkout location',
|
||||||
|
'company' => 'company',
|
||||||
|
'item_name' => 'item name',
|
||||||
|
'item_number' => 'item number',
|
||||||
|
'image' => 'image',
|
||||||
|
'expiration_date' => 'expiration date',
|
||||||
|
'location' => 'location',
|
||||||
|
'notes' => 'notes',
|
||||||
|
'license_email' => 'licensed to email',
|
||||||
|
'license_name' => 'licensed to name',
|
||||||
|
'maintained' => 'maintained',
|
||||||
|
'manufacturer' => 'manufacturer',
|
||||||
|
'asset_model' => 'model name',
|
||||||
|
'model_number' => 'model number',
|
||||||
|
'order_number' => 'order number',
|
||||||
|
'purchase_cost' => 'purchase cost',
|
||||||
|
'purchase_date' => 'purchase date',
|
||||||
|
'purchase_order' => 'purchase order',
|
||||||
|
'qty' => 'quantity',
|
||||||
|
'reassignable' => 'reassignable',
|
||||||
|
'requestable' => 'requestable',
|
||||||
|
'seats' => 'seats',
|
||||||
|
'serial' => 'serial number',
|
||||||
|
'status' => 'status',
|
||||||
|
'supplier' => 'supplier',
|
||||||
|
'termination_date' => 'termination date',
|
||||||
|
'warranty_months' => 'warranty',
|
||||||
|
'full_name' => 'full name',
|
||||||
|
'email' => 'email',
|
||||||
|
'username' => 'username',
|
||||||
|
'address' => 'address',
|
||||||
|
'address2' => 'address2',
|
||||||
|
'city' => 'city',
|
||||||
|
'state' => 'state',
|
||||||
|
'country' => 'country',
|
||||||
|
'zip' => 'zip',
|
||||||
|
'jobtitle' => 'job title',
|
||||||
|
'employee_num' => 'employee number',
|
||||||
|
'phone_number' => 'phone number',
|
||||||
|
'first_name' => 'first name',
|
||||||
|
'last_name' => 'last name',
|
||||||
|
'department' => 'department',
|
||||||
|
'manager_name' => 'manager full name',
|
||||||
|
'manager_username' => 'manager username',
|
||||||
|
'min_amt' => 'minimum quantity',
|
||||||
|
'remote' => 'remote',
|
||||||
|
'vip' => 'vip',
|
||||||
];
|
];
|
||||||
/**
|
/**
|
||||||
* Map of item fields->csv names
|
* Map of item fields->csv names
|
||||||
|
@ -281,9 +335,11 @@ abstract class Importer
|
||||||
$user_array['email'] = User::generateEmailFromFullName($user_array['full_name']);
|
$user_array['email'] = User::generateEmailFromFullName($user_array['full_name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get some variables for $user_formatted_array in case we need them later
|
||||||
|
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
|
||||||
|
|
||||||
if (empty($user_array['first_name'])) {
|
if (empty($user_array['first_name'])) {
|
||||||
// Get some fields for first name and last name based off of full name
|
// Get some fields for first name and last name based off of full name
|
||||||
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
|
|
||||||
$user_array['first_name'] = $user_formatted_array['first_name'];
|
$user_array['first_name'] = $user_formatted_array['first_name'];
|
||||||
$user_array['last_name'] = $user_formatted_array['last_name'];
|
$user_array['last_name'] = $user_formatted_array['last_name'];
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue