diff --git a/.all-contributorsrc b/.all-contributorsrc
index f5085f95e..68aec8532 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1,11 +1,14 @@
{
"projectName": "snipe-it",
"projectOwner": "snipe",
+ "repoType": "github",
+ "repoHost": "https://github.com",
"files": [
- "README.md"
+ "CONTRIBUTORS.md"
],
"imageSize": 110,
"commit": true,
+ "commitConvention": "angular",
"contributors": [
{
"login": "snipe",
@@ -2988,6 +2991,205 @@
"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"
+ ]
+ },
+ {
+ "login": "koiakoia",
+ "name": "koiakoia",
+ "avatar_url": "https://avatars.githubusercontent.com/u/60405354?v=4",
+ "profile": "https://github.com/koiakoia",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "mustafa-online",
+ "name": "Mustafa Online",
+ "avatar_url": "https://avatars.githubusercontent.com/u/5323832?v=4",
+ "profile": "https://github.com/mustafa-online",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "franceslui",
+ "name": "franceslui",
+ "avatar_url": "https://avatars.githubusercontent.com/u/104601439?v=4",
+ "profile": "https://github.com/franceslui",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Q4kK",
+ "name": "Q4kK",
+ "avatar_url": "https://avatars.githubusercontent.com/u/125313163?v=4",
+ "profile": "https://github.com/Q4kK",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "squintfox",
+ "name": "squintfox",
+ "avatar_url": "https://avatars.githubusercontent.com/u/55590532?v=4",
+ "profile": "https://github.com/squintfox",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "jeffclay",
+ "name": "Jeff Clay",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1380084?v=4",
+ "profile": "https://github.com/jeffclay",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "PP-JN-RL",
+ "name": "Phil J R",
+ "avatar_url": "https://avatars.githubusercontent.com/u/52716446?v=4",
+ "profile": "https://github.com/PP-JN-RL",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "chandanchowdhury",
+ "name": "i_virus",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1496725?v=4",
+ "profile": "https://www.corelight.com/",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "gitgrimbo",
+ "name": "Paul Grime",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1020541?v=4",
+ "profile": "https://github.com/gitgrimbo",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "LeePorte",
+ "name": "Lee Porte",
+ "avatar_url": "https://avatars.githubusercontent.com/u/922815?v=4",
+ "profile": "https://leeporte.co.uk",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "bryanlopezinc",
+ "name": "BRYAN ",
+ "avatar_url": "https://avatars.githubusercontent.com/u/23613427?v=4",
+ "profile": "https://github.com/bryanlopezinc",
+ "contributions": [
+ "code",
+ "test"
+ ]
+ },
+ {
+ "login": "U-H-T",
+ "name": "U-H-T",
+ "avatar_url": "https://avatars.githubusercontent.com/u/64061710?v=4",
+ "profile": "https://github.com/U-H-T",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Tyree",
+ "name": "Matt Tyree",
+ "avatar_url": "https://avatars.githubusercontent.com/u/5395363?v=4",
+ "profile": "https://github.com/Tyree",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "FlorentDotMe",
+ "name": "Florent Bervas",
+ "avatar_url": "https://avatars.githubusercontent.com/u/292081?v=4",
+ "profile": "http://spoontux.net",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "dbakan",
+ "name": "Daniel Albertsen",
+ "avatar_url": "https://avatars.githubusercontent.com/u/4498077?v=4",
+ "profile": "https://ditscheri.com",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "r-xyz",
+ "name": "r-xyz",
+ "avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4",
+ "profile": "https://github.com/r-xyz",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "DrekiDegga",
+ "name": "Steven Mainor",
+ "avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4",
+ "profile": "https://github.com/DrekiDegga",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "arne-kroeger",
+ "name": "arne-kroeger",
+ "avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4",
+ "profile": "https://github.com/arne-kroeger",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Glukose1",
+ "name": "Glukose1",
+ "avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4",
+ "profile": "https://github.com/Glukose1",
+ "contributions": [
+ "code"
+ ]
}
]
}
diff --git a/.env.dev.docker b/.env.dev.docker
new file mode 100644
index 000000000..7b9e2000c
--- /dev/null
+++ b/.env.dev.docker
@@ -0,0 +1,166 @@
+# --------------------------------------------
+# REQUIRED: DB SETUP
+# --------------------------------------------
+MYSQL_DATABASE=snipeit
+MYSQL_USER=snipeit
+MYSQL_PASSWORD=changeme1234
+MYSQL_ROOT_PASSWORD=changeme1234
+# --------------------------------------------
+# REQUIRED: BASIC APP SETTINGS
+# --------------------------------------------
+APP_ENV=develop
+APP_DEBUG=false
+# please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here
+APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
+APP_URL=http://localhost:8000
+APP_TIMEZONE='UTC'
+APP_LOCALE=en
+MAX_RESULTS=500
+
+# --------------------------------------------
+# REQUIRED: UPLOADED FILE STORAGE SETTINGS
+# --------------------------------------------
+PRIVATE_FILESYSTEM_DISK=local
+PUBLIC_FILESYSTEM_DISK=local_public
+
+# --------------------------------------------
+# REQUIRED: DATABASE SETTINGS
+# --------------------------------------------
+DB_CONNECTION=mysql
+DB_HOST=mariadb
+DB_DATABASE=snipeit
+DB_USERNAME=snipeit
+DB_PASSWORD=changeme1234
+DB_PREFIX=null
+DB_DUMP_PATH='/usr/bin'
+DB_CHARSET=utf8mb4
+DB_COLLATION=utf8mb4_unicode_ci
+
+# --------------------------------------------
+# OPTIONAL: SSL DATABASE SETTINGS
+# --------------------------------------------
+DB_SSL=false
+DB_SSL_IS_PAAS=false
+DB_SSL_KEY_PATH=null
+DB_SSL_CERT_PATH=null
+DB_SSL_CA_PATH=null
+DB_SSL_CIPHER=null
+DB_SSL_VERIFY_SERVER=null
+
+# --------------------------------------------
+# REQUIRED: OUTGOING MAIL SERVER SETTINGS
+# --------------------------------------------
+MAIL_DRIVER=smtp
+MAIL_HOST=mailhog
+MAIL_PORT=1025
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+MAIL_FROM_ADDR=you@example.com
+MAIL_FROM_NAME='Snipe-IT'
+MAIL_REPLYTO_ADDR=you@example.com
+MAIL_REPLYTO_NAME='Snipe-IT'
+MAIL_AUTO_EMBED_METHOD='attachment'
+
+# --------------------------------------------
+# REQUIRED: IMAGE LIBRARY
+# This should be gd or imagick
+# --------------------------------------------
+IMAGE_LIB=gd
+
+
+# --------------------------------------------
+# OPTIONAL: BACKUP SETTINGS
+# --------------------------------------------
+MAIL_BACKUP_NOTIFICATION_DRIVER=null
+MAIL_BACKUP_NOTIFICATION_ADDRESS=null
+BACKUP_ENV=true
+
+
+# --------------------------------------------
+# OPTIONAL: SESSION SETTINGS
+# --------------------------------------------
+SESSION_LIFETIME=12000
+EXPIRE_ON_CLOSE=false
+ENCRYPT=false
+COOKIE_NAME=snipeit_session
+COOKIE_DOMAIN=null
+SECURE_COOKIES=false
+API_TOKEN_EXPIRATION_YEARS=40
+
+# --------------------------------------------
+# OPTIONAL: SECURITY HEADER SETTINGS
+# --------------------------------------------
+APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
+ALLOW_IFRAMING=false
+REFERRER_POLICY=same-origin
+ENABLE_CSP=false
+CORS_ALLOWED_ORIGINS=null
+ENABLE_HSTS=false
+
+# --------------------------------------------
+# OPTIONAL: CACHE SETTINGS
+# --------------------------------------------
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+QUEUE_DRIVER=sync
+CACHE_PREFIX=snipeit
+
+# --------------------------------------------
+# OPTIONAL: REDIS SETTINGS
+# --------------------------------------------
+REDIS_HOST=redis
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+# --------------------------------------------
+# OPTIONAL: MEMCACHED SETTINGS
+# --------------------------------------------
+MEMCACHED_HOST=null
+MEMCACHED_PORT=null
+
+# --------------------------------------------
+# OPTIONAL: PUBLIC S3 Settings
+# --------------------------------------------
+PUBLIC_AWS_SECRET_ACCESS_KEY=null
+PUBLIC_AWS_ACCESS_KEY_ID=null
+PUBLIC_AWS_DEFAULT_REGION=null
+PUBLIC_AWS_BUCKET=null
+PUBLIC_AWS_URL=null
+PUBLIC_AWS_BUCKET_ROOT=null
+
+# --------------------------------------------
+# OPTIONAL: PRIVATE S3 Settings
+# --------------------------------------------
+PRIVATE_AWS_ACCESS_KEY_ID=null
+PRIVATE_AWS_SECRET_ACCESS_KEY=null
+PRIVATE_AWS_DEFAULT_REGION=null
+PRIVATE_AWS_BUCKET=null
+PRIVATE_AWS_URL=null
+PRIVATE_AWS_BUCKET_ROOT=null
+
+# --------------------------------------------
+# OPTIONAL: AWS Settings
+# --------------------------------------------
+AWS_ACCESS_KEY_ID=null
+AWS_SECRET_ACCESS_KEY=null
+AWS_DEFAULT_REGION=null
+
+# --------------------------------------------
+# OPTIONAL: LOGIN THROTTLING
+# --------------------------------------------
+LOGIN_MAX_ATTEMPTS=5
+LOGIN_LOCKOUT_DURATION=60
+RESET_PASSWORD_LINK_EXPIRES=900
+
+# --------------------------------------------
+# OPTIONAL: MISC
+# --------------------------------------------
+LOG_CHANNEL=stderr
+LOG_MAX_DAYS=10
+APP_LOCKED=false
+APP_CIPHER=AES-256-CBC
+APP_FORCE_TLS=false
+GOOGLE_MAPS_API=
+LDAP_MEM_LIM=500M
+LDAP_TIME_LIM=600
diff --git a/.env.docker b/.env.docker
index 87897b10d..9e5038301 100644
--- a/.env.docker
+++ b/.env.docker
@@ -1,20 +1,20 @@
# --------------------------------------------
-# REQUIRED: DB SETUP
+# REQUIRED: DOCKER SPECIFIC SETTINGS
# --------------------------------------------
-MYSQL_DATABASE=snipeit
-MYSQL_USER=snipeit
-MYSQL_PASSWORD=changeme1234
-MYSQL_ROOT_PASSWORD=changeme1234
+APP_VERSION=v6.4.1
+APP_PORT=8000
+
# --------------------------------------------
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
-APP_ENV=develop
+APP_ENV=production
APP_DEBUG=false
-# please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here
+# Please regenerate the APP_KEY value by calling `docker compose run --rm snipeit php artisan key:generate --show`. Copy paste the value here
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
+# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE=en-US
MAX_RESULTS=500
# --------------------------------------------
@@ -27,10 +27,12 @@ PUBLIC_FILESYSTEM_DISK=local_public
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=mysql
-DB_HOST=mariadb
+DB_HOST=db
+DB_PORT='3306'
DB_DATABASE=snipeit
DB_USERNAME=snipeit
DB_PASSWORD=changeme1234
+MYSQL_ROOT_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4
@@ -45,29 +47,35 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null
+DB_SSL_VERIFY_SERVER=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
-MAIL_DRIVER=smtp
+MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
-MAIL_ENCRYPTION=null
+MAIL_TLS_VERIFY_PEER=true
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_AUTO_EMBED_METHOD='attachment'
+# --------------------------------------------
+# REQUIRED: DATA PROTECTION
+# --------------------------------------------
+ALLOW_BACKUP_DELETE=false
+ALLOW_DATA_PURGE=false
+
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
# This should be gd or imagick
# --------------------------------------------
IMAGE_LIB=gd
-
# --------------------------------------------
# OPTIONAL: BACKUP SETTINGS
# --------------------------------------------
@@ -75,7 +83,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
-
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------
@@ -90,7 +97,7 @@ API_TOKEN_EXPIRATION_YEARS=40
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
# --------------------------------------------
-APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
+APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.0.0.0/8
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
@@ -108,7 +115,7 @@ CACHE_PREFIX=snipeit
# --------------------------------------------
# OPTIONAL: REDIS SETTINGS
# --------------------------------------------
-REDIS_HOST=redis
+REDIS_HOST=null
REDIS_PASSWORD=null
REDIS_PORT=6379
diff --git a/.env.dusk.example b/.env.dusk.example
index 074f6fc3d..e306a06cf 100644
--- a/.env.dusk.example
+++ b/.env.dusk.example
@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk=
APP_URL=http://127.0.0.1:8000
APP_TIMEZONE='US/Eastern'
-APP_LOCALE=en
+APP_LOCALE=en-US
APP_LOCKED=false
MAX_RESULTS=200
@@ -36,11 +36,12 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null
+DB_SSL_VERIFY_SERVER=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
-MAIL_DRIVER="log"
+MAIL_MAILER="log"
# --------------------------------------------
diff --git a/.env.example b/.env.example
index 2d45ff580..426af4ff8 100644
--- a/.env.example
+++ b/.env.example
@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY=ChangeMe
APP_URL=null
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE='en-US'
MAX_RESULTS=500
# --------------------------------------------
@@ -42,21 +42,26 @@ DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null
+DB_SSL_VERIFY_SERVER=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
-MAIL_DRIVER=smtp
+MAIL_MAILER=smtp
MAIL_HOST=email-smtp.us-west-2.amazonaws.com
MAIL_PORT=587
MAIL_USERNAME=YOURUSERNAME
MAIL_PASSWORD=YOURPASSWORD
-MAIL_ENCRYPTION=null
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_AUTO_EMBED_METHOD='attachment'
+MAIL_TLS_VERIFY_PEER=true
+
+# MAIL_ENCRYPTION is no longer supported. SymfonyMailer will use tls if it's
+# advertised, and won't if it's not. If you want to use your mail server's IP but it's failing
+# because of certificate errors, set MAIL_TLS_VERIFY_PEER-true
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
@@ -82,10 +87,12 @@ SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false
ENCRYPT=false
COOKIE_NAME=snipeit_session
+PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
+BS_TABLE_DEEPLINK=true
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
@@ -94,6 +101,7 @@ APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
+ADDITIONAL_CSP_URLS=null
CORS_ALLOWED_ORIGINS=null
ENABLE_HSTS=false
@@ -176,6 +184,7 @@ REPORT_TIME_LIMIT=12000
REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
+LIVEWIRE_URL_PREFIX=null
# --------------------------------------------
# OPTIONAL: HASHING
@@ -190,4 +199,4 @@ ARGON_TIME=2
# OPTIONAL: SCIM
# --------------------------------------------
SCIM_TRACE=false
-SCIM_STANDARDS_COMPLIANCE=false
\ No newline at end of file
+SCIM_STANDARDS_COMPLIANCE=false
diff --git a/.env.testing-ci b/.env.testing-ci
index 82cd28570..3e00eb3fa 100644
--- a/.env.testing-ci
+++ b/.env.testing-ci
@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY='base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU='
APP_URL='http://localhost:8000'
APP_TIMEZONE='US/Pacific'
-APP_LOCALE=en
+APP_LOCALE='en-US'
FILESYSTEM_DISK=local
# --------------------------------------------
@@ -22,7 +22,7 @@ DB_PASSWORD=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
-MAIL_DRIVER=log
+MAIL_MAILER=log
# --------------------------------------------
diff --git a/.env.testing.example b/.env.testing.example
index 3391d6272..26211f95c 100644
--- a/.env.testing.example
+++ b/.env.testing.example
@@ -6,7 +6,7 @@ APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE='en-US'
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
diff --git a/.env.tests b/.env.tests
index 038884e5d..8128d22b5 100644
--- a/.env.tests
+++ b/.env.tests
@@ -18,6 +18,6 @@ APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
LOGIN_MAX_ATTEMPTS=1000000
LOGIN_LOCKOUT_DURATION=100000000
-MAIL_DRIVER=log
+MAIL_MAILER=log
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT
diff --git a/.env.unit-tests b/.env.unit-tests
index 3a7263f86..28177839d 100644
--- a/.env.unit-tests
+++ b/.env.unit-tests
@@ -15,6 +15,6 @@ APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
LOGIN_MAX_ATTEMPTS=1000000
LOGIN_LOCKOUT_DURATION=100000000
-MAIL_DRIVER=log
+MAIL_MAILER=log
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT
diff --git a/.github/workflows/SA-codeql.yml b/.github/workflows/SA-codeql.yml
index 05efd9118..29f3e1b1f 100644
--- a/.github/workflows/SA-codeql.yml
+++ b/.github/workflows/SA-codeql.yml
@@ -30,10 +30,10 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml
index 3184042c6..e3e935642 100644
--- a/.github/workflows/codacy-analysis.yml
+++ b/.github/workflows/codacy-analysis.yml
@@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
- uses: codacy/codacy-analysis-cli-action@v4.3.0
+ uses: codacy/codacy-analysis-cli-action@v4.4.5
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations
@@ -52,6 +52,6 @@ jobs:
# Upload the SARIF file generated in the previous step
- name: Upload SARIF results file
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml
index 325593453..7b9331c97 100644
--- a/.github/workflows/crowdin-upload.yml
+++ b/.github/workflows/crowdin-upload.yml
@@ -12,7 +12,7 @@ jobs:
uses: actions/checkout@v4
- name: Crowdin push
- uses: crowdin/github-action@v1
+ uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml
index 7223ab30d..bd46f9567 100644
--- a/.github/workflows/docker-alpine.yml
+++ b/.github/workflows/docker-alpine.yml
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 18d055627..adb87f3a5 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml
new file mode 100644
index 000000000..f9064dec9
--- /dev/null
+++ b/.github/workflows/dockerhub-description.yml
@@ -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
diff --git a/.github/workflows/tests-mysql.yml b/.github/workflows/tests-mysql.yml
new file mode 100644
index 000000000..737a86dca
--- /dev/null
+++ b/.github/workflows/tests-mysql.yml
@@ -0,0 +1,79 @@
+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:
+ - "8.1"
+ - "8.2"
+ - "8.3"
+
+ name: PHP ${{ matrix.php-version }}
+
+ steps:
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - uses: actions/checkout@v4
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Copy .env
+ run: |
+ cp -v .env.testing.example .env
+ cp -v .env.testing.example .env.testing
+
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+
+ - name: Setup Laravel
+ env:
+ DB_CONNECTION: mysql
+ DB_DATABASE: snipeit
+ DB_PORT: ${{ job.services.mysql.ports[3306] }}
+ DB_USERNAME: root
+ run: |
+ php artisan key:generate
+ php artisan migrate --force
+ php artisan passport:install
+ chmod -R 777 storage bootstrap/cache
+
+ - name: Execute tests (Unit and Feature tests) via PHPUnit
+ env:
+ DB_CONNECTION: mysql
+ DB_DATABASE: snipeit
+ DB_PORT: ${{ job.services.mysql.ports[3306] }}
+ DB_USERNAME: root
+ run: php artisan test --parallel
diff --git a/.github/workflows/tests-postgres.yml b/.github/workflows/tests-postgres.yml
new file mode 100644
index 000000000..0c361511b
--- /dev/null
+++ b/.github/workflows/tests-postgres.yml
@@ -0,0 +1,77 @@
+name: Tests in Postgres
+
+on: workflow_dispatch
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ services:
+ postgresql:
+ image: postgres
+ env:
+ POSTGRES_DB: snipeit
+ POSTGRES_USER: snipeit
+ POSTGRES_PASSWORD: password
+ ports:
+ - 5432:5432
+ options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version:
+ - "8.1"
+ - "8.2"
+ - "8.3"
+
+ name: PHP ${{ matrix.php-version }}
+
+ steps:
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - uses: actions/checkout@v4
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Copy .env
+ run: |
+ cp -v .env.testing.example .env
+ cp -v .env.testing.example .env.testing
+
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+
+ - name: Setup Laravel
+ env:
+ DB_CONNECTION: pgsql
+ DB_DATABASE: snipeit
+ DB_PORT: ${{ job.services.postgresql.ports[5432] }}
+ DB_USERNAME: snipeit
+ DB_PASSWORD: password
+ run: |
+ php artisan key:generate
+ php artisan migrate --force
+ php artisan passport:install
+ chmod -R 777 storage bootstrap/cache
+
+ - name: Execute tests (Unit and Feature tests) via PHPUnit
+ env:
+ DB_CONNECTION: pgsql
+ DB_DATABASE: snipeit
+ DB_PORT: ${{ job.services.postgresql.ports[5432] }}
+ DB_USERNAME: snipeit
+ DB_PASSWORD: password
+ run: php artisan test --parallel
diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml
new file mode 100644
index 000000000..49c7c92d8
--- /dev/null
+++ b/.github/workflows/tests-sqlite.yml
@@ -0,0 +1,61 @@
+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@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Copy .env
+ run: |
+ cp -v .env.testing.example .env
+ cp -v .env.testing.example .env.testing
+
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+
+ - name: Generate key
+ run: php artisan key:generate
+
+ - name: Setup Passport
+ run: php artisan passport:keys
+
+ - 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
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index a82946161..000000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-name: Tests
-
-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
diff --git a/.gitignore b/.gitignore
index bf8360ba2..0715ac049 100755
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,6 @@ _ide_helper_models.php
/.phplint-cache
storage/ldap_client_tls.cert
storage/ldap_client_tls.key
+/storage/framework/testing
+
+/.phpunit.cache
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
index 27bbca745..8ddbc0c64 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v12.22.1
+v18.16.0
diff --git a/.upgrade_requirements.json b/.upgrade_requirements.json
new file mode 100644
index 000000000..53d7337b6
--- /dev/null
+++ b/.upgrade_requirements.json
@@ -0,0 +1,10 @@
+{
+ "DOC1": "This file is meant to be pulled from the current HEAD of the desired branch, NOT referenced locally",
+ "DOC2": "In other words, what you see locally are the requirements for your _current_ install",
+ "DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version",
+ "DOC4": "You should really just ignore it and run upgrade.php. Really",
+ "php_min_version": "8.1.0",
+ "php_max_major_minor": "8.3",
+ "php_max_wontwork": "8.4.0",
+ "current_snipeit_version": "7.0"
+}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 000000000..6359814b4
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,58 @@
+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:
+
+
+| [
snipe](http://www.snipe.net)
[💻](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") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](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") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](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") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
+| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
+| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
+| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
+| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
+| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
+| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](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") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") |
+| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") |
+| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") |
+| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
+| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
+| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
+| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
+| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
+| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
+| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
+| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") |
+| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") |
+| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") |
+| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") |
+| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") |
+| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") |
+| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") |
+| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") |
+| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") |
+| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") |
+| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
+| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
+| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
+| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
+| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
+| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
+| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
+| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
+| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") |
+| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
+| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
+| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
+| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
+| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
+| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
+| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
+| [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
+| [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
+| [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
+| [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
+| [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
+| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
+| [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
+| [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
+| [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
+| [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
+
+
+This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
diff --git a/Dockerfile b/Dockerfile
index 88de52858..bd363ccd1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -105,7 +105,7 @@ RUN \
&& ln -fs "/var/lib/snipeit/keys/ldap_client_tls.cert" "/var/www/html/storage/ldap_client_tls.cert" \
&& ln -fs "/var/lib/snipeit/keys/ldap_client_tls.key" "/var/www/html/storage/ldap_client_tls.key" \
&& chown docker "/var/lib/snipeit/keys/" \
- && chown -h docker "/var/www/html/storage/" \
+ && chown -Rh docker "/var/www/html/storage/" \
&& chmod +x /var/www/html/artisan \
&& echo "Finished setting up application in /var/www/html"
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
index 62a928f8a..c08cbbd95 100644
--- a/Dockerfile.alpine
+++ b/Dockerfile.alpine
@@ -1,34 +1,35 @@
-FROM alpine:3.17.3
+FROM alpine:3.19
# Apache + PHP
RUN apk add --no-cache \
apache2 \
- php81 \
- php81-common \
- php81-apache2 \
- php81-curl \
- php81-ldap \
- php81-mysqli \
- php81-gd \
- php81-xml \
- php81-mbstring \
- php81-zip \
- php81-ctype \
- php81-tokenizer \
- php81-pdo_mysql \
- php81-openssl \
- php81-bcmath \
- php81-phar \
- php81-json \
- php81-iconv \
- php81-fileinfo \
- php81-simplexml \
- php81-session \
- php81-dom \
- php81-xmlwriter \
- php81-xmlreader \
- php81-sodium \
- php81-redis \
- php81-pecl-memcached \
+ php82 \
+ php82-common \
+ php82-apache2 \
+ php82-curl \
+ php82-ldap \
+ php82-mysqli \
+ php82-gd \
+ php82-xml \
+ php82-mbstring \
+ php82-zip \
+ php82-ctype \
+ php82-tokenizer \
+ php82-pdo_mysql \
+ php82-openssl \
+ php82-bcmath \
+ php82-phar \
+ php82-json \
+ php82-iconv \
+ php82-fileinfo \
+ php82-simplexml \
+ php82-session \
+ php82-dom \
+ php82-xmlwriter \
+ php82-xmlreader \
+ php82-sodium \
+ php82-redis \
+ php82-pecl-memcached \
+ php82-exif \
curl \
wget \
vim \
@@ -41,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
-RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
+RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite
diff --git a/README.md b/README.md
index f1ea60974..0086c7b32 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,18 @@
-[](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://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
+[](#contributing) [](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
-It is built on [Laravel 8](http://laravel.com).
+It is built on [Laravel 10](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
-__This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
+> [!TIP]
+> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
-----
@@ -19,7 +22,7 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
-[](https://heroku.com/deploy)
+
-----
### User's Manual
@@ -30,8 +33,9 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
-**PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
-
+> [!IMPORTANT]
+> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
+>
-----
### Upgrading
@@ -55,6 +59,9 @@ Please see the [translations documentation](https://snipe-it.readme.io/docs/tran
Since the release of the JSON REST API, several third-party developers have been developing modules and libraries to work with Snipe-IT.
+> [!NOTE]
+> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
+
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
@@ -65,14 +72,13 @@ Since the release of the JSON REST API, several third-party developers have been
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
-- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
-- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT
+- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
+- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
-- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
-
-As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
+- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
+- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
-----
@@ -80,73 +86,15 @@ 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 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).
+[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT.
+
-----
### Security
-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:
-
-
-| [
snipe](http://www.snipe.net)
[💻](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") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](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") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](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") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
-| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
-| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
-| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
-| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
-| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
-| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](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") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") |
-| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") |
-| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") |
-| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
-| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
-| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
-| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
-| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
-| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
-| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
-| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") |
-| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") |
-| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") |
-| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") |
-| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") |
-| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") |
-| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") |
-| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") |
-| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") |
-| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") |
-| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
-| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
-| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
-| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
-| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
-| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
-| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
-| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
-| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") |
-| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
-| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
-| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
-| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
-| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
-| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
-| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
-| [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
-| [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
-| [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
-| [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
-| [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
-| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
-
-
-This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
-
+> [!IMPORTANT]
+> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
diff --git a/TESTING.md b/TESTING.md
index 3a2f4e538..002cf27d3 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -20,7 +20,7 @@ APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE=en-US
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
@@ -45,8 +45,21 @@ DB_PASSWORD={}
Now you are ready to run the entire test suite from your terminal:
-`php artisan test`
+```shell
+php artisan test
+````
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`
+```shell
+php artisan test tests/Unit/AccessoryTest.php
+```
+
+Some tests, like ones concerning LDAP, are marked with the `@group` annotation. Those groups can be run, or excluded, using the `--group` or `--exclude-group` flags:
+
+```shell
+php artisan test --group=ldap
+
+php artisan test --exclude-group=ldap
+```
+This can be helpful if a set of tests are failing because you don't have an extension, like LDAP, installed.
diff --git a/app.json b/app.json
index 97a81d4d7..0bf523f5e 100644
--- a/app.json
+++ b/app.json
@@ -38,7 +38,7 @@
"description": "The maximum number of search results that can be returned at one time.",
"value": "500"
},
- "MAIL_DRIVER": {
+ "MAIL_MAILER": {
"description": "Mail driver - Generally SMTP on Heroku - https://snipe-it.readme.io/docs/configuration#required-outgoing-mail-settings",
"value": "smtp"
},
@@ -58,9 +58,9 @@
"description": "SMTP Server Password",
"value": "YOURPASSWORD"
},
- "MAIL_ENCRYPTION": {
- "description": "Encryption protocol for email sending.",
- "value": "null"
+ "MAIL_TLS_VERIFY_PEER": {
+ "description": "Ensure validity of TLS certificate on remote mail server",
+ "value": true
},
"MAIL_FROM_ADDR": {
"description": "Email from address",
diff --git a/app/Console/Commands/FixupAssignedToWithoutAssignedType.php b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php
new file mode 100644
index 000000000..ea3a55439
--- /dev/null
+++ b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php
@@ -0,0 +1,66 @@
+whereNotNull("assigned_to")->withTrashed();
+ $this->withProgressBar($assets->get(), function (Asset $asset) {
+ //now check each action log, from the most recent backwards, to find the last checkin or checkout
+ foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
+ if($this->option("debug")) {
+ $this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
+ }
+ switch($action_log->action_type) {
+ case 'checkin from':
+ if($this->option("debug")) {
+ $this->info("Doing a checkin for ".$asset->id);
+ }
+ $asset->assigned_to = null;
+ // if you have a required custom field, we still want to save, and we *don't* want an action_log
+ $asset->saveQuietly();
+ return;
+
+ case 'checkout':
+ if($this->option("debug")) {
+ $this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
+ }
+ if($asset->assigned_to != $action_log->target_id) {
+ $this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
+ //FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
+ }
+ $asset->assigned_type = $action_log->target_type;
+ $asset->saveQuietly(); // see above
+ return;
+ }
+ }
+ $asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
+ $asset->saveQuietly(); //see above
+ });
+ $this->newLine();
+ $this->info("Assets assigned_type are fixed");
+ }
+}
diff --git a/app/Console/Commands/GeneratePersonalAccessToken.php b/app/Console/Commands/GeneratePersonalAccessToken.php
index 098d9678a..c5f531213 100644
--- a/app/Console/Commands/GeneratePersonalAccessToken.php
+++ b/app/Console/Commands/GeneratePersonalAccessToken.php
@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
use App\Models\User;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
-use DB;
+use Illuminate\Support\Facades\DB;
class GeneratePersonalAccessToken extends Command
{
diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php
index 594f6f064..845db27ef 100755
--- a/app/Console/Commands/LdapSync.php
+++ b/app/Console/Commands/LdapSync.php
@@ -9,7 +9,7 @@ use App\Models\Setting;
use App\Models\Ldap;
use App\Models\User;
use App\Models\Location;
-use Log;
+use Illuminate\Support\Facades\Log;
class LdapSync extends Command
{
@@ -66,6 +66,7 @@ class LdapSync extends Command
$ldap_result_dept = Setting::getSettings()->ldap_dept;
$ldap_result_manager = Setting::getSettings()->ldap_manager;
$ldap_default_group = Setting::getSettings()->ldap_default_group;
+ $search_base = Setting::getSettings()->ldap_base_dn;
try {
$ldapconn = Ldap::connectToLdap();
@@ -83,26 +84,37 @@ class LdapSync extends Command
$summary = [];
try {
- if ( $this->option('location_id') != '') {
+
+ /**
+ * 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');
+ $location_ou = Location::where('id', '=', $location_id)->value('ldap_ou');
$search_base = $location_ou;
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
}
}
- else if ($this->option('base_dn') != '') {
+ /**
+ * if a manual base DN has been specified, use that. Allow the Base DN to override
+ * even if there's a location-based DN - if you picked it, you must have picked it for a reason.
+ */
+ if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
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') != '') {
$results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
} else {
$results = Ldap::findLdapUsers($search_base);
}
+
} catch (\Exception $e) {
if ($this->option('json_summary')) {
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
@@ -115,14 +127,14 @@ class LdapSync extends Command
/* 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
- 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') != '') {
+ 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 . ')');
+ }
- } elseif ($this->option('location_id') != '') {
- foreach($this->option('location_id') as $location_id) {
+ } elseif ($this->option('location_id')) {
+ foreach($this->option('location_id') as $location_id) {
if ($location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
@@ -239,6 +251,7 @@ class LdapSync extends Command
// Creating a new user.
$user = new User;
$user->password = $user->noPassword();
+ $user->locale = app()->getLocale();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created';
}
@@ -286,7 +299,7 @@ class LdapSync extends Command
try {
$ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter'));
} catch (\Exception $e) {
- \Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
+ Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
// Hail-mary for Okta manager 'shortnames' - will only work if
// Okta configuration is using full email-address-style usernames
$ldap_manager = [
@@ -378,7 +391,7 @@ class LdapSync extends Command
$user->location_id = $location->id;
}
}
-
+ $location = null;
$user->ldap_import = 1;
$errors = '';
diff --git a/app/Console/Commands/LdapTroubleshooter.php b/app/Console/Commands/LdapTroubleshooter.php
index 8c2831886..5bb3cdd36 100644
--- a/app/Console/Commands/LdapTroubleshooter.php
+++ b/app/Console/Commands/LdapTroubleshooter.php
@@ -5,7 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Setting;
use Exception;
-use Crypt;
+use Illuminate\Support\Facades\Crypt;
/**
* Check if a given ip is in a network
@@ -160,7 +160,7 @@ class LdapTroubleshooter extends Command
$output[] = "-x";
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
- $output[] = "-w ".escapeshellarg(\Crypt::Decrypt($settings->ldap_pword));
+ $output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword));
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
if($settings->ldap_tls) {
$this->line("# adding STARTTLS option");
diff --git a/app/Console/Commands/MergeUsersByUsername.php b/app/Console/Commands/MergeUsersByUsername.php
index 390942708..0c5e966ab 100644
--- a/app/Console/Commands/MergeUsersByUsername.php
+++ b/app/Console/Commands/MergeUsersByUsername.php
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
+use App\Events\UserMerged;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
@@ -51,7 +52,7 @@ class MergeUsersByUsername extends Command
$bad_users = User::where('username', '=', trim($parts[0]))
->whereNull('deleted_at')
- ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations')
+ ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')
->get();
@@ -105,10 +106,26 @@ class MergeUsersByUsername extends Command
$managedLocation->save();
}
+ foreach ($bad_user->uploads as $upload) {
+ $this->info('Updating upload log record '.$upload->id.' to user '.$user->id);
+ $upload->item_id = $user->id;
+ $upload->save();
+ }
+
+ foreach ($bad_user->acceptances as $acceptance) {
+ $this->info('Updating acceptance log record '.$acceptance->id.' to user '.$user->id);
+ $acceptance->item_id = $user->id;
+ $acceptance->save();
+ }
+
// Mark the user as deleted
$this->info('Marking the user as deleted');
$bad_user->deleted_at = Carbon::now()->timestamp;
$bad_user->save();
+
+ event(new UserMerged($bad_user, $user, null));
+
+
}
}
}
diff --git a/app/Console/Commands/MoveUploadsToNewDisk.php b/app/Console/Commands/MoveUploadsToNewDisk.php
index 798ab9987..a27b06fee 100644
--- a/app/Console/Commands/MoveUploadsToNewDisk.php
+++ b/app/Console/Commands/MoveUploadsToNewDisk.php
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
class MoveUploadsToNewDisk extends Command
{
@@ -74,7 +75,7 @@ class MoveUploadsToNewDisk extends Command
$new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename);
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
$this->error($e);
}
}
@@ -116,7 +117,7 @@ class MoveUploadsToNewDisk extends Command
$new_url = Storage::url($private_type . '/' . $filename, $filename);
$this->info($type_count . '. PRIVATE: ' . $filename . ' was copied to ' . $new_url);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
$this->error($e);
}
}
@@ -140,7 +141,7 @@ class MoveUploadsToNewDisk extends Command
unlink($filename);
$public_delete_count++;
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
$this->error($e);
}
}
@@ -153,7 +154,7 @@ class MoveUploadsToNewDisk extends Command
unlink($filename);
$private_delete_count++;
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
$this->error($e);
}
}
diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php
index c89018400..8370e7c05 100644
--- a/app/Console/Commands/ObjectImportCommand.php
+++ b/app/Console/Commands/ObjectImportCommand.php
@@ -5,6 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
+use Illuminate\Support\Facades\Log;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
@@ -59,7 +60,7 @@ class ObjectImportCommand extends Command
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile');
- // \Log::useFiles($logFile);
+ // Log::useFiles($logFile);
$this->comment('======= Importing Items from '.$filename.' =========');
$importer->import();
@@ -112,10 +113,10 @@ class ObjectImportCommand extends Command
public function log($string, $level = 'info')
{
if ($level === 'warning') {
- \Log::warning($string);
+ Log::warning($string);
$this->comment($string);
} else {
- \Log::Info($string);
+ Log::Info($string);
if ($this->option('verbose')) {
$this->comment($string);
}
diff --git a/app/Console/Commands/PaveIt.php b/app/Console/Commands/PaveIt.php
index 9ccb5bf7f..09c846ea6 100644
--- a/app/Console/Commands/PaveIt.php
+++ b/app/Console/Commands/PaveIt.php
@@ -5,7 +5,7 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use Schema;
-use DB;
+use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
class PaveIt extends Command
diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php
index 7abd83c85..1dd2aaa51 100644
--- a/app/Console/Commands/Purge.php
+++ b/app/Console/Commands/Purge.php
@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Accessory;
+use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
@@ -15,6 +16,8 @@ use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
class Purge extends Command
{
@@ -141,6 +144,20 @@ class Purge extends Command
$this->info($users->count().' users purged.');
$user_assoc = 0;
foreach ($users as $user) {
+
+ $rel_path = 'private_uploads/users';
+ $filenames = Actionlog::where('action_type', 'uploaded')
+ ->where('item_id', $user->id)
+ ->pluck('filename');
+ foreach($filenames as $filename) {
+ try {
+ if (Storage::exists($rel_path . '/' . $filename)) {
+ Storage::delete($rel_path . '/' . $filename);
+ }
+ } catch (\Exception $e) {
+ Log::info('An error occurred while deleting files: ' . $e->getMessage());
+ }
+ }
$this->info('- User "'.$user->username.'" deleted.');
$user_assoc += $user->userlog()->count();
$user->userlog()->forceDelete();
diff --git a/app/Console/Commands/RecryptFromMcrypt.php b/app/Console/Commands/RecryptFromMcrypt.php
index 805774a51..33c8ae65c 100644
--- a/app/Console/Commands/RecryptFromMcrypt.php
+++ b/app/Console/Commands/RecryptFromMcrypt.php
@@ -103,7 +103,7 @@ class RecryptFromMcrypt extends Command
$this->comment('INFO: No LDAP password found. Skipping... ');
} else {
$decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword);
- $settings->ldap_pword = \Crypt::encrypt($decrypted_ldap_pword);
+ $settings->ldap_pword = Crypt::encrypt($decrypted_ldap_pword);
$settings->save();
}
/** @var CustomField[] $custom_fields */
@@ -132,7 +132,7 @@ class RecryptFromMcrypt extends Command
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($asset->{$columnName});
- $asset->{$columnName} = \Crypt::encrypt($decrypted_field);
+ $asset->{$columnName} = Crypt::encrypt($decrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
diff --git a/app/Console/Commands/RegenerateAssetTags.php b/app/Console/Commands/RegenerateAssetTags.php
index bd5cf37a4..a49d3d077 100644
--- a/app/Console/Commands/RegenerateAssetTags.php
+++ b/app/Console/Commands/RegenerateAssetTags.php
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Setting;
-use Artisan;
+use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command;
class RegenerateAssetTags extends Command
diff --git a/app/Console/Commands/ResetDemoSettings.php b/app/Console/Commands/ResetDemoSettings.php
index fde95a368..22508377d 100644
--- a/app/Console/Commands/ResetDemoSettings.php
+++ b/app/Console/Commands/ResetDemoSettings.php
@@ -63,7 +63,7 @@ class ResetDemoSettings extends Command
$settings->date_display_format = 'D M d, Y';
$settings->time_display_format = 'g:iA';
$settings->thumbnail_max_h = '30';
- $settings->locale = 'en';
+ $settings->locale = 'en-US';
$settings->version_footer = 'on';
$settings->support_footer = null;
$settings->saml_enabled = '0';
@@ -73,12 +73,13 @@ class ResetDemoSettings extends Command
$settings->saml_forcelogin = '0';
$settings->saml_slo = null;
$settings->saml_custom_settings = null;
+ $settings->default_avatar = 'default.png';
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
- $user->locale = 'en';
+ $user->locale = 'en-US';
$user->save();
}
diff --git a/app/Console/Commands/RestoreDeletedUsers.php b/app/Console/Commands/RestoreDeletedUsers.php
index 4bf26a544..9d5582a61 100644
--- a/app/Console/Commands/RestoreDeletedUsers.php
+++ b/app/Console/Commands/RestoreDeletedUsers.php
@@ -6,8 +6,8 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\License;
use App\Models\User;
-use Artisan;
-use DB;
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
class RestoreDeletedUsers extends Command
diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php
index b1f175356..846c2933c 100644
--- a/app/Console/Commands/RestoreFromBackup.php
+++ b/app/Console/Commands/RestoreFromBackup.php
@@ -4,6 +4,159 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use ZipArchive;
+use Illuminate\Support\Facades\Log;
+
+class SQLStreamer {
+ private $input;
+ private $output;
+ // embed the prefix here?
+ public ?string $prefix;
+
+ private bool $reading_beginning_of_line = true;
+
+ public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
+
+ public array $tablenames = [];
+ private bool $should_guess = false;
+ private bool $statement_is_permitted = false;
+
+ public function __construct($input, $output, string $prefix = null)
+ {
+ $this->input = $input;
+ $this->output = $output;
+ $this->prefix = $prefix;
+ }
+
+ public function parse_sql(string $line): string {
+ // take into account the 'start of line or not' setting as an instance variable?
+ // 'continuation' lines for a permitted statement are PERMITTED.
+ // remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
+ // Windows dumps
+ $line = trim($line, "\r\n");
+ if($this->statement_is_permitted && $line[0] === ' ') {
+ return $line . "\n"; //re-add the newline
+ }
+
+ $table_regex = '`?([a-zA-Z0-9_]+)`?';
+ $allowed_statements = [
+ "/^(DROP TABLE (?:IF EXISTS )?)`$table_regex(.*)$/" => false,
+ "/^(CREATE TABLE )$table_regex(.*)$/" => true, //sets up 'continuation'
+ "/^(LOCK TABLES )$table_regex(.*)$/" => false,
+ "/^(INSERT INTO )$table_regex(.*)$/" => false,
+ "/^UNLOCK TABLES/" => false,
+ // "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
+ "/^\\)[a-zA-Z0-9_= ]*;$/" => false,
+ // ^^^^^^ that bit should *exit* the 'permitted' block
+ "/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
+ /* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
+ a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
+ assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
+ ];
+
+ foreach($allowed_statements as $statement => $statechange) {
+// $this->info("Checking regex: $statement...\n");
+ $matches = [];
+ if (preg_match($statement,$line,$matches)) {
+ $this->statement_is_permitted = $statechange;
+ // matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement
+ // (with of course 0 being "the whole match")
+ if (@$matches[2]) {
+// print "Found a tablename! It's: ".$matches[2]."\n";
+ if ($this->should_guess) {
+ @$this->tablenames[$matches[2]] += 1;
+ continue; //oh? FIXME
+ } else {
+ $cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/','',$matches[2]);
+ $line = preg_replace($statement,'$1`'.$cleaned_tablename.'`$3' , $line);
+ }
+ } else {
+ // no explicit tablename in this one, leave the line alone
+ }
+ //how do we *replace* the tablename?
+// print "RETURNING LINE: $line";
+ return $line . "\n"; //re-add newline
+ }
+ }
+ // all that is not allowed is denied.
+ return "";
+ }
+
+ //this is used in exactly *TWO* places, and in both cases should return a prefix I think?
+ // first - if you do the --sanitize-only one (which is mostly for testing/development)
+ // next - when you run *without* a guessed prefix, this is run first to figure out the prefix
+ // I think we have to *duplicate* the call to be able to run it again?
+ public static function guess_prefix($input):string
+ {
+ $parser = new self($input, null);
+ $parser->should_guess = true;
+ $parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
+
+ $check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
+ //can't use 'users' because the 'accessories_checkout' table?
+ // can't use 'assets' because 'ver1_components_assets'
+ foreach($check_tables as $check_table => $_ignore) {
+ foreach ($parser->tablenames as $tablename => $_count) {
+// print "Comparing $tablename to $check_table\n";
+ if (str_ends_with($tablename,$check_table)) {
+// print "Found one!\n";
+ $check_tables[$check_table] = substr($tablename,0,-strlen($check_table));
+ }
+ }
+ }
+ $guessed_prefix = null;
+ foreach ($check_tables as $clean_table => $prefix_guess) {
+ if(is_null($prefix_guess)) {
+ print("Couldn't find table $clean_table\n");
+ die();
+ }
+ if(is_null($guessed_prefix)) {
+ $guessed_prefix = $prefix_guess;
+ } else {
+ if ($guessed_prefix != $prefix_guess) {
+ print("Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n");
+ die();
+ }
+ }
+ }
+
+ return $guessed_prefix;
+
+ }
+
+ public function line_aware_piping(): int
+ {
+ $bytes_read = 0;
+ if (! $this->input) {
+ throw new \Exception("No Input available for line_aware_piping");
+ }
+
+ while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) {
+ $bytes_read += strlen($buffer);
+ if ($this->reading_beginning_of_line) {
+ // Log::debug("Buffer is: '$buffer'");
+ $cleaned_buffer = $this->parse_sql($buffer);
+ if ($this->output) {
+ $bytes_written = fwrite($this->output, $cleaned_buffer);
+
+ if ($bytes_written === false) {
+ throw new \Exception("Unable to write to pipe");
+ }
+ }
+ }
+ // if we got a newline at the end of this, then the _next_ read is the beginning of a line
+ if($buffer[strlen($buffer)-1] === "\n") {
+ $this->reading_beginning_of_line = true;
+ } else {
+ $this->reading_beginning_of_line = false;
+ }
+
+ }
+ return $bytes_read;
+
+ }
+}
+
+
class RestoreFromBackup extends Command
{
@@ -12,10 +165,14 @@ class RestoreFromBackup extends Command
*
* @var string
*/
+ // FIXME - , stripping prefixes and nonstandard SQL statements. Without --prefix, guess and return the correct prefix to strip
protected $signature = 'snipeit:restore
{--force : Skip the danger prompt; assuming you enter "y"}
{filename : The zip file to be migrated}
- {--no-progress : Don\'t show a progress bar}';
+ {--no-progress : Don\'t show a progress bar}
+ {--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
+ {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}
+ {--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }';
/**
* The console command description.
@@ -34,8 +191,6 @@ class RestoreFromBackup extends Command
parent::__construct();
}
- public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
-
/**
* Execute the console command.
*
@@ -45,7 +200,7 @@ class RestoreFromBackup extends Command
{
$dir = getcwd();
if( $dir != base_path() ) { // usually only the case when running via webserver, not via command-line
- \Log::debug("Current working directory is: $dir, changing directory to: ".base_path());
+ Log::debug("Current working directory is: $dir, changing directory to: ".base_path());
chdir(base_path()); // TODO - is this *safe* to change on a running script?!
}
//
@@ -55,7 +210,7 @@ class RestoreFromBackup extends Command
return $this->error('Missing required filename');
}
- if (! $this->option('force') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
+ if (! $this->option('force') && ! $this->option('sanitize-guess-prefix') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
return $this->error('Data loss not confirmed');
}
@@ -84,35 +239,36 @@ class RestoreFromBackup extends Command
$private_dirs = [
+ 'storage/private_uploads/accessories',
+ 'storage/private_uploads/assetmodels',
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
'storage/private_uploads/audits',
+ 'storage/private_uploads/components',
+ 'storage/private_uploads/consumables',
+ 'storage/private_uploads/eula-pdfs',
'storage/private_uploads/imports',
- 'storage/private_uploads/assetmodels',
- 'storage/private_uploads/users',
'storage/private_uploads/licenses',
'storage/private_uploads/signatures',
+ 'storage/private_uploads/users',
];
$private_files = [
'storage/oauth-private.key',
'storage/oauth-public.key',
];
$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/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/departments',
- 'public/uploads/avatars',
- 'public/uploads/suppliers',
- 'public/uploads/assets', // these are asset _pictures_, not asset files
'public/uploads/locations',
- 'public/uploads/accessories',
- 'public/uploads/models',
- 'public/uploads/categories',
- 'public/uploads/avatars',
'public/uploads/manufacturers',
+ 'public/uploads/models',
+ 'public/uploads/suppliers',
];
$public_files = [
@@ -150,18 +306,18 @@ class RestoreFromBackup extends Command
continue;
}
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
- \Log::debug("Found a sql file!");
+ Log::debug("Found a sql file!");
$sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i;
continue;
}
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
- $last_pos = strrpos($raw_path, $dir.'/');
+ $last_pos = strrpos($raw_path, $dir . '/');
if ($last_pos !== false) {
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
- $interesting_files[$raw_path] = ['dest' =>$dir, 'index' => $i];
+ $interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
continue 2;
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
// we don't care about that; we just want files with the appropriate prefix
@@ -170,7 +326,7 @@ class RestoreFromBackup extends Command
}
}
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
- 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', ];
+ 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
foreach (array_merge($private_files, $public_files) as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
@@ -179,8 +335,8 @@ class RestoreFromBackup extends Command
$last_pos = strrpos($raw_path, $file); // no trailing slash!
if ($last_pos !== false) {
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
- if (! in_array($extension, $good_extensions)) {
- $this->warn('Potentially unsafe file '.$raw_path.' is being skipped');
+ if (!in_array($extension, $good_extensions)) {
+ $this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
$boring_files[] = $raw_path;
continue 2;
}
@@ -195,7 +351,6 @@ class RestoreFromBackup extends Command
}
$boring_files[] = $raw_path; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
} // end of pre-processing the ZIP file for-loop
-
// print_r($interesting_files);exit(-1);
if (count($sqlfiles) != 1) {
@@ -207,6 +362,26 @@ class RestoreFromBackup extends Command
//older Snipe-IT installs don't have the db-dumps subdirectory component
}
+ $sql_stat = $za->statIndex($sqlfile_indices[0]);
+ //$this->info("SQL Stat is: ".print_r($sql_stat,true));
+ $sql_contents = $za->getStream($sql_stat['name']); // maybe copy *THIS* thing?
+
+ // OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think?
+ if ($this->option('sanitize-guess-prefix')) {
+ $prefix = SQLStreamer::guess_prefix($sql_contents);
+ $this->line($prefix);
+ return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
+ }
+
+ // If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
+ if ($this->option('sql-stdout-only')) {
+ $sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
+ $bytes_read = $sql_importer->line_aware_piping();
+ return $this->warn("$bytes_read total bytes read");
+ //TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
+ // which would be good for redirecting to a file, and not having to trim the last line off of it
+ }
+
//how to invoke the restore?
$pipes = [];
@@ -227,6 +402,7 @@ class RestoreFromBackup extends Command
return $this->error('Unable to invoke mysql via CLI');
}
+ // I'm not sure about these?
stream_set_blocking($pipes[1], false); // use non-blocking reads for stdout
stream_set_blocking($pipes[2], false); // use non-blocking reads for stderr
@@ -237,9 +413,9 @@ class RestoreFromBackup extends Command
//$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
- $sql_stat = $za->statIndex($sqlfile_indices[0]);
- //$this->info("SQL Stat is: ".print_r($sql_stat,true));
- $sql_contents = $za->getStream($sql_stat['name']);
+ // FIXME - this feels like it wants to go somewhere else?
+ // and it doesn't seem 'right' - if you can't get a stream to the .sql file,
+ // why do we care what's happening with pipes and stdout and stderr?!
if ($sql_contents === false) {
$stdout = fgets($pipes[1]);
$this->info($stdout);
@@ -248,29 +424,35 @@ class RestoreFromBackup extends Command
return false;
}
- $bytes_read = 0;
try {
- while (($buffer = fgets($sql_contents, self::$buffer_size)) !== false) {
- $bytes_read += strlen($buffer);
- // \Log::debug("Buffer is: '$buffer'");
+ if ( $this->option('sanitize-with-prefix') === null) {
+ // "Legacy" direct-piping
+ $bytes_read = 0;
+ while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) {
+ $bytes_read += strlen($buffer);
+ // Log::debug("Buffer is: '$buffer'");
$bytes_written = fwrite($pipes[0], $buffer);
- if ($bytes_written === false) {
- throw new Exception("Unable to write to pipe");
+ if ($bytes_written === false) {
+ throw new Exception("Unable to write to pipe");
+ }
}
+ } else {
+ $sql_importer = new SQLStreamer($sql_contents, $pipes[0], $this->option('sanitize-with-prefix'));
+ $bytes_read = $sql_importer->line_aware_piping();
}
} catch (\Exception $e) {
- \Log::error("Error during restore!!!! ".$e->getMessage());
+ Log::error("Error during restore!!!! ".$e->getMessage());
+ // FIXME - put these back and/or put them in the right places?!
$err_out = fgets($pipes[1]);
$err_err = fgets($pipes[2]);
- \Log::error("Error OUTPUT: ".$err_out);
+ Log::error("Error OUTPUT: ".$err_out);
$this->info($err_out);
- \Log::error("Error ERROR : ".$err_err);
+ Log::error("Error ERROR : ".$err_err);
$this->error($err_err);
throw $e;
}
-
if (!feof($sql_contents) || $bytes_read == 0) {
return $this->error("Not at end of file for sql file, or zero bytes read. aborting!");
}
@@ -301,8 +483,11 @@ class RestoreFromBackup extends Command
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
$fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
+ if (!is_dir($file_details['dest'])) {
+ mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
+ }
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
- while (($buffer = fgets($fp, self::$buffer_size)) !== false) {
+ while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
fwrite($migrated_file, $buffer);
}
fclose($migrated_file);
diff --git a/app/Console/Commands/RotateAppKey.php b/app/Console/Commands/RotateAppKey.php
index 08e528e4a..e4529962b 100644
--- a/app/Console/Commands/RotateAppKey.php
+++ b/app/Console/Commands/RotateAppKey.php
@@ -5,8 +5,9 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Setting;
-use Artisan;
+use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command;
+use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Encryption\Encrypter;
class RotateAppKey extends Command
@@ -16,14 +17,17 @@ class RotateAppKey extends Command
*
* @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.
*
* @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.
@@ -42,26 +46,42 @@ class RotateAppKey extends Command
*/
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
// We put them in a variable since we clear the cache partway through here.
- $old_app_key = config('app.key');
- $cipher = config('app.cipher');
+ if ($this->option('emergency')) {
+ $old_app_key = config('app.key');
+ $cipher = config('app.cipher');
- // Generate a new one
- Artisan::call('key:generate', ['--show' => true]);
- $new_app_key = Artisan::output();
+ // Generate a new one
+ Artisan::call('key:generate', ['--show' => true]);
+ $new_app_key = trim(Artisan::output());
- // Clear the config cache
- Artisan::call('config:clear');
+ // Clear the config cache
+ Artisan::call('config:clear');
- $this->warn('Your app cipher is: '.$cipher);
- $this->warn('Your old APP_KEY is: '.$old_app_key);
- $this->warn('Your new APP_KEY is: '.$new_app_key);
+ // Write the new app key to the .env file
+ $this->writeNewEnvironmentFileWith($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->writeNewEnvironmentFileWith($new_app_key);
+ $this->warn('Your app cipher is: ' . $cipher);
+ $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
// 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();
foreach ($assets as $asset) {
- $asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
- $this->line('DECRYPTED: '.$field->db_column);
+ try {
+ $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});
$this->line('ENCRYPTED: '.$field->db_column);
$asset->save();
@@ -86,10 +114,14 @@ class RotateAppKey extends Command
// Handle the LDAP password if one is provided
$setting = Setting::first();
if ($setting->ldap_pword != '') {
- $setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
- $setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
- $setting->save();
- $this->warn('LDAP password has been re-encrypted.');
+ try {
+ $setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
+ $setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
+ $setting->save();
+ $this->warn('LDAP password has been re-encrypted.');
+ } catch(DecryptException $e) {
+ $this->warn("Unable to decrypt old LDAP password; skipping");
+ }
}
} else {
$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(
$this->keyReplacementPattern(),
- 'APP_KEY='.$key,
+ 'APP_KEY="'.$key.'"',
file_get_contents($this->laravel->environmentFilePath())
));
}
@@ -118,7 +150,7 @@ class RotateAppKey extends Command
*/
protected function keyReplacementPattern()
{
- $escaped = preg_quote('='.$this->laravel['config']['app.key'], '/');
+ $escaped = '="?'.preg_quote($this->laravel['config']['app.key'], '/').'"?';
return "/^APP_KEY{$escaped}/m";
}
diff --git a/app/Console/Commands/SamlClearExpiredNonces.php b/app/Console/Commands/SamlClearExpiredNonces.php
new file mode 100644
index 000000000..f03b55095
--- /dev/null
+++ b/app/Console/Commands/SamlClearExpiredNonces.php
@@ -0,0 +1,44 @@
+delete();
+ return 0;
+ }
+}
diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php
new file mode 100644
index 000000000..dd9e59f61
--- /dev/null
+++ b/app/Console/Commands/SendAcceptanceReminder.php
@@ -0,0 +1,105 @@
+where('checkoutable_type', 'App\Models\Asset')
+ ->whereHas('checkoutable', function($query) {
+ $query->where('archived', 0);
+ })
+ ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
+ ->get();
+
+ $count = 0;
+ $unacceptedAssetGroups = $pending
+ ->filter(function($acceptance) {
+ return $acceptance->checkoutable_type == 'App\Models\Asset';
+ })
+ ->map(function($acceptance) {
+ return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
+ })
+ ->groupBy(function($item) {
+ return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
+ });
+
+ $no_mail_address = [];
+
+ foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
+ $item_count = $unacceptedAssetGroup->count();
+ foreach ($unacceptedAssetGroup as $unacceptedAsset) {
+// if ($unacceptedAsset['acceptance']->assignedTo->email == ''){
+// $no_mail_address[] = $unacceptedAsset['checkoutable']->assignedTo->present()->fullName;
+// }
+ if ($unacceptedAsset['acceptance']->assignedTo) {
+
+ if (!$unacceptedAsset['acceptance']->assignedTo->locale) {
+ Notification::locale(Setting::getSettings()->locale)->send(
+ $unacceptedAsset['acceptance']->assignedTo,
+ new UnacceptedAssetReminderNotification($unacceptedAsset['assetItem'], $count)
+ );
+ } else {
+ Notification::send(
+ $unacceptedAsset['acceptance']->assignedTo,
+ new UnacceptedAssetReminderNotification($unacceptedAsset, $item_count)
+ );
+ }
+ $count++;
+ }
+ }
+ }
+
+ if (!empty($no_mail_address)) {
+ foreach($no_mail_address as $user) {
+ return $user.' has no email.';
+ }
+
+
+ }
+
+
+
+ $this->info($count.' users notified.');
+ }
+}
diff --git a/app/Console/Commands/SendCurrentInventoryToUsers.php b/app/Console/Commands/SendCurrentInventoryToUsers.php
index 74acc9cc0..c5180203e 100644
--- a/app/Console/Commands/SendCurrentInventoryToUsers.php
+++ b/app/Console/Commands/SendCurrentInventoryToUsers.php
@@ -43,7 +43,7 @@ class SendCurrentInventoryToUsers extends Command
$count = 0;
foreach ($users as $user) {
- if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0)) {
+ if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0) || ($user->consumables->count() > 0)) {
$count++;
$user->notify((new CurrentInventory($user)));
}
diff --git a/app/Console/Commands/SendExpectedCheckinAlerts.php b/app/Console/Commands/SendExpectedCheckinAlerts.php
index 83a93a8a6..e042e8b08 100644
--- a/app/Console/Commands/SendExpectedCheckinAlerts.php
+++ b/app/Console/Commands/SendExpectedCheckinAlerts.php
@@ -42,24 +42,31 @@ class SendExpectedCheckinAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
- $whenNotify = Carbon::now()->addDays(7);
- $assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
+ $interval = $settings->audit_warning_days ?? 0;
+ $today = Carbon::now();
+ $interval_date = $today->copy()->addDays($interval);
+
+ $assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
+
+ $this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
- $this->info($whenNotify.' is deadline');
- $this->info($assets->count().' assets');
foreach ($assets as $asset) {
- if ($asset->assigned && $asset->checkedOutToUser()) {
- $asset->assigned->notify((new ExpectedCheckinNotification($asset)));
+ if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
+ $this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
+ $asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
}
}
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
- $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
+ $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
+
+ $this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
+
}
}
}
diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php
index e9cba106c..7c81a37d3 100644
--- a/app/Console/Commands/SendUpcomingAuditReport.php
+++ b/app/Console/Commands/SendUpcomingAuditReport.php
@@ -3,13 +3,11 @@
namespace App\Console\Commands;
use App\Models\Asset;
-use App\Models\License;
-use App\Models\Recipients;
+use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
-use App\Notifications\ExpiringAssetsNotification;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
class SendUpcomingAuditReport extends Command
@@ -46,39 +44,24 @@ class SendUpcomingAuditReport extends Command
public function handle()
{
$settings = Setting::getSettings();
+ $interval = $settings->audit_warning_days ?? 0;
+ $today = Carbon::now();
+ $interval_date = $today->copy()->addDays($interval);
- if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) {
+ $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
+ $this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
+
+ if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
- $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
- return new \App\Models\Recipients\AlertRecipient($item);
+ $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
+ return new AlertRecipient($item);
});
- // Assets due for auditing
+ $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
+ \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
- $assets = Asset::whereNotNull('next_audit_date')
- ->DueOrOverdueForAudit($settings)
- ->orderBy('last_audit_date', 'asc')->get();
-
- if ($assets->count() > 0) {
- $this->info(trans_choice('mail.upcoming-audits', $assets->count(),
- ['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
- \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
- $this->info('Audit report sent to '.$settings->alert_email);
- } else {
- $this->info('No assets to be audited. No report sent.');
- }
- } elseif ($settings->alert_email == '') {
- $this->error('Could not send email. No alert email configured in settings');
- } elseif (! $settings->audit_warning_days) {
- $this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
- } elseif ($settings->alerts_enabled != 1) {
- $this->info('Alerts are disabled in the settings. No mail will be sent');
- } else {
- $this->error('Something went wrong. :( ');
- $this->error('Admin Notifications Email Setting: '.$settings->alert_email);
- $this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
- $this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
}
+
}
}
diff --git a/app/Console/Commands/SyncAssetCounters.php b/app/Console/Commands/SyncAssetCounters.php
index b3bc15776..2e2bb18f3 100644
--- a/app/Console/Commands/SyncAssetCounters.php
+++ b/app/Console/Commands/SyncAssetCounters.php
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
class SyncAssetCounters extends Command
{
@@ -58,7 +59,7 @@ class SyncAssetCounters extends Command
$asset->save();
$bar->advance();
- \Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests');
+ Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests');
}
diff --git a/app/Console/Commands/ToggleCustomfieldEncryption.php b/app/Console/Commands/ToggleCustomfieldEncryption.php
new file mode 100644
index 000000000..2ba07f7bd
--- /dev/null
+++ b/app/Console/Commands/ToggleCustomfieldEncryption.php
@@ -0,0 +1,76 @@
+argument('fieldname');
+
+ if ($field = CustomField::where('db_column', $fieldname)->first()) {
+
+ // If the field is not encrypted, make it encrypted and encrypt the data in the assets table for the
+ // corresponding field.
+ DB::transaction(function () use ($field) {
+
+ if ($field->field_encrypted == 0) {
+ $assets = Asset::whereNotNull($field->db_column)->get();
+
+ foreach ($assets as $asset) {
+ $asset->{$field->db_column} = encrypt($asset->{$field->db_column});
+ $asset->save();
+ }
+
+ $field->field_encrypted = 1;
+ $field->save();
+
+ // This field is already encrypted. Do nothing.
+ } else {
+ $this->error('The custom field ' . $field->db_column.' is already encrypted. No action was taken.');
+ }
+ });
+
+ // No matching column name found
+ } else {
+ $this->error('No matching results for unencrypted custom fields with db_column name: ' . $fieldname.'. Please check the fieldname.');
+ }
+
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 0b80d2ecc..8d512f303 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -25,6 +25,7 @@ class Kernel extends ConsoleKernel
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
+ $schedule->command('saml:clear_expired_nonces')->weekly();
}
/**
diff --git a/app/Events/UserMerged.php b/app/Events/UserMerged.php
index b045fdef0..3a7f4d6a2 100644
--- a/app/Events/UserMerged.php
+++ b/app/Events/UserMerged.php
@@ -15,7 +15,7 @@ class UserMerged
*
* @return void
*/
- public function __construct(User $from_user, User $to_user, User $admin)
+ public function __construct(User $from_user, User $to_user, ?User $admin)
{
$this->merged_from = $from_user;
$this->merged_to = $to_user;
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index e76d8e5da..2b8eaa362 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -7,7 +7,7 @@ use App\Helpers\Helper;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
-use Log;
+use Illuminate\Support\Facades\Log;
use Throwable;
use JsonException;
use Carbon\Exceptions\InvalidFormatException;
@@ -44,8 +44,8 @@ class Handler extends ExceptionHandler
public function report(Throwable $exception)
{
if ($this->shouldReport($exception)) {
- if (class_exists(\Log::class)) {
- \Log::error($exception);
+ if (class_exists(Log::class)) {
+ Log::error($exception);
}
return parent::report($exception);
}
diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php
index 800a2491d..18e149b57 100644
--- a/app/Helpers/Helper.php
+++ b/app/Helpers/Helper.php
@@ -11,13 +11,74 @@ use App\Models\CustomFieldset;
use App\Models\Depreciation;
use App\Models\Setting;
use App\Models\Statuslabel;
-use Crypt;
+use App\Models\License;
+use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
-use Image;
use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
+use Intervention\Image\ImageManagerStatic as Image;
+use Illuminate\Support\Facades\Session;
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' => 'nb-NO', // Norwegian Bokmål
+ 'pl' => 'pl-PL', // Polish
+ 'pt' => 'pt-PT', // Portuguese
+ '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
*
@@ -354,7 +415,7 @@ class Helper
if ($index >= $total_colors) {
- \Log::error('Status label count is '.$index.' and exceeds the allowed count of 266.');
+ Log::info('Status label count is '.$index.' and exceeds the allowed count of 266.');
//patch fix for array key overflow (color count starts at 1, array starts at 0)
$index = $index - $total_colors - 1;
@@ -658,18 +719,19 @@ class Helper
*/
public static function checkLowInventory()
{
+ $alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$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('checkouts as checkouts_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
+ $licenses = License::where('min_amt', '>', 0)->get();
- $avail_consumables = 0;
$items_array = [];
$all_count = 0;
foreach ($consumables as $consumable) {
$avail = $consumable->numRemaining();
- if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
+ if ($avail < ($consumable->min_amt) + $alert_threshold) {
if ($consumable->qty > 0) {
$percent = number_format((($avail / $consumable->qty) * 100), 0);
} else {
@@ -687,8 +749,8 @@ class Helper
}
foreach ($accessories as $accessory) {
- $avail = $accessory->qty - $accessory->users_count;
- if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
+ $avail = $accessory->qty - $accessory->checkouts_count;
+ if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0);
} else {
@@ -707,7 +769,7 @@ class Helper
foreach ($components as $component) {
$avail = $component->numRemaining();
- if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
+ if ($avail < ($component->min_amt) + $alert_threshold) {
if ($component->qty > 0) {
$percent = number_format((($avail / $component->qty) * 100), 0);
} else {
@@ -730,7 +792,7 @@ class Helper
$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 < ($asset_model->min_amt) + $alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $total_owned) * 100), 0);
} else {
@@ -746,6 +808,26 @@ class Helper
}
}
+ foreach ($licenses as $license){
+ $avail = $license->remaincount();
+ if ($avail < ($license->min_amt) + $alert_threshold) {
+ if ($avail > 0) {
+ $percent = number_format((($avail / $license->min_amt) * 100), 0);
+ } else {
+ $percent = 100;
+ }
+
+ $items_array[$all_count]['id'] = $license->id;
+ $items_array[$all_count]['name'] = $license->name;
+ $items_array[$all_count]['type'] = 'licenses';
+ $items_array[$all_count]['percent'] = $percent;
+ $items_array[$all_count]['remaining'] = $avail;
+ $items_array[$all_count]['min_amt'] = $license->min_amt;
+ $all_count++;
+ }
+
+ }
+
return $items_array;
}
@@ -763,7 +845,7 @@ class Helper
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
- if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif')) {
+ if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
return $filetype;
}
@@ -796,12 +878,15 @@ class Helper
$permission_name = $permission[$x]['permission'];
if ($permission[$x]['display'] === true) {
- if ($selected_arr) {
+
+ if (is_array($selected_arr)) {
+
if (array_key_exists($permission_name, $selected_arr)) {
$permissions_arr[$permission_name] = $selected_arr[$permission_name];
} else {
$permissions_arr[$permission_name] = '0';
}
+
} else {
$permissions_arr[$permission_name] = '0';
}
@@ -829,13 +914,22 @@ class Helper
$rules = $class::rules();
foreach ($rules as $rule_name => $rule) {
if ($rule_name == $field) {
- if (strpos($rule, 'required') === false) {
- return false;
+ if (is_array($rule)) {
+ if (in_array('required', $rule)) {
+ return true;
+ } else {
+ return false;
+ }
} else {
- return true;
- }
+ if (strpos($rule, 'required') === false) {
+ return false;
+ } else {
+ return true;
+ }
+ }
}
}
+ return false;
}
/**
@@ -933,7 +1027,7 @@ class Helper
try {
- $tmp_date = new \Carbon($date);
+ $tmp_date = new Carbon($date);
if ($type == 'datetime') {
$dt['datetime'] = $tmp_date->format('Y-m-d H:i:s');
@@ -950,7 +1044,7 @@ class Helper
return $dt['formatted'];
} catch (\Exception $e) {
- \Log::warning($e);
+ Log::warning($e);
return $date.' (Invalid '.$type.' value.)';
}
@@ -1027,6 +1121,8 @@ class Helper
'jpeg' => 'far fa-image',
'gif' => 'far fa-image',
'png' => 'far fa-image',
+ 'webp' => 'far fa-image',
+ 'avif' => 'far fa-image',
// word
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
@@ -1062,6 +1158,8 @@ class Helper
case 'jpeg':
case 'gif':
case 'png':
+ case 'webp':
+ case 'avif':
return true;
break;
default:
@@ -1259,7 +1357,7 @@ class Helper
public static function isDemoMode() {
if (config('app.lock_passwords') === true) {
return true;
- \Log::debug('app locked!');
+ Log::debug('app locked!');
}
return false;
@@ -1317,7 +1415,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.
*/
public static function showDemoModeFieldWarning() {
@@ -1325,4 +1423,120 @@ class Helper
return "
" . trans('general.feature_disabled') . "
";
}
}
+
+
+ /**
+ * 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
+ * @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) {
+ 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'
+ }
+
+ public static function determineLanguageDirection() {
+ return in_array(app()->getLocale(),
+ [
+ 'ar-SA',
+ 'fa-IR',
+ 'he-IL'
+ ]) ? 'rtl' : 'ltr';
+ }
+
+
+ static public function getRedirectOption($request, $id, $table, $item_id = null)
+ {
+
+ $redirect_option = Session::get('redirect_option');
+ $checkout_to_type = Session::get('checkout_to_type');
+
+ // return to index
+ if ($redirect_option == 'index') {
+ switch ($table) {
+ case "Assets":
+ return route('hardware.index');
+ case "Users":
+ return route('users.index');
+ case "Licenses":
+ return route('licenses.index');
+ case "Accessories":
+ return route('accessories.index');
+ case "Components":
+ return route('components.index');
+ case "Consumables":
+ return route('consumables.index');
+ }
+ }
+
+ // return to thing being assigned
+ if ($redirect_option == 'item') {
+ switch ($table) {
+ case "Assets":
+ return route('hardware.show', $id ?? $item_id);
+ case "Users":
+ return route('users.show', $id ?? $item_id);
+ case "Licenses":
+ return route('licenses.show', $id ?? $item_id);
+ case "Accessories":
+ return route('accessories.show', $id ?? $item_id);
+ case "Components":
+ return route('components.show', $id ?? $item_id);
+ case "Consumables":
+ return route('consumables.show', $id ?? $item_id);
+ }
+ }
+
+ // return to assignment target
+ if ($redirect_option == 'target') {
+ switch ($checkout_to_type) {
+ case 'user':
+ return route('users.show', ['user' => $request->assigned_user]);
+ case 'location':
+ return route('locations.show', ['location' => $request->assigned_location]);
+ case 'asset':
+ return route('hardware.show', ['hardware' => $request->assigned_asset]);
+ }
+ }
+ return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
+ }
}
diff --git a/app/Helpers/StorageHelper.php b/app/Helpers/StorageHelper.php
index 94dfe62c7..2cdab1d66 100644
--- a/app/Helpers/StorageHelper.php
+++ b/app/Helpers/StorageHelper.php
@@ -3,10 +3,13 @@
namespace App\Helpers;
use Illuminate\Support\Facades\Storage;
-
+use Illuminate\Http\Response;
+use Illuminate\Http\RedirectResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
class StorageHelper
{
- public static function downloader($filename, $disk = 'default')
+ public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
{
if ($disk == 'default') {
$disk = config('filesystems.default');
diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php
index 6ef6fb993..4fd5a4c54 100755
--- a/app/Http/Controllers/Accessories/AccessoriesController.php
+++ b/app/Http/Controllers/Accessories/AccessoriesController.php
@@ -7,10 +7,11 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Accessory;
use App\Models\Company;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
-use Redirect;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Log;
/** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application.
@@ -26,13 +27,10 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] []
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('index', Accessory::class);
-
return view('accessories/index');
}
@@ -40,10 +38,8 @@ class AccessoriesController extends Controller
* Returns a view with a form to create a new Accessory.
*
* @author [A. Gianotto] []
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Accessory::class);
$category_type = 'accessory';
@@ -57,10 +53,8 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param ImageUploadRequest $request
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize(Accessory::class);
@@ -79,16 +73,17 @@ class AccessoriesController extends Controller
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
- $accessory->user_id = Auth::user()->id;
+ $accessory->user_id = auth()->id();
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
- return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
@@ -99,15 +94,12 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param int $accessoryId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($accessoryId = null)
+ public function edit($accessoryId = null) : View | RedirectResponse
{
if ($item = Accessory::find($accessoryId)) {
$this->authorize($item);
-
return view('accessories/edit', compact('item'))->with('category_type', 'accessory');
}
@@ -121,9 +113,8 @@ class AccessoriesController extends Controller
* @author [J. Vinsmoke]
* @param int $accessoryId
* @since [v6.0]
- * @return View
*/
- public function getClone($accessoryId = null)
+ public function getClone($accessoryId = null) : View | RedirectResponse
{
$this->authorize('create', Accessory::class);
@@ -150,17 +141,15 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] []
* @param ImageUploadRequest $request
* @param int $accessoryId
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $accessoryId = null)
+ public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
{
- if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
+ if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
- "qty" => "required|numeric|min:$accessory->users_count"
+ "qty" => "required|numeric|min:$accessory->checkouts_count"
]);
if ($validator->fails()) {
@@ -188,9 +177,10 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
- // Was the accessory updated?
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($accessory->save()) {
- return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
@@ -204,10 +194,8 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param int $accessoryId
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($accessoryId)
+ public function destroy($accessoryId) : RedirectResponse
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
@@ -224,7 +212,7 @@ class AccessoriesController extends Controller
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -242,12 +230,10 @@ class AccessoriesController extends Controller
* @param int $accessoryID
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($accessoryID = null)
+ public function show($accessoryID = null) : View | RedirectResponse
{
- $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
+ $accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));
diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
index 6a94a897a..b63c202d3 100644
--- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php
+++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
@@ -4,35 +4,35 @@ namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
-use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
-use Symfony\Accessory\HttpFoundation\JsonResponse;
-use enshrined\svgSanitize\Sanitizer;
+use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Response;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
- * @todo Switch to using the AssetFileRequest form request validator.
+ * @param UploadFileRequest $request
+ * @param int $accessoryId
* @author [A. Gianotto] []
* @since [v1.0]
- * @param AssetFileRequest $request
- * @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @todo Switch to using the AssetFileRequest form request validator.
*/
- public function store(AssetFileRequest $request, $accessoryId = null)
+ public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
}
-
$accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) {
@@ -45,30 +45,7 @@ class AccessoriesFilesController extends Controller
foreach ($request->file('file') as $file) {
- $extension = $file->getClientOriginalExtension();
- $file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
-
-
- // Check for SVG and sanitize it
- if ($extension == 'svg') {
- \Log::debug('This is an SVG');
- \Log::debug($file_name);
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/accessories/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
-
- } else {
- Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
- }
-
+ $file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
@@ -92,10 +69,8 @@ class AccessoriesFilesController extends Controller
* @since [v1.0]
* @param int $accessoryId
* @param int $fileId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($accessoryId = null, $fileId = null)
+ public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
{
$accessory = Accessory::find($accessoryId);
@@ -109,7 +84,7 @@ class AccessoriesFilesController extends Controller
try {
Storage::delete('accessories/'.$log->filename);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -130,13 +105,11 @@ class AccessoriesFilesController extends Controller
* @since [v1.4]
* @param int $accessoryId
* @param int $fileId
- * @return \Symfony\Accessory\HttpFoundation\Response
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($accessoryId = null, $fileId = null, $download = true)
+ public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{
- \Log::debug('Private filesystem is: '.config('filesystems.default'));
+ Log::debug('Private filesystem is: '.config('filesystems.default'));
$accessory = Accessory::find($accessoryId);
@@ -153,8 +126,8 @@ class AccessoriesFilesController extends Controller
$file = 'private_uploads/accessories/'.$log->filename;
if (Storage::missing($file)) {
- \Log::debug('FILE DOES NOT EXISTS for '.$file);
- \Log::debug('URL should be '.Storage::url($file));
+ Log::debug('FILE DOES NOT EXISTS for '.$file);
+ Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
index 3424c2aa1..e36f8a240 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
@@ -3,12 +3,15 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
+use App\Models\AccessoryCheckout;
use App\Models\User;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AccessoryCheckinController extends Controller
{
@@ -19,15 +22,10 @@ class AccessoryCheckinController extends Controller
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
- * @return View
- * @internal param int $accessoryId
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($accessoryUserId = null, $backto = null)
+ public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
- // Redirect to the accessory management page with error
+ if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
@@ -42,36 +40,32 @@ class AccessoryCheckinController extends Controller
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] []
- * @param null $accessoryUserId
+ * @param null $accessoryCheckoutId
* @param string $backto
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
- * @internal param int $accessoryId
*/
- public function store(Request $request, $accessoryUserId = null, $backto = null)
+ public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
- // Redirect to the accessory management page with error
+ if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
- $accessory = Accessory::find($accessory_user->accessory_id);
+ $accessory = Accessory::find($accessory_checkout->accessory_id);
$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')) {
- $checkin_at = $request->input('checkin_at');
+ $checkin_at = $request->input('checkin_at').' '.$checkin_hours;
}
// Was the accessory updated?
- if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
- $return_to = e($accessory_user->assigned_to);
+ if ($accessory_checkout->delete()) {
+ event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at));
- event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at));
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
- return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
index 1ea036e6e..03fb6ac25 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
@@ -3,46 +3,56 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
+use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
+use App\Http\Requests\AccessoryCheckoutRequest;
use App\Models\Accessory;
+use App\Models\AccessoryCheckout;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Input;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AccessoryCheckoutController extends Controller
{
+
+ use CheckInOutRequest;
+
/**
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] []
- * @param int $accessoryId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @param int $id
*/
- public function create($accessoryId)
+ public function create($id) : View | RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
- // Redirect to the accessory management page with error
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
- }
- // 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'));
- }
-
- if ($accessory->category) {
+ if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
+
$this->authorize('checkout', $accessory);
- // Get the dropdown of users and then pass it to the checkout view
- return view('accessories/checkout', compact('accessory'));
+ if ($accessory->category) {
+ // 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'));
+
}
/**
@@ -53,46 +63,38 @@ class AccessoryCheckoutController extends Controller
*
* @author [A. Gianotto] []
* @param Request $request
- * @param int $accessoryId
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @param Accessory $accessory
*/
- public function store(Request $request, $accessoryId)
+ public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
- // Redirect to the accessory management page with error
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
- }
-
+
$this->authorize('checkout', $accessory);
- if (!$user = User::find($request->input('assigned_to'))) {
- return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
+ $target = $this->determineCheckoutTarget();
+
+ $accessory->checkout_qty = $request->input('checkout_qty', 1);
+
+ for ($i = 0; $i < $accessory->checkout_qty; $i++) {
+ AccessoryCheckout::create([
+ 'accessory_id' => $accessory->id,
+ 'created_at' => Carbon::now(),
+ 'user_id' => Auth::id(),
+ 'assigned_to' => $target->id,
+ 'assigned_type' => $target::class,
+ 'note' => $request->input('note'),
+ ]);
}
+ event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
- // Make sure there is at least one available to checkout
- if ($accessory->numRemaining() <= 0){
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
- }
+ // Set this as user since we only allow checkout to user for this item type
+ $request->request->add(['checkout_to_type' => request('checkout_to_type')]);
+ $request->request->add(['assigned_user' => $target->id]);
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
- // Update the accessory data
- $accessory->assigned_to = e($request->input('assigned_to'));
-
- $accessory->users()->attach($accessory->id, [
- 'accessory_id' => $accessory->id,
- 'created_at' => Carbon::now(),
- 'user_id' => Auth::id(),
- 'assigned_to' => $request->get('assigned_to'),
- 'note' => $request->input('note'),
- ]);
-
- DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
-
- event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
// Redirect to the new accessory page
- return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
+ ->with('success', trans('admin/accessories/message.checkout.success'));
}
}
diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php
index 030e069bd..6d84861fb 100644
--- a/app/Http/Controllers/Account/AcceptanceController.php
+++ b/app/Http/Controllers/Account/AcceptanceController.php
@@ -23,25 +23,23 @@ use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
-use phpDocumentor\Reflection\Types\Compound;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Log;
class AcceptanceController extends Controller
{
/**
* Show a listing of pending checkout acceptances for the current user
- *
- * @return View
*/
- public function index()
+ public function index() : View
{
- $acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
-
+ $acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get();
return view('account/accept.index', compact('acceptances'));
}
@@ -49,9 +47,8 @@ class AcceptanceController extends Controller
* Shows a form to either accept or decline the checkout acceptance
*
* @param int $id
- * @return mixed
*/
- public function create($id)
+ public function create($id) : View | RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
@@ -64,7 +61,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
- if (! $acceptance->isCheckedOutTo(Auth::user())) {
+ if (! $acceptance->isCheckedOutTo(auth()->user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
@@ -80,9 +77,8 @@ class AcceptanceController extends Controller
*
* @param Request $request
* @param int $id
- * @return Redirect
*/
- public function store(Request $request, $id)
+ public function store(Request $request, $id) : RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
@@ -94,7 +90,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
- if (! $acceptance->isCheckedOutTo(Auth::user())) {
+ if (! $acceptance->isCheckedOutTo(auth()->user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
@@ -222,7 +218,9 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
+ 'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
+ 'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_to,
@@ -233,12 +231,12 @@ class AcceptanceController extends Controller
];
if ($pdf_view_route!='') {
- \Log::debug($pdf_filename.' is the filename, and the route was specified.');
+ Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
}
- $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename);
+ $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
event(new CheckoutAccepted($acceptance));
@@ -306,10 +304,13 @@ class AcceptanceController extends Controller
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
}
+
$data = [
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
+ 'item_status' => $item->assetstatus?->name,
+ 'note' => $request->input('note'),
'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,
@@ -318,12 +319,12 @@ class AcceptanceController extends Controller
];
if ($pdf_view_route!='') {
- \Log::debug($pdf_filename.' is the filename, and the route was specified.');
+ Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
}
- $acceptance->decline($sig_filename);
+ $acceptance->decline($sig_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
diff --git a/app/Http/Controllers/ActionlogController.php b/app/Http/Controllers/ActionlogController.php
index c0c8a997d..f143c4b73 100644
--- a/app/Http/Controllers/ActionlogController.php
+++ b/app/Http/Controllers/ActionlogController.php
@@ -3,34 +3,44 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
-use App\Models\Actionlog;
-use Response;
-
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use \Illuminate\Http\Response;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ActionlogController extends Controller
{
- public function displaySig($filename)
+ public function displaySig($filename) : RedirectResponse | Response | bool
{
// PHP doesn't let you handle file not found errors well with
// file_get_contents, so we set the error reporting for just this class
error_reporting(0);
- $this->authorize('view', \App\Models\Asset::class);
- $file = config('app.private_uploads').'/signatures/'.$filename;
- $filetype = Helper::checkUploadIsImage($file);
+ $disk = config('filesystems.default');
+ switch (config("filesystems.disks.$disk.driver")) {
- $contents = file_get_contents($file, false, stream_context_create(['http' => ['ignore_errors' => true]]));
- if ($contents === false) {
- \Log::warn('File '.$file.' not found');
- return false;
- } else {
- return Response::make($contents)->header('Content-Type', $filetype);
+ case 's3':
+ $file = 'private_uploads/signatures/'.$filename;
+ return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5)));
+ default:
+ $this->authorize('view', \App\Models\Asset::class);
+ $file = config('app.private_uploads').'/signatures/'.$filename;
+ $filetype = Helper::checkUploadIsImage($file);
+
+ $contents = file_get_contents($file, false, stream_context_create(['http' => ['ignore_errors' => true]]));
+ if ($contents === false) {
+ Log::warning('File '.$file.' not found');
+ return false;
+ } else {
+ return response()->make($contents)->header('Content-Type', $filetype);
+ }
}
-
}
- public function getStoredEula($filename){
+
+ public function getStoredEula($filename) : Response | BinaryFileResponse
+ {
$this->authorize('view', \App\Models\Asset::class);
$file = config('app.private_uploads').'/eula-pdfs/'.$filename;
-
- return Response::download($file);
+ return response()->download($file);
}
}
diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php
index 654f3c2e2..b1506e4f4 100644
--- a/app/Http/Controllers/Api/AccessoriesController.php
+++ b/app/Http/Controllers/Api/AccessoriesController.php
@@ -2,21 +2,28 @@
namespace App\Http\Controllers\Api;
+use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
+use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
+use App\Http\Requests\AccessoryCheckoutRequest;
+use App\Http\Requests\StoreAccessoryRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
use App\Models\Company;
use App\Models\User;
-use Auth;
+use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
+use App\Models\AccessoryCheckout;
class AccessoriesController extends Controller
{
+ use CheckInOutRequest;
+
/**
* Display a listing of the resource.
*
@@ -44,13 +51,13 @@ class AccessoriesController extends Controller
'min_amt',
'company_id',
'notes',
- 'users_count',
+ 'checkouts_count',
'qty',
];
- $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
- ->withCount('users as users_count');
+ $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier')
+ ->withCount('checkouts as checkouts_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
@@ -119,12 +126,12 @@ class AccessoriesController extends Controller
/**
* Store a newly created resource in storage.
*
+ * @param \App\Http\Requests\ImageUploadRequest $request
+ * @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] []
* @since [v4.0]
- * @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAccessoryRequest $request)
{
$this->authorize('create', Accessory::class);
$accessory = new Accessory;
@@ -142,15 +149,15 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
+ * @param int $id
+ * @return array
* @author [A. Gianotto] []
* @since [v4.0]
- * @param int $id
- * @return \Illuminate\Http\Response
*/
public function show($id)
{
$this->authorize('view', Accessory::class);
- $accessory = Accessory::withCount('users as users_count')->findOrFail($id);
+ $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
@@ -159,10 +166,10 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
+ * @param int $id
+ * @return array
* @author [A. Gianotto] []
* @since [v4.0]
- * @param int $id
- * @return \Illuminate\Http\Response
*/
public function accessory_detail($id)
{
@@ -193,28 +200,23 @@ class AccessoriesController extends Controller
$offset = request('offset', 0);
$limit = request('limit', 50);
- $accessory_users = $accessory->users;
- $total = $accessory_users->count();
+ $accessory_checkouts = $accessory->checkouts;
+ $total = $accessory_checkouts->count();
if ($total < $offset) {
$offset = 0;
}
- $accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
+ $accessory_checkouts = $accessory->checkouts()->skip($offset)->take($limit)->get();
if ($request->filled('search')) {
- $accessory_users = $accessory->users()
- ->where(function ($query) use ($request) {
- $search_str = '%' . $request->input('search') . '%';
- $query->where('first_name', 'like', $search_str)
- ->orWhere('last_name', 'like', $search_str)
- ->orWhere('note', 'like', $search_str);
- })
+
+ $accessory_checkouts = $accessory->checkouts()->TextSearch($request->input('search'))
->get();
- $total = $accessory_users->count();
+ $total = $accessory_checkouts->count();
}
- return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
+ return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_checkouts, $total);
}
@@ -271,43 +273,31 @@ class AccessoriesController extends Controller
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
- * @author [A. Gianotto] []
* @param int $accessoryId
- * @return Redirect
+ * @return \Illuminate\Http\JsonResponse
+ * @author [A. Gianotto] []
*/
- public function checkout(Request $request, $accessoryId)
+ public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::find($accessoryId))) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
- }
-
$this->authorize('checkout', $accessory);
+ $target = $this->determineCheckoutTarget();
+ $accessory->checkout_qty = $request->input('checkout_qty', 1);
-
- if ($accessory->numRemaining() > 0) {
-
- if (! $user = User::find($request->input('assigned_to'))) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
- }
-
- // Update the accessory data
- $accessory->assigned_to = $request->input('assigned_to');
-
- $accessory->users()->attach($accessory->id, [
+ for ($i = 0; $i < $accessory->checkout_qty; $i++) {
+ AccessoryCheckout::create([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
- 'assigned_to' => $request->get('assigned_to'),
- 'note' => $request->get('note'),
+ 'assigned_to' => $target->id,
+ 'assigned_type' => $target::class,
+ 'note' => $request->input('note'),
]);
-
- $accessory->logCheckout($request->input('note'), $user);
-
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
- return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
+ // Set this value to be able to pass the qty through to the event
+ event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
+
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
@@ -319,24 +309,24 @@ class AccessoriesController extends Controller
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
* @internal param int $accessoryId
*/
public function checkin(Request $request, $accessoryUserId = null)
{
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
+ if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
- $accessory = Accessory::find($accessory_user->accessory_id);
+ $accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
- $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
+ $logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note'));
// Was the accessory updated?
- if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
- if (! is_null($accessory_user->assigned_to)) {
- $user = User::find($accessory_user->assigned_to);
+ if ($accessory_checkout->delete()) {
+ if (! is_null($accessory_checkout->assigned_to)) {
+ $user = User::find($accessory_checkout->assigned_to);
}
$data['log_id'] = $logaction->id;
diff --git a/app/Http/Controllers/Api/AssetFilesController.php b/app/Http/Controllers/Api/AssetFilesController.php
new file mode 100644
index 000000000..4369d287d
--- /dev/null
+++ b/app/Http/Controllers/Api/AssetFilesController.php
@@ -0,0 +1,200 @@
+
+ *
+ * @version v1.0
+ * @author [T. Scarsbrook] []
+ */
+class AssetFilesController extends Controller
+{
+ /**
+ * Accepts a POST to upload a file to the server.
+ *
+ * @param \App\Http\Requests\UploadFileRequest $request
+ * @param int $assetId
+ * @since [v6.0]
+ * @author [T. Scarsbrook] []
+ */
+ public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
+ {
+ // Start by checking if the asset being acted upon exists
+ if (! $asset = Asset::find($assetId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
+ }
+
+ // Make sure we are allowed to update this asset
+ $this->authorize('update', $asset);
+
+ if ($request->hasFile('file')) {
+ // If the file storage directory doesn't exist; create it
+ if (! Storage::exists('private_uploads/assets')) {
+ Storage::makeDirectory('private_uploads/assets', 775);
+ }
+
+ // Loop over the attached files and add them to the asset
+ foreach ($request->file('file') as $file) {
+ $file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
+
+ $asset->logUpload($file_name, e($request->get('notes')));
+ }
+
+ // All done - report success
+ return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success')));
+ }
+
+ // We only reach here if no files were included in the POST, so tell the user this
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500);
+ }
+
+ /**
+ * List the files for an asset.
+ *
+ * @param int $assetId
+ * @since [v6.0]
+ * @author [T. Scarsbrook] []
+ */
+ public function list($assetId = null) : JsonResponse
+ {
+ // Start by checking if the asset being acted upon exists
+ if (! $asset = Asset::find($assetId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
+ }
+
+ // the asset is valid
+ if (isset($asset->id)) {
+ $this->authorize('view', $asset);
+
+ // Check that there are some uploads on this asset that can be listed
+ if ($asset->uploads->count() > 0) {
+ $files = array();
+ foreach ($asset->uploads as $upload) {
+ array_push($files, $upload);
+ }
+ // Give the list of files back to the user
+ return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/hardware/message.upload.success')));
+ }
+
+ // There are no files.
+ return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
+ }
+
+ // Send back an error message
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
+ }
+
+ /**
+ * Check for permissions and display the file.
+ *
+ * @param int $assetId
+ * @param int $fileId
+ * @return \Illuminate\Http\JsonResponse
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @since [v6.0]
+ * @author [T. Scarsbrook] []
+ */
+ public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
+ {
+ // Start by checking if the asset being acted upon exists
+ if (! $asset = Asset::find($assetId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
+ }
+
+ // the asset is valid
+ if (isset($asset->id)) {
+ $this->authorize('view', $asset);
+
+ // Check that the file being requested exists for the asset
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
+ }
+
+ // Form the full filename with path
+ $file = 'private_uploads/assets/'.$log->filename;
+ Log::debug('Checking for '.$file);
+
+ if ($log->action_type == 'audit') {
+ $file = 'private_uploads/audits/'.$log->filename;
+ }
+
+ // Check the file actually exists on the filesystem
+ if (! Storage::exists($file)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404);
+ }
+
+ if (request('inline') == 'true') {
+
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+
+ return Storage::download($file, $log->filename, $headers);
+ }
+
+ return StorageHelper::downloader($file);
+ }
+
+ // Send back an error message
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500);
+ }
+
+ /**
+ * Delete the associated file
+ *
+ * @param int $assetId
+ * @param int $fileId
+ * @since [v6.0]
+ * @author [T. Scarsbrook] []
+ */
+ public function destroy($assetId = null, $fileId = null) : JsonResponse
+ {
+ // Start by checking if the asset being acted upon exists
+ if (! $asset = Asset::find($assetId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
+ }
+
+ $rel_path = 'private_uploads/assets';
+
+ // the asset is valid
+ if (isset($asset->id)) {
+ $this->authorize('update', $asset);
+
+ // Check for the file
+ $log = Actionlog::find($fileId);
+ if ($log) {
+ // Check the file actually exists, and delete it
+ if (Storage::exists($rel_path.'/'.$log->filename)) {
+ Storage::delete($rel_path.'/'.$log->filename);
+ }
+ // Delete the record of the file
+ $log->delete();
+
+ // All deleting done - notify the user of success
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200);
+ }
+
+ // The file doesn't seem to really exist, so report an error
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
+ }
+}
diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php
index 6da7ce23a..ac247a887 100644
--- a/app/Http/Controllers/Api/AssetMaintenancesController.php
+++ b/app/Http/Controllers/Api/AssetMaintenancesController.php
@@ -8,10 +8,9 @@ use App\Http\Transformers\AssetMaintenancesTransformer;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
-use Auth;
-use Carbon\Carbon;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Input;
+use Illuminate\Http\JsonResponse;
/**
* This controller handles all actions related to Asset Maintenance for
@@ -22,7 +21,6 @@ use Illuminate\Support\Facades\Input;
class AssetMaintenancesController extends Controller
{
-
/**
* Generates the JSON response for asset maintenances listing view.
*
@@ -30,13 +28,13 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return string JSON
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Asset::class);
- $maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin');
+ $maintenances = AssetMaintenance::select('asset_maintenances.*')
+ ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@@ -47,7 +45,7 @@ class AssetMaintenancesController extends Controller
}
if ($request->filled('supplier_id')) {
- $maintenances->where('supplier_id', '=', $request->input('supplier_id'));
+ $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('asset_maintenance_type')) {
@@ -70,10 +68,13 @@ class AssetMaintenancesController extends Controller
'notes',
'asset_tag',
'asset_name',
+ 'serial',
'user_id',
'supplier',
'is_warranty',
+ 'status_label',
];
+
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@@ -90,6 +91,12 @@ class AssetMaintenancesController extends Controller
case 'asset_name':
$maintenances = $maintenances->OrderByAssetName($order);
break;
+ case 'serial':
+ $maintenances = $maintenances->OrderByAssetSerial($order);
+ break;
+ case 'status_label':
+ $maintenances = $maintenances->OrderStatusName($order);
+ break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
@@ -110,47 +117,22 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return string JSON
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('update', Asset::class);
// create a new model instance
- $assetMaintenance = new AssetMaintenance();
- $assetMaintenance->supplier_id = $request->input('supplier_id');
- $assetMaintenance->is_warranty = $request->input('is_warranty');
- $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);
- }
+ $maintenance = new AssetMaintenance();
+ $maintenance->fill($request->all());
+ $maintenance->user_id = Auth::id();
// Was the asset maintenance created?
- if ($assetMaintenance->save()) {
- return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.create.success')));
+ if ($maintenance->save()) {
+ 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 +140,38 @@ class AssetMaintenancesController extends Controller
* Validates and stores an update to an asset maintenance
*
* @author A. Gianotto
- * @param int $assetMaintenanceId
+ * @param int $id
* @param int $request
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function update(Request $request, $assetMaintenanceId = null)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', Asset::class);
- // Check if the asset maintenance exists
- $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
- if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot edit a maintenance for that asset'));
- }
+ if ($maintenance = AssetMaintenance::with('asset')->find($id)) {
- $assetMaintenance->supplier_id = e($request->input('supplier_id'));
- $assetMaintenance->is_warranty = e($request->input('is_warranty'));
- $assetMaintenance->cost = $request->input('cost');
- $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;
+ // Can this user manage this asset?
+ if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
}
+
+ // 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)
- && ($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);
- }
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id])));
- // 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()));
}
/**
@@ -226,9 +181,8 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function destroy($assetMaintenanceId)
+ public function destroy($assetMaintenanceId) : JsonResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
@@ -252,9 +206,8 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function show($assetMaintenanceId)
+ public function show($assetMaintenanceId) : JsonResponse
{
$this->authorize('view', Asset::class);
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php
index e77c648b3..835f4d22e 100644
--- a/app/Http/Controllers/Api/AssetModelsController.php
+++ b/app/Http/Controllers/Api/AssetModelsController.php
@@ -4,14 +4,16 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
+use App\Http\Requests\StoreAssetModelRequest;
use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Http\Request;
-use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
/**
* This class controls all actions related to asset models for
@@ -27,9 +29,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', AssetModel::class);
$allowed_columns =
@@ -115,10 +116,9 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
+ * @param \App\Http\Requests\StoreAssetModelRequest $request
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAssetModelRequest $request) : JsonResponse
{
$this->authorize('create', AssetModel::class);
$assetmodel = new AssetModel;
@@ -139,9 +139,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', AssetModel::class);
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
@@ -155,9 +154,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function assets($id)
+ public function assets($id) : array
{
$this->authorize('view', AssetModel::class);
$assets = Asset::where('model_id', '=', $id)->get();
@@ -175,7 +173,7 @@ class AssetModelsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(StoreAssetModelRequest $request, $id) : JsonResponse
{
$this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -208,9 +206,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -224,7 +221,7 @@ class AssetModelsController extends Controller
try {
Storage::disk('public')->delete('assetmodels/'.$assetmodel->image);
} catch (\Exception $e) {
- \Log::info($e);
+ Log::info($e);
}
}
@@ -240,7 +237,7 @@ class AssetModelsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php
index 954ba3a4d..855bc5126 100644
--- a/app/Http/Controllers/Api/AssetsController.php
+++ b/app/Http/Controllers/Api/AssetsController.php
@@ -4,15 +4,20 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
+use App\Http\Requests\UpdateAssetRequest;
+use App\Http\Traits\MigratesLegacyAssetLocations;
+use App\Models\CheckoutAcceptance;
+use App\Models\LicenseSeat;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Transformers\AssetsTransformer;
-use App\Http\Transformers\DepreciationReportTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
-use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
@@ -21,18 +26,13 @@ use App\Models\License;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
-use Auth;
+use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
-use Input;
-use Paginator;
-use Slack;
-use Str;
-use TCPDF;
-use Validator;
-use Route;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Route;
/**
@@ -44,15 +44,16 @@ use Route;
*/
class AssetsController extends Controller
{
+ use MigratesLegacyAssetLocations;
+
/**
* Returns JSON listing of all assets
*
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function index(Request $request, $audit = null)
+ public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
{
$filter_non_deprecable_assets = false;
@@ -87,6 +88,7 @@ class AssetsController extends Controller
'serial',
'model_number',
'last_checkout',
+ 'last_checkin',
'notes',
'expected_checkin',
'order_number',
@@ -104,6 +106,7 @@ class AssetsController extends Controller
'requests_counter',
'byod',
'asset_eol_date',
+ 'requestable',
];
$filter = [];
@@ -135,7 +138,7 @@ class AssetsController extends Controller
// Search custom fields by column name
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()));
}
}
@@ -146,17 +149,44 @@ class AssetsController extends Controller
$assets->TextSearch($request->input('search'));
}
- // This is used by the audit reporting routes
- if (Gate::allows('audit', Asset::class)) {
- switch ($audit) {
- case 'due':
- $assets->DueOrOverdueForAudit($settings);
- break;
- case 'overdue':
- $assets->overdueForAudit($settings);
- break;
+
+ /**
+ * Handle due and overdue audits and checkin dates
+ */
+ switch ($action) {
+ case 'audits':
+
+ switch ($upcoming_status) {
+ case 'due':
+ $assets->DueForAudit($settings);
+ break;
+ case 'overdue':
+ $assets->OverdueForAudit();
+ break;
+ case 'due-or-overdue':
+ $assets->DueOrOverdueForAudit($settings);
+ break;
+ }
+ break;
+
+ case 'checkins':
+ switch ($upcoming_status) {
+ case 'due':
+ $assets->DueForCheckin($settings);
+ break;
+ case 'overdue':
+ $assets->OverdueForCheckin();
+ break;
+ case 'due-or-overdue':
+ $assets->DueOrOverdueForCheckin($settings);
+ break;
+ }
+ break;
}
- }
+
+ /**
+ * End handling due and overdue audits and checkin dates
+ */
// This is used by the sidenav, mostly
@@ -380,9 +410,8 @@ class AssetsController extends Controller
* @param string $tag
* @since [v4.2.1]
* @author [A. Gianotto] []
- * @return \Illuminate\Http\JsonResponse
*/
- public function showByTag(Request $request, $tag)
+ public function showByTag(Request $request, $tag) : JsonResponse | array
{
$this->authorize('index', Asset::class);
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
@@ -420,7 +449,7 @@ class AssetsController extends Controller
* @since [v4.2.1]
* @return \Illuminate\Http\JsonResponse
*/
- public function showBySerial(Request $request, $serial)
+ public function showBySerial(Request $request, $serial) : JsonResponse | array
{
$this->authorize('index', Asset::class);
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
@@ -447,19 +476,20 @@ class AssetsController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/
- public function show(Request $request, $id)
+ public function show(Request $request, $id) : JsonResponse | array
{
- if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
- ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
+ if ($asset = Asset::with('assetstatus')
+ ->with('assignedTo')->withTrashed()
+ ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset, $request->input('components') );
}
-
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
- public function licenses(Request $request, $id)
+ public function licenses(Request $request, $id) : array
{
$this->authorize('view', Asset::class);
$this->authorize('view', License::class);
@@ -476,9 +506,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
- * @return \Illuminate\Http\JsonResponse
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$assets = Asset::select([
@@ -532,36 +561,14 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function store(StoreAssetRequest $request)
+ public function store(StoreAssetRequest $request): JsonResponse
{
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
- $asset->name = $request->get('name');
- $asset->serial = $request->get('serial');
- $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);
-
+ $asset->fill($request->validated());
+ $asset->user_id = Auth::id();
/**
* this is here just legacy reasons. Api\AssetController
@@ -574,10 +581,11 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
// Update custom fields in the database.
- // Validation for these fields is handled through the AssetRequest form request
- $model = AssetModel::find($request->get('model_id'));
+ $model = AssetModel::find($request->input('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) {
// Set the field value based on what was sent in the request
@@ -585,25 +593,30 @@ class AssetsController extends Controller
// If input value is null, use custom field's default value
if ($field_val == null) {
- \Log::debug('Field value for '.$field->db_column.' is null');
+ Log::debug('Field value for '.$field->db_column.' is null');
$field_val = $field->defaultValue($request->get('model_id'));
- \Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
+ Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
if ($field->field_encrypted == '1') {
- \Log::debug('This model field is encrypted in this fieldset.');
+ Log::debug('This model field is encrypted in this fieldset.');
if (Gate::allows('admin')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
- $field_val = \Crypt::encrypt($field->defaultValue($request->get('model_id')));
+ $field_val = Crypt::encrypt($field->defaultValue($request->get('model_id')));
} else {
- $field_val = \Crypt::encrypt($request->input($field->db_column));
+ $field_val = Crypt::encrypt($request->input($field->db_column));
}
}
}
+ if ($field->element == 'checkbox') {
+ if(is_array($field_val)) {
+ $field_val = implode(',', $field_val);
+ }
+ }
$asset->{$field->db_column} = $field_val;
@@ -619,7 +632,7 @@ class AssetsController extends Controller
$target = Location::find(request('assigned_location'));
}
if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
}
if ($asset->image) {
@@ -627,6 +640,8 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
+
+ return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
@@ -637,81 +652,87 @@ class AssetsController extends Controller
* Accepts a POST request to update an asset
*
* @author [A. Gianotto] []
- * @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
{
- $this->authorize('update', Asset::class);
+ $asset->fill($request->validated());
- if ($asset = Asset::find($id)) {
- $asset->fill($request->all());
+ if ($request->has('model_id')) {
+ $asset->model()->associate(AssetModel::find($request->validated()['model_id']));
+ }
+ if ($request->has('company_id')) {
+ $asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
+ }
+ if ($request->has('rtd_location_id') && !$request->has('location_id')) {
+ $asset->location_id = $request->validated()['rtd_location_id'];
+ }
+ if ($request->input('last_audit_date')) {
+ $asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
+ }
- ($request->filled('model_id')) ?
- $asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
- ($request->filled('rtd_location_id')) ?
- $asset->location_id = $request->get('rtd_location_id') : '';
- ($request->filled('company_id')) ?
- $asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
+ /**
+ * this is here just legacy reasons. Api\AssetController
+ * used image_source once to allow encoded image uploads.
+ */
+ if ($request->has('image_source')) {
+ $request->offsetSet('image', $request->offsetGet('image_source'));
+ }
- ($request->filled('rtd_location_id')) ?
- $asset->location_id = $request->get('rtd_location_id') : null;
-
- /**
- * this is here just legacy reasons. Api\AssetController
- * used image_source once to allow encoded image uploads.
- */
- if ($request->has('image_source')) {
- $request->offsetSet('image', $request->offsetGet('image_source'));
- }
-
- $asset = $request->handleImages($asset);
- $model = AssetModel::find($asset->model_id);
+ $asset = $request->handleImages($asset);
+ $model = $asset->model;
// Update custom fields
+ $problems_updating_encrypted_custom_fields = false;
if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
+ $field_val = $request->input($field->db_column, null);
+
if ($request->has($field->db_column)) {
+ if ($field->element == 'checkbox') {
+ if(is_array($field_val)) {
+ $field_val = implode(',', $field_val);
+ }
+ }
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
- $asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
+ $field_val = Crypt::encrypt($field_val);
+ } else {
+ $problems_updating_encrypted_custom_fields = true;
+ continue;
}
- } else {
- $asset->{$field->db_column} = $request->input($field->db_column);
}
+ $asset->{$field->db_column} = $field_val;
}
}
}
-
-
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id;
- Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id)
+ Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id;
}
if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
}
if ($asset->image) {
$asset->image = $asset->getImageUrl();
}
+ if ($problems_updating_encrypted_custom_fields) {
+ return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
+ } else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
}
-
- return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
-
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
+ return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
@@ -721,9 +742,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Asset::class);
@@ -750,38 +770,27 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v5.1.18]
- * @return \Illuminate\Http\JsonResponse
*/
- public function restore(Request $request, $assetId = null)
+ public function restore(Request $request, $assetId = null) : JsonResponse
{
- // 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=='') {
- $message = 'Asset was not deleted. No data was changed.';
-
- } 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');
+ if ($asset->deleted_at == '') {
+ return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.asset')])), 200);
}
- 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);
+
}
/**
@@ -790,9 +799,8 @@ class AssetsController extends Controller
* @author [N. Butler]
* @param string $tag
* @since [v6.0.5]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkoutByTag(AssetCheckoutRequest $request, $tag)
+ public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonResponse
{
if ($asset = Asset::where('asset_tag', $tag)->first()) {
return $this->checkout($request, $asset->id);
@@ -806,9 +814,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkout(AssetCheckoutRequest $request, $asset_id)
+ public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonResponse
{
$this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id);
@@ -825,7 +832,6 @@ class AssetsController extends Controller
'asset_tag' => $asset->asset_tag,
];
-
// This item is checked out to a location
if (request('checkout_to_type') == 'location') {
$target = Location::find(request('assigned_location'));
@@ -852,13 +858,10 @@ class AssetsController extends Controller
$asset->status_id = $request->get('status_id');
}
-
if (! isset($target)) {
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
}
-
-
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null);
$note = request('note', null);
@@ -874,9 +877,7 @@ class AssetsController extends Controller
// $asset->location_id = $target->rtd_location_id;
// }
-
-
- if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
+ if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
@@ -890,24 +891,24 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkin(Request $request, $asset_id)
+ public function checkin(Request $request, $asset_id) : JsonResponse
{
- $this->authorize('checkin', Asset::class);
- $asset = Asset::findOrFail($asset_id);
+ $asset = Asset::with('model')->findOrFail($asset_id);
$this->authorize('checkin', $asset);
-
$target = $asset->assignedTo;
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->last_checkout = null;
+ //$asset->last_checkout = null;
$asset->last_checkin = now();
- $asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -915,13 +916,19 @@ class AssetsController extends Controller
$asset->name = $request->input('name');
}
+ $this->migrateLegacyLocations($asset);
+
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
$asset->location_id = $request->input('location_id');
+
+ if ($request->input('update_default_location')){
+ $asset->rtd_location_id = $request->input('location_id');
+ }
}
- if ($request->has('status_id')) {
+ if ($request->filled('status_id')) {
$asset->status_id = $request->input('status_id');
}
@@ -932,10 +939,31 @@ class AssetsController extends Controller
$originalValues['action_date'] = $checkin_at;
}
- if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
+ $asset->licenseseats->each(function (LicenseSeat $seat) {
+ $seat->update(['assigned_to' => null]);
+ });
- return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
+ // Get all pending Acceptances for this asset and delete them
+ CheckoutAcceptance::pending()
+ ->whereHasMorph(
+ 'checkoutable',
+ [Asset::class],
+ function (Builder $query) use ($asset) {
+ $query->where('id', $asset->id);
+ })
+ ->get()
+ ->map(function ($acceptance) {
+ $acceptance->delete();
+ });
+
+ if ($asset->save()) {
+ event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
+
+ 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')));
@@ -946,12 +974,11 @@ class AssetsController extends Controller
*
* @author [A. Janes] []
* @since [v6.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkinByTag(Request $request, $tag = null)
+ public function checkinByTag(Request $request, $tag = null) : JsonResponse
{
$this->authorize('checkin', Asset::class);
- if(null == $tag && null !== ($request->input('asset_tag'))) {
+ if (null == $tag && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag');
}
$asset = Asset::where('asset_tag', $tag)->first();
@@ -972,31 +999,44 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function audit(Request $request)
+ public function audit(Request $request) : JsonResponse
{
$this->authorize('audit', Asset::class);
- $rules = [
- 'asset_tag' => 'required',
- 'location_id' => 'exists:locations,id|nullable|numeric',
- 'next_audit_date' => 'date|nullable',
- ];
-
- $validator = Validator::make($request->all(), $rules);
- if ($validator->fails()) {
- return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
- }
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
+ // No tag passed - return an error
+ if (!$request->filled('asset_tag')) {
+ return response()->json(Helper::formatStandardApiResponse('error', [
+ 'asset_tag'=> '',
+ 'error'=> trans('admin/hardware/message.no_tag'),
+ ], trans('admin/hardware/message.no_tag')), 200);
+ }
+
+
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
if ($asset) {
- // We don't want to log this as a normal update, so let's bypass that
+
+ /**
+ * Even though we do a save() further down, we don't want to log this as a "normal" asset update,
+ * which would trigger the Asset Observer and would log an asset *update* log entry (because the
+ * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
+ * the audit log entry we're creating through this controller.
+ *
+ * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
+ * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
+ * will bypass normal model-level validation that's usually handled at the observer )
+ *
+ * We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
+ * which manually invokes Watson Validating to make sure the asset's model is valid.
+ *
+ * @see \App\Observers\AssetObserver::updating()
+ */
$asset->unsetEventDispatcher();
$asset->next_audit_date = $dt;
@@ -1012,8 +1052,12 @@ class AssetsController extends Controller
$asset->last_audit_date = date('Y-m-d H:i:s');
- if ($asset->save()) {
- $log = $asset->logAudit(request('note'), request('location_id'));
+ /**
+ * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
+ * We have to invoke this manually because of the unsetEventDispatcher() above.)
+ */
+ if ($asset->isValid() && $asset->save()) {
+ $asset->logAudit(request('note'), request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag),
@@ -1021,9 +1065,23 @@ class AssetsController extends Controller
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
], trans('admin/hardware/message.audit.success')));
}
+
+ // Asset failed validation or was not able to be saved
+ return response()->json(Helper::formatStandardApiResponse('error', [
+ 'asset_tag'=> e($asset->asset_tag),
+ 'error'=> $asset->getErrors()->first(),
+ ], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
+
}
- return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
+
+ // No matching asset for the asset tag that was passed.
+ return response()->json(Helper::formatStandardApiResponse('error', [
+ 'asset_tag'=> e($request->input('asset_tag')),
+ 'error'=> trans('admin/hardware/message.audit.error'),
+ ], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
+
+
}
@@ -1033,9 +1091,8 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function requestable(Request $request)
+ public function requestable(Request $request) : JsonResponse | array
{
$this->authorize('viewRequestable', Asset::class);
@@ -1057,8 +1114,7 @@ class AssetsController extends Controller
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
- 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
- ->requestableAssets();
+ 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests');
@@ -1066,7 +1122,7 @@ class AssetsController extends Controller
if ($request->filled('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())) {
@@ -1096,6 +1152,7 @@ class AssetsController extends Controller
break;
}
+ $assets->requestableAssets();
// 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');
diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php
index 2aa4b3741..6e9866f90 100644
--- a/app/Http/Controllers/Api/CategoriesController.php
+++ b/app/Http/Controllers/Api/CategoriesController.php
@@ -8,9 +8,9 @@ use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Category;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\Validator;
class CategoriesController extends Controller
{
@@ -21,7 +21,7 @@ class CategoriesController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', Category::class);
$allowed_columns = [
@@ -115,7 +115,7 @@ class CategoriesController extends Controller
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Category::class);
$category = new Category;
@@ -136,9 +136,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
@@ -156,7 +155,7 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Category::class);
$category = Category::findOrFail($id);
@@ -164,7 +163,7 @@ class CategoriesController extends Controller
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
return response()->json(
- Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
+ Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422)
);
}
$category->fill($request->all());
@@ -185,7 +184,7 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
@@ -208,7 +207,7 @@ class CategoriesController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request, $category_type = 'asset')
+ public function selectlist(Request $request, $category_type = 'asset') : array
{
$this->authorize('view.selectlists');
$categories = Category::select([
diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php
index 580bc5d9b..0d78df9ac 100644
--- a/app/Http/Controllers/Api/CompaniesController.php
+++ b/app/Http/Controllers/Api/CompaniesController.php
@@ -10,6 +10,7 @@ use App\Models\Company;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class CompaniesController extends Controller
{
@@ -18,9 +19,8 @@ class CompaniesController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Company::class);
@@ -40,7 +40,9 @@ class CompaniesController extends Controller
'components_count',
];
- $companies = Company::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
+ $companies = Company::withCount(['assets as assets_count' => function ($query) {
+ $query->AssetsForShow();
+ }])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
@@ -77,9 +79,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Company::class);
$company = new Company;
@@ -100,9 +101,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Company::class);
$company = Company::findOrFail($id);
@@ -118,9 +118,8 @@ class CompaniesController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Company::class);
$company = Company::findOrFail($id);
@@ -142,9 +141,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Company::class);
$company = Company::findOrFail($id);
@@ -167,7 +165,7 @@ class CompaniesController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
$companies = Company::select([
diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php
index 9202b10b7..69bd82848 100644
--- a/app/Http/Controllers/Api/ComponentsController.php
+++ b/app/Http/Controllers/Api/ComponentsController.php
@@ -5,15 +5,17 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ComponentsTransformer;
-use App\Models\Company;
use App\Models\Component;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn;
-use App\Events\ComponentCheckedIn;
use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Query\Builder;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
class ComponentsController extends Controller
{
@@ -23,9 +25,8 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Component::class);
@@ -115,9 +116,8 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Component::class);
$component = new Component;
@@ -136,9 +136,8 @@ class ComponentsController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Component::class);
$component = Component::findOrFail($id);
@@ -155,9 +154,8 @@ class ComponentsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Component::class);
$component = Component::findOrFail($id);
@@ -178,9 +176,8 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Component::class);
$component = Component::findOrFail($id);
@@ -197,9 +194,8 @@ class ComponentsController extends Controller
* @since [v4.0]
* @param Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function getAssets(Request $request, $id)
+ public function getAssets(Request $request, $id) : array
{
$this->authorize('view', \App\Models\Asset::class);
@@ -240,10 +236,8 @@ class ComponentsController extends Controller
* @since [v5.1.8]
* @param Request $request
* @param int $componentId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function checkout(Request $request, $componentId)
+ public function checkout(Request $request, $componentId) : JsonResponse
{
// Check if the component exists
if (!$component = Component::find($componentId)) {
@@ -274,9 +268,9 @@ class ComponentsController extends Controller
$component->assets()->attach($component->id, [
'component_id' => $component->id,
- 'created_at' => \Carbon::now(),
+ 'created_at' => Carbon::now(),
'assigned_qty' => $request->get('assigned_qty', 1),
- 'user_id' => \Auth::id(),
+ 'user_id' => auth()->id(),
'asset_id' => $request->get('assigned_to'),
'note' => $request->get('note'),
]);
@@ -296,12 +290,10 @@ class ComponentsController extends Controller
* @since [v5.1.8]
* @param Request $request
* @param $component_asset_id
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function checkin(Request $request, $component_asset_id)
+ public function checkin(Request $request, $component_asset_id) : JsonResponse
{
- if ($component_assets = \DB::table('components_assets')->find($component_asset_id)) {
+ if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
@@ -314,7 +306,7 @@ class ComponentsController extends Controller
if ($max_to_checkin > 1) {
- $validator = \Validator::make($request->all(), [
+ $validator = Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
]);
@@ -331,21 +323,21 @@ class ComponentsController extends Controller
// actually checked out.
$component_assets->assigned_qty = $qty_remaining_in_checkout;
- \Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
+ Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
- \DB::table('components_assets')->where('id',
+ DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) {
- \DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
+ DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
$asset = Asset::find($component_assets->asset_id);
- event(new CheckoutableCheckedIn($component, $asset, \Auth::user(), $request->input('note'), \Carbon::now()));
+ event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php
index 1f3a347fb..1665b7f4f 100644
--- a/app/Http/Controllers/Api/ConsumablesController.php
+++ b/app/Http/Controllers/Api/ConsumablesController.php
@@ -2,8 +2,10 @@
namespace App\Http\Controllers\Api;
+use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
+use App\Http\Requests\StoreConsumableRequest;
use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
@@ -11,6 +13,8 @@ use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
class ConsumablesController extends Controller
{
@@ -19,34 +23,13 @@ class ConsumablesController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- *
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('index', Consumable::class);
- // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
- // Relations will be handled in query scopes a little further down.
- $allowed_columns =
- [
- 'id',
- 'name',
- 'order_number',
- 'min_amt',
- 'purchase_date',
- 'purchase_cost',
- 'company',
- 'category',
- 'model_number',
- 'item_no',
- 'qty',
- 'image',
- 'notes',
- ];
-
- $consumables = Consumable::select('consumables.*')
- ->with('company', 'location', 'category', 'users', 'manufacturer');
+ $consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
+ ->withCount('users as consumables_users_count');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
@@ -88,15 +71,9 @@ class ConsumablesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
- $allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort_override = $request->input('sort');
- $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
-
-
- switch ($sort_override) {
+ switch ($request->input('sort')) {
case 'category':
$consumables = $consumables->OrderCategory($order);
break;
@@ -110,10 +87,30 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderCompany($order);
break;
case 'supplier':
- $components = $consumables->OrderSupplier($order);
+ $consumables = $consumables->OrderSupplier($order);
break;
default:
- $consumables = $consumables->orderBy($column_sort, $order);
+ // This array is what determines which fields should be allowed to be sorted on ON the table itself.
+ // These must match a column on the consumables table directly.
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'order_number',
+ 'min_amt',
+ 'purchase_date',
+ 'purchase_cost',
+ 'company',
+ 'category',
+ 'model_number',
+ 'item_no',
+ 'manufacturer',
+ 'location',
+ 'qty',
+ 'image'
+ ];
+
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+ $consumables = $consumables->orderBy($sort, $order);
break;
}
@@ -129,9 +126,8 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreConsumableRequest $request) : JsonResponse
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable;
@@ -150,9 +146,8 @@ class ConsumablesController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Consumable::class);
$consumable = Consumable::with('users')->findOrFail($id);
@@ -167,9 +162,8 @@ class ConsumablesController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(StoreConsumableRequest $request, $id) : JsonResponse
{
$this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id);
@@ -189,9 +183,8 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Consumable::class);
$consumable = Consumable::findOrFail($id);
@@ -208,9 +201,8 @@ class ConsumablesController extends Controller
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
- * @return array
*/
- public function getDataView($consumableId)
+ public function getDataView($consumableId) : array
{
$consumable = Consumable::with(['consumableAssignments'=> function ($query) {
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
@@ -249,9 +241,8 @@ class ConsumablesController extends Controller
* @author [A. Gutierrez] []
* @param int $id
* @since [v4.9.5]
- * @return JsonResponse
*/
- public function checkout(Request $request, $id)
+ public function checkout(Request $request, $id) : JsonResponse
{
// Check if the consumable exists
if (!$consumable = Consumable::with('users')->find($id)) {
@@ -263,14 +254,11 @@ class ConsumablesController extends Controller
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
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')])));
-
- return redirect()->route('consumables.index')->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
}
@@ -278,7 +266,6 @@ class ConsumablesController extends Controller
if (!$user = User::find($request->input('assigned_to'))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
- \Log::debug('No valid user');
}
// Update the consumable data
@@ -293,17 +280,9 @@ class ConsumablesController extends Controller
]
);
- // Log checkout event
- $logaction = $consumable->logCheckout($request->input('note'), $user);
- $data['log_id'] = $logaction->id;
- $data['eula'] = $consumable->getEula();
- $data['first_name'] = $user->first_name;
- $data['item_name'] = $consumable->name;
- $data['checkout_date'] = $logaction->created_at;
- $data['note'] = $logaction->note;
- $data['require_acceptance'] = $consumable->requireAcceptance();
+ event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
}
@@ -312,7 +291,7 @@ class ConsumablesController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$consumables = Consumable::select([
'consumables.id',
diff --git a/app/Http/Controllers/Api/CustomFieldsController.php b/app/Http/Controllers/Api/CustomFieldsController.php
index 688e1146f..207a92e27 100644
--- a/app/Http/Controllers/Api/CustomFieldsController.php
+++ b/app/Http/Controllers/Api/CustomFieldsController.php
@@ -8,7 +8,8 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
-use Validator;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Http\JsonResponse;
class CustomFieldsController extends Controller
{
@@ -20,7 +21,7 @@ class CustomFieldsController extends Controller
* @since [v3.0]
* @return array
*/
- public function index()
+ public function index() : array
{
$this->authorize('index', CustomField::class);
$fields = CustomField::get();
@@ -33,9 +34,8 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] []
* @param int $id
* @since [v4.1.10]
- * @return View
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) {
@@ -52,9 +52,8 @@ class CustomFieldsController extends Controller
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id);
@@ -86,9 +85,8 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] []
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', CustomField::class);
$field = new CustomField;
@@ -136,7 +134,7 @@ class CustomFieldsController extends Controller
return $fieldset->fields()->sync($fields);
}
- public function associate(Request $request, $field_id)
+ public function associate(Request $request, $field_id) : JsonResponse
{
$this->authorize('update', CustomFieldset::class);
@@ -155,10 +153,9 @@ class CustomFieldsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
- public function disassociate(Request $request, $field_id)
+ public function disassociate(Request $request, $field_id) : JsonResponse
{
$this->authorize('update', CustomFieldset::class);
-
$field = CustomField::findOrFail($field_id);
$fieldset_id = $request->input('fieldset_id');
@@ -179,9 +176,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return Redirect
*/
- public function destroy($field_id)
+ public function destroy($field_id) : JsonResponse
{
$field = CustomField::findOrFail($field_id);
diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php
index df0fc940c..5dbd50791 100644
--- a/app/Http/Controllers/Api/CustomFieldsetsController.php
+++ b/app/Http/Controllers/Api/CustomFieldsetsController.php
@@ -9,8 +9,7 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Illuminate\Http\Request;
-use Redirect;
-use View;
+use Illuminate\Http\JsonResponse;
/**
* This controller handles all actions related to Custom Asset Fieldsets for
@@ -30,9 +29,8 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson]
* @param int $id
* @since [v1.8]
- * @return View
*/
- public function index()
+ public function index() : array
{
$this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
@@ -46,9 +44,8 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson]
* @param int $id
* @since [v1.8]
- * @return View
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
@@ -65,9 +62,8 @@ class CustomFieldsetsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -86,9 +82,8 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset;
@@ -118,9 +113,8 @@ class CustomFieldsetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return Redirect
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -147,7 +141,7 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId
* @return string JSON
*/
- public function fields($id)
+ public function fields($id) : array
{
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id);
@@ -164,14 +158,11 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId
* @return string JSON
*/
- public function fieldsWithDefaultValues($fieldsetId, $modelId)
+ public function fieldsWithDefaultValues($fieldsetId, $modelId) : array
{
$this->authorize('view', CustomField::class);
-
$set = CustomFieldset::findOrFail($fieldsetId);
-
$fields = $set->fields;
-
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
}
}
diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php
index f211089b9..eabc79ec2 100644
--- a/app/Http/Controllers/Api/DepartmentsController.php
+++ b/app/Http/Controllers/Api/DepartmentsController.php
@@ -6,12 +6,11 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer;
-use App\Models\Company;
use App\Models\Department;
-use Auth;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class DepartmentsController extends Controller
{
@@ -20,9 +19,8 @@ class DepartmentsController extends Controller
*
* @author [Godfrey Martinez] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count'];
@@ -91,16 +89,15 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
$department = $request->handleImages($department);
- $department->user_id = Auth::user()->id;
+ $department->user_id = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
if ($department->save()) {
@@ -116,13 +113,11 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Department::class);
$department = Department::findOrFail($id);
-
return (new DepartmentsTransformer)->transformDepartment($department);
}
@@ -133,9 +128,8 @@ class DepartmentsController extends Controller
* @since [v5.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Department::class);
$department = Department::findOrFail($id);
@@ -156,9 +150,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v4.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$department = Department::findOrFail($id);
@@ -180,7 +173,7 @@ class DepartmentsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/DepreciationsController.php b/app/Http/Controllers/Api/DepreciationsController.php
index 502a0741b..0209eae39 100644
--- a/app/Http/Controllers/Api/DepreciationsController.php
+++ b/app/Http/Controllers/Api/DepreciationsController.php
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\DepreciationsTransformer;
use App\Models\Depreciation;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
class DepreciationsController extends Controller
{
@@ -15,14 +16,13 @@ class DepreciationsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Depreciation::class);
- $allowed_columns = ['id','name','months','depreciation_min','created_at'];
+ $allowed_columns = ['id','name','months','depreciation_min', 'depreciation_type','created_at'];
- $depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at');
+ $depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','user_id','created_at','updated_at');
if ($request->filled('search')) {
$depreciations = $depreciations->TextSearch($request->input('search'));
@@ -48,9 +48,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', Depreciation::class);
$depreciation = new Depreciation;
@@ -69,9 +68,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -86,9 +84,8 @@ class DepreciationsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -107,9 +104,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Depreciation::class);
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);
diff --git a/app/Http/Controllers/Api/GroupsController.php b/app/Http/Controllers/Api/GroupsController.php
index 8548af0ba..878650c71 100644
--- a/app/Http/Controllers/Api/GroupsController.php
+++ b/app/Http/Controllers/Api/GroupsController.php
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\GroupsTransformer;
use App\Models\Group;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
class GroupsController extends Controller
@@ -16,16 +17,15 @@ class GroupsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('superadmin');
$this->authorize('view', Group::class);
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
- $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at')->withCount('users as users_count');
+ $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count');
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
@@ -55,18 +55,21 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('superadmin');
$group = new Group;
+ // Get all the available permissions
+ $permissions = config('permissions');
+ $groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$group->name = $request->input('name');
- $group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here
+ $group->created_by = auth()->id();
+ $group->permissions = json_encode($request->input('permissions', $groupPermissions));
if ($group->save()) {
- return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));
+ return response()->json(Helper::formatStandardApiResponse('success', (new GroupsTransformer)->transformGroup($group), trans('admin/groups/message.success.create')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors()));
@@ -78,13 +81,11 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
-
return (new GroupsTransformer)->transformGroup($group);
}
@@ -95,9 +96,8 @@ class GroupsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
@@ -106,7 +106,7 @@ class GroupsController extends Controller
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
if ($group->save()) {
- return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.update.success')));
+ return response()->json(Helper::formatStandardApiResponse('success', (new GroupsTransformer)->transformGroup($group), trans('admin/groups/message.success.update')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $group->getErrors()));
@@ -118,9 +118,8 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php
index 2d7e2f7d7..13f430121 100644
--- a/app/Http/Controllers/Api/ImportController.php
+++ b/app/Http/Controllers/Api/ImportController.php
@@ -9,22 +9,23 @@ use App\Http\Transformers\ImportsTransformer;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Import;
-use Artisan;
+use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
class ImportController extends Controller
{
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Http\Response
*/
- public function index()
+ public function index() : JsonResponse | array
{
$this->authorize('import');
$imports = Import::latest()->get();
@@ -36,9 +37,8 @@ class ImportController extends Controller
* Process and store a CSV upload file.
*
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\JsonResponse
*/
- public function store()
+ public function store() : JsonResponse
{
$this->authorize('import');
if (! config('app.lock_passwords')) {
@@ -151,18 +151,17 @@ class ImportController extends Controller
* Processes the specified Import.
*
* @param int $import_id
- * @return \Illuminate\Http\Response
*/
- public function process(ItemImportRequest $request, $import_id)
+ public function process(ItemImportRequest $request, $import_id) : JsonResponse
{
$this->authorize('import');
// Run a backup immediately before processing
if ($request->get('run-backup')) {
- \Log::debug('Backup manually requested via importer');
+ Log::debug('Backup manually requested via importer');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
} else {
- \Log::debug('NO BACKUP requested via importer');
+ Log::debug('NO BACKUP requested via importer');
}
$import = Import::find($import_id);
@@ -211,9 +210,8 @@ class ImportController extends Controller
* Remove the specified resource from storage.
*
* @param int $import_id
- * @return \Illuminate\Http\Response
*/
- public function destroy($import_id)
+ public function destroy($import_id) : JsonResponse
{
$this->authorize('create', Asset::class);
@@ -230,6 +228,8 @@ class ImportController extends Controller
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
+
}
+ return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
}
diff --git a/app/Http/Controllers/Api/LabelsController.php b/app/Http/Controllers/Api/LabelsController.php
index 6576ec037..0b5423542 100644
--- a/app/Http/Controllers/Api/LabelsController.php
+++ b/app/Http/Controllers/Api/LabelsController.php
@@ -8,7 +8,7 @@ use App\Http\Transformers\LabelsTransformer;
use App\Models\Labels\Label;
use Illuminate\Http\Request;
use Illuminate\Support\ItemNotFoundException;
-use Auth;
+use Illuminate\Http\JsonResponse;
class LabelsController extends Controller
{
@@ -16,9 +16,8 @@ class LabelsController extends Controller
* Returns JSON listing of all labels.
*
* @author Grant Le Roux
- * @return JsonResponse
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Label::class);
@@ -50,9 +49,8 @@ class LabelsController extends Controller
*
* @author Grant Le Roux
* @param string $labelName
- * @return JsonResponse
*/
- public function show(string $labelName)
+ public function show(string $labelName) : JsonResponse | array
{
$labelName = str_replace('/', '\\', $labelName);
try {
diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php
index 5e79c49b2..a9630aa29 100644
--- a/app/Http/Controllers/Api/LicenseSeatsController.php
+++ b/app/Http/Controllers/Api/LicenseSeatsController.php
@@ -9,7 +9,7 @@ use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
-use Auth;
+use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class LicenseSeatsController extends Controller
@@ -19,11 +19,10 @@ class LicenseSeatsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $licenseId
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request, $licenseId)
+ public function index(Request $request, $licenseId) : JsonResponse | array
{
- //
+
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
@@ -64,11 +63,10 @@ class LicenseSeatsController extends Controller
*
* @param int $licenseId
* @param int $seatId
- * @return \Illuminate\Http\Response
*/
- public function show($licenseId, $seatId)
+ public function show($licenseId, $seatId) : JsonResponse | array
{
- //
+
$this->authorize('view', License::class);
// sanity checks:
// 1. does the license seat exist?
@@ -89,19 +87,18 @@ class LicenseSeatsController extends Controller
* @param \Illuminate\Http\Request $request
* @param int $licenseId
* @param int $seatId
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $licenseId, $seatId)
+ public function update(Request $request, $licenseId, $seatId) : JsonResponse | array
{
$this->authorize('checkout', License::class);
- // sanity checks:
- // 1. does the license seat exist?
+
if (! $licenseSeat = LicenseSeat::find($seatId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
- // 2. does the seat belong to the specified license?
- if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
+
+ $license = $licenseSeat->license()->first();
+ if (!$license || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
}
@@ -110,7 +107,7 @@ class LicenseSeatsController extends Controller
// attempt to update the license seat
$licenseSeat->fill($request->all());
- $licenseSeat->user_id = Auth::user()->id;
+ $licenseSeat->user_id = auth()->id();
// check if this update is a checkin operation
// 1. are relevant fields touched at all?
diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php
index d456e3cd6..71ad01b59 100644
--- a/app/Http/Controllers/Api/LicensesController.php
+++ b/app/Http/Controllers/Api/LicensesController.php
@@ -4,14 +4,12 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
-use App\Http\Transformers\LicenseSeatsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
-use App\Models\Company;
use App\Models\License;
-use App\Models\LicenseSeat;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
+use Illuminate\Http\JsonResponse;
class LicensesController extends Controller
{
@@ -21,13 +19,12 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', License::class);
- $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
+ $licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) {
$licenses->where('company_id', '=', $request->input('company_id'));
@@ -73,6 +70,9 @@ class LicensesController extends Controller
$licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
}
+ if ($request->filled('user_id')) {
+ $licenses->where('user_id', '=', $request->input('user_id'));
+ }
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
$licenses->where('maintained','=',1);
@@ -116,6 +116,9 @@ class LicensesController extends Controller
case 'company':
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break;
+ case 'created_by':
+ $licenses = $licenses->OrderCreatedBy($order);
+ break;
default:
$allowed_columns =
[
@@ -136,6 +139,7 @@ class LicensesController extends Controller
'seats',
'termination_date',
'depreciation_id',
+ 'min_amt',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$licenses = $licenses->orderBy($sort, $order);
@@ -155,11 +159,9 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
- //
$this->authorize('create', License::class);
$license = new License;
$license->fill($request->all());
@@ -176,9 +178,8 @@ class LicensesController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', License::class);
$license = License::withCount('freeSeats')->findOrFail($id);
@@ -194,9 +195,8 @@ class LicensesController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse | array
{
//
$this->authorize('update', License::class);
@@ -217,9 +217,8 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
//
$license = License::findOrFail($id);
@@ -247,7 +246,7 @@ class LicensesController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$licenses = License::select([
'licenses.id',
diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php
index b88849328..10744bee2 100644
--- a/app/Http/Controllers/Api/LocationsController.php
+++ b/app/Http/Controllers/Api/LocationsController.php
@@ -5,12 +5,15 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Controllers\Controller;
+use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
+use App\Models\Asset;
use App\Models\Location;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
+use Illuminate\Http\JsonResponse;
class LocationsController extends Controller
{
@@ -21,13 +24,31 @@ class LocationsController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Location::class);
$allowed_columns = [
- 'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
- 'updated_at', 'manager_id', 'image',
- 'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
+ 'id',
+ 'name',
+ 'address',
+ 'address2',
+ 'city',
+ 'state',
+ 'country',
+ 'zip',
+ 'created_at',
+ 'updated_at',
+ 'manager_id',
+ 'image',
+ 'assigned_assets_count',
+ 'users_count',
+ 'assets_count',
+ 'assigned_assets_count',
+ 'assets_count',
+ 'rtd_assets_count',
+ 'currency',
+ 'ldap_ou',
+ ];
$locations = Location::with('parent', 'manager', 'children')->select([
'locations.id',
@@ -50,6 +71,7 @@ class LocationsController extends Controller
])->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
+ ->withCount('children as children_count')
->withCount('users as users_count');
if ($request->filled('search')) {
@@ -80,6 +102,10 @@ class LocationsController extends Controller
$locations->where('locations.country', '=', $request->input('country'));
}
+ if ($request->filled('manager_id')) {
+ $locations->where('locations.manager_id', '=', $request->input('manager_id'));
+ }
+
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -115,9 +141,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Location::class);
$location = new Location;
@@ -137,9 +162,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children')
@@ -176,9 +200,8 @@ class LocationsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\JsonResponse
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Location::class);
$location = Location::findOrFail($id);
@@ -201,21 +224,36 @@ class LocationsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
}
+ public function assets(Request $request, Location $location) : JsonResponse | array
+ {
+ $this->authorize('view', Asset::class);
+ $this->authorize('view', $location);
+ $assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
+ $assets = $assets->get();
+ return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
+ }
+
/**
* Remove the specified resource from storage.
*
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Location::class);
- $location = Location::findOrFail($id);
+ $location = Location::withCount('assignedAssets as assigned_assets_count')
+ ->withCount('assets as assets_count')
+ ->withCount('rtd_assets as rtd_assets_count')
+ ->withCount('children as children_count')
+ ->withCount('users as users_count')
+ ->withCount('accessories as accessories_count')
+ ->findOrFail($id);
+
if (! $location->isDeletable()) {
return response()
- ->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
+ ->json(Helper::formatStandardApiResponse('error', null, trans('admin/locations/message.assoc_users')));
}
$this->authorize('delete', $location);
$location->delete();
@@ -251,7 +289,7 @@ class LocationsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
// If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location.
@@ -296,7 +334,6 @@ class LocationsController extends Controller
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
- //return [];
return (new SelectlistTransformer)->transformSelectlist($paginated_results);
}
}
diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php
index dadef87e2..eb89693e5 100644
--- a/app/Http/Controllers/Api/ManufacturersController.php
+++ b/app/Http/Controllers/Api/ManufacturersController.php
@@ -6,10 +6,12 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ManufacturersTransformer;
use App\Http\Transformers\SelectlistTransformer;
+use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class ManufacturersController extends Controller
{
@@ -20,7 +22,7 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Manufacturer::class);
$allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
@@ -81,9 +83,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
@@ -103,9 +104,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
@@ -120,9 +120,8 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
@@ -142,9 +141,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
@@ -159,6 +157,43 @@ class ManufacturersController extends Controller
}
+ /**
+ * Restore a given Manufacturer (mark as un-deleted)
+ *
+ * @author [A. Gianotto] []
+ * @since [v6.3.4]
+ * @param int $id
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ */
+ public function restore($id) : JsonResponse
+ {
+ $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()->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
*
@@ -166,7 +201,7 @@ class ManufacturersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/PredefinedKitsController.php b/app/Http/Controllers/Api/PredefinedKitsController.php
index b398dbfae..26ccb5035 100644
--- a/app/Http/Controllers/Api/PredefinedKitsController.php
+++ b/app/Http/Controllers/Api/PredefinedKitsController.php
@@ -7,6 +7,8 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\PredefinedKitsTransformer;
use App\Models\PredefinedKit;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+use App\Http\Transformers\SelectlistTransformer;
/**
* @author [D. Minaev.] []
@@ -18,7 +20,7 @@ class PredefinedKitsController extends Controller
*
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', PredefinedKit::class);
$allowed_columns = ['id', 'name'];
@@ -47,9 +49,8 @@ class PredefinedKitsController extends Controller
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', PredefinedKit::class);
$kit = new PredefinedKit;
@@ -66,9 +67,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -81,9 +81,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $id kit id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -100,9 +99,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -123,7 +121,7 @@ class PredefinedKitsController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$kits = PredefinedKit::select([
'id',
@@ -145,7 +143,7 @@ class PredefinedKitsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function indexLicenses($kit_id)
+ public function indexLicenses($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -160,7 +158,7 @@ class PredefinedKitsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function storeLicense(Request $request, $kit_id)
+ public function storeLicense(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -186,9 +184,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateLicense(Request $request, $kit_id, $license_id)
+ public function updateLicense(Request $request, $kit_id, $license_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -205,9 +202,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachLicense($kit_id, $license_id)
+ public function detachLicense($kit_id, $license_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -221,9 +217,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexModels($kit_id)
+ public function indexModels($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -236,9 +231,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function storeModel(Request $request, $kit_id)
+ public function storeModel(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -252,7 +246,7 @@ class PredefinedKitsController extends Controller
$relation = $kit->models();
if ($relation->find($model_id)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
+ return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')]));
}
$relation->attach($model_id, ['quantity' => $quantity]);
@@ -264,9 +258,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateModel(Request $request, $kit_id, $model_id)
+ public function updateModel(Request $request, $kit_id, $model_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -283,9 +276,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachModel($kit_id, $model_id)
+ public function detachModel($kit_id, $model_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -299,9 +291,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexConsumables($kit_id)
+ public function indexConsumables($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -314,9 +305,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function storeConsumable(Request $request, $kit_id)
+ public function storeConsumable(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -342,9 +332,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateConsumable(Request $request, $kit_id, $consumable_id)
+ public function updateConsumable(Request $request, $kit_id, $consumable_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -361,9 +350,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachConsumable($kit_id, $consumable_id)
+ public function detachConsumable($kit_id, $consumable_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -377,9 +365,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexAccessories($kit_id)
+ public function indexAccessories($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -392,9 +379,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function storeAccessory(Request $request, $kit_id)
+ public function storeAccessory(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -420,9 +406,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateAccessory(Request $request, $kit_id, $accessory_id)
+ public function updateAccessory(Request $request, $kit_id, $accessory_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -439,9 +424,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachAccessory($kit_id, $accessory_id)
+ public function detachAccessory($kit_id, $accessory_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php
index ef56ed537..f7f91e094 100644
--- a/app/Http/Controllers/Api/ProfileController.php
+++ b/app/Http/Controllers/Api/ProfileController.php
@@ -6,13 +6,13 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\CheckoutRequest;
use Illuminate\Http\Response;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
-use DB;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Http\JsonResponse;
class ProfileController extends Controller
{
@@ -42,12 +42,10 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.3.0]
- *
- * @return array
*/
- public function requestedAssets()
+ public function requestedAssets() : array
{
- $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
+ $checkoutRequests = CheckoutRequest::where('user_id', '=', auth()->id())->get();
$results = array();
$show_field = array();
@@ -95,10 +93,9 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function createApiToken(Request $request) {
+ public function createApiToken(Request $request) : JsonResponse
+ {
if (!Gate::allows('self.api')) {
abort(403);
@@ -106,14 +103,14 @@ class ProfileController extends Controller
$accessTokenName = $request->input('name', 'Auth Token');
- if ($accessToken = Auth::user()->createToken($accessTokenName)->accessToken) {
+ if ($accessToken = auth()->user()->createToken($accessTokenName)->accessToken) {
// Get the ID so we can return that with the payload
- $token = DB::table('oauth_access_tokens')->where('user_id', '=', Auth::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
+ $token = DB::table('oauth_access_tokens')->where('user_id', '=', auth()->id())->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
$accessTokenData['id'] = $token->id;
$accessTokenData['token'] = $accessToken;
$accessTokenData['name'] = $accessTokenName;
- return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, 'Personal access token '.$accessTokenName.' created successfully'));
+ return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, trans('account/general.personal_api_keys_success', ['key' => $accessTokenName])));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.'));
@@ -125,17 +122,16 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function deleteApiToken($tokenId) {
+ public function deleteApiToken($tokenId) : Response
+ {
if (!Gate::allows('self.api')) {
abort(403);
}
$token = $this->tokenRepository->findForUser(
- $tokenId, Auth::user()->getAuthIdentifier()
+ $tokenId, auth()->user()->getAuthIdentifier()
);
if (is_null($token)) {
@@ -154,16 +150,15 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function showApiTokens(Request $request) {
+ public function showApiTokens() : JsonResponse
+ {
if (!Gate::allows('self.api')) {
abort(403);
}
- $tokens = $this->tokenRepository->forUser(Auth::user()->getAuthIdentifier());
+ $tokens = $this->tokenRepository->forUser(auth()->user()->getAuthIdentifier());
$token_values = $tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked;
})->values();
diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php
index 7335e7d8e..931886fb2 100644
--- a/app/Http/Controllers/Api/ReportsController.php
+++ b/app/Http/Controllers/Api/ReportsController.php
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\ActionlogsTransformer;
use App\Models\Actionlog;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
class ReportsController extends Controller
{
@@ -14,9 +15,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('reports.view');
@@ -32,14 +32,34 @@ class ReportsController extends Controller
}
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
- $actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id'))
- ->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
+ $actionlogs = $actionlogs->where(function($query) use ($request)
+ {
+ $query->where('item_id', '=', $request->input('item_id'))
+ ->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')))
+ ->orWhere(function($query) use ($request)
+ {
+ $query->where('target_id', '=', $request->input('item_id'))
+ ->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
+ });
+ });
}
if ($request->filled('action_type')) {
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
}
+ if ($request->filled('user_id')) {
+ $actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id'));
+ }
+
+ 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')) {
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
}
@@ -52,18 +72,30 @@ class ReportsController extends Controller
'accept_signature',
'action_type',
'note',
+ 'remote_ip',
+ 'user_agent',
+ 'action_source',
];
+ $total = $actionlogs->count();
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : app('api_offset_value');
+ $offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
- $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
- $total = $actionlogs->count();
- $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
+ switch ($request->input('sort')) {
+ case 'admin':
+ $actionlogs->OrderAdmin($order);
+ break;
+ default:
+ $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
+ $actionlogs = $actionlogs->orderBy($sort, $order);
+ break;
+ }
+
+ $actionlogs = $actionlogs->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php
index a0438ef07..7eb28a481 100644
--- a/app/Http/Controllers/Api/SettingsController.php
+++ b/app/Http/Controllers/Api/SettingsController.php
@@ -9,42 +9,38 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ldap;
use App\Models\Setting;
-use Mail;
-use App\Notifications\SlackTest;
use App\Notifications\MailTest;
-use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\Validator;
-use App\Http\Requests\SlackSettingsRequest;
+use Illuminate\Support\Facades\Validator;
use App\Http\Transformers\LoginAttemptsTransformer;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class SettingsController extends Controller
{
- public function ldaptest()
+ public function ldaptest() : JsonResponse
{
$settings = Setting::getSettings();
if ($settings->ldap_enabled!='1') {
- \Log::debug('LDAP is not enabled cannot test.');
+ Log::debug('LDAP is not enabled cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
}
- \Log::debug('Preparing to test LDAP connection');
+ Log::debug('Preparing to test LDAP connection');
$message = []; //where we collect together test messages
try {
$connection = Ldap::connectToLdap();
try {
$message['bind'] = ['message' => 'Successfully bound to LDAP server.'];
- \Log::debug('attempting to bind to LDAP for LDAP test');
+ Log::debug('attempting to bind to LDAP for LDAP test');
Ldap::bindAdminToLdap($connection);
$message['login'] = [
'message' => 'Successfully connected to LDAP server.',
@@ -75,24 +71,24 @@ class SettingsController extends Controller
return response()->json($message, 200);
} catch (\Exception $e) {
- \Log::debug('Bind failed');
- \Log::debug("Exception was: ".$e->getMessage());
+ Log::debug('Bind failed');
+ Log::debug("Exception was: ".$e->getMessage());
return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
- \Log::debug('Connection failed but we cannot debug it any further on our end.');
+ Log::debug('Connection failed but we cannot debug it any further on our end.');
return response()->json(['message' => $e->getMessage()], 500);
}
}
- public function ldaptestlogin(Request $request)
+ public function ldaptestlogin(Request $request) : JsonResponse
{
if (Setting::getSettings()->ldap_enabled != '1') {
- \Log::debug('LDAP is not enabled. Cannot test.');
+ Log::debug('LDAP is not enabled. Cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
}
@@ -104,39 +100,39 @@ class SettingsController extends Controller
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
- \Log::debug('LDAP Validation test failed.');
+ Log::debug('LDAP Validation test failed.');
$validation_errors = implode(' ',$validator->errors()->all());
return response()->json(['message' => $validator->errors()->all()], 400);
}
- \Log::debug('Preparing to test LDAP login');
+ Log::debug('Preparing to test LDAP login');
try {
$connection = Ldap::connectToLdap();
try {
Ldap::bindAdminToLdap($connection);
- \Log::debug('Attempting to bind to LDAP for LDAP test');
+ Log::debug('Attempting to bind to LDAP for LDAP test');
try {
$ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password'));
if ($ldap_user) {
- \Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
+ Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
}
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
} catch (\Exception $e) {
- \Log::debug('LDAP login failed');
+ Log::debug('LDAP login failed');
return response()->json(['message' => $e->getMessage()], 400);
}
} catch (\Exception $e) {
- \Log::debug('Bind failed');
+ Log::debug('Bind failed');
return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
- \Log::debug('Connection failed');
+ Log::debug('Connection failed');
return response()->json(['message' => $e->getMessage()], 500);
}
@@ -148,9 +144,8 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return Redirect
*/
- public function ajaxTestEmail()
+ public function ajaxTestEmail() : JsonResponse
{
if (!config('app.lock_passwords')) {
try {
@@ -170,9 +165,8 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] []
* @since [v5.0.0]
- * @return Response
*/
- public function purgeBarcodes()
+ public function purgeBarcodes() : JsonResponse
{
$file_count = 0;
$files = Storage::disk('public')->files('barcodes');
@@ -181,19 +175,19 @@ class SettingsController extends Controller
$file_parts = explode('.', $file);
$extension = end($file_parts);
- \Log::debug($extension);
+ Log::debug($extension);
// Only generated barcodes would have a .png file extension
if ($extension == 'png') {
- \Log::debug('Deleting: '.$file);
+ Log::debug('Deleting: '.$file);
try {
Storage::disk('public')->delete($file);
- \Log::debug('Deleting: '.$file);
+ Log::debug('Deleting: '.$file);
$file_count++;
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
}
@@ -211,9 +205,8 @@ class SettingsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0.0]
* @param \Illuminate\Http\Request $request
- * @return array
*/
- public function showLoginAttempts(Request $request)
+ public function showLoginAttempts(Request $request) : array
{
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at'];
@@ -229,7 +222,13 @@ class SettingsController extends Controller
}
- public function listBackups() {
+ /**
+ * Lists backup files
+ *
+ * @author [A. Gianotto]
+ */
+ public function listBackups() : array
+ {
$settings = Setting::getSettings();
$path = 'app/backups';
$backup_files = Storage::files($path);
@@ -249,12 +248,12 @@ class SettingsController extends Controller
'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])),
'modified_value' => $file_timestamp,
'modified_display' => date($settings->date_display_format.' '.$settings->time_display_format, $file_timestamp),
+ 'backup_url' => config('app.url').'/settings/backups/download/'.basename($backup_files[$f]),
];
$count++;
}
-
}
}
@@ -264,15 +263,56 @@ class SettingsController extends Controller
}
- public function downloadBackup($file) {
+ /**
+ * Downloads a backup file.
+ * We use response()->download() here instead of Storage::download() because Storage::download()
+ * exhausts memory on larger files.
+ *
+ * @author [A. Gianotto]
+ */
+ public function downloadBackup($file) : JsonResponse | BinaryFileResponse
+ {
- $path = 'app/backups';
- if (Storage::exists($path.'/'.$file)) {
+ $path = storage_path('app/backups');
+
+ if (Storage::exists('app/backups/'.$file)) {
$headers = ['ContentType' => 'application/zip'];
- return Storage::download($path.'/'.$file, $file, $headers);
+ return response()->download($path.'/'.$file, $file, $headers);
} else {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')));
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
}
}
+
+ /**
+ * Determines and downloads the latest backup
+ *
+ * @author [A. Gianotto]
+ * @since [v6.3.1]
+ */
+ public function downloadLatestBackup() : JsonResponse | BinaryFileResponse
+ {
+
+ $fileData = collect();
+ foreach (Storage::files('app/backups') as $file) {
+ if (pathinfo($file, PATHINFO_EXTENSION) == 'zip') {
+ $fileData->push([
+ 'file' => $file,
+ 'date' => Storage::lastModified($file)
+ ]);
+ }
+ }
+
+ $newest = $fileData->sortByDesc('date')->first();
+ if (Storage::exists($newest['file'])) {
+ $headers = ['ContentType' => 'application/zip'];
+ return response()->download(storage_path($newest['file']), basename($newest['file']), $headers);
+ } else {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
+ }
+
+
+ }
+
+
}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php
index 7c8e260c2..ce61d653f 100644
--- a/app/Http/Controllers/Api/StatuslabelsController.php
+++ b/app/Http/Controllers/Api/StatuslabelsController.php
@@ -8,10 +8,11 @@ use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset;
+use App\Models\Setting;
use App\Models\Statuslabel;
use Illuminate\Http\Request;
use App\Http\Transformers\PieChartTransformer;
-use Illuminate\Support\Arr;
+use Illuminate\Http\JsonResponse;
class StatuslabelsController extends Controller
{
@@ -20,9 +21,8 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', Statuslabel::class);
$allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label'];
@@ -72,9 +72,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending', 'archived');
@@ -108,9 +107,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -126,9 +124,8 @@ class StatuslabelsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -163,9 +160,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -188,13 +184,18 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return array
*/
- public function getAssetCountByStatuslabel()
+ public function getAssetCountByStatuslabel() : array
{
$this->authorize('view', Statuslabel::class);
- $statuslabels = Statuslabel::withCount('assets')->get();
- $total = Array();
+
+ if (Setting::getSettings()->show_archived_in_list == 0 ) {
+ $statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
+ } else {
+ $statuslabels = Statuslabel::withCount('assets')->get();
+ }
+
+ $total = [];
foreach ($statuslabels as $statuslabel) {
@@ -215,9 +216,8 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.11]
- * @return array
*/
- public function getAssetCountByMetaStatus()
+ public function getAssetCountByMetaStatus() : array
{
$this->authorize('view', Statuslabel::class);
@@ -245,9 +245,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function assets(Request $request, $id)
+ public function assets(Request $request, $id) : array
{
$this->authorize('view', Statuslabel::class);
$this->authorize('index', Asset::class);
@@ -281,9 +280,8 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return bool
*/
- public function checkIfDeployable($id)
+ public function checkIfDeployable($id) : string
{
$statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') {
@@ -300,7 +298,7 @@ class StatuslabelsController extends Controller
* @since [v6.1.1]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php
index 3e3d637be..f752f2224 100644
--- a/app/Http/Controllers/Api/SuppliersController.php
+++ b/app/Http/Controllers/Api/SuppliersController.php
@@ -10,6 +10,7 @@ use App\Models\Supplier;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class SuppliersController extends Controller
{
@@ -20,7 +21,7 @@ class SuppliersController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request): array
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['
@@ -114,9 +115,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Supplier::class);
$supplier = new Supplier;
@@ -136,9 +136,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Supplier::class);
$supplier = Supplier::findOrFail($id);
@@ -154,9 +153,8 @@ class SuppliersController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Supplier::class);
$supplier = Supplier::findOrFail($id);
@@ -176,9 +174,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
@@ -209,7 +206,7 @@ class SuppliersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php
index 5a2cd7dcf..9200f80b1 100644
--- a/app/Http/Controllers/Api/UsersController.php
+++ b/app/Http/Controllers/Api/UsersController.php
@@ -11,16 +11,20 @@ use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\UsersTransformer;
+use App\Models\Actionlog;
use App\Models\Asset;
-use App\Models\Company;
+use App\Models\Accessory;
+use App\Models\Consumable;
use App\Models\License;
use App\Models\User;
use App\Notifications\CurrentInventory;
-use Auth;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
-use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Facades\Log;
+use App\Http\Requests\DeleteUserRequest;
+use Illuminate\Http\JsonResponse;
class UsersController extends Controller
{
@@ -30,9 +34,9 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*
- * @return \Illuminate\Http\Response
+ * @return array
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', User::class);
@@ -72,11 +76,16 @@ class UsersController extends Controller
'users.end_date',
'users.vip',
'users.autoassign_licenses',
+ 'users.website',
- ])->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');
+ ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
+ ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count');
+ if ($request->filled('search') != '') {
+ $users = $users->TextSearch($request->input('search'));
+ }
+
if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated'));
}
@@ -121,6 +130,10 @@ class UsersController extends Controller
$users = $users->where('users.country', '=', $request->input('country'));
}
+ if ($request->filled('website')) {
+ $users = $users->where('users.website', '=', $request->input('website'));
+ }
+
if ($request->filled('zip')) {
$users = $users->where('users.zip', '=', $request->input('zip'));
}
@@ -181,21 +194,27 @@ class UsersController extends Controller
$users->has('accessories', '=', $request->input('accessories_count'));
}
+ if ($request->filled('manages_users_count')) {
+ $users->has('manages_users_count', '=', $request->input('manages_users_count'));
+ }
+
+ if ($request->filled('manages_locations_count')) {
+ $users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
+ }
+
if ($request->filled('autoassign_licenses')) {
$users->where('autoassign_licenses', '=', $request->input('autoassign_licenses'));
}
- if ($request->filled('search')) {
- $users = $users->TextSearch($request->input('search'));
+
+ if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
+ $users = $users->onlyTrashed();
+ } elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
+ $users = $users->withTrashed();
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- // 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');
-
-
switch ($request->input('sort')) {
case 'manager':
$users = $users->OrderManager($order);
@@ -229,10 +248,6 @@ class UsersController extends Controller
'jobtitle',
'username',
'employee_num',
- 'assets',
- 'accessories',
- 'consumables',
- 'licenses',
'groups',
'activated',
'created_at',
@@ -243,6 +258,8 @@ class UsersController extends Controller
'licenses_count',
'consumables_count',
'accessories_count',
+ 'manages_users_count',
+ 'manages_locations_count',
'phone',
'address',
'city',
@@ -258,21 +275,20 @@ class UsersController extends Controller
'start_date',
'end_date',
'autoassign_licenses',
+ 'website',
];
- $sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
$users = $users->orderBy($sort, $order);
break;
}
- if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
- $users = $users->onlyTrashed();
- } elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
- $users = $users->withTrashed();
- }
- $users = Company::scopeCompanyables($users);
-
+
+ // Make sure the offset and limit are actually integers and do not exceed system limits
+ $offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
+ $limit = app('api_limit_value');
+
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@@ -286,7 +302,7 @@ class UsersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$users = User::select(
[
@@ -301,8 +317,6 @@ class UsersController extends Controller
]
)->where('show_in_list', '=', '1');
- $users = Company::scopeCompanyables($users);
-
if ($request->filled('search')) {
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search'))
@@ -344,20 +358,20 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(SaveUserRequest $request)
+ public function store(SaveUserRequest $request) : JsonResponse
{
$this->authorize('create', User::class);
$user = new User;
$user->fill($request->all());
+ $user->created_by = auth()->id();
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
- if (! Auth::user()->isSuperUser()) {
+ if (! auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
}
$user->permissions = $permissions_array;
@@ -390,14 +404,18 @@ class UsersController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', User::class);
- $user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id);
- return (new UsersTransformer)->transformUser($user);
+ if ($user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count')->find($id)) {
+ $this->authorize('view', $user);
+ return (new UsersTransformer)->transformUser($user);
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
+
}
@@ -408,89 +426,88 @@ class UsersController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(SaveUserRequest $request, $id)
+ public function update(SaveUserRequest $request, $id) : JsonResponse
{
$this->authorize('update', User::class);
- $user = User::findOrFail($id);
-
- /**
- * This is a janky hack to prevent people from changing admin demo user data on the public demo.
- *
- * The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
- *
- * Thanks, jerks. You are why we can't have nice things. - snipe
- *
- */
+ if ($user = User::find($id)) {
- if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
- }
+ $this->authorize('update', $user);
+
+ /**
+ * This is a janky hack to prevent people from changing admin demo user data on the public demo.
+ * The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
+ * Thanks, jerks. You are why we can't have nice things. - snipe
+ *
+ */
- $user->fill($request->all());
-
- if ($user->id == $request->input('manager_id')) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
- }
-
- if ($request->filled('password')) {
- $user->password = bcrypt($request->input('password'));
- }
-
- // We need to use has() instead of filled()
- // here because we need to overwrite permissions
- // if someone needs to null them out
- if ($request->has('permissions')) {
- $permissions_array = $request->input('permissions');
-
- // Strip out the superuser permission if the API user isn't a superadmin
- if (! Auth::user()->isSuperUser()) {
- unset($permissions_array['superuser']);
+ if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
}
- $user->permissions = $permissions_array;
- }
+ $user->fill($request->all());
- // Update the location of any assets checked out to this user
- Asset::where('assigned_type', User::class)
- ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
+ if ($user->id == $request->input('manager_id')) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
+ }
-
- app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
-
- if ($user->save()) {
+ if ($request->filled('password')) {
+ $user->password = bcrypt($request->input('password'));
+ }
- // Sync group memberships:
- // This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5
- // which changes the behavior of has vs filled.
- // The $request->has method will now return true even if the input value is an empty string or null.
- // A new $request->filled method has was added that provides the previous behavior of the has method.
+ // We need to use has() instead of filled()
+ // here because we need to overwrite permissions
+ // if someone needs to null them out
+ if ($request->has('permissions')) {
+ $permissions_array = $request->input('permissions');
- // Check if the request has groups passed and has a value
- if ($request->filled('groups')) {
- $validator = Validator::make($request->all(), [
- 'groups.*' => 'integer|exists:permission_groups,id',
- ]);
-
- if ($validator->fails()){
- return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
+ // Strip out the individual superuser permission if the API user isn't a superadmin
+ if (!auth()->user()->isSuperUser()) {
+ unset($permissions_array['superuser']);
}
- $user->groups()->sync($request->input('groups'));
- // The groups field has been passed but it is null, so we should blank it out
- } elseif ($request->has('groups')) {
- $user->groups()->sync([]);
+
+ $user->permissions = $permissions_array;
}
- return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
+ // Update the location of any assets checked out to this user
+ Asset::where('assigned_type', User::class)
+ ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
+
+
+ app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
+
+ if ($user->save()) {
+
+ // Check if the request has groups passed and has a value, AND that the user us a superuser
+ if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
+
+ $validator = Validator::make($request->only('groups'), [
+ 'groups.*' => 'integer|exists:permission_groups,id',
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
+ }
+
+ // Sync the groups since the user is a superuser and the groups pass validation
+ $user->groups()->sync($request->input('groups'));
+
+
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
- return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
+
}
/**
@@ -499,45 +516,35 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy(DeleteUserRequest $request, $id) : JsonResponse
{
$this->authorize('delete', User::class);
- $user = User::findOrFail($id);
- $this->authorize('delete', $user);
- if (($user->assets) && ($user->assets->count() > 0)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
- }
+ if ($user = User::withTrashed()->find($id)) {
- if (($user->licenses) && ($user->licenses->count() > 0)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->licenses->count().' license(s) associated with them and cannot be deleted.'));
- }
+ $this->authorize('delete', $user);
- if (($user->accessories) && ($user->accessories->count() > 0)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->accessories->count().' accessories associated with them.'));
- }
+ if ($user->delete()) {
- if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->managedLocations()->count().' locations that they manage.'));
- }
-
- if ($user->delete()) {
-
- // Remove the user's avatar if they have one
- if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
- try {
- Storage::disk('public')->delete('avatars/'.$user->avatar);
- } catch (\Exception $e) {
- \Log::debug($e);
+ // Remove the user's avatar if they have one
+ if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
+ try {
+ Storage::disk('public')->delete('avatars/' . $user->avatar);
+ } catch (\Exception $e) {
+ Log::debug($e);
+ }
}
+
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
}
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
+
}
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')));
+
}
/**
@@ -546,15 +553,41 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return string JSON
*/
- public function assets(Request $request, $id)
+ public function assets(Request $request, $id) : JsonResponse | array
{
$this->authorize('view', User::class);
$this->authorize('view', Asset::class);
- $assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
- return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
+ if ($user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id)) {
+ $this->authorize('view', $user);
+
+ $assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
+
+
+ // Filter on category ID
+ if ($request->filled('category_id')) {
+ $assets = $assets->InCategory($request->input('category_id'));
+ }
+
+
+ // Filter on model ID
+ if ($request->filled('model_id')) {
+
+ $model_ids = $request->input('model_id');
+ if (!is_array($model_ids)) {
+ $model_ids = array($model_ids);
+ }
+ $assets = $assets->InModelList($model_ids);
+ }
+
+ $assets = $assets->get();
+
+ return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
+
}
/**
@@ -564,19 +597,26 @@ class UsersController extends Controller
* @since [v6.0.13]
* @param Request $request
* @param $id
- * @return string JSON
*/
- public function emailAssetList(Request $request, $id)
- {
- $user = User::findOrFail($id);
+ public function emailAssetList(Request $request, $id) : JsonResponse
- if (empty($user->email)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
+ {
+ $this->authorize('update', User::class);
+
+ if ($user = User::find($id)) {
+ $this->authorize('update', $user);
+
+ if (empty($user->email)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
+ }
+
+ $user->notify((new CurrentInventory($user)));
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
}
- $user->notify((new CurrentInventory($user)));
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
+
}
/**
@@ -585,13 +625,13 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return string JSON
*/
- public function consumables(Request $request, $id)
+ public function consumables(Request $request, $id) : array
{
$this->authorize('view', User::class);
$this->authorize('view', Consumable::class);
$user = User::findOrFail($id);
+ $this->authorize('view', $user);
$consumables = $user->consumables;
return (new ConsumablesTransformer)->transformConsumables($consumables, $consumables->count(), $request);
}
@@ -602,12 +642,12 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.6.14]
* @param $userId
- * @return string JSON
*/
- public function accessories($id)
+ public function accessories($id) : array
{
$this->authorize('view', User::class);
$user = User::findOrFail($id);
+ $this->authorize('view', $user);
$this->authorize('view', Accessory::class);
$accessories = $user->accessories;
@@ -620,14 +660,14 @@ class UsersController extends Controller
* @author [N. Mathar] []
* @since [v5.0]
* @param $userId
- * @return string JSON
*/
- public function licenses($id)
+ public function licenses($id) : JsonResponse | array
{
$this->authorize('view', User::class);
$this->authorize('view', License::class);
if ($user = User::where('id', $id)->withTrashed()->first()) {
+ $this->authorize('update', $user);
$licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
@@ -642,18 +682,28 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return string JSON
*/
- public function postTwoFactorReset(Request $request)
+ public function postTwoFactorReset(Request $request) : JsonResponse
{
$this->authorize('update', User::class);
if ($request->filled('id')) {
try {
$user = User::find($request->get('id'));
+ $this->authorize('update', $user);
$user->two_factor_secret = null;
$user->two_factor_enrolled = 0;
- $user->save();
+ $user->saveQuietly();
+
+ // Log the reset
+ $logaction = new Actionlog();
+ $logaction->target_type = User::class;
+ $logaction->target_id = $user->id;
+ $logaction->item_type = User::class;
+ $logaction->item_id = $user->id;
+ $logaction->created_at = date('Y-m-d H:i:s');
+ $logaction->user_id = auth()->id();
+ $logaction->logaction('2FA reset');
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
} catch (\Exception $e) {
@@ -671,9 +721,8 @@ class UsersController extends Controller
* @author [Juan Font] []
* @since [v4.4.2]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function getCurrentUserInfo(Request $request)
+ public function getCurrentUserInfo(Request $request) : array
{
return (new UsersTransformer)->transformUser($request->user());
}
@@ -684,21 +733,34 @@ class UsersController extends Controller
* @author [E. Taylor] []
* @param int $userId
* @since [v6.0.0]
- * @return JsonResponse
*/
- public function restore($userId = null)
+ public function restore($userId) : JsonResponse
{
- // 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();
+ $this->authorize('delete', User::class);
+
+ 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()->id();
+ $logaction->logaction('restore');
+
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200);
+ }
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
}
-
- $id = $userId;
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))), 200);
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
+
}
}
diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php
index dc6bc8434..02be1e606 100644
--- a/app/Http/Controllers/AssetMaintenancesController.php
+++ b/app/Http/Controllers/AssetMaintenancesController.php
@@ -2,17 +2,14 @@
namespace App\Http\Controllers;
-use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
-use Auth;
+use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Http\Request;
-use Slack;
-use Str;
-use TCPDF;
-use View;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
/**
* This controller handles all actions related to Asset Maintenance for
@@ -29,9 +26,8 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return View
*/
- private static function getInsufficientPermissionsRedirect()
+ private static function getInsufficientPermissionsRedirect(): RedirectResponse
{
return redirect()->route('maintenances.index')
->with('error', trans('general.insufficient_permissions'));
@@ -46,9 +42,8 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return View
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', Asset::class);
return view('asset_maintenances/index');
@@ -63,7 +58,7 @@ class AssetMaintenancesController extends Controller
* @since [v1.8]
* @return mixed
*/
- public function create()
+ public function create() : View
{
$this->authorize('update', Asset::class);
$asset = null;
@@ -92,9 +87,8 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function store(Request $request)
+ public function store(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
// create a new model instance
@@ -144,42 +138,26 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function edit($assetMaintenanceId = null)
+ public function edit($assetMaintenanceId = null) : View | RedirectResponse
{
+ $this->authorize('update', Asset::class);
+ // Check if the asset maintenance exists
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
- // Redirect to the improvement management page
- return redirect()->route('maintenances.index')
- ->with('error', trans('admin/asset_maintenances/message.not_found'));
- } elseif (! $assetMaintenance->asset) {
- return redirect()->route('maintenances.index')
- ->with('error', 'The asset associated with this maintenance does not exist.');
+ // Redirect to the asset maintenance management page
+ return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
+ } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
+ // Redirect to the asset maintenance management page
+ return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
- if ($assetMaintenance->completion_date == '0000-00-00') {
- $assetMaintenance->completion_date = null;
- }
-
- if ($assetMaintenance->start_date == '0000-00-00') {
- $assetMaintenance->start_date = null;
- }
-
- if ($assetMaintenance->cost == '0.00') {
- $assetMaintenance->cost = null;
- }
-
// Prepare Improvement Type List
- $assetMaintenanceType = [
- '' => 'Select an improvement type',
- ] + AssetMaintenance::getImprovementOptions();
+ $assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions();
- // Get Supplier List
- // Render the view
return view('asset_maintenances/edit')
->with('selectedAsset', null)
->with('assetMaintenanceType', $assetMaintenanceType)
@@ -193,18 +171,19 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @param Request $request
* @param int $assetMaintenanceId
- * @return mixed
* @version v1.0
* @since [v1.8]
*/
- public function update(Request $request, $assetMaintenanceId = null)
+ public function update(Request $request, $assetMaintenanceId = null) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
- return redirect()->route('maintenances.index')
- ->with('error', trans('admin/asset_maintenances/message.not_found'));
+ return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
+ } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
+ // Redirect to the asset maintenance management page
+ return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -263,9 +242,8 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function destroy($assetMaintenanceId)
+ public function destroy($assetMaintenanceId) : RedirectResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
@@ -292,9 +270,8 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return View
*/
- public function show($assetMaintenanceId)
+ public function show($assetMaintenanceId) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php
index 1783b3392..4421829f4 100755
--- a/app/Http/Controllers/AssetModelsController.php
+++ b/app/Http/Controllers/AssetModelsController.php
@@ -4,16 +4,21 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
+use App\Http\Requests\StoreAssetModelRequest;
+use App\Models\Actionlog;
use App\Models\AssetModel;
+use App\Models\CustomField;
+use App\Models\SnipeModel;
+use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Input;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Validator;
-use Redirect;
-use Request;
-use Storage;
-use Symfony\Component\HttpFoundation\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+
/**
* This class controls all actions related to asset models for
@@ -30,10 +35,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('index', AssetModel::class);
@@ -45,10 +48,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', AssetModel::class);
@@ -63,16 +64,12 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAssetModelRequest $request) : RedirectResponse
{
$this->authorize('create', AssetModel::class);
- // Create a new asset model
$model = new AssetModel;
- // Save the model data
$model->eol = $request->input('eol');
$model->depreciation_id = $request->input('depreciation_id');
$model->name = $request->input('name');
@@ -82,15 +79,14 @@ class AssetModelsController extends Controller
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
$model->user_id = Auth::id();
- $model->requestable = Request::has('requestable');
+ $model->requestable = $request->has('requestable');
if ($request->input('fieldset_id') != '') {
- $model->fieldset_id = e($request->input('fieldset_id'));
+ $model->fieldset_id = $request->input('fieldset_id');
}
$model = $request->handleImages($model);
- // Was it created?
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
@@ -98,7 +94,6 @@ class AssetModelsController extends Controller
}
}
- // Redirect to the new model page
return redirect()->route('models.index')->with('success', trans('admin/models/message.create.success'));
}
@@ -111,18 +106,14 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($modelId = null)
+ public function edit($modelId = null) : View | RedirectResponse
{
$this->authorize('update', AssetModel::class);
if ($item = AssetModel::find($modelId)) {
$category_type = 'asset';
- $view = View::make('models/edit', compact('item', 'category_type'));
- $view->with('depreciation_list', Helper::depreciationList());
+ return view('models/edit', compact('item', 'category_type'))->with('depreciation_list', Helper::depreciationList());
- return $view;
}
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
@@ -137,15 +128,14 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @param ImageUploadRequest $request
* @param int $modelId
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $modelId = null)
+ public function update(StoreAssetModelRequest $request, $modelId) : RedirectResponse
{
$this->authorize('update', AssetModel::class);
- // Check if the model exists
+
if (is_null($model = AssetModel::find($modelId))) {
- // Redirect to the models management page
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
@@ -163,20 +153,14 @@ class AssetModelsController extends Controller
$this->removeCustomFieldsDefaultValues($model);
- if ($request->input('fieldset_id') == '') {
- $model->fieldset_id = null;
- } else {
- $model->fieldset_id = $request->input('fieldset_id');
+ $model->fieldset_id = $request->input('fieldset_id');
- if ($this->shouldAddDefaultValues($request->input())) {
- if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
- return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
- }
+ if ($this->shouldAddDefaultValues($request->input())) {
+ if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
+ return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
}
}
-
-
-
+
if ($model->save()) {
if ($model->wasChanged('eol')) {
if ($model->eol > 0) {
@@ -201,15 +185,13 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($modelId)
+ public function destroy($modelId) : RedirectResponse
{
$this->authorize('delete', AssetModel::class);
// Check if the model exists
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) {
@@ -221,7 +203,7 @@ class AssetModelsController extends Controller
try {
Storage::disk('public')->delete('models/'.$model->image);
} catch (\Exception $e) {
- \Log::info($e);
+ Log::info($e);
}
}
@@ -237,22 +219,40 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @param int $modelId
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @param int $id
*/
- public function getRestore($modelId = null)
+ public function getRestore($id) : RedirectResponse
{
$this->authorize('create', AssetModel::class);
- // Get user information
- $model = AssetModel::withTrashed()->find($modelId);
- if (isset($model->id)) {
- $model->restore();
+ if ($model = AssetModel::withTrashed()->find($id)) {
- 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()->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'));
}
@@ -263,10 +263,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($modelId = null)
+ public function show($modelId = null) : View | RedirectResponse
{
$this->authorize('view', AssetModel::class);
$model = AssetModel::withTrashed()->find($modelId);
@@ -284,9 +282,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
*/
- public function getClone($modelId = null)
+ public function getClone($modelId = null) : View | RedirectResponse
{
$this->authorize('create', AssetModel::class);
// Check if the model exists
@@ -312,9 +309,8 @@ class AssetModelsController extends Controller
* @author [B. Wetherington] []
* @since [v2.0]
* @param int $modelId
- * @return View
*/
- public function getCustomFields($modelId)
+ public function getCustomFields($modelId) : View
{
return view('models.custom_fields_form')->with('model', AssetModel::find($modelId));
}
@@ -326,9 +322,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.7]
- * @return \Illuminate\Contracts\View\View
*/
- public function postBulkEdit(Request $request)
+ public function postBulkEdit(Request $request) : View | RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -370,9 +365,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.7]
- * @return \Illuminate\Contracts\View\View
*/
- public function postBulkEditSave(Request $request)
+ public function postBulkEditSave(Request $request) : RedirectResponse
{
$models_raw_array = $request->input('ids');
$update_array = [];
@@ -410,9 +404,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return Redirect
*/
- public function postBulkDelete(Request $request)
+ public function postBulkDelete(Request $request) : RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -423,7 +416,6 @@ class AssetModelsController extends Controller
$del_count = 0;
foreach ($models as $model) {
- \Log::debug($model->id);
if ($model->assets_count > 0) {
$del_error_count++;
@@ -433,8 +425,6 @@ class AssetModelsController extends Controller
}
}
- \Log::debug($del_count);
- \Log::debug($del_error_count);
if ($del_error_count == 0) {
return redirect()->route('models.index')
@@ -454,9 +444,8 @@ class AssetModelsController extends Controller
* any default values were entered into the form.
*
* @param array $input
- * @return bool
*/
- private function shouldAddDefaultValues(array $input)
+ private function shouldAddDefaultValues(array $input) : bool
{
return ! empty($input['add_default_values'])
&& ! empty($input['default_values'])
@@ -468,28 +457,27 @@ class AssetModelsController extends Controller
*
* @param AssetModel $model
* @param array $defaultValues
- * @return void
*/
- private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
+ private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, array $defaultValues): bool
{
$data = array();
foreach ($defaultValues as $customFieldId => $defaultValue) {
- $customField = \App\Models\CustomField::find($customFieldId);
+ $customField = CustomField::find($customFieldId);
$data[$customField->db_column] = $defaultValue;
}
- $fieldsets = $model->fieldset->validation_rules();
+ $allRules = $model->fieldset->validation_rules();
$rules = array();
- foreach ($fieldsets as $fieldset => $validation){
+ foreach ($allRules as $field => $validation) {
// If the field is marked as required, eliminate the rule so it doesn't interfere with the default values
// (we are at model level, the rule still applies when creating a new asset using this model)
$index = array_search('required', $validation);
if ($index !== false){
$validation[$index] = 'nullable';
}
- $rules[$fieldset] = $validation;
+ $rules[$field] = $validation;
}
$validator = Validator::make($data, $rules);
@@ -511,9 +499,8 @@ class AssetModelsController extends Controller
/**
* Removes all default values
*
- * @return void
*/
- private function removeCustomFieldsDefaultValues(AssetModel $model)
+ private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void
{
$model->defaultValues()->detach();
}
diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php
index 9889cd29c..c905282cc 100644
--- a/app/Http/Controllers/AssetModelsFilesController.php
+++ b/app/Http/Controllers/AssetModelsFilesController.php
@@ -3,26 +3,28 @@
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
-use Illuminate\Support\Facades\Response;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
-use enshrined\svgSanitize\Sanitizer;
+use \Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetModelsFilesController extends Controller
{
/**
* Upload a file to the server.
*
- * @author [A. Gianotto] []
- * @param AssetFileRequest $request
+ * @param UploadFileRequest $request
* @param int $modelId
- * @return Redirect
- * @since [v1.0]
+ * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
+ *@since [v1.0]
+ * @author [A. Gianotto] []
*/
- public function store(AssetFileRequest $request, $modelId = null)
+ public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,29 +39,9 @@ class AssetModelsFilesController extends Controller
foreach ($request->file('file') as $file) {
- $extension = $file->getClientOriginalExtension();
- $file_name = 'model-'.$model->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
+ $file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
- // Check for SVG and sanitize it
- if ($extension=='svg') {
- \Log::debug('This is an SVG');
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/assetmodels/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
- } else {
- Storage::put('private_uploads/assetmodels/'.$file_name, file_get_contents($file));
- }
-
-
- $model->logUpload($file_name, e($request->get('notes')));
+ $model->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->with('success', trans('general.file_upload_success'));
@@ -75,10 +57,8 @@ class AssetModelsFilesController extends Controller
* @param int $modelId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($modelId = null, $fileId = null)
+ public function show($modelId = null, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$model = AssetModel::find($modelId);
// the asset is valid
@@ -91,8 +71,6 @@ class AssetModelsFilesController extends Controller
}
$file = 'private_uploads/assetmodels/'.$log->filename;
- \Log::debug('Checking for '.$file);
-
if (! Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
@@ -124,10 +102,8 @@ class AssetModelsFilesController extends Controller
* @param int $modelId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($modelId = null, $fileId = null)
+ public function destroy($modelId = null, $fileId = null) : RedirectResponse
{
$model = AssetModel::find($modelId);
$this->authorize('update', $model);
diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php
index 4fe10e895..f84a468a6 100644
--- a/app/Http/Controllers/Assets/AssetCheckinController.php
+++ b/app/Http/Controllers/Assets/AssetCheckinController.php
@@ -6,26 +6,28 @@ use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest;
+use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
+use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Redirect;
-use Illuminate\Support\Facades\View;
+use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AssetCheckinController extends Controller
{
+ use MigratesLegacyAssetLocations;
+
/**
* Returns a view that presents a form to check an asset back into inventory.
*
* @author [A. Gianotto] []
* @param int $assetId
* @param string $backto
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function create($assetId, $backto = null)
+ public function create($assetId, $backto = null) : View | RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
@@ -35,7 +37,17 @@ class AssetCheckinController extends Controller
$this->authorize('checkin', $asset);
- return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto);
+ // This asset is already checked in, redirect
+
+ if (is_null($asset->assignedTo)) {
+ return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
+ }
+
+ if (!$asset->model) {
+ return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ }
+
+ return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
}
/**
@@ -45,11 +57,9 @@ class AssetCheckinController extends Controller
* @param AssetCheckinRequest $request
* @param int $assetId
* @param null $backto
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
+ public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) : RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
@@ -60,6 +70,11 @@ class AssetCheckinController extends Controller
if (is_null($target = $asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
+
+ if (!$asset->model) {
+ return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ }
+
$this->authorize('checkin', $asset);
if ($asset->assignedType() == Asset::USER) {
@@ -67,11 +82,8 @@ class AssetCheckinController extends Controller
}
$asset->expected_checkin = null;
- $asset->last_checkout = null;
$asset->last_checkin = now();
- $asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
- $asset->assigned_type = null;
$asset->accepted = null;
$asset->name = $request->get('name');
@@ -79,29 +91,12 @@ class AssetCheckinController extends Controller
$asset->status_id = e($request->get('status_id'));
}
- // This is just meant to correct legacy issues where some user data would have 0
- // as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
- // rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
- // people (and their data) in the long run
-
- if ($asset->rtd_location_id == '0') {
- \Log::debug('Manually override the RTD location IDs');
- \Log::debug('Original RTD Location ID: '.$asset->rtd_location_id);
- $asset->rtd_location_id = '';
- \Log::debug('New RTD Location ID: '.$asset->rtd_location_id);
- }
-
- if ($asset->location_id == '0') {
- \Log::debug('Manually override the location IDs');
- \Log::debug('Original Location ID: '.$asset->location_id);
- $asset->location_id = '';
- \Log::debug('New Location ID: '.$asset->location_id);
- }
+ $this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
- \Log::debug('NEW Location ID: '.$request->get('location_id'));
+ Log::debug('NEW Location ID: '.$request->get('location_id'));
$asset->location_id = $request->get('location_id');
if ($request->get('update_default_location') == 0){
@@ -117,12 +112,9 @@ class AssetCheckinController extends Controller
$checkin_at = $request->get('checkin_at');
}
- if(!empty($asset->licenseseats->all())){
- foreach ($asset->licenseseats as $seat){
- $seat->assigned_to = null;
- $seat->save();
- }
- }
+ $asset->licenseseats->each(function (LicenseSeat $seat) {
+ $seat->update(['assigned_to' => null]);
+ });
// Get all pending Acceptances for this asset and delete them
$acceptances = CheckoutAcceptance::pending()->whereHasMorph('checkoutable',
@@ -134,15 +126,12 @@ class AssetCheckinController extends Controller
$acceptance->delete();
});
- // Was the asset updated?
+ session()->put('redirect_option', $request->get('redirect_option'));
+
if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
- if ((isset($user)) && ($backto == 'user')) {
- return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
- }
-
- return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkin.success'));
+ event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
+ return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success'));
}
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php
index 1fdd0a0cc..05b766916 100644
--- a/app/Http/Controllers/Assets/AssetCheckoutController.php
+++ b/app/Http/Controllers/Assets/AssetCheckoutController.php
@@ -9,7 +9,9 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\Asset;
use Illuminate\Database\Eloquent\ModelNotFoundException;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Session;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AssetCheckoutController extends Controller
{
@@ -22,9 +24,9 @@ class AssetCheckoutController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function create($assetId)
+ public function create($assetId) : View | RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
@@ -33,11 +35,17 @@ class AssetCheckoutController extends Controller
$this->authorize('checkout', $asset);
+ if (!$asset->model) {
+ return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ }
+
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'))
- ->with('statusLabel_list', Helper::deployableStatusLabelList());
+ ->with('statusLabel_list', Helper::deployableStatusLabelList())
+ ->with('table_name', 'Assets');
}
+
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
}
@@ -46,11 +54,9 @@ class AssetCheckoutController extends Controller
*
* @author [A. Gianotto] []
* @param AssetCheckoutRequest $request
- * @param int $assetId
- * @return Redirect
* @since [v1.0]
*/
- public function store(AssetCheckoutRequest $request, $assetId)
+ public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
{
try {
// Check if the asset exists
@@ -60,9 +66,14 @@ class AssetCheckoutController extends Controller
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
}
$this->authorize('checkout', $asset);
- $admin = Auth::user();
- $target = $this->determineCheckoutTarget($asset);
+ if (!$asset->model) {
+ return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ }
+
+ $admin = auth()->user();
+
+ $target = $this->determineCheckoutTarget();
$asset = $this->updateAssetLocation($asset, $target);
@@ -97,11 +108,13 @@ class AssetCheckoutController extends Controller
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
}
}
-
- if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
- return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
- }
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
+ if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
+ return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
+ ->with('success', trans('admin/hardware/message.checkout.success'));
+ }
// Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
} catch (ModelNotFoundException $e) {
diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php
index 610705c60..b5a04759b 100644
--- a/app/Http/Controllers/Assets/AssetFilesController.php
+++ b/app/Http/Controllers/Assets/AssetFilesController.php
@@ -4,26 +4,29 @@ namespace App\Http\Controllers\Assets;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
-use Illuminate\Support\Facades\Response;
+use \Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
-use enshrined\svgSanitize\Sanitizer;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
- * @author [A. Gianotto] []
- * @param AssetFileRequest $request
+ * @param UploadFileRequest $request
* @param int $assetId
- * @return Redirect
- * @since [v1.0]
+ * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
+ *@since [v1.0]
+ * @author [A. Gianotto] []
*/
- public function store(AssetFileRequest $request, $assetId = null)
+ public function store(UploadFileRequest $request, $assetId = null) : RedirectResponse
{
if (! $asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,30 +40,9 @@ class AssetFilesController extends Controller
}
foreach ($request->file('file') as $file) {
-
- $extension = $file->getClientOriginalExtension();
- $file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
-
- // Check for SVG and sanitize it
- if ($extension=='svg') {
- \Log::debug('This is an SVG');
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/assets/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
- } else {
- Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
- }
-
+ $file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
- $asset->logUpload($file_name, e($request->get('notes')));
+ $asset->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
@@ -76,10 +58,8 @@ class AssetFilesController extends Controller
* @param int $assetId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($assetId = null, $fileId = null)
+ public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
$asset = Asset::find($assetId);
// the asset is valid
@@ -92,7 +72,6 @@ class AssetFilesController extends Controller
}
$file = 'private_uploads/assets/'.$log->filename;
- \Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
@@ -128,10 +107,8 @@ class AssetFilesController extends Controller
* @param int $assetId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($assetId = null, $fileId = null)
+ public function destroy($assetId = null, $fileId = null) : RedirectResponse
{
$asset = Asset::find($assetId);
$this->authorize('update', $asset);
@@ -154,7 +131,6 @@ class AssetFilesController extends Controller
->with('success', trans('admin/hardware/message.deletefile.success'));
}
- // Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
}
diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php
index 28d7906cd..75646e726 100755
--- a/app/Http/Controllers/Assets/AssetsController.php
+++ b/app/Http/Controllers/Assets/AssetsController.php
@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
+use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Log;
use App\Models\Asset;
use App\Models\AssetModel;
@@ -19,14 +20,16 @@ use Illuminate\Support\Facades\Auth;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;
-use Illuminate\Support\Facades\Redirect;
+use Illuminate\Http\Response;
+use Illuminate\Contracts\View\View;
+use Illuminate\Http\RedirectResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls all actions related to assets for
@@ -54,10 +57,8 @@ class AssetsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @param Request $request
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index(Request $request)
+ public function index(Request $request) : View
{
$this->authorize('index', Asset::class);
$company = Company::find($request->input('company_id'));
@@ -71,13 +72,12 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param Request $request
- * @return View
* @internal param int $model_id
*/
- public function create(Request $request)
+ public function create(Request $request) : View
{
$this->authorize('create', Asset::class);
- $view = View::make('hardware/edit')
+ $view = view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('item', new Asset)
->with('statuslabel_types', Helper::statusTypeList());
@@ -95,12 +95,15 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return Redirect
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize(Asset::class);
+ // There are a lot more rules to add here but prevents
+ // errors around `asset_tags` not being present below.
+ $this->validate($request, ['asset_tags' => ['required', 'array']]);
+
// Handle asset tags - there could be one, or potentially many.
// This is only necessary on create, not update, since bulk editing is handled
// differently
@@ -130,9 +133,6 @@ class AssetsController extends Controller
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->user_id = Auth::id();
- $asset->archived = '0';
- $asset->physical = '1';
- $asset->depreciate = '0';
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
@@ -148,7 +148,8 @@ class AssetsController extends Controller
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
}
- if ($asset->assigned_to == '') {
+ // Set location_id to rtd_location_id ONLY if the asset isn't being checked out
+ if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}
@@ -195,7 +196,7 @@ class AssetsController extends Controller
}
if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
$success = true;
@@ -203,9 +204,13 @@ class AssetsController extends Controller
}
}
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
+
if ($success) {
- return redirect()->route('hardware.index')
- ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => $asset->asset_tag]));
+
+ return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
+ ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
}
@@ -213,11 +218,6 @@ class AssetsController extends Controller
return redirect()->back()->withInput()->withErrors($asset->getErrors());
}
- public function getOptionCookie(Request $request){
- $value = $request->cookie('optional_info');
- echo $value;
- return $value;
- }
/**
* Returns a view that presents a form to edit an existing asset.
@@ -225,9 +225,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function edit($assetId = null)
+ public function edit($assetId = null) : View | RedirectResponse
{
if (! $item = Asset::find($assetId)) {
// Redirect to the asset management page with error
@@ -248,9 +248,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function show($assetId = null)
+ public function show($assetId = null) : View | RedirectResponse
{
$asset = Asset::withTrashed()->find($assetId);
$this->authorize('view', $asset);
@@ -288,12 +288,12 @@ class AssetsController extends Controller
* Validate and process asset edit form.
*
* @param int $assetId
- * @return \Illuminate\Http\RedirectResponse|Redirect
- *@since [v1.0]
+ * @since [v1.0]
* @author [A. Gianotto] []
*/
- public function update(ImageUploadRequest $request, $assetId = null)
+ public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse
{
+
// Check if the asset exists
if (! $asset = Asset::find($assetId)) {
// Redirect to the asset management page with error
@@ -304,7 +304,8 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = $request->input('purchase_cost', null);
- $asset->purchase_date = $request->input('purchase_date', null);
+ $asset->purchase_date = $request->input('purchase_date', null);
+ $asset->next_audit_date = $request->input('next_audit_date', null);
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
$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');
@@ -335,7 +336,7 @@ class AssetsController extends Controller
$status = Statuslabel::find($asset->status_id);
- if($status->archived){
+ if ($status && $status->archived) {
$asset->assigned_to = null;
}
@@ -354,16 +355,27 @@ class AssetsController extends Controller
}
// Update the asset data
- $asset_tag = $request->input('asset_tags');
+
$serial = $request->input('serials');
+ $asset->serial = $request->input('serials');
+
+ if (is_array($request->input('serials'))) {
+ $asset->serial = $serial[1];
+ }
+
$asset->name = $request->input('name');
- $asset->serial = $serial[1];
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
- $asset->asset_tag = $asset_tag[1];
+
+ $asset_tags = $request->input('asset_tags');
+ $asset->asset_tag = $request->input('asset_tags');
+
+ if (is_array($request->input('asset_tags'))) {
+ $asset->asset_tag = $asset_tags[1];
+ }
+
$asset->notes = $request->input('notes');
- $asset->physical = '1';
$asset = $request->handleImages($asset);
@@ -374,6 +386,7 @@ class AssetsController extends Controller
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
+
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
@@ -392,9 +405,10 @@ class AssetsController extends Controller
}
}
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->save()) {
- return redirect()->route('hardware.show', $assetId)
+ return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets'))
->with('success', trans('admin/hardware/message.update.success'));
}
@@ -407,9 +421,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return Redirect
*/
- public function destroy($assetId)
+ public function destroy($assetId) : RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
@@ -441,9 +454,8 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return Redirect
*/
- public function getAssetBySerial(Request $request)
+ public function getAssetBySerial(Request $request) : RedirectResponse
{
$topsearch = ($request->get('topsearch')=="true");
@@ -459,16 +471,23 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
- public function getAssetByTag(Request $request, $tag=null)
+ public function getAssetByTag(Request $request, $tag=null) : RedirectResponse
{
$tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
- if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
+ // Search for an exact and unique asset tag match
+ $assets = Asset::where('asset_tag', '=', $tag);
+
+ // If not a unique result, redirect to the index view
+ if ($assets->count() != 1) {
+ return redirect()->route('hardware.index')
+ ->with('search', $tag)
+ ->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ]));
}
+ $asset = $assets->first();
$this->authorize('view', $asset);
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
@@ -481,9 +500,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return Response
*/
- public function getQrCode($assetId = null)
+ public function getQrCode($assetId = null) : Response | BinaryFileResponse | string | bool
{
$settings = Setting::getSettings();
@@ -510,6 +528,7 @@ class AssetsController extends Controller
return 'That asset is invalid';
}
+ return false;
}
/**
@@ -523,31 +542,33 @@ class AssetsController extends Controller
public function getBarCode($assetId = null)
{
$settings = Setting::getSettings();
- $asset = Asset::find($assetId);
- $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
+ if ($asset = Asset::withTrashed()->find($assetId)) {
+ $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
- if (isset($asset->id, $asset->asset_tag)) {
- if (file_exists($barcode_file)) {
- $header = ['Content-type' => 'image/png'];
+ if (isset($asset->id, $asset->asset_tag)) {
+ if (file_exists($barcode_file)) {
+ $header = ['Content-type' => 'image/png'];
- return response()->file($barcode_file, $header);
- } else {
- // Calculate barcode width in pixel based on label width (inch)
- $barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
+ return response()->file($barcode_file, $header);
+ } else {
+ // Calculate barcode width in pixel based on label width (inch)
+ $barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
- $barcode = new \Com\Tecnick\Barcode\Barcode();
- try {
- $barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
- file_put_contents($barcode_file, $barcode_obj->getPngData());
+ $barcode = new \Com\Tecnick\Barcode\Barcode();
+ try {
+ $barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
+ file_put_contents($barcode_file, $barcode_obj->getPngData());
- return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
- } catch (\Exception $e) {
- Log::debug('The barcode format is invalid.');
+ return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
+ } catch (\Exception $e) {
+ 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');
+ }
}
}
}
+ return null;
}
/**
@@ -555,7 +576,7 @@ class AssetsController extends Controller
*
* @author [L. Swartzendruber] [
* @param int $assetId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getLabel($assetId = null)
{
@@ -579,28 +600,22 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function getClone($assetId = null)
+ public function getClone(Asset $asset)
{
- // Check if the asset exists
- if (is_null($asset_to_clone = Asset::find($assetId))) {
- // Redirect to the asset management page
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
-
- $this->authorize('create', $asset_to_clone);
-
- $asset = clone $asset_to_clone;
- $asset->id = null;
- $asset->asset_tag = '';
- $asset->serial = '';
- $asset->assigned_to = '';
+ $this->authorize('create', $asset);
+ $cloned = clone $asset;
+ $cloned->id = null;
+ $cloned->asset_tag = '';
+ $cloned->serial = '';
+ $cloned->assigned_to = '';
+ $cloned->deleted_at = '';
return view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList())
- ->with('item', $asset);
+ ->with('item', $cloned);
}
/**
@@ -608,7 +623,7 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getImportHistory()
{
@@ -630,7 +645,7 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.3]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function postImportHistory(Request $request)
{
@@ -724,8 +739,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([
'item_id' => $asset->id,
'item_type' => Asset::class,
- 'user_id' => Auth::user()->id,
- 'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer',
+ 'user_id' => auth()->id(),
+ 'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer',
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
'target_type' => User::class,
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
@@ -736,11 +751,11 @@ class AssetsController extends Controller
if ($isCheckinHeaderExplicit) {
- //if checkin date header exists, assume that empty or future date is still checked out
- //if checkin is before todays date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items is checked out
+ // if checkin date header exists, assume that empty or future date is still checked out
+ // if checkin is before today's date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items are checked out
- if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date))
- ) {
+ if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date)))
+ {
//only do this if item is checked out
$asset->assigned_to = $user->id;
$asset->assigned_type = User::class;
@@ -752,8 +767,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
'item_type' => Asset::class,
- 'user_id' => Auth::user()->id,
- 'note' => 'Checkin imported by '.Auth::user()->present()->fullName().' from history importer',
+ 'user_id' => auth()->id(),
+ 'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer',
'target_id' => null,
'created_at' => $checkin_date,
'action_type' => 'checkin',
@@ -790,25 +805,28 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getRestore($assetId = null)
{
- // Get asset information
- $asset = Asset::withTrashed()->find($assetId);
- $this->authorize('delete', $asset);
- if (isset($asset->id)) {
- // Restore the asset
- Asset::withTrashed()->where('id', $assetId)->restore();
+ if ($asset = Asset::withTrashed()->find($assetId)) {
+ $this->authorize('delete', $asset);
- $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');
+ if ($asset->deleted_at == '') {
+ return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset')]));
+ }
- 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'));
@@ -826,7 +844,7 @@ class AssetsController extends Controller
{
$this->authorize('checkin', Asset::class);
- return view('hardware/quickscan-checkin');
+ return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
public function audit($id)
@@ -846,15 +864,15 @@ class AssetsController extends Controller
return view('hardware/audit-due');
}
- public function overdueForAudit()
+ public function dueForCheckin()
{
- $this->authorize('audit', Asset::class);
+ $this->authorize('checkin', Asset::class);
- return view('hardware/audit-overdue');
+ return view('hardware/checkin-due');
}
- public function auditStore(Request $request, $id)
+ public function auditStore(UploadFileRequest $request, $id)
{
$this->authorize('audit', Asset::class);
@@ -871,7 +889,21 @@ class AssetsController extends Controller
$asset = Asset::findOrFail($id);
- // We don't want to log this as a normal update, so let's bypass that
+ /**
+ * Even though we do a save() further down, we don't want to log this as a "normal" asset update,
+ * which would trigger the Asset Observer and would log an asset *update* log entry (because the
+ * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
+ * the audit log entry we're creating through this controller.
+ *
+ * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
+ * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
+ * will bypass normal model-level validation that's usually handled at the observer )
+ *
+ * We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
+ * which manually invokes Watson Validating to make sure the asset's model is valid.
+ *
+ * @see \App\Observers\AssetObserver::updating()
+ */
$asset->unsetEventDispatcher();
$asset->next_audit_date = $request->input('next_audit_date');
@@ -880,29 +912,27 @@ class AssetsController extends Controller
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location') == '1') {
- Log::debug('update location in audit');
$asset->location_id = $request->input('location_id');
}
+
+ /**
+ * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
+ * We have to invoke this manually because of the unsetEventDispatcher() above.)
+ */
+ if ($asset->isValid() && $asset->save()) {
- if ($asset->save()) {
- $file_name = '';
- // Upload an image, if attached
+ $file_name = null;
+ // Create the image (if one was chosen.)
if ($request->hasFile('image')) {
- $path = 'private_uploads/audits';
- if (! Storage::exists($path)) {
- Storage::makeDirectory($path, 775);
- }
- $upload = $image = $request->file('image');
- $ext = $image->getClientOriginalExtension();
- $file_name = 'audit-'.str_random(18).'.'.$ext;
- Storage::putFileAs($path, $upload, $file_name);
+ $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
}
-
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
}
+
+ return redirect()->back()->withInput()->withErrors($asset->getErrors());
}
public function getRequestedIndex($user_id = null)
diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php
index 45ca5bab7..d58edbaca 100644
--- a/app/Http/Controllers/Assets/BulkAssetsController.php
+++ b/app/Http/Controllers/Assets/BulkAssetsController.php
@@ -2,19 +2,24 @@
namespace App\Http\Controllers\Assets;
-use App\Models\Actionlog;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\Asset;
+use App\Models\AssetModel;
+use App\Models\Statuslabel;
use App\Models\Setting;
use App\View\Label;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Session;
+use Illuminate\Support\Facades\Gate;
+use Illuminate\Support\Facades\Log;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField;
+use Illuminate\Contracts\View\View;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
class BulkAssetsController extends Controller
{
@@ -23,81 +28,162 @@ class BulkAssetsController extends Controller
/**
* 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] []
- * @return View
* @internal param int $assetId
* @since [v2.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit(Request $request)
+ public function edit(Request $request) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
-
+
+ /**
+ * No asset IDs were passed
+ */
if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
+ $asset_ids = $request->input('ids');
+
// Figure out where we need to send the user after the update is complete, and store that in the session
$bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]);
- $asset_ids = array_values(array_unique($request->input('ids')));
-
- //custom fields logic
- $asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) {
- return $query->where('fieldset_id', '!=', null);
- })->get();
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'asset_tag',
+ 'serial',
+ 'model_number',
+ 'last_checkout',
+ 'notes',
+ 'expected_checkin',
+ 'order_number',
+ 'image',
+ 'assigned_to',
+ 'created_at',
+ 'updated_at',
+ 'purchase_date',
+ 'purchase_cost',
+ 'last_audit_date',
+ 'next_audit_date',
+ 'warranty_months',
+ 'checkout_counter',
+ 'checkin_counter',
+ 'requests_counter',
+ 'byod',
+ 'asset_eol_date',
+ ];
- $models = $asset_custom_field->unique('model_id');
+
+ /**
+ * Make sure the column is allowed, and if it's a custom field, make sure we strip the custom_fields. prefix
+ */
+ $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
+ $sort_override = str_replace('custom_fields.', '', $request->input('sort'));
+
+ // This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
+
+ $assets = Asset::with('assignedTo', 'location', 'model')
+ ->whereIn('assets.id', $asset_ids)
+ ->withTrashed();
+
+ $assets = $assets->get();
+
+ if ($assets->isEmpty()) {
+ Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
+ return redirect()->back()->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
+ }
+
+ $models = $assets->unique('model_id');
$modelNames = [];
foreach($models as $model) {
$modelNames[] = $model->model->name;
- }
+ }
if ($request->filled('bulk_actions')) {
+
+
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
- $assets_found = Asset::find($asset_ids);
-
- if ($assets_found->isEmpty()){
- return redirect()->back();
- }
return (new Label)
- ->with('assets', $assets_found)
+ ->with('assets', $assets)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
case 'delete':
$this->authorize('delete', Asset::class);
- $assets = Asset::with('assignedTo', 'location')->find($asset_ids);
- $assets->each(function ($asset) {
- $this->authorize('delete', $asset);
+ $assets->each(function ($assets) {
+ $this->authorize('delete', $assets);
});
return view('hardware/bulk-delete')->with('assets', $assets);
-
+
case 'restore':
$this->authorize('update', Asset::class);
- $assets = Asset::withTrashed()->find($asset_ids);
+ $assets = Asset::withTrashed()->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
-
return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit':
$this->authorize('update', Asset::class);
+
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList())
- ->with('models', $models->pluck(['model']))
+ ->with('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
+ switch ($sort_override) {
+ case 'model':
+ $assets->OrderModels($order);
+ break;
+ case 'model_number':
+ $assets->OrderModelNumber($order);
+ break;
+ case 'category':
+ $assets->OrderCategory($order);
+ break;
+ case 'manufacturer':
+ $assets->OrderManufacturer($order);
+ break;
+ case 'company':
+ $assets->OrderCompany($order);
+ break;
+ case 'location':
+ $assets->OrderLocation($order);
+ case 'rtd_location':
+ $assets->OrderRtdLocation($order);
+ break;
+ case 'status_label':
+ $assets->OrderStatus($order);
+ break;
+ case 'supplier':
+ $assets->OrderSupplier($order);
+ break;
+ case 'assigned_to':
+ $assets->OrderAssigned($order);
+ break;
+ default:
+ $assets->orderBy($column_sort, $order);
+ break;
+ }
+
return redirect()->back()->with('error', 'No action selected');
}
@@ -105,11 +191,10 @@ class BulkAssetsController extends Controller
* Save bulk edits
*
* @author [A. Gianotto] []
- * @return Redirect
* @internal param array $assets
* @since [v2.0]
*/
- public function update(Request $request)
+ public function update(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
$has_errors = 0;
@@ -117,26 +202,33 @@ class BulkAssetsController extends Controller
// Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index');
+
if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
+
- if (Session::exists('ids')) {
- $assets = Session::get('ids');
- } elseif (! $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'));
}
-
- $assets = array_keys($request->input('ids'));
-
- if ($request->anyFilled($custom_field_columns)) {
- $custom_fields_present = true;
- } else {
- $custom_fields_present = false;
- }
- if (($request->filled('purchase_date'))
+
+
+ $assets = Asset::whereIn('id', $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('name'))
+ || ($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
|| ($request->filled('supplier_id'))
@@ -148,27 +240,44 @@ class BulkAssetsController extends Controller
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('next_audit_date'))
+ || ($request->filled('null_name'))
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_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->conditionallyAddItem('purchase_date')
+ /**
+ * 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('name')
+ ->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
- ->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->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_name')=='1') {
+
+ $this->update_array['name'] = null;
+ }
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
@@ -186,7 +295,6 @@ class BulkAssetsController extends Controller
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
}
-
if ($request->filled('company_id')) {
$this->update_array['company_id'] = $request->input('company_id');
if ($request->input('company_id') == 'clear') {
@@ -194,88 +302,131 @@ 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('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')) {
$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 = [];
- $asset = Asset::find($assetId);
foreach ($this->update_array as $key => $value) {
+
if ($this->update_array[$key] != $asset->{$key}) {
$changed[$key]['old'] = $asset->{$key};
$changed[$key]['new'] = $this->update_array[$key];
}
+
}
-
- if ($custom_fields_present) {
- $model = $asset->model()->first();
+ /**
+ * Start all the custom fields shenanigans
+ */
- // Use the rules of the new model fieldsets if the model changed
- if ($request->filled('model_id')) {
- $this->update_array['model_id'] = $request->input('model_id');
- $model = \App\Models\AssetModel::find($request->input('model_id'));
- }
+ // Does the model have a fieldset?
+ if ($asset->model->fieldset) {
+ foreach ($asset->model->fieldset->fields as $field) {
+ if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
+ if (Gate::allows('admin')) {
+ $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
- // Make sure this model is valid
- $assetCustomFields = ($model) ? $model->fieldset : null;
-
- if ($assetCustomFields && $assetCustomFields->fields) {
-
- foreach ($assetCustomFields->fields as $field) {
-
- 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 {
/*
- * 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.
+ * Remove the encrypted custom field from the update_array, since nothing changed
*/
- 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});
- }
+ 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 {
-
-
- 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];
- }
- }
+ $asset->{$field->db_column} = $this->update_array[$field->db_column];
}
+ }
+ }
- } // endforeach
- } // end custom field check
- } // end custom fields handler
-
+ } // endforeach
+ }
// Check if it passes validation, and then try to save
@@ -312,9 +463,8 @@ class BulkAssetsController extends Controller
/**
* Adds parameter to update array for an item if it exists in request
* @param string $field field name
- * @return BulkAssetsController Model for Chaining
*/
- protected function conditionallyAddItem($field)
+ protected function conditionallyAddItem($field) : BulkAssetsController
{
if (request()->filled($field)) {
$this->update_array[$field] = request()->input($field);
@@ -328,12 +478,10 @@ class BulkAssetsController extends Controller
*
* @author [A. Gianotto] []
* @param Request $request
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param array $assets
* @since [v2.0]
*/
- public function destroy(Request $request)
+ public function destroy(Request $request) : RedirectResponse
{
$this->authorize('delete', Asset::class);
@@ -345,12 +493,7 @@ class BulkAssetsController extends Controller
if ($request->filled('ids')) {
$assets = Asset::find($request->get('ids'));
foreach ($assets as $asset) {
- $update_array['deleted_at'] = date('Y-m-d H:i:s');
- $update_array['assigned_to'] = null;
-
- DB::table('assets')
- ->where('id', $asset->id)
- ->update($update_array);
+ $asset->delete();
} // endforeach
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
@@ -362,27 +505,23 @@ class BulkAssetsController extends Controller
/**
* Show Bulk Checkout Page
- * @return View View to checkout multiple assets
*/
- public function showCheckout()
+ public function showCheckout() : View
{
$this->authorize('checkout', Asset::class);
- // Filter out assets that are not deployable.
-
return view('hardware/bulk-checkout');
}
/**
* Process Multiple Checkout Request
- * @return View
*/
- public function storeCheckout(AssetCheckoutRequest $request)
+ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
$this->authorize('checkout', Asset::class);
try {
- $admin = Auth::user();
+ $admin = auth()->user();
$target = $this->determineCheckoutTarget();
@@ -441,17 +580,19 @@ class BulkAssetsController extends Controller
}
}
- public function restore(Request $request) {
+ public function restore(Request $request) : RedirectResponse
+ {
$this->authorize('update', Asset::class);
- $assetIds = $request->get('ids');
- if (empty($assetIds)) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
+ $assetIds = $request->get('ids');
+
+ if (empty($assetIds)) {
+ return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
} else {
foreach ($assetIds as $key => $assetId) {
- $asset = Asset::withTrashed()->find($assetId);
- $asset->restore();
+ $asset = Asset::withTrashed()->find($assetId);
+ $asset->restore();
}
- return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
+ return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
}
}
}
diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php
index cb661eabd..a7322e7e5 100644
--- a/app/Http/Controllers/Auth/ForgotPasswordController.php
+++ b/app/Http/Controllers/Auth/ForgotPasswordController.php
@@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
-
+use Illuminate\Support\Facades\Log;
class ForgotPasswordController extends Controller
{
/*
@@ -79,16 +79,16 @@ class ForgotPasswordController extends Controller
)
);
} catch(\Exception $e) {
- \Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e );
+ Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e );
}
// Prevent timing attack to enumerate users.
usleep(500000 + random_int(0, 1500000));
if ($response === \Password::RESET_LINK_SENT) {
- \Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
+ Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
} else {
- \Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive');
+ Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive');
}
/**
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index 319ebd041..e7b10877c 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
+use App\Models\SamlNonce;
use App\Models\Setting;
use App\Models\User;
use App\Models\Ldap;
@@ -15,7 +16,7 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
-use Log;
+use Illuminate\Support\Facades\Log;
use Redirect;
/**
@@ -56,7 +57,6 @@ class LoginController extends Controller
parent::__construct();
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
Session::put('backUrl', \URL::previous());
- // $this->ldap = $ldap;
$this->saml = $saml;
}
@@ -82,7 +82,6 @@ class LoginController extends Controller
}
if (Setting::getSettings()->login_common_disabled == '1') {
- \Log::debug('login_common_disabled is set to 1 - return a 403');
return view('errors.403');
}
@@ -111,24 +110,35 @@ class LoginController extends Controller
try {
$user = $saml->samlLogin($samlData);
-
+ $notValidAfter = new \Carbon\Carbon(@$samlData['assertionNotOnOrAfter']);
+ if(\Carbon::now()->greaterThanOrEqualTo($notValidAfter)) {
+ abort(400,"Expired SAML Assertion");
+ }
+ if(SamlNonce::where('nonce', @$samlData['nonce'])->count() > 0) {
+ abort(400,"Assertion has already been used");
+ }
+ Log::debug("okay, fine, this is a new nonce then. Good for you.");
if (!is_null($user)) {
Auth::login($user);
} else {
$username = $saml->getUsername();
- \Log::debug("SAML user '$username' could not be found in database.");
+ Log::debug("SAML user '$username' could not be found in database.");
$request->session()->flash('error', trans('auth/message.signin.error'));
$saml->clearData();
}
- if ($user = Auth::user()) {
+ if ($user = auth()->user()) {
$user->last_login = \Carbon::now();
- $user->save();
+ $user->saveQuietly();
}
-
+ $s = new SamlNonce();
+ $s->nonce = @$samlData['nonce'];
+ $s->not_valid_after = $notValidAfter;
+ $s->save();
+
} catch (\Exception $e) {
- \Log::debug('There was an error authenticating the SAML user: '.$e->getMessage());
- throw new \Exception($e->getMessage());
+ Log::debug('There was an error authenticating the SAML user: '.$e->getMessage());
+ throw $e;
}
// Fallthrough with better logging
@@ -136,7 +146,7 @@ class LoginController extends Controller
// Better logging
if (empty($samlData)) {
- \Log::debug("SAML page requested, but samlData seems empty.");
+ Log::debug("SAML page requested, but samlData seems empty.");
}
}
@@ -199,7 +209,7 @@ class LoginController extends Controller
$user->email = $ldap_attr['email'];
$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->save();
+ $user->saveQuietly();
} // End if(!user)
return $user;
}
@@ -251,19 +261,19 @@ class LoginController extends Controller
/**
* Account sign in form processing.
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function login(Request $request)
{
//If the environment is set to ALWAYS require SAML, return access denied
if (config('app.require_saml')) {
- \Log::debug('require SAML is enabled in the .env - return a 403');
+ Log::debug('require SAML is enabled in the .env - return a 403');
return view('errors.403');
}
if (Setting::getSettings()->login_common_disabled == '1') {
- \Log::debug('login_common_disabled is set to 1 - return a 403');
+ Log::debug('login_common_disabled is set to 1 - return a 403');
return view('errors.403');
}
@@ -316,10 +326,10 @@ class LoginController extends Controller
}
}
- if ($user = Auth::user()) {
+ if ($user = auth()->user()) {
$user->last_login = \Carbon::now();
$user->activated = 1;
- $user->save();
+ $user->saveQuietly();
}
// Redirect to the users page
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
@@ -329,7 +339,7 @@ class LoginController extends Controller
/**
* Two factor enrollment page
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function getTwoFactorEnroll()
{
@@ -340,7 +350,7 @@ class LoginController extends Controller
}
$settings = Setting::getSettings();
- $user = Auth::user();
+ $user = auth()->user();
// We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
@@ -371,7 +381,7 @@ class LoginController extends Controller
[-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);
}
@@ -379,7 +389,7 @@ class LoginController extends Controller
/**
* Two factor code form page
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function getTwoFactorAuth()
{
@@ -388,7 +398,7 @@ class LoginController extends Controller
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
- $user = Auth::user();
+ $user = auth()->user();
// Check whether there is a device enrolled.
// This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware
@@ -405,7 +415,7 @@ class LoginController extends Controller
*
* @param Request $request
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function postTwoFactorAuth(Request $request)
{
@@ -417,19 +427,15 @@ class LoginController extends Controller
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
}
- if (! $request->has('two_factor_secret')) { // TODO this seems almost the same as above?
- return redirect()->route('two-factor')->with('error', 'Two-factor code is required.');
- }
-
- $user = Auth::user();
+ $user = auth()->user();
$secret = $request->input('two_factor_secret');
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
- $user->save();
+ $user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);
- return redirect()->route('home')->with('success', 'You are logged in!');
+ return redirect()->route('home')->with('success', trans('auth/message.signin.success'));
}
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
@@ -441,7 +447,7 @@ class LoginController extends Controller
*
* @param Request $request
*
- * @return Redirect
+ * @return Illuminate\Http\RedirectResponse
*/
public function logout(Request $request)
{
@@ -502,8 +508,8 @@ class LoginController extends Controller
protected function validator(array $data)
{
return Validator::make($data, [
- 'username' => 'required',
- 'password' => 'required',
+ 'username' => 'required|not_array',
+ 'password' => 'required|not_array',
]);
}
@@ -527,7 +533,7 @@ class LoginController extends Controller
$minutes = round($seconds / 60);
- $message = \Lang::get('auth/message.throttle', ['minutes' => $minutes]);
+ $message = trans('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php
index e7a0fb1af..f1cfbc853 100644
--- a/app/Http/Controllers/Auth/ResetPasswordController.php
+++ b/app/Http/Controllers/Auth/ResetPasswordController.php
@@ -7,7 +7,7 @@ use App\Models\Setting;
use App\Models\User;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
-
+use Illuminate\Support\Facades\Log;
class ResetPasswordController extends Controller
{
@@ -66,7 +66,7 @@ class ResetPasswordController extends Controller
$credentials = $request->only('email', 'token');
if (is_null($this->broker()->getUser($credentials))) {
- \Log::debug('Password reset form FAILED - this token is not valid.');
+ Log::debug('Password reset form FAILED - this token is not valid.');
return redirect()->route('password.request')->with('error', trans('passwords.token'));
}
@@ -87,12 +87,12 @@ class ResetPasswordController extends Controller
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
];
- $request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
+ $request->validate($this->rules());
- \Log::debug('Checking if '.$request->input('username').' exists');
+ Log::debug('Checking if '.$request->input('username').' exists');
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing
if ($user = User::where('username', '=', $request->input('username'))->where('activated', '1')->whereNotNull('email')->first()) {
- \Log::debug($user->username.' exists');
+ Log::debug($user->username.' exists');
// handle the password validation rules set by the admin settings
@@ -112,17 +112,17 @@ class ResetPasswordController extends Controller
// Check if the password reset above actually worked
if ($response == \Password::PASSWORD_RESET) {
- \Log::debug('Password reset for '.$user->username.' worked');
+ Log::debug('Password reset for '.$user->username.' worked');
return redirect()->guest('login')->with('success', trans('passwords.reset'));
}
- \Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
+ Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
}
- \Log::debug('Password reset for '.$request->input('username').' FAILED - user does not exist or does not have an email address - but make it look like it succeeded');
+ Log::debug('Password reset for '.$request->input('username').' FAILED - user does not exist or does not have an email address - but make it look like it succeeded');
return redirect()->guest('login')->with('success', trans('passwords.reset'));
}
diff --git a/app/Http/Controllers/Auth/SamlController.php b/app/Http/Controllers/Auth/SamlController.php
index b6218988e..6a4c1f65b 100644
--- a/app/Http/Controllers/Auth/SamlController.php
+++ b/app/Http/Controllers/Auth/SamlController.php
@@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Services\Saml;
use Illuminate\Http\Request;
-use Log;
+use Illuminate\Support\Facades\Log;
/**
* This controller provides the endpoint for SAML communication and metadata.
@@ -51,7 +51,7 @@ class SamlController extends Controller
$metadata = $this->saml->getSPMetadata();
if (empty($metadata)) {
- \Log::debug('SAML metadata is empty - return a 403');
+ Log::debug('SAML metadata is empty - return a 403');
return response()->view('errors.403', [], 403);
}
@@ -71,7 +71,7 @@ class SamlController extends Controller
*
* @param Request $request
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function login(Request $request)
{
@@ -93,18 +93,24 @@ class SamlController extends Controller
*
* @param Request $request
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function acs(Request $request)
{
$saml = $this->saml;
$auth = $saml->getAuth();
- $auth->processResponse();
+ $saml_exception = false;
+ try {
+ $auth->processResponse();
+ } catch (\Exception $e) {
+ Log::warning("Exception caught in SAML login: " . $e->getMessage());
+ $saml_exception = true;
+ }
$errors = $auth->getErrors();
- if (! empty($errors)) {
- Log::error('There was an error with SAML ACS: '.implode(', ', $errors));
- Log::error('Reason: '.$auth->getLastErrorReason());
+ if (!empty($errors) || $saml_exception) {
+ Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors));
+ Log::warning('Reason: ' . $auth->getLastErrorReason());
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
}
@@ -126,18 +132,24 @@ class SamlController extends Controller
*
* @param Request $request
*
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function sls(Request $request)
{
$auth = $this->saml->getAuth();
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
- $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
+ $saml_exception = false;
+ try {
+ $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
+ } catch (\Exception $e) {
+ Log::warning("Exception caught in SAML single-logout: " . $e->getMessage());
+ $saml_exception = true;
+ }
$errors = $auth->getErrors();
- if (! empty($errors)) {
- Log::error('There was an error with SAML SLS: '.implode(', ', $errors));
- Log::error('Reason: '.$auth->getLastErrorReason());
+ if (!empty($errors) || $saml_exception) {
+ Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors));
+ Log::warning('Reason: ' . $auth->getLastErrorReason());
return view('errors.403');
}
diff --git a/app/Http/Controllers/BulkAssetModelsController.php b/app/Http/Controllers/BulkAssetModelsController.php
index a312ba1f6..36b21178b 100644
--- a/app/Http/Controllers/BulkAssetModelsController.php
+++ b/app/Http/Controllers/BulkAssetModelsController.php
@@ -5,8 +5,8 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Input;
-use Illuminate\Support\Facades\Redirect;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
class BulkAssetModelsController extends Controller
{
@@ -16,9 +16,8 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.7]
* @param Request $request
- * @return \Illuminate\Contracts\View\View
*/
- public function edit(Request $request)
+ public function edit(Request $request) : View | RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -61,9 +60,8 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.7]
* @param Request $request
- * @return \Illuminate\Contracts\View\View
*/
- public function update(Request $request)
+ public function update(Request $request): View | RedirectResponse
{
$this->authorize('update', AssetModel::class);
@@ -105,9 +103,8 @@ class BulkAssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return Redirect
*/
- public function destroy(Request $request)
+ public function destroy(Request $request) : RedirectResponse
{
$this->authorize('delete', AssetModel::class);
diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php
index 5844b54dc..ac57ad6a6 100755
--- a/app/Http/Controllers/CategoriesController.php
+++ b/app/Http/Controllers/CategoriesController.php
@@ -4,10 +4,11 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
-use App\Models\Category as Category;
-use Auth;
+use App\Models\Category;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
-use Str;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This class controls all actions related to Categories for
@@ -25,10 +26,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
// Show the page
$this->authorize('view', Category::class);
@@ -42,10 +41,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::store() method that stores the data
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
// Show the page
$this->authorize('create', Category::class);
@@ -61,10 +58,8 @@ class CategoriesController extends Controller
* @see CategoriesController::create() method that makes the form.
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Category::class);
$category = new Category();
@@ -91,10 +86,8 @@ class CategoriesController extends Controller
* @see CategoriesController::postEdit() method saves the data
* @param int $categoryId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($categoryId = null)
+ public function edit($categoryId = null) : RedirectResponse | View
{
$this->authorize('update', Category::class);
if (is_null($item = Category::find($categoryId))) {
@@ -112,23 +105,31 @@ class CategoriesController extends Controller
* @see CategoriesController::getEdit() method that makes the form.
* @param ImageUploadRequest $request
* @param int $categoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $categoryId = null)
+ public function update(ImageUploadRequest $request, $categoryId = null) : RedirectResponse
{
$this->authorize('update', Category::class);
if (is_null($category = Category::find($categoryId))) {
// Redirect to the categories management page
- return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist'));
+ return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
// Update the category data
$category->name = $request->input('name');
// If the item count is > 0, we disable the category type in the edit. Disabled items
// don't POST, so if the category_type is blank we just set it to the default.
+
+
+ // Don't allow the user to change the category_type once it's been created
+ if (($request->filled('category_type') && ($category->itemCount() > 0))) {
+ $request->validate(['category_type' => 'in:'.$category->category_type]);
+ }
+
$category->category_type = $request->input('category_type', $category->category_type);
+
+ $category->fill($request->all());
+
$category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
@@ -150,10 +151,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $categoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($categoryId)
+ public function destroy($categoryId) : RedirectResponse
{
$this->authorize('delete', Category::class);
// Check if the category exists
@@ -178,11 +177,9 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::getDataView() method that generates the JSON response
* @param $id
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
$this->authorize('view', Category::class);
if ($category = Category::find($id)) {
diff --git a/app/Http/Controllers/CheckInOutRequest.php b/app/Http/Controllers/CheckInOutRequest.php
index 6dd7e4aba..6ab327fff 100644
--- a/app/Http/Controllers/CheckInOutRequest.php
+++ b/app/Http/Controllers/CheckInOutRequest.php
@@ -11,9 +11,8 @@ trait CheckInOutRequest
{
/**
* Find target for checkout
- * @return SnipeModel Target asset is being checked out to.
*/
- protected function determineCheckoutTarget()
+ protected function determineCheckoutTarget() : ?SnipeModel
{
// This item is checked out to a location
switch (request('checkout_to_type')) {
@@ -21,7 +20,7 @@ trait CheckInOutRequest
return Location::findOrFail(request('assigned_location'));
case 'asset':
return Asset::findOrFail(request('assigned_asset'));
- case 'user':
+ default:
return User::findOrFail(request('assigned_user'));
}
@@ -34,7 +33,7 @@ trait CheckInOutRequest
* @param SnipeModel $target Target with location
* @return Asset Asset being updated
*/
- protected function updateAssetLocation($asset, $target)
+ protected function updateAssetLocation($asset, $target) : Asset
{
switch (request('checkout_to_type')) {
case 'location':
diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php
index 6c4072362..589832af7 100644
--- a/app/Http/Controllers/CompaniesController.php
+++ b/app/Http/Controllers/CompaniesController.php
@@ -6,6 +6,9 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Companies for
@@ -20,10 +23,8 @@ final class CompaniesController extends Controller
*
* @author [Abdullah Alansari] []
* @since [v1.8]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', Company::class);
@@ -35,10 +36,8 @@ final class CompaniesController extends Controller
*
* @author [Abdullah Alansari] []
* @since [v1.8]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Company::class);
@@ -51,10 +50,8 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param Request $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Company::class);
@@ -80,10 +77,8 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param int $companyId
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($companyId)
+ public function edit($companyId) : View | RedirectResponse
{
if (is_null($item = Company::find($companyId))) {
return redirect()->route('companies.index')
@@ -102,10 +97,8 @@ final class CompaniesController extends Controller
* @since [v1.8]
* @param ImageUploadRequest $request
* @param int $companyId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $companyId)
+ public function update(ImageUploadRequest $request, $companyId) : RedirectResponse
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist'));
@@ -134,10 +127,8 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param int $companyId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($companyId)
+ public function destroy($companyId) : RedirectResponse
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')
@@ -154,7 +145,7 @@ final class CompaniesController extends Controller
try {
Storage::disk('public')->delete('companies'.'/'.$company->image);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -164,7 +155,7 @@ final class CompaniesController extends Controller
->with('success', trans('admin/companies/message.delete.success'));
}
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
$this->authorize('view', Company::class);
diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php
index 9f4724e35..379882c3c 100644
--- a/app/Http/Controllers/Components/ComponentCheckinController.php
+++ b/app/Http/Controllers/Components/ComponentCheckinController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
@@ -95,13 +96,11 @@ class ComponentCheckinController extends Controller
$asset = Asset::find($component_assets->asset_id);
- 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'));
- }
+ event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
- return redirect()->route('components.index')->with('success',
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
trans('admin/components/message.checkin.success'));
}
diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php
index 412d9dde6..e9db70811 100644
--- a/app/Http/Controllers/Components/ComponentCheckoutController.php
+++ b/app/Http/Controllers/Components/ComponentCheckoutController.php
@@ -4,9 +4,11 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
+use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@@ -20,25 +22,38 @@ class ComponentCheckoutController extends Controller
* @author [A. Gianotto] []
* @see ComponentCheckoutController::store() method that stores the data.
* @since [v3.0]
- * @param int $componentId
+ * @param int $id
* @return \Illuminate\Contracts\View\View
* @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->numRemaining() <= 0){
- return redirect()->route('components.index')->with('error', trans('admin/components/message.checkout.unavailable'));
+ if ($component = Component::find($id)) {
+
+ $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'));
+
}
/**
@@ -80,22 +95,31 @@ class ComponentCheckoutController extends Controller
->withInput();
}
- // Check if the user exists
+ // Check if the asset exists
$asset = Asset::find($request->input('asset_id'));
+ if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
+ return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
+ }
+
// Update the component data
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
- 'user_id' => Auth::user(),
+ 'user_id' => auth()->user()->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'),
]);
- event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
+ event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note')));
- return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
+ $request->request->add(['checkout_to_type' => 'asset']);
+ $request->request->add(['assigned_asset' => $asset->id]);
+
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
}
}
diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php
index 34c9aed16..57cd0a2b4 100644
--- a/app/Http/Controllers/Components/ComponentsController.php
+++ b/app/Http/Controllers/Components/ComponentsController.php
@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Facades\Log;
/**
* This class controls all actions related to Components for
@@ -85,8 +86,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($component->save()) {
- return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -159,8 +162,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($component->save()) {
- return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -188,7 +193,7 @@ class ComponentsController extends Controller
try {
Storage::disk('public')->delete('components/'.$component->image);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php
index 0f4e782aa..a7d42bb07 100644
--- a/app/Http/Controllers/Components/ComponentsFilesController.php
+++ b/app/Http/Controllers/Components/ComponentsFilesController.php
@@ -4,28 +4,28 @@ namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
-use enshrined\svgSanitize\Sanitizer;
+use Illuminate\Support\Facades\Log;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
- * @todo Switch to using the AssetFileRequest form request validator.
- * @author [A. Gianotto] []
- * @since [v1.0]
- * @param AssetFileRequest $request
+ * @param UploadFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
+ *@author [A. Gianotto] []
+ * @since [v1.0]
+ * @todo Switch to using the AssetFileRequest form request validator.
*/
- public function store(AssetFileRequest $request, $componentId = null)
+ public function store(UploadFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
@@ -43,30 +43,7 @@ class ComponentsFilesController extends Controller
}
foreach ($request->file('file') as $file) {
-
- $extension = $file->getClientOriginalExtension();
- $file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
-
-
- // Check for SVG and sanitize it
- if ($extension == 'svg') {
- \Log::debug('This is an SVG');
- \Log::debug($file_name);
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/components/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
-
- } else {
- Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
- }
+ $file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
@@ -108,7 +85,7 @@ class ComponentsFilesController extends Controller
try {
Storage::delete('components/'.$log->filename);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -134,7 +111,7 @@ class ComponentsFilesController extends Controller
*/
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);
// the component is valid
@@ -150,8 +127,8 @@ class ComponentsFilesController extends Controller
$file = 'private_uploads/components/'.$log->filename;
if (Storage::missing($file)) {
- \Log::debug('FILE DOES NOT EXISTS for '.$file);
- \Log::debug('URL should be '.Storage::url($file));
+ Log::debug('FILE DOES NOT EXISTS for '.$file);
+ Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
index 5723c2c7e..1bdb16af9 100644
--- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
+++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
@@ -3,12 +3,13 @@
namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class ConsumableCheckoutController extends Controller
{
@@ -18,30 +19,36 @@ class ConsumableCheckoutController extends Controller
* @author [A. Gianotto] []
* @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0]
- * @param int $consumableId
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @param int $id
*/
- public function create($consumableId)
+ public function create($id) : View | RedirectResponse
{
- if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
- return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
+ if ($consumable = Consumable::find($id)) {
+
+ $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', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
+ }
+
+ // 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
- if ($consumable->numRemaining() <= 0){
- return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
- }
+ // Not found
+ return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
- // Make sure there is a valid category
- if (!$consumable->category){
- return redirect()->route('consumables.edit', ['consumable' => $consumable->id])->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
- }
-
- $this->authorize('checkout', $consumable);
-
- return view('consumables/checkout', compact('consumable'));
}
/**
@@ -62,13 +69,18 @@ class ConsumableCheckoutController extends Controller
$this->authorize('checkout', $consumable);
- // 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'));
+ // If the quantity is not present in the request or is not a positive integer, set it to 1
+ $quantity = $request->input('qty');
+ if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
+ $quantity = 1;
}
+ // Make sure there is at least one available to checkout
+ if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) {
+ return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ]));
+ }
- $admin_user = Auth::user();
+ $admin_user = auth()->user();
$assigned_to = e($request->input('assigned_to'));
// Check if the user exists
@@ -80,16 +92,23 @@ class ConsumableCheckoutController extends Controller
// Update the consumable data
$consumable->assigned_to = e($request->input('assigned_to'));
+ for($i = 0; $i < $quantity; $i++){
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $admin_user->id,
'assigned_to' => e($request->input('assigned_to')),
'note' => $request->input('note'),
]);
+ }
+ event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
+
+ $request->request->add(['checkout_to_type' => 'user']);
+ $request->request->add(['assigned_user' => $user->id]);
+
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
- event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
// Redirect to the new consumable page
- return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success'));
}
}
diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php
index b33e6e07a..42c0766fe 100644
--- a/app/Http/Controllers/Consumables/ConsumablesController.php
+++ b/app/Http/Controllers/Consumables/ConsumablesController.php
@@ -8,8 +8,10 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Consumable;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
+use App\Http\Requests\StoreConsumableRequest;
/**
* This controller handles all actions related to Consumables for
@@ -62,7 +64,7 @@ class ConsumablesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreConsumableRequest $request)
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable();
@@ -85,8 +87,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($consumable->save()) {
- return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -99,10 +103,8 @@ class ConsumablesController extends Controller
* @param int $consumableId
* @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($consumableId = null)
+ public function edit($consumableId = null) : View | RedirectResponse
{
if ($item = Consumable::find($consumableId)) {
$this->authorize($item);
@@ -124,7 +126,7 @@ class ConsumablesController extends Controller
* @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $consumableId = null)
+ public function update(StoreConsumableRequest $request, $consumableId = null)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
@@ -160,8 +162,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($consumable->save()) {
- return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -182,6 +186,7 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize($consumable);
+
$consumable->delete();
// Redirect to the locations management page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success'));
@@ -199,7 +204,7 @@ class ConsumablesController extends Controller
*/
public function show($consumableId = null)
{
- $consumable = Consumable::find($consumableId);
+ $consumable = Consumable::withCount('users as users_consumables')->find($consumableId);
$this->authorize($consumable);
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
@@ -208,4 +213,16 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.does_not_exist'));
}
+
+ public function clone(Consumable $consumable) : View
+ {
+ $this->authorize('create', $consumable);
+ $consumable_to_close = $consumable;
+ $consumable = clone $consumable_to_close;
+ $consumable->id = null;
+ $consumable->image = null;
+ $consumable->user_id = null;
+
+ return view('consumables/edit')->with('item', $consumable);
+ }
}
diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
index 6053e82cc..35a4ae841 100644
--- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php
+++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
-use enshrined\svgSanitize\Sanitizer;
-
+use Illuminate\Support\Facades\Log;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
- * @todo Switch to using the AssetFileRequest form request validator.
- * @author [A. Gianotto] []
- * @since [v1.0]
- * @param AssetFileRequest $request
+ * @param UploadFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
+ *@author [A. Gianotto] []
+ * @since [v1.0]
+ * @todo Switch to using the AssetFileRequest form request validator.
*/
- public function store(AssetFileRequest $request, $consumableId = null)
+ public function store(UploadFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
@@ -42,30 +41,7 @@ class ConsumablesFilesController extends Controller
}
foreach ($request->file('file') as $file) {
-
- $extension = $file->getClientOriginalExtension();
- $file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
-
-
- // Check for SVG and sanitize it
- if ($extension == 'svg') {
- \Log::debug('This is an SVG');
- \Log::debug($file_name);
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/consumables/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
-
- } else {
- Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
- }
+ $file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
@@ -107,7 +83,7 @@ class ConsumablesFilesController extends Controller
try {
Storage::delete('consumables/'.$log->filename);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -148,8 +124,8 @@ class ConsumablesFilesController extends Controller
$file = 'private_uploads/consumables/'.$log->filename;
if (Storage::missing($file)) {
- \Log::debug('FILE DOES NOT EXISTS for '.$file);
- \Log::debug('URL should be '.Storage::url($file));
+ Log::debug('FILE DOES NOT EXISTS for '.$file);
+ Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index c0e9454d6..74fff19a3 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -22,7 +22,7 @@
namespace App\Http\Controllers;
-use Auth;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
@@ -35,6 +35,6 @@ abstract class Controller extends BaseController
public function __construct()
{
view()->share('signedIn', Auth::check());
- view()->share('user', Auth::user());
+ view()->share('user', auth()->user());
}
}
diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php
index ffe5eceec..42f6c212d 100644
--- a/app/Http/Controllers/CustomFieldsController.php
+++ b/app/Http/Controllers/CustomFieldsController.php
@@ -8,7 +8,8 @@ use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
-use Redirect;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -26,10 +27,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', CustomField::class);
@@ -46,10 +45,8 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField()
* @author [A. Gianotto] []
* @since [v5.1.5]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show()
+ public function show() : RedirectResponse
{
return redirect()->route('fields.index');
}
@@ -61,10 +58,8 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField()
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create(Request $request)
+ public function create(Request $request) : View
{
$this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get();
@@ -83,10 +78,8 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::createField()
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(CustomFieldRequest $request)
+ public function store(CustomFieldRequest $request) : RedirectResponse
{
$this->authorize('create', CustomField::class);
@@ -145,10 +138,8 @@ class CustomFieldsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function deleteFieldFromFieldset($field_id, $fieldset_id)
+ public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
{
$field = CustomField::find($field_id);
@@ -177,10 +168,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($field_id)
+ public function destroy($field_id) : RedirectResponse
{
if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field);
@@ -203,10 +192,8 @@ class CustomFieldsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit(Request $request, $id)
+ public function edit(Request $request, $id) : View | RedirectResponse
{
if ($field = CustomField::find($id)) {
@@ -242,7 +229,7 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(CustomFieldRequest $request, $id)
+ public function update(CustomFieldRequest $request, $id) : RedirectResponse
{
$field = CustomField::find($id);
@@ -260,7 +247,7 @@ class CustomFieldsController extends Controller
$field->name = trim(e($request->get("name")));
$field->element = e($request->get("element"));
- $field->field_values = e($request->get("field_values"));
+ $field->field_values = $request->get("field_values");
$field->user_id = Auth::id();
$field->help_text = $request->get("help_text");
$field->show_in_email = $show_in_email;
diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php
index abf7c1d18..8b9844d15 100644
--- a/app/Http/Controllers/CustomFieldsetsController.php
+++ b/app/Http/Controllers/CustomFieldsetsController.php
@@ -6,10 +6,9 @@ use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
-use Redirect;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -23,7 +22,7 @@ use Redirect;
class CustomFieldsetsController extends Controller
{
- public function index()
+ public function index() : RedirectResponse
{
return redirect()->route("fields.index")
->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
@@ -34,11 +33,9 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @param int $id
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
$cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
@@ -70,10 +67,8 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', CustomField::class);
@@ -86,16 +81,16 @@ class CustomFieldsetsController extends Controller
* @author [Brady Wetherington] []
* @since [v1.8]
* @param Request $request
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(Request $request)
+ public function store(Request $request) : RedirectResponse
{
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset([
'name' => $request->get('name'),
- 'user_id' => Auth::user()->id,
+ 'user_id' => auth()->id(),
]);
$validator = Validator::make($request->all(), $fieldset->rules);
@@ -126,10 +121,8 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v6.0.14]
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($id)
+ public function edit($id) : View | RedirectResponse
{
$this->authorize('create', CustomField::class);
@@ -147,10 +140,8 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v6.0.14]
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : RedirectResponse
{
$this->authorize('create', CustomField::class);
@@ -175,10 +166,8 @@ class CustomFieldsetsController extends Controller
* @author [Brady Wetherington] []
* @param int $id
* @since [v1.8]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
$fieldset = CustomFieldset::find($id);
@@ -203,9 +192,8 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return View
*/
- public function associate(Request $request, $id)
+ public function associate(Request $request, $id) : RedirectResponse
{
$set = CustomFieldset::find($id);
@@ -223,7 +211,7 @@ class CustomFieldsetsController extends Controller
return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success'));
}
- return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.');
+ return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected'));
}
/**
@@ -232,7 +220,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0]
*/
- public function makeFieldRequired($fieldset_id, $field_id)
+ public function makeFieldRequired($fieldset_id, $field_id) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
@@ -250,7 +238,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0]
*/
- public function makeFieldOptional($fieldset_id, $field_id)
+ public function makeFieldOptional($fieldset_id, $field_id) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index 89283a7c2..fc01c496c 100755
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Artisan;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
@@ -21,12 +22,11 @@ class DashboardController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function index()
+ public function index() : View | RedirectResponse
{
// Show the page
- if (Auth::user()->hasAccess('admin')) {
+ if (auth()->user()->hasAccess('admin')) {
$asset_stats = null;
$counts['asset'] = \App\Models\Asset::count();
@@ -34,7 +34,7 @@ class DashboardController extends Controller
$counts['license'] = \App\Models\License::assetcount();
$counts['consumable'] = \App\Models\Consumable::count();
$counts['component'] = \App\Models\Component::count();
- $counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count();
+ $counts['user'] = \App\Models\Company::scopeCompanyables(auth()->user())->count();
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php
index 2d456c0a4..5818435de 100644
--- a/app/Http/Controllers/DepartmentsController.php
+++ b/app/Http/Controllers/DepartmentsController.php
@@ -4,9 +4,12 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Department;
+use App\Models\Company;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
class DepartmentsController extends Controller
{
@@ -24,10 +27,8 @@ class DepartmentsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v4.0]
* @param Request $request
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index(Request $request)
+ public function index(Request $request) : View
{
$this->authorize('index', Department::class);
$company = null;
@@ -44,15 +45,13 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\Response
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
- $department->user_id = Auth::user()->id;
+ $department->user_id = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
@@ -72,10 +71,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
$department = Department::find($id);
@@ -94,10 +91,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @see DepartmentsController::postCreate() method that validates and stores the data
* @since [v4.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Department::class);
@@ -110,10 +105,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v4.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
if (is_null($department = Department::find($id))) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found'));
@@ -129,7 +122,7 @@ class DepartmentsController extends Controller
try {
Storage::disk('public')->delete('departments'.'/'.$department->image);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
$department->delete();
@@ -144,10 +137,8 @@ class DepartmentsController extends Controller
* @see LocationsController::postCreate() method that validates and stores
* @param int $departmentId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($departmentId = null)
+ public function edit($departmentId = null) : View | RedirectResponse
{
if (is_null($item = Department::find($departmentId))) {
return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
@@ -158,7 +149,15 @@ class DepartmentsController extends Controller
return view('departments/edit', compact('item'));
}
- public function update(ImageUploadRequest $request, $id)
+ /**
+ * Save updated Department information.
+ *
+ * @author [A. Gianotto] []
+ * @see LocationsController::postCreate() method that validates and stores
+ * @param int $departmentId
+ * @since [v1.0]
+ */
+ public function update(ImageUploadRequest $request, $id) : RedirectResponse
{
if (is_null($department = Department::find($id))) {
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
diff --git a/app/Http/Controllers/DepreciationsController.php b/app/Http/Controllers/DepreciationsController.php
index 70bfb78cb..c564cc98f 100755
--- a/app/Http/Controllers/DepreciationsController.php
+++ b/app/Http/Controllers/DepreciationsController.php
@@ -5,7 +5,8 @@ namespace App\Http\Controllers;
use App\Models\Depreciation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Depreciations for
* the Snipe-IT Asset Management application.
@@ -21,14 +22,10 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [authorize('view', Depreciation::class);
-
- // Show the page
return view('depreciations/index');
}
@@ -38,10 +35,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [authorize('create', Depreciation::class);
@@ -56,10 +51,8 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postCreate()
* @since [v1.0]
* @param Request $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(Request $request)
+ public function store(Request $request) : RedirectResponse
{
$this->authorize('create', Depreciation::class);
@@ -69,6 +62,20 @@ class DepreciationsController extends Controller
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
$depreciation->user_id = Auth::id();
+
+ $request->validate([
+ 'depreciation_min' => [
+ 'required',
+ 'numeric',
+ function ($attribute, $value, $fail) use ($request) {
+ if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
+ $fail(trans('validation.percent'));
+ }
+ },
+ ],
+ 'depreciation_type' => 'required|in:amount,percent',
+ ]);
+ $depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -87,10 +94,8 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($depreciationId = null)
+ public function edit($depreciationId = null) : RedirectResponse | View
{
// Check if the depreciation exists
if (is_null($item = Depreciation::find($depreciationId))) {
@@ -110,11 +115,9 @@ class DepreciationsController extends Controller
* @see DepreciationsController::getEdit()
* @param Request $request
* @param int $depreciationId
- * @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(Request $request, $depreciationId = null)
+ public function update(Request $request, $depreciationId = null) : RedirectResponse
{
// Check if the depreciation exists
if (is_null($depreciation = Depreciation::find($depreciationId))) {
@@ -127,6 +130,20 @@ class DepreciationsController extends Controller
// Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
+
+ $request->validate([
+ 'depreciation_min' => [
+ 'required',
+ 'numeric',
+ function ($attribute, $value, $fail) use ($request) {
+ if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
+ $fail(trans('validation.percent'));
+ }
+ },
+ ],
+ 'depreciation_type' => 'required|in:amount,percent',
+ ]);
+ $depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -146,10 +163,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [find($depreciationId))) {
@@ -175,10 +190,8 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
if (is_null($depreciation = Depreciation::find($id))) {
// Redirect to the blogs management page
diff --git a/app/Http/Controllers/GoogleAuthController.php b/app/Http/Controllers/GoogleAuthController.php
index a93fcd1fc..d873491c5 100644
--- a/app/Http/Controllers/GoogleAuthController.php
+++ b/app/Http/Controllers/GoogleAuthController.php
@@ -2,13 +2,13 @@
namespace App\Http\Controllers;
-use Illuminate\Http\Request;
+use Illuminate\Http\RedirectResponse;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;
use App\Models\Setting;
-
+use Illuminate\Support\Facades\Log;
class GoogleAuthController extends Controller
{
@@ -30,13 +30,13 @@ class GoogleAuthController extends Controller
return Socialite::driver('google')->redirect();
}
- public function handleGoogleCallback()
+ public function handleGoogleCallback() : RedirectResponse
{
try {
$socialUser = Socialite::driver('google')->user();
- \Log::debug('Google user found in Google Workspace');
+ Log::debug('Google user found in Google Workspace');
} catch (InvalidStateException $exception) {
- \Log::debug('Google user NOT found in Google Workspace');
+ Log::debug('Google user NOT found in Google Workspace');
return redirect()->route('login')
->withErrors(
[
@@ -52,7 +52,7 @@ class GoogleAuthController extends Controller
if ($user) {
- \Log::debug('Google user '.$socialUser->getEmail().' found in Snipe-IT');
+ Log::debug('Google user '.$socialUser->getEmail().' found in Snipe-IT');
$user->update([
'avatar' => $socialUser->avatar,
]);
@@ -61,7 +61,7 @@ class GoogleAuthController extends Controller
return redirect()->route('home');
}
- \Log::debug('Google user '.$socialUser->getEmail().' NOT found in Snipe-IT');
+ Log::debug('Google user '.$socialUser->getEmail().' NOT found in Snipe-IT');
return redirect()->route('login')
->withErrors(
[
diff --git a/app/Http/Controllers/GroupsController.php b/app/Http/Controllers/GroupsController.php
index b98156824..a85cabf24 100755
--- a/app/Http/Controllers/GroupsController.php
+++ b/app/Http/Controllers/GroupsController.php
@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Group;
use Illuminate\Http\Request;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to User Groups for
@@ -21,11 +23,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
+ $group->created_by = auth()->id();
if ($group->save()) {
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
@@ -78,9 +77,8 @@ class GroupsController extends Controller
* @see GroupsController::postEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function edit($id)
+ public function edit($id) : View | RedirectResponse
{
$group = Group::find($id);
@@ -102,9 +100,8 @@ class GroupsController extends Controller
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function update(Request $request, $id = null)
+ public function update(Request $request, $id = null) : RedirectResponse
{
if (! $group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
@@ -130,10 +127,8 @@ class GroupsController extends Controller
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Exception
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
if (! config('app.lock_passwords')) {
if (! $group = Group::find($id)) {
@@ -152,10 +147,9 @@ class GroupsController extends Controller
*
* @author [A. Gianotto] []
* @param $id
- * @return \Illuminate\Contracts\View\View
* @since [v4.0.11]
*/
- public function show($id)
+ public function show($id) : View | RedirectResponse
{
$group = Group::find($id);
diff --git a/app/Http/Controllers/HealthController.php b/app/Http/Controllers/HealthController.php
index 456f6b6f9..c75b903b0 100644
--- a/app/Http/Controllers/HealthController.php
+++ b/app/Http/Controllers/HealthController.php
@@ -5,10 +5,13 @@ namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
/**
- * This controller provide the healthz route for
+ * This controller provide the health route for
* the Snipe-IT Asset Management application.
*
- * @version v1.0
+ * @version v1.0
+ *
+ * @return \Illuminate\Http\JsonResponse
+
*/
class HealthController extends BaseController
{
diff --git a/app/Http/Controllers/Kits/CheckoutKitController.php b/app/Http/Controllers/Kits/CheckoutKitController.php
index 7a0f93391..bf4f64a8d 100644
--- a/app/Http/Controllers/Kits/CheckoutKitController.php
+++ b/app/Http/Controllers/Kits/CheckoutKitController.php
@@ -33,7 +33,7 @@ class CheckoutKitController extends Controller
* Show Bulk Checkout Page
*
* @author [D. Minaev.] []
- * @return View View to checkout
+ * @return \Illuminate\Contracts\View\View View to checkout
*/
public function showCheckout($kit_id)
{
@@ -48,7 +48,7 @@ class CheckoutKitController extends Controller
* Validate and process the new Predefined Kit data.
*
* @author [D. Minaev.] []
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request, $kit_id)
{
@@ -62,10 +62,10 @@ class CheckoutKitController extends Controller
$checkout_result = $this->kitService->checkout($request, $kit, $user);
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
- return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
+ return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']);
}
- return redirect()->back()->with('success', trans('general.checkout_success'))
+ return redirect()->back()->with('success', trans('admin/kits/general.checkout_success'))
->with('assets', Arr::get($checkout_result, 'assets', null))
->with('accessories', Arr::get($checkout_result, 'accessories', null))
->with('consumables', Arr::get($checkout_result, 'consumables', null));
diff --git a/app/Http/Controllers/Kits/PredefinedKitsController.php b/app/Http/Controllers/Kits/PredefinedKitsController.php
index c6e5cf72a..187f5aad1 100644
--- a/app/Http/Controllers/Kits/PredefinedKitsController.php
+++ b/app/Http/Controllers/Kits/PredefinedKitsController.php
@@ -47,7 +47,7 @@ class PredefinedKitsController extends Controller
* Validate and process the new Predefined Kit data.
*
* @author [D. Minaev] []
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function store(ImageUploadRequest $request)
{
@@ -73,7 +73,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $kit_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function edit($kit_id = null)
{
@@ -95,7 +95,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $kit_id
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function update(ImageUploadRequest $request, $kit_id = null)
{
@@ -122,7 +122,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $kit_id
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse
*/
public function destroy($kit_id)
{
@@ -150,7 +150,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function show($kit_id = null)
{
@@ -162,7 +162,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $kit_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editModel($kit_id, $model_id)
{
@@ -184,7 +184,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateModel(Request $request, $kit_id, $model_id)
{
@@ -214,7 +214,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachModel($kit_id, $model_id)
{
@@ -237,7 +237,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editLicense($kit_id, $license_id)
{
@@ -262,7 +262,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateLicense(Request $request, $kit_id, $license_id)
{
@@ -293,7 +293,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachLicense($kit_id, $license_id)
{
@@ -316,7 +316,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $accessoryId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editAccessory($kit_id, $accessory_id)
{
@@ -341,7 +341,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $accessory_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateAccessory(Request $request, $kit_id, $accessory_id)
{
@@ -371,7 +371,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $accessory_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachAccessory($kit_id, $accessory_id)
{
@@ -394,7 +394,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $consumable_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editConsumable($kit_id, $consumable_id)
{
@@ -419,7 +419,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $consumableId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateConsumable(Request $request, $kit_id, $consumable_id)
{
@@ -449,7 +449,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $consumable_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachConsumable($kit_id, $consumable_id)
{
diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php
index 950094bc4..8e6ba5e2c 100755
--- a/app/Http/Controllers/LabelsController.php
+++ b/app/Http/Controllers/LabelsController.php
@@ -6,6 +6,7 @@ use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
+use App\Models\CustomField;
use App\Models\Labels\Label;
use App\Models\Location;
use App\Models\Manufacturer;
@@ -13,16 +14,14 @@ use App\Models\Setting;
use App\Models\Supplier;
use App\Models\User;
use App\View\Label as LabelView;
-use Illuminate\Support\Facades\Storage;
class LabelsController extends Controller
{
/**
* Returns the Label view with test data
*
+ * @param string $labelName
* @author Grant Le Roux
- * @param string $labelName
- * @return \Illuminate\Contracts\View\View
*/
public function show(string $labelName)
{
@@ -41,29 +40,43 @@ class LabelsController extends Controller
$exampleAsset->status_id = 1;
$exampleAsset->company = new Company([
- 'name' => 'Test Company Limited',
+ 'name' => trans('admin/labels/table.example_company'),
'phone' => '1-555-555-5555',
'email' => 'company@example.com',
]);
$exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker']));
- $exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']);
- $exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']);
+ $exampleAsset->defaultLoc = new Location(['name' => trans('admin/labels/table.example_defaultloc'), 'phone' => '1-555-555-5555']);
+ $exampleAsset->location = new Location(['name' => trans('admin/labels/table.example_location'), 'phone' => '1-555-555-5555']);
$exampleAsset->model = new AssetModel();
$exampleAsset->model->id = 999999;
- $exampleAsset->model->name = 'Test Model';
+ $exampleAsset->model->name = trans('admin/labels/table.example_model');
$exampleAsset->model->model_number = 'MDL5678';
$exampleAsset->model->manufacturer = new Manufacturer();
$exampleAsset->model->manufacturer->id = 999999;
- $exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
+ $exampleAsset->model->manufacturer->name = trans('admin/labels/table.example_manufacturer');
$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->supplier = new Supplier(['name' => trans('admin/labels/table.example_company')]);
$exampleAsset->model->category = new Category();
$exampleAsset->model->category->id = 999999;
- $exampleAsset->model->category->name = 'Test Category';
+ $exampleAsset->model->category->name = trans('admin/labels/table.example_category');
+
+ $customFieldColumns = CustomField::where('field_encrypted', '=', 0)->pluck('db_column');
+
+ collect(explode(';', Setting::getSettings()->label2_fields))
+ ->filter()
+ ->each(function ($item) use ($customFieldColumns, $exampleAsset) {
+ $pair = explode('=', $item);
+
+ if (array_key_exists(1, $pair)) {
+ if ($customFieldColumns->contains($pair[1])) {
+ $exampleAsset->{$pair[1]} = "{{$pair[0]}}";
+ }
+ }
+ });
$settings = Setting::getSettings();
if (request()->has('settings')) {
@@ -80,6 +93,5 @@ class LabelsController extends Controller
->with('bulkedit', false)
->with('count', 0);
- return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist'));
}
}
diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php
index 367ff3f1d..dd83d0154 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckinController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\License;
use App\Models\LicenseSeat;
@@ -13,6 +14,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Facades\Log;
class LicenseCheckinController extends Controller
{
@@ -99,15 +101,15 @@ class LicenseCheckinController extends Controller
$licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes');
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
+
// Was the asset updated?
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
+ event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
- if ($backTo == 'user') {
- return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
- }
- return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
}
// Redirect to the license page with error
@@ -145,7 +147,7 @@ class LicenseCheckinController extends Controller
$user_seat->assigned_to = null;
if ($user_seat->save()) {
- \Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
+ Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
}
}
@@ -160,7 +162,7 @@ class LicenseCheckinController extends Controller
$asset_seat->asset_id = null;
if ($asset_seat->save()) {
- \Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
+ Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
$count++;
}
diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
index 07ca8bbd5..c08980fc0 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
@@ -3,13 +3,16 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\LicenseCheckoutRequest;
+use App\Models\Accessory;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Log;
class LicenseCheckoutController extends Controller
{
@@ -21,23 +24,35 @@ class LicenseCheckoutController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @param $licenseId
+ * @param $id
* @return \Illuminate\Contracts\View\View
* @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);
- // If the license is valid, check that there is an available seat
- if ($license->avail_seats_count < 1) {
- return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
+
+ if ($license->category) {
+
+ // 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'));
@@ -67,10 +82,27 @@ class LicenseCheckoutController extends Controller
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
- if ($this->$checkoutMethod($licenseSeat)) {
- return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success'));
+
+ if ($request->filled('asset_id')) {
+
+ $checkoutTarget = $this->checkoutToAsset($licenseSeat);
+ $request->request->add(['assigned_asset' => $checkoutTarget->id]);
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
+
+ } elseif ($request->filled('assigned_to')) {
+ $checkoutTarget = $this->checkoutToUser($licenseSeat);
+ $request->request->add(['assigned_user' => $checkoutTarget->id]);
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
}
+
+
+ if ($checkoutTarget) {
+ return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
+ }
+
+
+
return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.'));
}
@@ -80,14 +112,14 @@ class LicenseCheckoutController extends Controller
if (! $licenseSeat) {
if ($seatId) {
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable')));
}
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
}
if (! $licenseSeat->license->is($license)) {
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
}
return $licenseSeat;
@@ -105,9 +137,8 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
-
- return true;
+ event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
+ return $target;
}
return false;
@@ -122,9 +153,8 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
-
- return true;
+ event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
+ return $target;
}
return false;
@@ -142,16 +172,16 @@ class LicenseCheckoutController extends Controller
public function bulkCheckout($licenseId) {
- \Log::debug('Checking out '.$licenseId.' via bulk');
+ Log::debug('Checking out '.$licenseId.' via bulk');
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
$avail_count = $license->getAvailSeatsCountAttribute();
$users = User::whereNull('deleted_at')->where('autoassign_licenses', '=', 1)->with('licenses')->get();
- \Log::debug($avail_count.' will be assigned');
+ Log::debug($avail_count.' will be assigned');
if ($users->count() > $avail_count) {
- \Log::debug('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
+ Log::debug('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
}
// If the license is valid, check that there is an available seat
@@ -166,7 +196,7 @@ class LicenseCheckoutController extends Controller
// Check to make sure this user doesn't already have this license checked out to them
if ($user->licenses->where('id', '=', $licenseId)->count()) {
- \Log::debug($user->username.' already has this license checked out to them. Skipping... ');
+ Log::debug($user->username.' already has this license checked out to them. Skipping... ');
continue;
}
@@ -179,7 +209,7 @@ class LicenseCheckoutController extends Controller
$avail_count--;
$assigned_count++;
$licenseSeat->logCheckout(trans('admin/licenses/general.bulk.checkout_all.log_msg'), $user);
- \Log::debug('License '.$license->name.' seat '.$licenseSeat->id.' checked out to '.$user->username);
+ Log::debug('License '.$license->name.' seat '.$licenseSeat->id.' checked out to '.$user->username);
}
if ($avail_count == 0) {
diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php
index f6f7c1ad0..fa18e8cf4 100644
--- a/app/Http/Controllers/Licenses/LicenseFilesController.php
+++ b/app/Http/Controllers/Licenses/LicenseFilesController.php
@@ -4,28 +4,26 @@ namespace App\Http\Controllers\Licenses;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
-use App\Http\Requests\AssetFileRequest;
+use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\License;
-use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use enshrined\svgSanitize\Sanitizer;
+use Illuminate\Support\Facades\Log;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
- * @todo Switch to using the AssetFileRequest form request validator.
- * @author [A. Gianotto] []
- * @since [v1.0]
- * @param AssetFileRequest $request
+ * @param UploadFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
+ * @author [A. Gianotto] []
+ * @since [v1.0]
+ * @todo Switch to using the AssetFileRequest form request validator.
*/
- public function store(AssetFileRequest $request, $licenseId = null)
+ public function store(UploadFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
@@ -38,30 +36,7 @@ class LicenseFilesController extends Controller
}
foreach ($request->file('file') as $file) {
-
- $extension = $file->getClientOriginalExtension();
- $file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
-
-
- // Check for SVG and sanitize it
- if ($extension == 'svg') {
- \Log::debug('This is an SVG');
- \Log::debug($file_name);
-
- $sanitizer = new Sanitizer();
- $dirtySVG = file_get_contents($file->getRealPath());
- $cleanSVG = $sanitizer->sanitize($dirtySVG);
-
- try {
- Storage::put('private_uploads/licenses/'.$file_name, $cleanSVG);
- } catch (\Exception $e) {
- \Log::debug('Upload no workie :( ');
- \Log::debug($e);
- }
-
- } else {
- Storage::put('private_uploads/licenses/'.$file_name, file_get_contents($file));
- }
+ $file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
@@ -102,7 +77,7 @@ class LicenseFilesController extends Controller
try {
Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -145,8 +120,8 @@ class LicenseFilesController extends Controller
$file = 'private_uploads/licenses/'.$log->filename;
if (Storage::missing($file)) {
- \Log::debug('NOT EXISTS for '.$file);
- \Log::debug('NOT EXISTS URL should be '.Storage::url($file));
+ Log::debug('NOT EXISTS for '.$file);
+ Log::debug('NOT EXISTS URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php
index 02e216320..7a51344dd 100755
--- a/app/Http/Controllers/Licenses/LicensesController.php
+++ b/app/Http/Controllers/Licenses/LicensesController.php
@@ -11,6 +11,7 @@ use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
+use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* This controller handles all actions related to Licenses for
@@ -99,9 +100,12 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
+ $license->min_amt = $request->input('min_amt');
+
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
- return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($license->getErrors());
@@ -176,9 +180,12 @@ class LicensesController extends Controller
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
+ $license->min_amt = $request->input('min_amt');
+
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
- return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success'));
}
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors());
@@ -245,12 +252,6 @@ class LicensesController extends Controller
$available_seats_count = $license->availCount()->count();
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
- \Log::debug('Total: '.$total_seats_count);
- \Log::debug('Users: '.$users_count);
- \Log::debug('Available: '.$available_seats_count);
- \Log::debug('Checkedout: '.$checkedout_seats_count);
-
-
$this->authorize('view', $license);
return view('licenses.view', compact('license'))
->with('users_count', $users_count)
@@ -293,4 +294,106 @@ class LicensesController extends Controller
->with('item', $license)
->with('maintained_list', $maintained_list);
}
+
+ /**
+ * Exports Licenses to CSV
+ *
+ * @author [G. Martinez]
+ * @since [v6.3]
+ * @return StreamedResponse
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ */
+ public function getExportLicensesCsv()
+ {
+ $this->authorize('view', License::class);
+ \Debugbar::disable();
+
+ $response = new StreamedResponse(function () {
+ // Open output stream
+ $handle = fopen('php://output', 'w');
+ $licenses= License::with('company',
+ 'manufacturer',
+ 'category',
+ 'supplier',
+ 'adminuser',
+ 'assignedusers')
+ ->orderBy('created_at', 'DESC');
+ Company::scopeCompanyables($licenses)
+ ->chunk(500, function ($licenses) use ($handle) {
+ $headers = [
+ // strtolower to prevent Excel from trying to open it as a SYLK file
+ strtolower(trans('general.id')),
+ trans('general.company'),
+ trans('general.name'),
+ trans('general.serial_number'),
+ trans('general.purchase_date'),
+ trans('general.purchase_cost'),
+ trans('general.order_number'),
+ trans('general.licenses_available'),
+ trans('admin/licenses/table.seats'),
+ trans('general.created_by'),
+ trans('general.depreciation'),
+ trans('general.updated_at'),
+ trans('admin/licenses/table.deleted_at'),
+ trans('general.email'),
+ trans('admin/hardware/form.fully_depreciated'),
+ trans('general.supplier'),
+ trans('admin/licenses/form.expiration'),
+ trans('admin/licenses/form.purchase_order'),
+ trans('admin/licenses/form.termination_date'),
+ trans('admin/licenses/form.maintained'),
+ trans('general.manufacturer'),
+ trans('general.category'),
+ trans('general.min_amt'),
+ trans('admin/licenses/form.reassignable'),
+ trans('general.notes'),
+ trans('general.created_at'),
+ ];
+
+ fputcsv($handle, $headers);
+
+ foreach ($licenses as $license) {
+ // Add a new row with data
+ $values = [
+ $license->id,
+ $license->company ? $license->company->name: '',
+ $license->name,
+ $license->serial,
+ $license->purchase_date,
+ $license->purchase_cost,
+ $license->order_number,
+ $license->free_seat_count,
+ $license->seats,
+ ($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')),
+ $license->depreciation ? $license->depreciation->name: '',
+ $license->updated_at,
+ $license->deleted_at,
+ $license->email,
+ ( $license->depreciate == '1') ? trans('general.yes') : trans('general.no'),
+ ($license->supplier) ? $license->supplier->name: '',
+ $license->expiration_date,
+ $license->purchase_order,
+ $license->termination_date,
+ ( $license->maintained == '1') ? trans('general.yes') : trans('general.no'),
+ $license->manufacturer ? $license->manufacturer->name: '',
+ $license->category ? $license->category->name: '',
+ $license->min_amt,
+ ( $license->reassignable == '1') ? trans('general.yes') : trans('general.no'),
+ $license->notes,
+ $license->created_at,
+ ];
+
+ fputcsv($handle, $values);
+ }
+ });
+
+ // Close the output stream
+ fclose($handle);
+ }, 200, [
+ 'Content-Type' => 'text/csv; charset=UTF-8',
+ 'Content-Disposition' => 'attachment; filename="licenses-'.date('Y-m-d-his').'.csv"',
+ ]);
+
+ return $response;
+ }
}
diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php
index 08dc38b3a..c498f0992 100755
--- a/app/Http/Controllers/LocationsController.php
+++ b/app/Http/Controllers/LocationsController.php
@@ -6,9 +6,11 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
-
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Locations for
* the Snipe-IT Asset Management application.
@@ -24,10 +26,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @see LocationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
// Grab all the locations
$this->authorize('view', Location::class);
@@ -41,10 +41,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @see LocationsController::postCreate() method that validates and stores the data
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Location::class);
@@ -60,10 +58,8 @@ class LocationsController extends Controller
* @see LocationsController::getCreate() method that makes the form
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Location::class);
$location = new Location();
@@ -78,7 +74,7 @@ class LocationsController extends Controller
$location->zip = $request->input('zip');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
- $location->user_id = Auth::id();
+ $location->user_id = auth()->id();
$location->phone = request('phone');
$location->fax = request('fax');
@@ -98,10 +94,8 @@ class LocationsController extends Controller
* @see LocationsController::postCreate() method that validates and stores
* @param int $locationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($locationId = null)
+ public function edit($locationId = null) : View | RedirectResponse
{
$this->authorize('update', Location::class);
// Check if the location exists
@@ -119,11 +113,9 @@ class LocationsController extends Controller
* @see LocationsController::getEdit() method that makes the form view
* @param ImageUploadRequest $request
* @param int $locationId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $locationId = null)
+ public function update(ImageUploadRequest $request, $locationId = null) : RedirectResponse
{
$this->authorize('update', Location::class);
// Check if the location exists
@@ -161,14 +153,12 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($locationId)
+ public function destroy($locationId) : RedirectResponse
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
- return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
+ return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($location->users()->count() > 0) {
@@ -185,7 +175,7 @@ class LocationsController extends Controller
try {
Storage::disk('public')->delete('locations/'.$location->image);
} catch (\Exception $e) {
- \Log::error($e);
+ Log::error($e);
}
}
$location->delete();
@@ -200,9 +190,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function show($id = null)
+ public function show($id = null) : View | RedirectResponse
{
$location = Location::find($id);
@@ -213,7 +202,7 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
- public function print_assigned($id)
+ public function print_assigned($id) : View | RedirectResponse
{
if ($location = Location::where('id', $id)->first()) {
@@ -238,9 +227,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v6.0.14]
- * @return View
*/
- public function getClone($locationId = null)
+ public function getClone($locationId = null) : View | RedirectResponse
{
$this->authorize('create', Location::class);
@@ -261,7 +249,7 @@ class LocationsController extends Controller
}
- public function print_all_assigned($id)
+ public function print_all_assigned($id) : View | RedirectResponse
{
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
@@ -272,8 +260,102 @@ class LocationsController extends Controller
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
+ }
+ /**
+ * Returns a view that allows the user to bulk delete locations
+ *
+ * @author [A. Gianotto] []
+ * @since [v6.3.1]
+ */
+ public function postBulkDelete(Request $request) : View | RedirectResponse
+ {
+ $locations_raw_array = $request->input('ids');
+
+ // Make sure some IDs have been selected
+ if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
+ $locations = Location::whereIn('id', $locations_raw_array)
+ ->withCount('assignedAssets as assigned_assets_count')
+ ->withCount('assets as assets_count')
+ ->withCount('rtd_assets as rtd_assets_count')
+ ->withCount('children as children_count')
+ ->withCount('users as users_count')->get();
+
+ $valid_count = 0;
+ foreach ($locations as $location) {
+ if ($location->isDeletable()) {
+ $valid_count++;
+ }
+ }
+ return view('locations/bulk-delete', compact('locations'))->with('valid_count', $valid_count);
+ }
+
+ return redirect()->route('models.index')
+ ->with('error', 'You must select at least one model to edit.');
+ }
+
+ /**
+ * Checks that locations can be deleted and deletes them if they can
+ *
+ * @author [A. Gianotto] []
+ * @since [v6.3.1]
+
+ */
+ public function postBulkDeleteStore(Request $request) : RedirectResponse
+ {
+ $locations_raw_array = $request->input('ids');
+
+ if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
+ $locations = Location::whereIn('id', $locations_raw_array)
+ ->withCount('assignedAssets as assigned_assets_count')
+ ->withCount('assets as assets_count')
+ ->withCount('rtd_assets as rtd_assets_count')
+ ->withCount('children as children_count')
+ ->withCount('users as users_count')->get();
+
+ $success_count = 0;
+ $error_count = 0;
+
+ foreach ($locations as $location) {
+
+ // Can we delete this location?
+ if ($location->isDeletable()) {
+ $location->delete();
+ $success_count++;
+ } else {
+ $error_count++;
+ }
+ }
+
+ Log::debug('Success count: '.$success_count);
+ Log::debug('Error count: '.$error_count);
+ // Complete success
+ if ($success_count == count($locations_raw_array)) {
+ return redirect()
+ ->route('locations.index')
+ ->with('success', trans_choice('general.bulk.delete.success', $success_count,
+ ['object_type' => trans_choice('general.location_plural', $success_count), 'count' => $success_count]
+ ));
+ }
+
+ // Partial success
+ if ($error_count > 0) {
+ return redirect()
+ ->route('locations.index')
+ ->with('warning', trans('general.bulk.delete.partial',
+ ['success' => $success_count, 'error' => $error_count, 'object_type' => trans('general.locations')]
+ ));
+ }
+ }
+
+
+ // Nothing was selected - return to the index
+ return redirect()
+ ->route('locations.index')
+ ->with('error', trans('general.bulk.nothing_selected',
+ ['object_type' => trans('general.locations')]
+ ));
}
}
diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php
index e98644f46..8e979e389 100755
--- a/app/Http/Controllers/ManufacturersController.php
+++ b/app/Http/Controllers/ManufacturersController.php
@@ -3,11 +3,14 @@
namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
+use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
-use Redirect;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Manufacturers for
@@ -24,13 +27,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('index', Manufacturer::class);
-
return view('manufacturers/index');
}
@@ -40,10 +40,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @see ManufacturersController::store()
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Manufacturer::class);
@@ -57,10 +55,8 @@ class ManufacturersController extends Controller
* @see ManufacturersController::create()
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
@@ -87,10 +83,8 @@ class ManufacturersController extends Controller
* @see ManufacturersController::update()
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($manufacturerId = null)
+ public function edit($manufacturerId = null) : View | RedirectResponse
{
// Handles manufacturer checks and permissions.
$this->authorize('update', Manufacturer::class);
@@ -111,11 +105,9 @@ class ManufacturersController extends Controller
* @see ManufacturersController::getEdit()
* @param Request $request
* @param int $manufacturerId
- * @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $manufacturerId = null)
+ public function update(ImageUploadRequest $request, $manufacturerId = null) : RedirectResponse
{
$this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists
@@ -152,10 +144,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($manufacturerId)
+ public function destroy($manufacturerId) : RedirectResponse
{
$this->authorize('delete', Manufacturer::class);
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
@@ -170,7 +160,7 @@ class ManufacturersController extends Controller
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
- \Log::info($e);
+ Log::info($e);
}
}
@@ -192,10 +182,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($manufacturerId = null)
+ public function show($manufacturerId = null) : View | RedirectResponse
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::find($manufacturerId);
@@ -215,25 +203,38 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.1.15]
* @param int $manufacturers_id
- * @return Redirect
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function restore($manufacturers_id)
+ public function restore($id) : RedirectResponse
{
- $this->authorize('create', Manufacturer::class);
- $manufacturer = Manufacturer::onlyTrashed()->where('id', $manufacturers_id)->first();
+ $this->authorize('delete', Manufacturer::class);
- 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()) {
+ $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()->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()->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'));
+
}
}
diff --git a/app/Http/Controllers/ModalController.php b/app/Http/Controllers/ModalController.php
index 6f6b39dd1..fab491a5f 100644
--- a/app/Http/Controllers/ModalController.php
+++ b/app/Http/Controllers/ModalController.php
@@ -15,7 +15,7 @@ class ModalController extends Controller
* @version v5.3.7-pre
* @author [Brady Wetherington] []
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function getIndex()
+ public function getIndex() : View
{
- $user = Auth::user();
-
+ $this->authorize('self.profile');
+ $user = auth()->user();
return view('account/profile', compact('user'));
}
@@ -43,20 +38,22 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function postIndex(ImageUploadRequest $request)
+ public function postIndex(ImageUploadRequest $request) : RedirectResponse
{
- $user = Auth::user();
+ $this->authorize('self.profile');
+ $user = auth()->user();
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->skin = $request->input('skin');
$user->phone = $request->input('phone');
+ $user->enable_sounds = $request->input('enable_sounds', false);
+ $user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) {
- $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')))) {
@@ -72,7 +69,7 @@ class ProfileController extends Controller
if ($user->save()) {
- return redirect()->route('profile')->with('success', 'Account successfully updated');
+ return redirect()->route('profile')->with('success', trans('account/general.profile_updated'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
@@ -87,11 +84,9 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function api()
+ public function api(): View
{
-
// Make sure the self.api permission has been granted
if (!Gate::allows('self.api')) {
abort(403);
@@ -103,27 +98,23 @@ class ProfileController extends Controller
/**
* User change email page.
*
- * @return View
*/
- public function password()
+ public function password() : View
{
- $user = Auth::user();
-
+ $user = auth()->user();
return view('account/change-password', compact('user'));
}
/**
* Users change password form processing page.
- *
- * @return Redirect
*/
- public function passwordSave(Request $request)
+ public function passwordSave(Request $request) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords'));
}
- $user = Auth::user();
+ $user = auth()->user();
if ($user->ldap_import == '1') {
return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap'));
}
@@ -134,6 +125,7 @@ class ProfileController extends Controller
];
$validator = \Validator::make($request->all(), $rules);
+
$validator->after(function ($validator) use ($request, $user) {
if (! Hash::check($request->input('current_password'), $user->password)) {
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
@@ -159,12 +151,14 @@ class ProfileController extends Controller
});
if (! $validator->fails()) {
- $user->password = Hash::make($request->input('password'));
- $user->save();
+ $user->password = Hash::make($request->input('password'));
+ // 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
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);
@@ -181,9 +175,8 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function getMenuState(Request $request)
+ public function getMenuState(Request $request) : void
{
if ($request->input('state') == 'open') {
$request->session()->put('menu_state', 'open');
@@ -198,14 +191,13 @@ class ProfileController extends Controller
*
* @author A. Gianotto
* @since [v6.0.12]
- * @return Illuminate\View\View
*/
- public function printInventory()
+ public function printInventory() : View
{
- $show_user = Auth::user();
+ $show_user = auth()->user();
return view('users/print')
- ->with('assets', Auth::user()->assets)
+ ->with('assets', auth()->user()->assets)
->with('licenses', $show_user->licenses()->get())
->with('accessories', $show_user->accessories()->get())
->with('consumables', $show_user->consumables()->get())
@@ -218,12 +210,11 @@ class ProfileController extends Controller
*
* @author A. Gianotto
* @since [v6.0.12]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function emailAssetList()
+ public function emailAssetList() : RedirectResponse
{
- if (!$user = User::find(Auth::user()->id)) {
+ if (!$user = User::find(auth()->id())) {
return redirect()->back()
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
}
diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php
index ea036e8d8..105dac635 100644
--- a/app/Http/Controllers/ReportsController.php
+++ b/app/Http/Controllers/ReportsController.php
@@ -6,6 +6,8 @@ use App\Helpers\Helper;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
+use App\Models\AssetModel;
+use App\Models\Category;
use App\Models\AssetMaintenance;
use App\Models\CheckoutAcceptance;
use App\Models\CustomField;
@@ -14,17 +16,16 @@ use App\Models\License;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
-use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
+use Illuminate\Http\Response;
use Illuminate\Support\Facades\Notification;
-use Illuminate\Support\Facades\Response;
-use Illuminate\Support\Facades\View;
-use Input;
+use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
-
+use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
/**
* This controller handles all actions related to Reports for
@@ -47,9 +48,9 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function getAccessoryReport()
+ public function getAccessoryReport() : View
{
$this->authorize('reports.view');
@@ -65,7 +66,7 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
- public function exportAccessoryReport()
+ public function exportAccessoryReport() : Response
{
$this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->get();
@@ -92,7 +93,7 @@ class ReportsController extends Controller
}
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -104,9 +105,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getDeprecationReport()
+ public function getDeprecationReport() : View
{
$this->authorize('reports.view');
$depreciations = Depreciation::get();
@@ -119,9 +119,8 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function exportDeprecationReport()
+ public function exportDeprecationReport() : Response
{
$this->authorize('reports.view');
// Grab all the assets
@@ -197,12 +196,10 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function audit()
+ public function audit() : View
{
$this->authorize('reports.view');
-
return view('reports/audit');
}
@@ -212,9 +209,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getActivityReport()
+ public function getActivityReport() : View
{
$this->authorize('reports.view');
@@ -226,16 +222,15 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v5.0.7]
- * @return \Illuminate\Http\Response
*/
- public function postActivityReport(Request $request)
+ public function postActivityReport(Request $request) : StreamedResponse
{
ini_set('max_execution_time', 12000);
$this->authorize('reports.view');
\Debugbar::disable();
$response = new StreamedResponse(function () {
- \Log::debug('Starting streamed response');
+ Log::debug('Starting streamed response');
// Open output stream
$handle = fopen('php://output', 'w');
@@ -252,20 +247,23 @@ class ReportsController extends Controller
trans('general.model_no'),
'To',
trans('general.notes'),
+ trans('admin/settings/general.login_ip'),
+ trans('admin/settings/general.login_user_agent'),
+ trans('general.action_source'),
'Changed',
];
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Starting headers: '.$executionTime);
+ Log::debug('Starting headers: '.$executionTime);
fputcsv($handle, $header);
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Added headers: '.$executionTime);
+ Log::debug('Added headers: '.$executionTime);
$actionlogs = Actionlog::with('item', 'user', 'target', 'location')
->orderBy('created_at', 'DESC')
->chunk(20, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Walking results: '.$executionTime);
+ Log::debug('Walking results: '.$executionTime);
$count = 0;
foreach ($actionlogs as $actionlog) {
@@ -292,12 +290,15 @@ class ReportsController extends Controller
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($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,
+ ($actionlog->item) ? $actionlog->item->serial : null,
+ (($actionlog->item) && ($actionlog->item->model)) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
+ (($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
+ $actionlog->remote_ip,
+ $actionlog->user_agent,
+ $actionlog->action_source,
];
fputcsv($handle, $row);
}
@@ -306,7 +307,7 @@ class ReportsController extends Controller
// Close the output stream
fclose($handle);
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('-- SCRIPT COMPLETED IN '.$executionTime);
+ Log::debug('-- SCRIPT COMPLETED IN '.$executionTime);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="activity-report-'.date('Y-m-d-his').'.csv"',
@@ -322,9 +323,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getLicenseReport()
+ public function getLicenseReport() : View
{
$this->authorize('reports.view');
$licenses = License::with('depreciation')->orderBy('created_at', 'DESC')
@@ -340,9 +340,8 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function exportLicenseReport()
+ public function exportLicenseReport() : Response
{
$this->authorize('reports.view');
$licenses = License::orderBy('created_at', 'DESC')->get();
@@ -379,7 +378,7 @@ class ReportsController extends Controller
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -392,9 +391,8 @@ class ReportsController extends Controller
* @author [A. Gianotto] []
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function getCustomReport()
+ public function getCustomReport() : View
{
$this->authorize('reports.view');
$customfields = CustomField::get();
@@ -408,9 +406,8 @@ class ReportsController extends Controller
* @author [A. Gianotto] []
* @see ReportsController::getCustomReport() method that generates form view
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function postCustom(CustomAssetReportRequest $request)
+ public function postCustom(CustomAssetReportRequest $request) : StreamedResponse
{
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
@@ -419,8 +416,8 @@ class ReportsController extends Controller
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
- \Log::debug('Starting streamed response');
- \Log::debug('CSV escaping is set to: '.config('app.escape_formulas'));
+ Log::debug('Starting streamed response');
+ Log::debug('CSV escaping is set to: '.config('app.escape_formulas'));
// Open output stream
$handle = fopen('php://output', 'w');
@@ -610,7 +607,7 @@ class ReportsController extends Controller
}
if ($request->filled('url')) {
- $header[] = trans('admin/manufacturers/table.url');
+ $header[] = trans('general.url');
}
@@ -621,10 +618,10 @@ class ReportsController extends Controller
}
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Starting headers: '.$executionTime);
+ Log::debug('Starting headers: '.$executionTime);
fputcsv($handle, $header);
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Added headers: '.$executionTime);
+ Log::debug('Added headers: '.$executionTime);
$assets = Asset::select('assets.*')->with(
'location', 'assetstatus', 'company', 'defaultLoc', 'assignedTo',
@@ -675,25 +672,32 @@ class ReportsController extends Controller
}
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
- $created_start = \Carbon::parse($request->input('created_start'))->startOfDay();
- $created_end = \Carbon::parse($request->input('created_end'))->endOfDay();
+ $created_start = Carbon::parse($request->input('created_start'))->startOfDay();
+ $created_end = Carbon::parse($request->input('created_end'))->endOfDay();
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
}
- if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
- $checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
- $checkout_end = \Carbon::parse($request->input('checkout_date_end'))->endOfDay();
- $assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
+ if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
+ $checkout_start = Carbon::parse($request->input('checkout_date_start'))->startOfDay();
+ $checkout_end = Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
+
+ $actionlogassets = Actionlog::where('action_type','=', 'checkout')
+ ->where('item_type', 'LIKE', '%Asset%',)
+ ->whereBetween('action_date',[$checkout_start, $checkout_end])
+ ->pluck('item_id');
+
+ $assets->whereIn('assets.id',$actionlogassets);
}
if (($request->filled('checkin_date_start'))) {
- $assets->whereBetween('last_checkin', [
- Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
+ $checkin_start = Carbon::parse($request->input('checkin_date_start'))->startOfDay();
// use today's date is `checkin_date_end` is not provided
- Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
- ]);
+ $checkin_end = Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
+
+ $assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]);
}
+ //last checkin is exporting, but currently is a date and not a datetime in the custom report ONLY.
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')]);
@@ -704,8 +708,8 @@ class ReportsController extends Controller
}
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
- $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
- $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
+ $last_audit_start = Carbon::parse($request->input('last_audit_start'))->startOfDay();
+ $last_audit_end = Carbon::parse($request->input('last_audit_end'))->endOfDay();
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
}
@@ -716,18 +720,18 @@ class ReportsController extends Controller
if ($request->filled('exclude_archived')) {
$assets->notArchived();
}
- if ($request->input('deleted_assets') == '1') {
+ if ($request->input('deleted_assets') == 'include_deleted') {
$assets->withTrashed();
}
- if ($request->input('deleted_assets') == '0') {
+ if ($request->input('deleted_assets') == 'only_deleted') {
$assets->onlyTrashed();
}
- \Log::debug($assets->toSql());
+ Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('Walking results: '.$executionTime);
+ Log::debug('Walking results: '.$executionTime);
$count = 0;
$formatter = new EscapeFormula("`");
@@ -924,7 +928,7 @@ class ReportsController extends Controller
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
- $row[] = ($asset->depreciation) ? $asset->depreciated_date()->format('Y-m-d') : '';
+ $row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('checkout_date')) {
@@ -987,14 +991,14 @@ class ReportsController extends Controller
}
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('-- Record '.$count.' Asset ID:'.$asset->id.' in '.$executionTime);
+ Log::debug('-- Record '.$count.' Asset ID:'.$asset->id.' in '.$executionTime);
}
});
// Close the output stream
fclose($handle);
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
- \Log::debug('-- SCRIPT COMPLETED IN '.$executionTime);
+ Log::debug('-- SCRIPT COMPLETED IN '.$executionTime);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="custom-assets-report-'.date('Y-m-d-his').'.csv"',
@@ -1006,11 +1010,10 @@ class ReportsController extends Controller
/**
* getImprovementsReport
*
- * @return View
* @author Vincent Sposato
* @version v1.0
*/
- public function getAssetMaintenancesReport()
+ public function getAssetMaintenancesReport() : View
{
$this->authorize('reports.view');
@@ -1020,11 +1023,10 @@ class ReportsController extends Controller
/**
* exportImprovementsReport
*
- * @return \Illuminate\Http\Response
* @author Vincent Sposato
* @version v1.0
*/
- public function exportAssetMaintenancesReport()
+ public function exportAssetMaintenancesReport() : Response
{
$this->authorize('reports.view');
// Grab all the improvements
@@ -1071,7 +1073,7 @@ class ReportsController extends Controller
// spit out a csv
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -1081,13 +1083,10 @@ class ReportsController extends Controller
/**
* getAssetAcceptanceReport
*
- * @return mixed
- * @throws \Illuminate\Auth\Access\AuthorizationException
-
* @author Vincent Sposato