diff --git a/.all-contributorsrc b/.all-contributorsrc
index 41e7063e30..146f6178ce 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",
@@ -2961,6 +2964,60 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "Singrity",
+ "name": "Bogdan",
+ "avatar_url": "https://avatars.githubusercontent.com/u/58479551?v=4",
+ "profile": "http://@singrity",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "mmanjos",
+ "name": "mmanjos",
+ "avatar_url": "https://avatars.githubusercontent.com/u/3483684?v=4",
+ "profile": "https://github.com/mmanjos",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Azooz2014",
+ "name": "Abdelaziz Faki",
+ "avatar_url": "https://avatars.githubusercontent.com/u/7429229?v=4",
+ "profile": "https://azooz2014.github.io/",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "bilias",
+ "name": "bilias",
+ "avatar_url": "https://avatars.githubusercontent.com/u/47315739?v=4",
+ "profile": "https://github.com/bilias",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "coach1988",
+ "name": "coach1988",
+ "avatar_url": "https://avatars.githubusercontent.com/u/2565989?v=4",
+ "profile": "https://github.com/coach1988",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "mauro-miatello",
+ "name": "MrM",
+ "avatar_url": "https://avatars.githubusercontent.com/u/11910225?v=4",
+ "profile": "https://github.com/mauro-miatello",
+ "contributions": [
+ "code"
+ ]
}
]
}
diff --git a/.chipperci.yml b/.chipperci.yml
deleted file mode 100644
index 0c18b253c9..0000000000
--- a/.chipperci.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-version: 1
-
-environment:
- php: 8.0
- node: 12
-
-services:
- - mysql: 5.7
- - dusk:
-
-on:
- push:
- branches:
- - master
- - develop
-
- pull_request:
- branches: .*
-
-pipeline:
- - name: Setup
- cmd: |
- cp -v .env.testing.example .env
- cp -v .env.testing.example .env.testing
- composer install --no-interaction --prefer-dist --optimize-autoloader
-
- - name: Generate Key
- cmd: |
- php artisan key:generate --force
-
- - name: Passport Keys
- cmd: |
- php artisan passport:keys
-
- - name: Run Migrations
- cmd: |
- php artisan migrate --force
-
- - name: PHPUnit Unit Tests
- cmd: |
- php artisan test --testsuite Unit
-
- - name: PHPUnit Feature Tests
- cmd: |
- php artisan test --testsuite Feature
-
-# - name: Browser Tests
-# cmd: |
-# cp -v .env.dusk.example .env.dusk.ci
-# sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci
-# sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
-# #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci
-# sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci
-# sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci
-# sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci
-# sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci
-#
-# php -S [::0]:8000 -t public 2>server.log &
-# sleep 2
-# php artisan dusk:chrome-driver $CHROME_DRIVER
-# php artisan dusk --env=ci
diff --git a/.env.docker b/.env.docker
index 8c47c801e2..87897b10db 100644
--- a/.env.docker
+++ b/.env.docker
@@ -159,6 +159,7 @@ 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.example b/.env.example
index 2d45ff580e..f8e1df2987 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
# --------------------------------------------
diff --git a/.env.testing-ci b/.env.testing-ci
index 82cd285700..f17a5c6c30 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
# --------------------------------------------
diff --git a/.env.testing.example b/.env.testing.example
index 3391d62726..26211f95c3 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/.github/autolabeler.yml b/.github/autolabeler.yml
index a27d5fe5fc..60968e7cbf 100644
--- a/.github/autolabeler.yml
+++ b/.github/autolabeler.yml
@@ -18,5 +18,5 @@ importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views
cli / artisan: ["/app/Console/*"]
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
-tests: ["/tests/*", "/stubs"]
+tests: ["/tests/*", "/database/factories/*", "/stubs"]
config: .github
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 5ace4600a1..2b4c26c119 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -2,5 +2,6 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
+ target-branch: "develop"
schedule:
interval: "weekly"
diff --git a/.github/workflows/SA-codeql.yml b/.github/workflows/SA-codeql.yml
index acf65d5c16..29f3e1b1f1 100644
--- a/.github/workflows/SA-codeql.yml
+++ b/.github/workflows/SA-codeql.yml
@@ -26,14 +26,14 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4
# 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 758838307d..6b560b3cd2 100644
--- a/.github/workflows/codacy-analysis.yml
+++ b/.github/workflows/codacy-analysis.yml
@@ -32,7 +32,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
@@ -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 c986accf37..3255934534 100644
--- a/.github/workflows/crowdin-upload.yml
+++ b/.github/workflows/crowdin-upload.yml
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Crowdin push
uses: crowdin/github-action@v1
diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml
index 0a5c28ee53..7223ab30dc 100644
--- a/.github/workflows/docker-alpine.yml
+++ b/.github/workflows/docker-alpine.yml
@@ -32,6 +32,7 @@ jobs:
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
+ type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
@@ -41,17 +42,17 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -63,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -72,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@v4
+ uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.alpine
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 5aa2758e79..18d055627f 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -32,6 +32,7 @@ jobs:
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
+ type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
@@ -41,17 +42,17 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -63,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -72,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@v4
+ uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml
new file mode 100644
index 0000000000..f9064dec95
--- /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 0000000000..7c06599760
--- /dev/null
+++ b/.github/workflows/tests-mysql.yml
@@ -0,0 +1,73 @@
+name: Tests in MySQL
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ pull_request:
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ services:
+ mysql:
+ image: mysql:5.7
+ env:
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
+ MYSQL_DATABASE: snipeit
+ ports:
+ - 33306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version:
+ - "7.4"
+ - "8.0"
+ - "8.1.1"
+
+ name: PHP ${{ matrix.php-version }}
+
+ steps:
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - uses: actions/checkout@v4
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Copy .env
+ run: |
+ cp -v .env.testing.example .env
+ cp -v .env.testing.example .env.testing
+
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+
+ - name: Generate key
+ run: php artisan key:generate
+
+ - name: Directory Permissions
+ run: chmod -R 777 storage bootstrap/cache
+
+ - name: Execute tests (Unit and Feature tests) via PHPUnit
+ env:
+ DB_CONNECTION: mysql
+ DB_DATABASE: snipeit
+ DB_PORT: ${{ job.services.mysql.ports[3306] }}
+ DB_USERNAME: root
+ run: php artisan test --parallel
diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml
new file mode 100644
index 0000000000..5b41c60dea
--- /dev/null
+++ b/.github/workflows/tests-sqlite.yml
@@ -0,0 +1,58 @@
+name: Tests in SQLite
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ pull_request:
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version:
+ - "8.1.1"
+
+ name: PHP ${{ matrix.php-version }}
+
+ steps:
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - uses: actions/checkout@v4
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Copy .env
+ run: |
+ cp -v .env.testing.example .env
+ cp -v .env.testing.example .env.testing
+
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
+
+ - name: Generate key
+ run: php artisan key:generate
+
+ - name: Directory Permissions
+ run: chmod -R 777 storage bootstrap/cache
+
+ - name: Execute tests (Unit and Feature tests) via PHPUnit
+ env:
+ DB_CONNECTION: sqlite_testing
+ run: php artisan test --parallel
diff --git a/.gitignore b/.gitignore
index f0e9bfcec2..bf8360ba24 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,6 @@
.couscous
.DS_Store
.env
-.env.dusk.*
-!.env.dusk.example
.env.testing
phpstan.neon
.idea
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000000..afaad9a7a6
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,444 @@
+Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
+
+
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
index 62a928f8ad..689a65ed44 100644
--- a/Dockerfile.alpine
+++ b/Dockerfile.alpine
@@ -1,4 +1,4 @@
-FROM alpine:3.17.3
+FROM alpine:3.18.5
# Apache + PHP
RUN apk add --no-cache \
apache2 \
diff --git a/README.md b/README.md
index fe57aa2f36..ca6941b89d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
- [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
-[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
+
+
+[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
+[](#contributors) [](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System
@@ -80,73 +82,14 @@ As these were created by third-parties, Snipe-IT cannot provide support for thes
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
-
Please 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") |
-
-
-This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
-
diff --git a/TESTING.md b/TESTING.md
index 3f0e588105..3a2f4e5385 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -9,7 +9,39 @@ Before starting, follow the [instructions](README.md#installation) for installin
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
`cp .env.testing.example .env.testing`
-> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
+
+The following should work for running tests in memory with sqlite:
+```
+# --------------------------------------------
+# REQUIRED: BASIC APP SETTINGS
+# --------------------------------------------
+APP_ENV=testing
+APP_DEBUG=true
+APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
+APP_URL=http://localhost:8000
+APP_TIMEZONE='UTC'
+APP_LOCALE=en
+
+# --------------------------------------------
+# REQUIRED: DATABASE SETTINGS
+# --------------------------------------------
+DB_CONNECTION=sqlite_testing
+#DB_HOST=127.0.0.1
+#DB_PORT=3306
+#DB_DATABASE=null
+#DB_USERNAME=null
+#DB_PASSWORD=null
+```
+
+To use MySQL you should update the `DB_` variables to match your local test database:
+```
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE={}
+DB_USERNAME={}
+DB_PASSWORD={}
+```
Now you are ready to run the entire test suite from your terminal:
@@ -18,34 +50,3 @@ Now you are ready to run the entire test suite from your terminal:
To run individual test files, you can pass the path to the test that you want to run:
`php artisan test tests/Unit/AccessoryTest.php`
-
-## Browser Tests
-
-Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
-
-Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
-
-`cp .env.dusk.example .env.dusk.local`
-> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
-
-**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
-
-### Running Browser Tests
-
-Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
-
-`php artisan serve`
-
-Now you are ready to run the test suite. Use the following command from another terminal tab or window:
-
-`php artisan dusk`
-
-To run individual test files, you can pass the path to the test that you want to run:
-
-`php artisan dusk tests/Browser/LoginTest.php`
-
-If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
-
-`php artisan dusk:chrome-driver --detect`
-
-This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.
diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php
index d92bc1023b..e861b9af37 100755
--- a/app/Console/Commands/LdapSync.php
+++ b/app/Console/Commands/LdapSync.php
@@ -18,7 +18,7 @@ class LdapSync extends Command
*
* @var string
*/
- protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}';
+ protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=*} {--base_dn=} {--filter=} {--summary} {--json_summary}';
/**
* The console command description.
@@ -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,17 +84,35 @@ class LdapSync extends Command
$summary = [];
try {
- if ($this->option('base_dn') != '') {
+
+ /**
+ * if a location ID has been specified, use that OU
+ */
+ if ( $this->option('location_id') != '') {
+
+ foreach($this->option('location_id') as $location_id){
+ $location_ou = Location::where('id', '=', $location_id)->value('ldap_ou');
+ $search_base = $location_ou;
+ Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
+ }
+
+ /**
+ * Otherwise if a manual base DN has been specified, use that
+ */
+ } elseif ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
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' => []];
@@ -106,17 +125,21 @@ 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') != '') {
- $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') != '') {
- $location = Location::where('id', '=', $this->option('location_id'))->first();
- Log::debug('Location ID '.$this->option('location_id').' passed');
- Log::debug('Importing to '.$location->name.' ('.$location->id.')');
+ } 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 . ')');
+ }
+
+ }
}
-
if (! isset($location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
@@ -180,10 +203,6 @@ class LdapSync extends Command
}
}
- /* Create user account entries in Snipe-IT */
- $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
- $pass = bcrypt($tmp_pass);
-
$manager_cache = [];
if($ldap_default_group != null) {
@@ -229,22 +248,44 @@ class LdapSync extends Command
} else {
// Creating a new user.
$user = new User;
- $user->password = $pass;
+ $user->password = $user->noPassword();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created';
}
- $user->first_name = $item['firstname'];
- $user->last_name = $item['lastname'];
+ //If a sync option is not filled in on the LDAP settings don't populate the user field
+ if($ldap_result_username != null){
$user->username = $item['username'];
- $user->email = $item['email'];
+ }
+ if($ldap_result_last_name != null){
+ $user->last_name = $item['lastname'];
+ }
+ if($ldap_result_first_name != null){
+ $user->first_name = $item['firstname'];
+ }
+ if($ldap_result_emp_num != null){
$user->employee_num = e($item['employee_number']);
+ }
+ if($ldap_result_email != null){
+ $user->email = $item['email'];
+ }
+ if($ldap_result_phone != null){
$user->phone = $item['telephone'];
+ }
+ if($ldap_result_jobtitle != null){
$user->jobtitle = $item['jobtitle'];
+ }
+ if($ldap_result_country != null){
$user->country = $item['country'];
+ }
+ if($ldap_result_dept != null){
$user->department_id = $department->id;
+ }
+ if($ldap_result_location != null){
$user->location_id = $location ? $location->id : null;
+ }
+ if($ldap_result_manager != null){
if($item['manager'] != null) {
// Check Cache first
if (isset($manager_cache[$item['manager']])) {
@@ -284,6 +325,7 @@ class LdapSync extends Command
}
}
+ }
// Sync activated state for Active Directory.
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
diff --git a/app/Console/Commands/ResetDemoSettings.php b/app/Console/Commands/ResetDemoSettings.php
index fde95a368a..9ddade9efb 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';
@@ -78,7 +78,7 @@ class ResetDemoSettings extends Command
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
- $user->locale = 'en';
+ $user->locale = 'en-US';
$user->save();
}
diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php
index b1f1753564..7108568c5c 100644
--- a/app/Console/Commands/RestoreFromBackup.php
+++ b/app/Console/Commands/RestoreFromBackup.php
@@ -84,35 +84,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 = [
diff --git a/app/Console/Commands/RotateAppKey.php b/app/Console/Commands/RotateAppKey.php
index 08e528e4a4..f57a9462ee 100644
--- a/app/Console/Commands/RotateAppKey.php
+++ b/app/Console/Commands/RotateAppKey.php
@@ -7,6 +7,7 @@ use App\Models\CustomField;
use App\Models\Setting;
use 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/SendExpectedCheckinAlerts.php b/app/Console/Commands/SendExpectedCheckinAlerts.php
index 83a93a8a6b..34e9609ce5 100644
--- a/app/Console/Commands/SendExpectedCheckinAlerts.php
+++ b/app/Console/Commands/SendExpectedCheckinAlerts.php
@@ -9,6 +9,7 @@ use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
class SendExpectedCheckinAlerts extends Command
{
@@ -42,7 +43,7 @@ class SendExpectedCheckinAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
- $whenNotify = Carbon::now()->addDays(7);
+ $whenNotify = Carbon::now();
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
@@ -50,6 +51,7 @@ class SendExpectedCheckinAlerts extends Command
foreach ($assets as $asset) {
if ($asset->assigned && $asset->checkedOutToUser()) {
+ Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email);
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
}
}
diff --git a/app/Events/CheckoutableCheckedIn.php b/app/Events/CheckoutableCheckedIn.php
index 9609f7d415..48aed2a64d 100644
--- a/app/Events/CheckoutableCheckedIn.php
+++ b/app/Events/CheckoutableCheckedIn.php
@@ -15,18 +15,20 @@ class CheckoutableCheckedIn
public $checkedInBy;
public $note;
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
+ public $originalValues;
/**
* Create a new event instance.
*
* @return void
*/
- public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null)
+ public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null, $originalValues = [])
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
$this->action_date = $action_date ?? date('Y-m-d');
+ $this->originalValues = $originalValues;
}
}
diff --git a/app/Events/CheckoutableCheckedOut.php b/app/Events/CheckoutableCheckedOut.php
index 30f70ca0d8..3f215bd3bc 100644
--- a/app/Events/CheckoutableCheckedOut.php
+++ b/app/Events/CheckoutableCheckedOut.php
@@ -14,17 +14,19 @@ class CheckoutableCheckedOut
public $checkedOutTo;
public $checkedOutBy;
public $note;
+ public $originalValues;
/**
* Create a new event instance.
*
* @return void
*/
- public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
+ public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [])
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedOutBy = $checkedOutBy;
$this->note = $note;
+ $this->originalValues = $originalValues;
}
}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 3d4db93452..e76d8e5dae 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -150,6 +150,11 @@ class Handler extends ExceptionHandler
return redirect()->guest('login');
}
+ protected function invalidJson($request, ValidationException $exception)
+ {
+ return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200);
+ }
+
/**
* A list of the inputs that are never flashed for validation exceptions.
diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php
index 680c7acfd6..60474701cc 100644
--- a/app/Helpers/Helper.php
+++ b/app/Helpers/Helper.php
@@ -2,6 +2,8 @@
namespace App\Helpers;
use App\Models\Accessory;
+use App\Models\Asset;
+use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\CustomField;
@@ -16,6 +18,63 @@ use Carbon\Carbon;
class Helper
{
+
+
+ /**
+ * This is only used for reversing the migration that updates the locale to the 5-6 letter codes from two
+ * letter codes. The normal dropdowns use the autoglossonyms in the language files located
+ * in resources/en-US/localizations.php.
+ */
+ public static $language_map = [
+ 'af' => 'af-ZA', // Afrikaans
+ 'am' => 'am-ET', // Amharic
+ 'ar' => 'ar-SA', // Arabic
+ 'bg' => 'bg-BG', // Bulgarian
+ 'ca' => 'ca-ES', // Catalan
+ 'cs' => 'cs-CZ', // Czech
+ 'cy' => 'cy-GB', // Welsh
+ 'da' => 'da-DK', // Danish
+ 'de-i' => 'de-if', // German informal
+ 'de' => 'de-DE', // German
+ 'el' => 'el-GR', // Greek
+ 'en' => 'en-US', // English
+ 'et' => 'et-EE', // Estonian
+ 'fa' => 'fa-IR', // Persian
+ 'fi' => 'fi-FI', // Finnish
+ 'fil' => 'fil-PH', // Filipino
+ 'fr' => 'fr-FR', // French
+ 'he' => 'he-IL', // Hebrew
+ 'hr' => 'hr-HR', // Croatian
+ 'hu' => 'hu-HU', // Hungarian
+ 'id' => 'id-ID', // Indonesian
+ 'is' => 'is-IS', // Icelandic
+ 'it' => 'it-IT', // Italian
+ 'iu' => 'iu-NU', // Inuktitut
+ 'ja' => 'ja-JP', // Japanese
+ 'ko' => 'ko-KR', // Korean
+ 'lt' => 'lt-LT', // Lithuanian
+ 'lv' => 'lv-LV', // Latvian
+ 'mi' => 'mi-NZ', // Maori
+ 'mk' => 'mk-MK', // Macedonian
+ 'mn' => 'mn-MN', // Mongolian
+ 'ms' => 'ms-MY', // Malay
+ 'nl' => 'nl-NL', // Dutch
+ 'no' => 'no-NO', // Norwegian
+ 'pl' => 'pl-PL', // Polish
+ 'ro' => 'ro-RO', // Romanian
+ 'ru' => 'ru-RU', // Russian
+ 'sk' => 'sk-SK', // Slovak
+ 'sl' => 'sl-SI', // Slovenian
+ 'so' => 'so-SO', // Somali
+ 'ta' => 'ta-IN', // Tamil
+ 'th' => 'th-TH', // Thai
+ 'tl' => 'tl-PH', // Tagalog
+ 'tr' => 'tr-TR', // Turkish
+ 'uk' => 'uk-UA', // Ukrainian
+ 'vi' => 'vi-VN', // Vietnamese
+ 'zu' => 'zu-ZA', // Zulu
+ ];
+
/**
* Simple helper to invoke the markdown parser
*
@@ -71,10 +130,14 @@ class Helper
*
* @author [A. Gianotto] []
* @since [v3.3]
- * @return array
+ * @return string
*/
- public static function defaultChartColors($index = 0)
+ public static function defaultChartColors(int $index = 0)
{
+ if ($index < 0) {
+ $index = 0;
+ }
+
$colors = [
'#008941',
'#FF4A46',
@@ -347,7 +410,19 @@ class Helper
$total_colors = count($colors);
if ($index >= $total_colors) {
- $index = $index - $total_colors;
+
+ \Log::info('Status label count is '.$index.' and exceeds the allowed count of 266.');
+ //patch fix for array key overflow (color count starts at 1, array starts at 0)
+ $index = $index - $total_colors - 1;
+
+ //constraints to keep result in 0-265 range. This should never be needed, but if something happens
+ //to create this many status labels and it DOES happen, this will keep it from failing at least.
+ if($index < 0) {
+ $index = 0;
+ }
+ elseif($index >($total_colors - 1)) {
+ $index = $total_colors - 1;
+ }
}
return $colors[$index];
@@ -643,6 +718,7 @@ class Helper
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
+ $asset_models = AssetModel::where('min_amt', '>', 0)->get();
$avail_consumables = 0;
$items_array = [];
@@ -705,6 +781,28 @@ class Helper
}
}
+ foreach ($asset_models as $asset_model){
+
+ $asset = new Asset();
+ $total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
+ $avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
+
+ if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
+ if ($avail > 0) {
+ $percent = number_format((($avail / $total_owned) * 100), 0);
+ } else {
+ $percent = 100;
+ }
+ $items_array[$all_count]['id'] = $asset_model->id;
+ $items_array[$all_count]['name'] = $asset_model->name;
+ $items_array[$all_count]['type'] = 'models';
+ $items_array[$all_count]['percent'] = $percent;
+ $items_array[$all_count]['remaining'] = $avail;
+ $items_array[$all_count]['min_amt'] = $asset_model->min_amt;
+ $all_count++;
+ }
+ }
+
return $items_array;
}
@@ -1276,7 +1374,7 @@ class Helper
/*
- * I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
+ * I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
* it seemed pretty safe to do here. Don't you judge me.
*/
public static function showDemoModeFieldWarning() {
@@ -1284,4 +1382,55 @@ 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) {
+ \Log::debug('Current language is '.$legacy.', using '.$new.' instead');
+ return $new;
+ }
+ }
+
+ // Return US english if we don't have a match
+ return 'en-US';
+ }
+
+ public static function mapBackToLegacyLocale($new_locale = null)
+ {
+ if (strlen($new_locale) <= 4) {
+ return $new_locale; //"new locale" apparently wasn't quite so new
+ }
+
+ // This does a *reverse* search against our new language map array - given the value, find the *key* for it
+ $legacy_locale = array_search($new_locale, self::$language_map);
+
+ if($legacy_locale !== false) {
+ return $legacy_locale;
+ }
+ return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
+ }
+
}
diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
index ef701020d8..6a94a897af 100644
--- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php
+++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
@@ -146,9 +146,8 @@ class AccessoriesFilesController extends Controller
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
- if (! $log = Actionlog::find($fileId)) {
- return response('No matching record for that asset/file', 500)
- ->header('Content-Type', 'text/plain');
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
+ return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
}
$file = 'private_uploads/accessories/'.$log->filename;
@@ -161,22 +160,19 @@ class AccessoriesFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
+ // Display the file inline
+ if (request('inline') == 'true') {
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+ return Storage::download($file, $log->filename, $headers);
+ }
+
+
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
- } else {
- if ($download != 'true') {
- \Log::debug('display the file');
- if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
-
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
- }
-
- return StorageHelper::downloader($file);
-
}
}
}
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
index 3424c2aa13..06d910c481 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
@@ -60,9 +60,10 @@ class AccessoryCheckinController extends Controller
$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?
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
index 1ea036e6ed..1cafe319d1 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
@@ -18,31 +18,36 @@ class AccessoryCheckoutController extends Controller
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] []
- * @param int $accessoryId
+ * @param int $id
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($accessoryId)
+ public function create($id)
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
- // Redirect to the accessory management page with error
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
- }
- // Make sure there is at least one available to checkout
- if ($accessory->numRemaining() <= 0){
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
- }
-
- if ($accessory->category) {
+ if ($accessory = Accessory::withCount('users as users_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'));
+
}
/**
diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php
index 645e2624b2..030e069bd2 100644
--- a/app/Http/Controllers/Account/AcceptanceController.php
+++ b/app/Http/Controllers/Account/AcceptanceController.php
@@ -69,7 +69,7 @@ class AcceptanceController extends Controller
}
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
- return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
+ return redirect()->route('account.accept')->with('error', trans('general.error_user_company'));
}
return view('account/accept.create', compact('acceptance'));
@@ -245,6 +245,36 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.accepted');
} else {
+
+ /**
+ * Check for the eula-pdfs directory
+ */
+ if (! Storage::exists('private_uploads/eula-pdfs')) {
+ Storage::makeDirectory('private_uploads/eula-pdfs', 775);
+ }
+
+ if (Setting::getSettings()->require_accept_signature == '1') {
+
+ // Check if the signature directory exists, if not create it
+ if (!Storage::exists('private_uploads/signatures')) {
+ Storage::makeDirectory('private_uploads/signatures', 775);
+ }
+
+ // The item was accepted, check for a signature
+ if ($request->filled('signature_output')) {
+ $sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
+ $data_uri = $request->input('signature_output');
+ $encoded_image = explode(',', $data_uri);
+ $decoded_image = base64_decode($encoded_image[1]);
+ Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
+
+ // No image data is present, kick them back.
+ // This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
+ } else {
+ return redirect()->back()->with('error', trans('general.shitty_browser'));
+ }
+ }
+
// Format the data to send the declined notification
$branding_settings = SettingsController::getPDFBranding();
@@ -281,11 +311,18 @@ class AcceptanceController extends Controller
'item_model' => $display_model,
'item_serial' => $item->serial,
'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,
'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format,
];
+ if ($pdf_view_route!='') {
+ \Log::debug($pdf_filename.' is the filename, and the route was specified.');
+ $pdf = Pdf::loadView($pdf_view_route, $data);
+ Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
+ }
+
$acceptance->decline($sig_filename);
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
event(new CheckoutDeclined($acceptance));
diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php
index 263bd2086f..654f3c2e24 100644
--- a/app/Http/Controllers/Api/AccessoriesController.php
+++ b/app/Http/Controllers/Api/AccessoriesController.php
@@ -331,7 +331,7 @@ class AccessoriesController extends Controller
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
- $logaction = $accessory->logCheckin(User::find($accessory_user->user_id), $request->input('note'));
+ $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php
index 6da7ce23a1..931e8e51c0 100644
--- a/app/Http/Controllers/Api/AssetMaintenancesController.php
+++ b/app/Http/Controllers/Api/AssetMaintenancesController.php
@@ -116,41 +116,17 @@ class AssetMaintenancesController extends Controller
{
$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 +134,39 @@ 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)
{
$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()));
}
/**
diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php
index cf13d24fc4..e77c648b34 100644
--- a/app/Http/Controllers/Api/AssetModelsController.php
+++ b/app/Http/Controllers/Api/AssetModelsController.php
@@ -38,6 +38,7 @@ class AssetModelsController extends Controller
'image',
'name',
'model_number',
+ 'min_amt',
'eol',
'notes',
'created_at',
@@ -45,6 +46,7 @@ class AssetModelsController extends Controller
'requestable',
'assets_count',
'category',
+ 'fieldset',
];
$assetmodels = AssetModel::select([
@@ -52,6 +54,7 @@ class AssetModelsController extends Controller
'models.image',
'models.name',
'model_number',
+ 'min_amt',
'eol',
'requestable',
'models.notes',
@@ -92,6 +95,9 @@ class AssetModelsController extends Controller
case 'category':
$assetmodels->OrderCategory($order);
break;
+ case 'fieldset':
+ $assetmodels->OrderFieldset($order);
+ break;
default:
$assetmodels->orderBy($sort, $order);
break;
diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php
index e8f37d8574..f5168a5914 100644
--- a/app/Http/Controllers/Api/AssetsController.php
+++ b/app/Http/Controllers/Api/AssetsController.php
@@ -3,15 +3,16 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
+use App\Http\Requests\StoreAssetRequest;
+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;
@@ -20,11 +21,12 @@ 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\Http\Request;
use App\Http\Requests\ImageUploadRequest;
+use Illuminate\Support\Facades\Log;
use Input;
use Paginator;
use Slack;
@@ -33,6 +35,7 @@ use TCPDF;
use Validator;
use Route;
+
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@@ -48,7 +51,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $audit = null)
{
@@ -133,7 +136,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()));
}
}
@@ -295,7 +298,7 @@ class AssetsController extends Controller
}
if ($request->filled('order_number')) {
- $assets->where('assets.order_number', '=', $request->get('order_number'));
+ $assets->where('assets.order_number', '=', strval($request->get('order_number')));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
@@ -346,7 +349,7 @@ class AssetsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
@@ -443,7 +446,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
@@ -474,7 +477,7 @@ 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)
{
@@ -530,38 +533,14 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
- * @return JsonResponse
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAssetRequest $request): JsonResponse
{
- $this->authorize('create', Asset::class);
-
$asset = new Asset();
$asset->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 +553,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,22 +565,22 @@ 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));
}
}
}
@@ -639,7 +619,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@@ -666,10 +646,11 @@ class AssetsController extends Controller
$request->offsetSet('image', $request->offsetGet('image_source'));
}
- $asset = $request->handleImages($asset);
+ $asset = $request->handleImages($asset);
+ $model = AssetModel::find($asset->model_id);
// Update custom fields
- if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
+ if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
@@ -720,7 +701,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
@@ -749,38 +730,28 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v5.1.18]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function restore(Request $request, $assetId = null)
{
- // Get asset information
- $asset = Asset::withTrashed()->find($assetId);
- $this->authorize('delete', $asset);
- if (isset($asset->id)) {
+ if ($asset = Asset::withTrashed()->find($assetId)) {
+ $this->authorize('delete', $asset);
- if ($asset->deleted_at=='') {
- $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);
+
}
/**
@@ -789,7 +760,7 @@ class AssetsController extends Controller
* @author [N. Butler]
* @param string $tag
* @since [v6.0.5]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
{
@@ -805,7 +776,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
@@ -889,22 +860,27 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function checkin(Request $request, $asset_id)
{
$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_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -924,12 +900,20 @@ class AssetsController extends Controller
}
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
+ $originalValues = $asset->getRawOriginal();
+ if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
+ $originalValues['action_date'] = $checkin_at;
+ }
if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
+ event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
- return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
+ return response()->json(Helper::formatStandardApiResponse('success', [
+ 'asset_tag'=> e($asset->asset_tag),
+ 'model' => e($asset->model->name),
+ 'model_number' => e($asset->model->model_number)
+ ], trans('admin/hardware/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
@@ -940,7 +924,7 @@ class AssetsController extends Controller
*
* @author [A. Janes] []
* @since [v6.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function checkinByTag(Request $request, $tag = null)
{
@@ -966,7 +950,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function audit(Request $request)
@@ -1027,24 +1011,54 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return JsonResponse
+ * @return \Illuminate\Http\JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
+ $allowed_columns = [
+ 'name',
+ 'asset_tag',
+ 'serial',
+ 'model_number',
+ 'image',
+ 'purchase_cost',
+ 'expected_checkin',
+ ];
+
+ $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
+
+ foreach ($all_custom_fields as $field) {
+ $allowed_columns[] = $field->db_column_name();
+ }
+
$assets = Asset::select('assets.*')
- ->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
- 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')
+ ->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
+ 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
->requestableAssets();
- $offset = request('offset', 0);
- $limit = $request->input('limit', 50);
- $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
+
+
+
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())) {
+ $assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
+ }
+ }
+
+ $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
+ $sort_override = str_replace('custom_fields.', '', $request->input('sort'));
+
+ // This handles all the pivot sorting (versus the assets.* fields
+ // in the allowed_columns array)
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
+
switch ($request->input('sort')) {
case 'model':
$assets->OrderModels($order);
@@ -1052,17 +1066,19 @@ class AssetsController extends Controller
case 'model_number':
$assets->OrderModelNumber($order);
break;
- case 'category':
- $assets->OrderCategory($order);
- break;
- case 'manufacturer':
- $assets->OrderManufacturer($order);
+ case 'location':
+ $assets->OrderLocation($order);
break;
default:
- $assets->orderBy('assets.created_at', $order);
+ $assets->orderBy($column_sort, $order);
break;
}
+
+ // Make sure the offset and limit are actually integers and do not exceed system limits
+ $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
+ $limit = app('api_limit_value');
+
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php
index 2781fa101f..2aa4b3741c 100644
--- a/app/Http/Controllers/Api/CategoriesController.php
+++ b/app/Http/Controllers/Api/CategoriesController.php
@@ -92,7 +92,7 @@ class CategoriesController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $categories->count()) ? $categories->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php
index 42b5302448..580bc5d9b9 100644
--- a/app/Http/Controllers/Api/CompaniesController.php
+++ b/app/Http/Controllers/Api/CompaniesController.php
@@ -56,7 +56,7 @@ class CompaniesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
$limit = app('api_limit_value');
diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php
index 4806caf8a3..9202b10b71 100644
--- a/app/Http/Controllers/Api/ComponentsController.php
+++ b/app/Http/Controllers/Api/ComponentsController.php
@@ -13,6 +13,7 @@ use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Database\Query\Builder;
class ComponentsController extends Controller
{
@@ -76,7 +77,7 @@ class ComponentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $components->count()) ? $components->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $components->count()) ? $components->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -203,12 +204,29 @@ class ComponentsController extends Controller
$this->authorize('view', \App\Models\Asset::class);
$component = Component::findOrFail($id);
- $assets = $component->assets();
-
+
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
- $total = $assets->count();
- $assets = $assets->skip($offset)->take($limit)->get();
+
+ if ($request->filled('search')) {
+ $assets = $component->assets()
+ ->where(function ($query) use ($request) {
+ $search_str = '%' . $request->input('search') . '%';
+ $query->where('name', 'like', $search_str)
+ ->orWhereIn('model_id', function (Builder $query) use ($request) {
+ $search_str = '%' . $request->input('search') . '%';
+ $query->selectRaw('id')->from('models')->where('name', 'like', $search_str);
+ })
+ ->orWhere('asset_tag', 'like', $search_str);
+ })
+ ->get();
+ $total = $assets->count();
+ } else {
+ $assets = $component->assets();
+
+ $total = $assets->count();
+ $assets = $assets->skip($offset)->take($limit)->get();
+ }
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
}
@@ -245,7 +263,7 @@ class ComponentsController extends Controller
}
// Make sure there is at least one available to checkout
- if ($component->numRemaining() <= $request->get('assigned_qty')) {
+ if ($component->numRemaining() < $request->get('assigned_qty')) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
}
diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php
index ba7e6fb302..1c31ec0ea2 100644
--- a/app/Http/Controllers/Api/ConsumablesController.php
+++ b/app/Http/Controllers/Api/ConsumablesController.php
@@ -86,7 +86,7 @@ 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() : abs($request->input('offset'));
+ $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'];
@@ -263,9 +263,14 @@ 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')])));
+ }
+
+
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
if (!$user = User::find($request->input('assigned_to'))) {
// Return error message
diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php
index ef988af597..f211089b93 100644
--- a/app/Http/Controllers/Api/DepartmentsController.php
+++ b/app/Http/Controllers/Api/DepartmentsController.php
@@ -27,7 +27,7 @@ class DepartmentsController extends Controller
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count'];
- $departments = Company::scopeCompanyables(Department::select(
+ $departments = Department::select(
'departments.id',
'departments.name',
'departments.phone',
@@ -37,8 +37,8 @@ class DepartmentsController extends Controller
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
- 'departments.image'),
- "company_id", "departments")->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
+ 'departments.image'
+ )->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
$departments = $departments->TextSearch($request->input('search'));
@@ -61,7 +61,7 @@ class DepartmentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $departments->count()) ? $departments->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $departments->count()) ? $departments->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/DepreciationsController.php b/app/Http/Controllers/Api/DepreciationsController.php
index 3d86c1b096..502a0741b7 100644
--- a/app/Http/Controllers/Api/DepreciationsController.php
+++ b/app/Http/Controllers/Api/DepreciationsController.php
@@ -29,7 +29,7 @@ class DepreciationsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/GroupsController.php b/app/Http/Controllers/Api/GroupsController.php
index 7cc5d2d756..8548af0ba5 100644
--- a/app/Http/Controllers/Api/GroupsController.php
+++ b/app/Http/Controllers/Api/GroupsController.php
@@ -36,7 +36,7 @@ class GroupsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -63,7 +63,7 @@ class GroupsController extends Controller
$group = new Group;
$group->name = $request->input('name');
- $group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
+ $group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here
if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));
diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php
index a091741e85..5e79c49b23 100644
--- a/app/Http/Controllers/Api/LicenseSeatsController.php
+++ b/app/Http/Controllers/Api/LicenseSeatsController.php
@@ -41,7 +41,7 @@ class LicenseSeatsController extends Controller
$total = $seats->count();
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $seats->count()) ? $seats->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $seats->count()) ? $seats->count() : app('api_offset_value');
if ($offset >= $total ){
$offset = 0;
diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php
index e021fc3d3e..d456e3cd64 100644
--- a/app/Http/Controllers/Api/LicensesController.php
+++ b/app/Http/Controllers/Api/LicensesController.php
@@ -95,7 +95,7 @@ class LicensesController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php
index 87bc266217..b888493286 100644
--- a/app/Http/Controllers/Api/LocationsController.php
+++ b/app/Http/Controllers/Api/LocationsController.php
@@ -81,7 +81,7 @@ class LocationsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $locations->count()) ? $locations->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php
index 9ff0a0c202..6b5a130229 100644
--- a/app/Http/Controllers/Api/ManufacturersController.php
+++ b/app/Http/Controllers/Api/ManufacturersController.php
@@ -6,9 +6,11 @@ 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\Auth;
use Illuminate\Support\Facades\Storage;
class ManufacturersController extends Controller
@@ -62,7 +64,7 @@ class ManufacturersController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -159,6 +161,44 @@ class ManufacturersController extends Controller
}
+ /**
+ * Restore a given Manufacturer (mark as un-deleted)
+ *
+ * @author [A. Gianotto] []
+ * @since [v6.3.4]
+ * @param int $id
+ * @return \Illuminate\Http\JsonResponse
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ */
+ public function restore($id)
+ {
+ $this->authorize('delete', Manufacturer::class);
+
+ if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
+
+ if ($manufacturer->deleted_at == '') {
+ return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')])), 200);
+ }
+
+ if ($manufacturer->restore()) {
+
+ $logaction = new Actionlog();
+ $logaction->item_type = Manufacturer::class;
+ $logaction->item_id = $manufacturer->id;
+ $logaction->created_at = date('Y-m-d H:i:s');
+ $logaction->user_id = Auth::user()->id;
+ $logaction->logaction('restore');
+
+ return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
+ }
+
+ // Check validation to make sure we're not restoring an item with the same unique attributes as a non-deleted one
+ return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()])), 200);
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
+ }
+
/**
* Gets a paginated collection for the select2 menus
*
diff --git a/app/Http/Controllers/Api/PredefinedKitsController.php b/app/Http/Controllers/Api/PredefinedKitsController.php
index 85d05c422e..b398dbfae2 100644
--- a/app/Http/Controllers/Api/PredefinedKitsController.php
+++ b/app/Http/Controllers/Api/PredefinedKitsController.php
@@ -30,7 +30,7 @@ class PredefinedKitsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $kits->count()) ? $kits->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $kits->count()) ? $kits->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php
index 4f5e3b1bdf..ef56ed5370 100644
--- a/app/Http/Controllers/Api/ProfileController.php
+++ b/app/Http/Controllers/Api/ProfileController.php
@@ -11,6 +11,7 @@ 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;
class ProfileController extends Controller
@@ -48,14 +49,23 @@ class ProfileController extends Controller
{
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
- $results = [];
+ $results = array();
+ $show_field = array();
+ $showable_fields = array();
$results['total'] = $checkoutRequests->count();
+ $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
+ foreach ($all_custom_fields as $field) {
+ if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
+ $showable_fields[] = $field->db_column_name();
+ }
+ }
+
foreach ($checkoutRequests as $checkoutRequest) {
// Make sure the asset and request still exist
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
- $results['rows'][] = [
+ $assets = [
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
'name' => e($checkoutRequest->itemRequested()->present()->name()),
'type' => e($checkoutRequest->itemType()),
@@ -64,7 +74,16 @@ class ProfileController extends Controller
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
+
+ foreach ($showable_fields as $showable_field_name) {
+ $show_field['custom_fields.'.$showable_field_name] = $checkoutRequest->itemRequested()->{$showable_field_name};
+ }
+
+ // Merge the plain asset data and the custom fields data
+ $results['rows'][] = array_merge($assets, $show_field);
}
+
+
}
return $results;
diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php
index 21294c5779..5c6eaebf50 100644
--- a/app/Http/Controllers/Api/ReportsController.php
+++ b/app/Http/Controllers/Api/ReportsController.php
@@ -40,6 +40,14 @@ class ReportsController extends Controller
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
}
+ if ($request->filled('action_source')) {
+ $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
+ }
+
+ if ($request->filled('remote_ip')) {
+ $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc');
+ }
+
if ($request->filled('uploads')) {
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
}
@@ -52,11 +60,14 @@ class ReportsController extends Controller
'accept_signature',
'action_type',
'note',
+ 'remote_ip',
+ 'user_agent',
+ 'action_source',
];
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : app('api_offset_value');
$limit = app('api_limit_value');
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php
index 30a0b699e9..7c8e260c2f 100644
--- a/app/Http/Controllers/Api/StatuslabelsController.php
+++ b/app/Http/Controllers/Api/StatuslabelsController.php
@@ -52,7 +52,7 @@ class StatuslabelsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php
index a26c33b1f8..3e3d637be0 100644
--- a/app/Http/Controllers/Api/SuppliersController.php
+++ b/app/Http/Controllers/Api/SuppliersController.php
@@ -41,7 +41,7 @@ class SuppliersController extends Controller
];
$suppliers = Supplier::select(
- ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes'])
+ ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url'])
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
@@ -94,7 +94,7 @@ class SuppliersController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : abs($request->input('offset'));
+ $offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php
index 7b22f3af4b..3eb7783e3d 100644
--- a/app/Http/Controllers/Api/UsersController.php
+++ b/app/Http/Controllers/Api/UsersController.php
@@ -11,6 +11,7 @@ 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\License;
@@ -75,7 +76,6 @@ class UsersController extends Controller
])->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');
- $users = Company::scopeCompanyables($users);
if ($request->filled('activated')) {
@@ -192,11 +192,6 @@ class UsersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- // Make sure the offset and limit are actually integers and do not exceed system limits
- $offset = ($request->input('offset') > $users->count()) ? $users->count() : abs($request->input('offset'));
- $limit = app('api_limit_value');
-
-
switch ($request->input('sort')) {
case 'manager':
$users = $users->OrderManager($order);
@@ -271,7 +266,19 @@ class UsersController extends Controller
} 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');
+
+ \Log::debug('Requested offset: '. $request->input('offset'));
+ \Log::debug('App offset: '. app('api_offset_value'));
+ \Log::debug('Actual offset: '. $offset);
+ \Log::debug('Limit: '. $limit);
+
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@@ -351,6 +358,7 @@ class UsersController extends Controller
$user = new User;
$user->fill($request->all());
+ $user->created_by = Auth::user()->id;
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
@@ -362,8 +370,12 @@ class UsersController extends Controller
$user->permissions = $permissions_array;
}
- $tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40);
- $user->password = bcrypt($request->get('password', $tmp_pass));
+ //
+ if ($request->filled('password')) {
+ $user->password = bcrypt($request->get('password'));
+ } else {
+ $user->password = $user->noPassword();
+ }
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
@@ -683,17 +695,31 @@ class UsersController extends Controller
*/
public function restore($userId = null)
{
- // Get asset information
- $user = User::withTrashed()->find($userId);
- $this->authorize('delete', $user);
- if (isset($user->id)) {
- // Restore the user
- User::withTrashed()->where('id', $userId)->restore();
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
+ if ($user = User::withTrashed()->find($userId)) {
+ $this->authorize('delete', $user);
+
+ if ($user->deleted_at == '') {
+ return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.user')])), 200);
+ }
+
+ if ($user->restore()) {
+
+ $logaction = new Actionlog();
+ $logaction->item_type = User::class;
+ $logaction->item_id = $user->id;
+ $logaction->created_at = date('Y-m-d H:i:s');
+ $logaction->user_id = Auth::user()->id;
+ $logaction->logaction('restore');
+
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200);
+ }
+
+ // Check validation to make sure we're not restoring a user with the same username as an existing user
+ return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
-
- $id = $userId;
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))), 200);
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
+
}
}
diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php
index dbefb2e7b7..5ac958a8ac 100755
--- a/app/Http/Controllers/AssetModelsController.php
+++ b/app/Http/Controllers/AssetModelsController.php
@@ -4,8 +4,12 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
+use App\Models\Actionlog;
+use App\Models\Asset;
use App\Models\AssetModel;
+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;
@@ -76,6 +80,7 @@ class AssetModelsController extends Controller
$model->depreciation_id = $request->input('depreciation_id');
$model->name = $request->input('name');
$model->model_number = $request->input('model_number');
+ $model->min_amt = $request->input('min_amt');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
@@ -83,7 +88,7 @@ class AssetModelsController extends Controller
$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);
@@ -96,7 +101,6 @@ class AssetModelsController extends Controller
}
}
- // Redirect to the new model page
return redirect()->route('models.index')->with('success', trans('admin/models/message.create.success'));
}
@@ -153,6 +157,7 @@ class AssetModelsController extends Controller
$model->eol = $request->input('eol');
$model->name = $request->input('name');
$model->model_number = $request->input('model_number');
+ $model->min_amt = $request->input('min_amt');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
@@ -160,19 +165,28 @@ 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) {
+ $newEol = $model->eol;
+ $model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
+ ->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
+ } elseif ($model->eol == 0) {
+ $model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
+ ->update(['asset_eol_date' => DB::raw('null')]);
+ }
+ }
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
}
@@ -194,7 +208,7 @@ class AssetModelsController extends Controller
$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) {
@@ -222,22 +236,42 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @param int $modelId
+ * @param int $id
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function getRestore($modelId = null)
+ public function getRestore($id)
{
$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::user()->id;
+ $logaction->logaction('restore');
+
+
+ // Redirect them to the deleted page if there are more, otherwise the section index
+ $deleted_models = AssetModel::onlyTrashed()->count();
+ if ($deleted_models > 0) {
+ return redirect()->back()->with('success', trans('admin/models/message.restore.success'));
+ }
+ return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
+ }
+
+ // Check validation
+ return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset_model'), 'error' => $model->getErrors()->first()]));
}
- return redirect()->back()->with('error', trans('admin/models/message.not_found'));
+
+ return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
@@ -286,6 +320,7 @@ class AssetModelsController extends Controller
return view('models/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
+ ->with('model_id', $model_to_clone->id)
->with('clone_model', $model_to_clone);
}
diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php
index a68ef482cc..9889cd29ca 100644
--- a/app/Http/Controllers/AssetModelsFilesController.php
+++ b/app/Http/Controllers/AssetModelsFilesController.php
@@ -78,7 +78,7 @@ class AssetModelsFilesController extends Controller
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($modelId = null, $fileId = null, $download = true)
+ public function show($modelId = null, $fileId = null)
{
$model = AssetModel::find($modelId);
// the asset is valid
@@ -99,12 +99,13 @@ class AssetModelsFilesController extends Controller
->header('Content-Type', 'text/plain');
}
- if ($download != 'true') {
- if ($contents = file_get_contents(Storage::url($file))) {
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
+ if (request('inline') == 'true') {
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+
+ return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php
index fd5cee30ea..4fe10e895f 100644
--- a/app/Http/Controllers/Assets/AssetCheckinController.php
+++ b/app/Http/Controllers/Assets/AssetCheckinController.php
@@ -68,6 +68,7 @@ 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;
@@ -108,8 +109,11 @@ class AssetCheckinController extends Controller
}
}
+ $originalValues = $asset->getRawOriginal();
+
$checkin_at = date('Y-m-d H:i:s');
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
+ $originalValues['action_date'] = $checkin_at;
$checkin_at = $request->get('checkin_at');
}
@@ -132,7 +136,7 @@ class AssetCheckinController extends Controller
// Was the asset updated?
if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
+ event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
if ((isset($user)) && ($backto == 'user')) {
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php
index e6326da6b1..a096f16678 100644
--- a/app/Http/Controllers/Assets/AssetCheckoutController.php
+++ b/app/Http/Controllers/Assets/AssetCheckoutController.php
@@ -89,7 +89,16 @@ class AssetCheckoutController extends Controller
}
}
- if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
+ $settings = \App\Models\Setting::getSettings();
+
+ // We have to check whether $target->company_id is null here since locations don't have a company yet
+ if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
+ if ($target->company_id != $asset->company_id){
+ return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
+ }
+ }
+
+ if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
}
diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php
index 2c7d6ff9ec..610705c604 100644
--- a/app/Http/Controllers/Assets/AssetFilesController.php
+++ b/app/Http/Controllers/Assets/AssetFilesController.php
@@ -79,14 +79,14 @@ class AssetFilesController extends Controller
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($assetId = null, $fileId = null, $download = true)
+ public function show($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
- if (! $log = Actionlog::find($fileId)) {
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
@@ -103,12 +103,13 @@ class AssetFilesController extends Controller
->header('Content-Type', 'text/plain');
}
- if ($download != 'true') {
- if ($contents = file_get_contents(Storage::url($file))) {
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
+ if (request('inline') == 'true') {
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+
+ return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php
index e699779a34..e4a6e792af 100755
--- a/app/Http/Controllers/Assets/AssetsController.php
+++ b/app/Http/Controllers/Assets/AssetsController.php
@@ -6,6 +6,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
+use App\Models\Manufacturer;
+use Illuminate\Support\Facades\Log;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CheckoutRequest;
@@ -14,26 +16,18 @@ use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
+use Illuminate\Support\Facades\Auth;
use App\View\Label;
-use Auth;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\Cookie;
-use Input;
-use Intervention\Image\Facades\Image;
+use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;
-use League\Csv\Statement;
-use Paginator;
-use Redirect;
-use Response;
-use Slack;
-use Str;
-use TCPDF;
-use View;
+use Illuminate\Support\Facades\Redirect;
/**
* This class controls all actions related to assets for
@@ -137,14 +131,11 @@ class AssetsController extends Controller
$asset->order_number = $request->input('order_number');
$asset->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');
$asset->purchase_date = request('purchase_date', null);
- $asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date());
+ $asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
@@ -173,9 +164,9 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
- $asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
+ $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
- $asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
+ $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
@@ -211,12 +202,9 @@ class AssetsController extends Controller
}
if ($success) {
- // Redirect to the asset listing page
- $minutes = 518400;
- // dd( $_POST['options']);
- // Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes));
+ \Log::debug(e($asset->asset_tag));
return redirect()->route('hardware.index')
- ->with('success', trans('admin/hardware/message.create.success'));
+ ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
}
@@ -298,10 +286,10 @@ class AssetsController extends Controller
/**
* Validate and process asset edit form.
*
- * @author [A. Gianotto] []
* @param int $assetId
- * @since [v1.0]
- * @return Redirect
+ * @return \Illuminate\Http\RedirectResponse|Redirect
+ *@since [v1.0]
+ * @author [A. Gianotto] []
*/
public function update(ImageUploadRequest $request, $assetId = null)
{
@@ -315,9 +303,27 @@ 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->asset_eol_date = request('asset_eol_date', null);
-
- $asset->purchase_date = $request->input('purchase_date', null);
+ $asset->purchase_date = $request->input('purchase_date', null);
+ if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
+ $asset->purchase_date = $request->input('purchase_date', null);
+ $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
+ $asset->eol_explicit = false;
+ } elseif ($request->filled('asset_eol_date')) {
+ $asset->asset_eol_date = $request->input('asset_eol_date', null);
+ $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
+ if($asset->model->eol) {
+ if($months != $asset->model->eol > 0) {
+ $asset->eol_explicit = true;
+ } else {
+ $asset->eol_explicit = false;
+ }
+ } else {
+ $asset->eol_explicit = true;
+ }
+ } elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
+ $asset->asset_eol_date = null;
+ $asset->eol_explicit = false;
+ }
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
@@ -342,7 +348,7 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
- \Log::info($e);
+ Log::info($e);
}
}
@@ -356,7 +362,6 @@ class AssetsController extends Controller
$asset->order_number = $request->input('order_number');
$asset->asset_tag = $asset_tag[1];
$asset->notes = $request->input('notes');
- $asset->physical = '1';
$asset = $request->handleImages($asset);
@@ -370,9 +375,9 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
- $asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
+ $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
- $asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
+ $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
@@ -420,7 +425,7 @@ class AssetsController extends Controller
try {
Storage::disk('public')->delete('assets'.'/'.$asset->image);
} catch (\Exception $e) {
- \Log::debug($e);
+ Log::debug($e);
}
}
@@ -535,7 +540,7 @@ class AssetsController extends Controller
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
- \Log::debug('The barcode format is invalid.');
+ Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
}
@@ -787,21 +792,24 @@ class AssetsController extends Controller
*/
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'));
@@ -856,7 +864,7 @@ class AssetsController extends Controller
'next_audit_date' => 'date|nullable',
];
- $validator = \Validator::make($request->all(), $rules);
+ $validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
@@ -873,7 +881,7 @@ class AssetsController extends Controller
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location') == '1') {
- \Log::debug('update location in audit');
+ Log::debug('update location in audit');
$asset->location_id = $request->input('location_id');
}
diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php
index 8419098936..2947344a50 100644
--- a/app/Http/Controllers/Assets/BulkAssetsController.php
+++ b/app/Http/Controllers/Assets/BulkAssetsController.php
@@ -7,6 +7,8 @@ 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;
@@ -14,6 +16,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest;
+use App\Models\CustomField;
class BulkAssetsController extends Controller
{
@@ -22,6 +25,13 @@ 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
@@ -32,6 +42,9 @@ class BulkAssetsController extends Controller
{
$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'));
}
@@ -40,41 +53,55 @@ class BulkAssetsController extends Controller
$bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]);
- $asset_ids = array_values(array_unique($request->input('ids')));
+
+ $asset_ids = $request->input('ids');
+ // Using the 'short-ternary' A/K/A "Elvis operator" '?:' here because ->input() might return an empty string
+ list($sortname,$sortdir) = explode(" ",$request->input('sort') ?: 'id ASC');
+ $assets = Asset::with('assignedTo', 'location', 'model')->whereIn('id', $asset_ids)->orderBy($sortname, $sortdir)->get();
+
+ $models = $assets->unique('model_id');
+ $modelNames = [];
+ foreach($models as $model) {
+ $modelNames[] = $model->model->name;
+ }
if ($request->filled('bulk_actions')) {
+
+
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
+
return (new Label)
- ->with('assets', Asset::find($asset_ids))
+ ->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('statuslabel_list', Helper::statusLabelList())
+ ->with('models', $models->pluck(['model']))
+ ->with('modelNames', $modelNames);
}
}
@@ -92,19 +119,35 @@ class BulkAssetsController extends Controller
public function update(Request $request)
{
$this->authorize('update', Asset::class);
+ $has_errors = 0;
+ $error_array = array();
// 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 (! $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'));
+
+ $assets = Asset::whereIn('id', array_keys($request->input('ids')))->get();
+
+
+
+ /**
+ * If ANY of these are filled, prepare to update the values on the assets.
+ *
+ * Additional checks will be needed for some of them to make sure the values
+ * make sense (for example, changing the status ID to something incompatible with
+ * its checkout status.
+ */
if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
@@ -121,22 +164,35 @@ class BulkAssetsController extends Controller
|| ($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 = [];
+ /**
+ * Leave out model_id and status here because we do math on that later. We have to do some extra
+ * validation and checks on those two.
+ *
+ * It's tempting to make these match the request check above, but some of these values require
+ * extra work to make sure the data makes sense.
+ */
$this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
- ->conditionallyAddItem('model_id')
->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_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
}
@@ -160,41 +216,152 @@ class BulkAssetsController extends Controller
}
}
+ /**
+ * We're trying to change the model ID - we need to do some extra checks here to make sure
+ * the custom field values work for the custom fieldset rules around this asset. Uniqueness
+ * and requiredness across the fieldset is particularly important, since those are
+ * fieldset-specific attributes.
+ */
+ if ($request->filled('model_id')) {
+ $this->update_array['model_id'] = AssetModel::find($request->input('model_id'))->id;
+ }
+
+ /**
+ * We're trying to change the status ID - we need to do some extra checks here to
+ * make sure the status label type is one that makes sense for the state of the asset,
+ * for example, we shouldn't be able to make an asset archived if it's currently assigned
+ * to someone/something.
+ */
+ if ($request->filled('status_id')) {
+ $updated_status = Statuslabel::find($request->input('status_id'));
+
+ // We cannot assign a non-deployable status type if the asset is already assigned.
+ // This could probably be added to a form request.
+ // If the asset isn't assigned, we don't care what the status is.
+ // Otherwise we need to make sure the status type is still a deployable one.
+ if (
+ ($asset->assigned_to == '')
+ || ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
+ ) {
+ $this->update_array['status_id'] = $updated_status->id;
+ }
+
+ }
+
+ /**
+ * We're changing the location ID - figure out which location we should apply
+ * this change to:
+ *
+ * 0 - RTD location only
+ * 1 - location ID and RTD location ID
+ * 2 - location ID only
+ *
+ * Note: this is kinda dumb and we should just use human-readable values IMHO. - snipe
+ */
if ($request->filled('rtd_location_id')) {
- $this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
+
+ if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) {
+ $this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
+ }
+
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
$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::where('id' ,$assetId)->get();
foreach ($this->update_array as $key => $value) {
- if ($this->update_array[$key] != $asset->toArray()[0][$key]) {
- $changed[$key]['old'] = $asset->toArray()[0][$key];
+
+ if ($this->update_array[$key] != $asset->{$key}) {
+ $changed[$key]['old'] = $asset->{$key};
$changed[$key]['new'] = $this->update_array[$key];
}
+
}
- $logAction = new Actionlog();
- $logAction->item_type = Asset::class;
- $logAction->item_id = $assetId;
- $logAction->created_at = date("Y-m-d H:i:s");
- $logAction->user_id = Auth::id();
- $logAction->log_meta = json_encode($changed);
- $logAction->logaction('update');
+ /**
+ * Start all the custom fields shenanigans
+ */
- DB::table('assets')
- ->where('id', $assetId)
- ->update($this->update_array);
- } // endforeach
+ // 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')) {
+ $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
+
+ /*
+ * Check if the decrypted existing value is different from one we just submitted
+ * and if not, pull it out of the object since it shouldn't really be updating at all.
+ * If we don't do this, it will try to re-encrypt it, and the same value encrypted two
+ * different times will have different values, so it will *look* like it was updated
+ * but it wasn't.
+ */
+ if ($decrypted_old != $this->update_array[$field->db_column]) {
+ $asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
+ } else {
+ /*
+ * Remove the encrypted custom field from the update_array, since nothing changed
+ */
+ unset($this->update_array[$field->db_column]);
+ unset($asset->{$field->db_column});
+ }
+
+ /*
+ * These custom fields aren't encrypted, just carry on as usual
+ */
+ } else {
+
+ if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
+
+ // Check if this is an array, and if so, flatten it
+ if (is_array($this->update_array[$field->db_column])) {
+ $asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]);
+ } else {
+ $asset->{$field->db_column} = $this->update_array[$field->db_column];
+ }
+ }
+ }
+
+ } // endforeach
+ }
+
+
+ // Check if it passes validation, and then try to save
+ if (!$asset->update($this->update_array)) {
+
+ // Build the error array
+ foreach ($asset->getErrors()->toArray() as $key => $message) {
+ for ($x = 0; $x < count($message); $x++) {
+ $error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x];
+ $has_errors++;
+ }
+ }
+
+ } // end if saved
+
+ } // end asset foreach
+
+ if ($has_errors > 0) {
+ return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
+ }
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
-
-
}
-
// no values given, nothing to update
return redirect($bulk_back_url)->with('warning', trans('admin/hardware/message.update.nothing_updated'));
}
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index 011184881c..100eed12b9 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -56,7 +56,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 +81,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');
}
@@ -123,7 +121,7 @@ class LoginController extends Controller
if ($user = Auth::user()) {
$user->last_login = \Carbon::now();
- $user->save();
+ $user->saveQuietly();
}
} catch (\Exception $e) {
@@ -191,13 +189,15 @@ class LoginController extends Controller
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
+ $user->password = $user->noPassword();
if (Setting::getSettings()->ldap_pw_sync=='1') {
$user->password = bcrypt($request->input('password'));
}
+
$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;
}
@@ -317,7 +317,7 @@ class LoginController extends Controller
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'));
@@ -369,7 +369,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);
}
@@ -424,7 +424,7 @@ class LoginController extends Controller
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!');
diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php
index 09acee0277..9f4724e353 100644
--- a/app/Http/Controllers/Components/ComponentCheckinController.php
+++ b/app/Http/Controllers/Components/ComponentCheckinController.php
@@ -56,10 +56,11 @@ class ComponentCheckinController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(Request $request, $component_asset_id)
+ public function store(Request $request, $component_asset_id, $backto = null)
{
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
+
return redirect()->route('components.index')->with('error',
trans('admin/components/message.not_found'));
}
@@ -95,6 +96,10 @@ 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'));
+ }
return redirect()->route('components.index')->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 412d9dde62..79ff57e7df 100644
--- a/app/Http/Controllers/Components/ComponentCheckoutController.php
+++ b/app/Http/Controllers/Components/ComponentCheckoutController.php
@@ -20,25 +20,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'));
+
}
/**
diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php
index 3fc93b74e5..0f4e782aa8 100644
--- a/app/Http/Controllers/Components/ComponentsFilesController.php
+++ b/app/Http/Controllers/Components/ComponentsFilesController.php
@@ -132,7 +132,7 @@ class ComponentsFilesController extends Controller
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($componentId = null, $fileId = null, $download = true)
+ public function show($componentId = null, $fileId = null)
{
\Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId);
@@ -142,7 +142,7 @@ class ComponentsFilesController extends Controller
$this->authorize('view', $component);
$this->authorize('components.files', $component);
- if (! $log = Actionlog::find($fileId)) {
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
@@ -157,21 +157,17 @@ class ComponentsFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
+ // Display the file inline
+ if (request('inline') == 'true') {
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+ return Storage::download($file, $log->filename, $headers);
+ }
+
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
- } else {
- if ($download != 'true') {
- \Log::debug('display the file');
- if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
-
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
- }
-
- return StorageHelper::downloader($file);
-
- }
+ }
}
}
diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
index 6585624d82..0cac973415 100644
--- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
+++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
+use App\Models\Accessory;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
@@ -18,25 +19,38 @@ class ConsumableCheckoutController extends Controller
* @author [A. Gianotto] []
* @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0]
- * @param int $consumableId
+ * @param int $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($consumableId)
+ public function create($id)
{
- 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::with('users')->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'));
+ }
+
+ // 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'));
- $this->authorize('checkout', $consumable);
-
- return view('consumables/checkout', compact('consumable'));
}
/**
diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
index 9b4007a43b..6053e82cca 100644
--- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php
+++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
@@ -131,7 +131,7 @@ class ConsumablesFilesController extends Controller
* @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($consumableId = null, $fileId = null, $download = true)
+ public function show($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
@@ -140,7 +140,7 @@ class ConsumablesFilesController extends Controller
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
- if (! $log = Actionlog::find($fileId)) {
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
@@ -155,22 +155,19 @@ class ConsumablesFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
+ // Display the file inline
+ if (request('inline') == 'true') {
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+ return Storage::download($file, $log->filename, $headers);
+ }
+
+
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
- } else {
- if ($download != 'true') {
- \Log::debug('display the file');
- if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
-
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
- }
-
- return StorageHelper::downloader($file);
-
}
}
}
diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php
index c9579ae7ef..ffe5eceec2 100644
--- a/app/Http/Controllers/CustomFieldsController.php
+++ b/app/Http/Controllers/CustomFieldsController.php
@@ -110,6 +110,7 @@ class CustomFieldsController extends Controller
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
+ "show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"user_id" => Auth::id()
]);
@@ -267,6 +268,7 @@ class CustomFieldsController extends Controller
$field->display_in_user_view = $display_in_user_view;
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
+ $field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));
diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php
index 97608cb5e2..950094bc45 100755
--- a/app/Http/Controllers/LabelsController.php
+++ b/app/Http/Controllers/LabelsController.php
@@ -7,8 +7,10 @@ use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Labels\Label;
+use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Setting;
+use App\Models\Supplier;
use App\Models\User;
use App\View\Label as LabelView;
use Illuminate\Support\Facades\Storage;
@@ -30,21 +32,23 @@ class LabelsController extends Controller
$exampleAsset = new Asset();
$exampleAsset->id = 999999;
- $exampleAsset->name = 'AST-AB-CD-1234';
- $exampleAsset->asset_tag = 'TCA-00001';
+ $exampleAsset->name = 'JEN-867-5309';
+ $exampleAsset->asset_tag = '100001';
$exampleAsset->serial = 'SN9876543210';
+ $exampleAsset->asset_eol_date = '2025-01-01';
+ $exampleAsset->order_number = '12345';
+ $exampleAsset->purchase_date = '2023-01-01';
+ $exampleAsset->status_id = 1;
- $exampleAsset->company = new Company();
- $exampleAsset->company->id = 999999;
- $exampleAsset->company->name = 'Test Company Limited';
- $exampleAsset->company->image = 'company-image-test.png';
+ $exampleAsset->company = new Company([
+ 'name' => 'Test Company Limited',
+ 'phone' => '1-555-555-5555',
+ 'email' => 'company@example.com',
+ ]);
- $exampleAsset->assignedto = new User();
- $exampleAsset->assignedto->id = 999999;
- $exampleAsset->assignedto->first_name = 'Test';
- $exampleAsset->assignedto->last_name = 'Person';
- $exampleAsset->assignedto->username = 'Test.Person';
- $exampleAsset->assignedto->employee_num = '0123456789';
+ $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->model = new AssetModel();
$exampleAsset->model->id = 999999;
@@ -53,6 +57,10 @@ class LabelsController extends Controller
$exampleAsset->model->manufacturer = new Manufacturer();
$exampleAsset->model->manufacturer->id = 999999;
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
+ $exampleAsset->model->manufacturer->support_email = 'support@test.com';
+ $exampleAsset->model->manufacturer->support_phone = '1-555-555-5555';
+ $exampleAsset->model->manufacturer->support_url = 'https://example.com';
+ $exampleAsset->supplier = new Supplier(['name' => 'Test Company Limited']);
$exampleAsset->model->category = new Category();
$exampleAsset->model->category->id = 999999;
$exampleAsset->model->category->name = 'Test Category';
diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php
index 50e20c7985..367ff3f1d9 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckinController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php
@@ -76,7 +76,7 @@ class LicenseCheckinController extends Controller
// Declare the rules for the form validation
$rules = [
- 'note' => 'string|nullable',
+ 'notes' => 'string|nullable',
];
// Create a new validator instance from our validation rules
@@ -97,10 +97,11 @@ class LicenseCheckinController extends Controller
// Update the asset data
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
+ $licenseSeat->notes = $request->input('notes');
// Was the asset updated?
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note')));
+ event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
if ($backTo == 'user') {
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
@@ -128,6 +129,13 @@ class LicenseCheckinController extends Controller
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
+ if (! $license->reassignable) {
+ // Not allowed to checkin
+ Session::flash('error', 'License not reassignable.');
+
+ return redirect()->back()->withInput();
+ }
+
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
->whereNotNull('assigned_to')
->with('user')
diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
index 3a0f806584..d61a1ad819 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
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;
@@ -21,23 +22,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'));
@@ -63,6 +76,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
$licenseSeat->user_id = Auth::id();
+ $licenseSeat->notes = $request->input('notes');
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
@@ -104,7 +118,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
+ event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}
@@ -121,7 +135,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
+ event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}
diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php
index d457d4983a..f6f7c1ad0c 100644
--- a/app/Http/Controllers/Licenses/LicenseFilesController.php
+++ b/app/Http/Controllers/Licenses/LicenseFilesController.php
@@ -137,7 +137,7 @@ class LicenseFilesController extends Controller
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
- if (! $log = Actionlog::find($fileId)) {
+ if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
@@ -152,21 +152,19 @@ class LicenseFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
+ if (request('inline') == 'true') {
+
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+
+ return Storage::download($file, $log->filename, $headers);
+ }
+
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
- } else {
- if ($download != 'true') {
- \Log::debug('display the file');
- if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
- return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
- }
-
- return JsonResponse::create(['error' => 'Failed validation: '], 500);
- }
-
- return StorageHelper::downloader($file);
}
}
diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php
index e98644f46f..c927a5affb 100755
--- a/app/Http/Controllers/ManufacturersController.php
+++ b/app/Http/Controllers/ManufacturersController.php
@@ -2,8 +2,12 @@
namespace App\Http\Controllers;
+use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
+use App\Models\Actionlog;
+use App\Models\Asset;
use App\Models\Manufacturer;
+use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
@@ -218,22 +222,37 @@ class ManufacturersController extends Controller
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function restore($manufacturers_id)
+ public function restore($id)
{
- $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::user()->id;
+ $logaction->logaction('restore');
+
+ // Redirect them to the deleted page if there are more, otherwise the section index
+ $deleted_manufacturers = Manufacturer::onlyTrashed()->count();
+ if ($deleted_manufacturers > 0) {
+ return redirect()->back()->with('success', trans('admin/manufacturers/message.success.restored'));
+ }
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
}
- return redirect()->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/ProfileController.php b/app/Http/Controllers/ProfileController.php
index d67d673a21..2e81facc76 100755
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -56,7 +56,7 @@ class ProfileController extends Controller
$user->phone = $request->input('phone');
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')))) {
@@ -134,6 +134,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 +160,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);
diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php
index 7765a28408..8a2e1f5f97 100644
--- a/app/Http/Controllers/ReportsController.php
+++ b/app/Http/Controllers/ReportsController.php
@@ -24,6 +24,7 @@ use Input;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
+use App\Http\Requests\CustomAssetReportRequest;
/**
@@ -247,8 +248,14 @@ class ReportsController extends Controller
trans('general.action'),
trans('general.type'),
trans('general.item'),
+ trans('general.license_serial'),
+ trans('general.model_name'),
+ 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',
];
@@ -289,9 +296,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,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
+ $actionlog->remote_ip,
+ $actionlog->user_agent,
+ $actionlog->action_source,
];
fputcsv($handle, $row);
}
@@ -416,11 +429,12 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
- public function postCustom(Request $request)
+ public function postCustom(CustomAssetReportRequest $request)
{
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
+
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
@@ -539,6 +553,30 @@ class ReportsController extends Controller
$header[] = trans('admin/users/table.title');
}
+ if ($request->filled('phone')) {
+ $header[] = trans('admin/users/table.phone');
+ }
+
+ if ($request->filled('user_address')) {
+ $header[] = trans('admin/reports/general.custom_export.user_address');
+ }
+
+ if ($request->filled('user_city')) {
+ $header[] = trans('admin/reports/general.custom_export.user_city');
+ }
+
+ if ($request->filled('user_state')) {
+ $header[] = trans('admin/reports/general.custom_export.user_state');
+ }
+
+ if ($request->filled('user_country')) {
+ $header[] = trans('admin/reports/general.custom_export.user_country');
+ }
+
+ if ($request->filled('user_zip')) {
+ $header[] = trans('admin/reports/general.custom_export.user_zip');
+ }
+
if ($request->filled('status')) {
$header[] = trans('general.status');
}
@@ -558,6 +596,10 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.checkout_date');
}
+ if ($request->filled('checkin_date')) {
+ $header[] = trans('admin/hardware/table.last_checkin_date');
+ }
+
if ($request->filled('expected_checkin')) {
$header[] = trans('admin/hardware/form.expected_checkin');
}
@@ -654,7 +696,7 @@ 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();
-
+
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
}
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
@@ -664,15 +706,23 @@ class ReportsController extends Controller
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
}
+ if (($request->filled('checkin_date_start'))) {
+ $assets->whereBetween('last_checkin', [
+ Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
+ // use today's date is `checkin_date_end` is not provided
+ Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
+ ]);
+ }
+
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
- $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
+ $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
- $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]);
+ $assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
}
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
@@ -743,7 +793,7 @@ class ReportsController extends Controller
}
if ($request->filled('eol')) {
- $row[] = ($asset->purchase_date != '') ? $asset->present()->eol_date() : '';
+ $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
}
if ($request->filled('order')) {
@@ -827,6 +877,54 @@ class ReportsController extends Controller
}
}
+ if ($request->filled('phone')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->phone : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
+ if ($request->filled('user_address')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->address : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
+ if ($request->filled('user_city')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->city : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
+ if ($request->filled('user_state')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->state : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
+ if ($request->filled('user_country')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->country : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
+ if ($request->filled('user_zip')) {
+ if ($asset->checkedOutToUser()) {
+ $row[] = ($asset->assignedto) ? $asset->assignedto->zip : '';
+ } else {
+ $row[] = ''; // Empty string if unassigned
+ }
+ }
+
if ($request->filled('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
@@ -848,6 +946,12 @@ class ReportsController extends Controller
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}
+ if ($request->filled('checkin_date')) {
+ $row[] = ($asset->last_checkin)
+ ? Carbon::parse($asset->last_checkin)->format('Y-m-d')
+ : '';
+ }
+
if ($request->filled('expected_checkin')) {
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
}
@@ -1016,7 +1120,12 @@ class ReportsController extends Controller
$assetsForReport = $acceptances
->filter(function ($acceptance) {
- return $acceptance->checkoutable_type == 'App\Models\Asset';
+ $acceptance_checkoutable_flag = false;
+ if ($acceptance->checkoutable){
+ $acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser();
+ }
+
+ return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag;
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
@@ -1033,27 +1142,34 @@ class ReportsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
- public function sentAssetAcceptanceReminder($acceptanceId = null)
+ public function sentAssetAcceptanceReminder(Request $request)
{
$this->authorize('reports.view');
- if (!$acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
+ if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
+ \Log::debug('No pending acceptances');
// Redirect to the unaccepted assets report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
+
$assetItem = $acceptance->checkoutable;
+ \Log::debug(print_r($assetItem, true));
+
if (is_null($acceptance->created_at)){
+ \Log::debug('No acceptance created_at');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} else {
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
+
if ($logItem_res->isEmpty()){
+ \Log::debug('Acceptance date mismatch');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$logItem = $logItem_res[0];
}
- if(!$assetItem->assignedTo->locale){
+ if (!$assetItem->assignedTo->locale){
Notification::locale(Setting::getSettings()->locale)->send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index 497ab7cea6..873d764a25 100755
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest;
+use App\Models\CustomField;
use App\Models\Group;
use App\Models\Setting;
use App\Models\Asset;
@@ -26,7 +27,7 @@ use Response;
use App\Http\Requests\SlackSettingsRequest;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
-use Validator;
+use Illuminate\Support\Facades\Validator;
/**
* This controller handles all actions related to Settings for
@@ -185,7 +186,7 @@ class SettingsController extends Controller
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
- $settings->locale = $request->input('locale', 'en');
+ $settings->locale = $request->input('locale', 'en-US');
$settings->default_currency = $request->input('default_currency', 'USD');
$settings->user_id = 1;
$settings->email_domain = $request->input('email_domain');
@@ -584,12 +585,13 @@ class SettingsController extends Controller
}
if (! config('app.lock_passwords')) {
- $setting->locale = $request->input('locale', 'en');
+ $setting->locale = $request->input('locale', 'en-US');
}
$setting->default_currency = $request->input('default_currency', '$');
$setting->date_display_format = $request->input('date_display_format');
$setting->time_display_format = $request->input('time_display_format');
$setting->digit_separator = $request->input('digit_separator');
+ $setting->name_display_format = $request->input('name_display_format');
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -808,9 +810,10 @@ class SettingsController extends Controller
*/
public function getLabels()
{
- $setting = Setting::getSettings();
-
- return view('settings.labels', compact('setting'));
+ return view('settings.labels', [
+ 'setting' => Setting::getSettings(),
+ 'customFields' => CustomField::all(),
+ ]);
}
/**
@@ -1247,13 +1250,11 @@ class SettingsController extends Controller
if (!$request->hasFile('file')) {
return redirect()->route('settings.backups.index')->with('error', 'No file uploaded');
} else {
+
$max_file_size = Helper::file_upload_max_size();
-
- $rules = [
+ $validator = Validator::make($request->all(), [
'file' => 'required|mimes:zip|max:'.$max_file_size,
- ];
-
- $validator = \Validator::make($request->all(), $rules);
+ ]);
if ($validator->passes()) {
@@ -1264,7 +1265,7 @@ class SettingsController extends Controller
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
}
- return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
+ return redirect()->route('settings.backups.index')->withErrors($validator);
}
diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php
index a2d3d496da..ca1e2a4897 100644
--- a/app/Http/Controllers/Users/BulkUsersController.php
+++ b/app/Http/Controllers/Users/BulkUsersController.php
@@ -125,10 +125,26 @@ class BulkUsersController extends Controller
];
}
+ /**
+ * Check to see if the user wants to actually blank out the values vs skip them
+ */
if ($request->input('null_location_id')=='1') {
$this->update_array['location_id'] = null;
}
+ if ($request->input('null_department_id')=='1') {
+ $this->update_array['department_id'] = null;
+ }
+
+ if ($request->input('null_manager_id')=='1') {
+ $this->update_array['manager_id'] = null;
+ }
+
+ if ($request->input('null_company_id')=='1') {
+ $this->update_array['company_id'] = null;
+ }
+
+
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
diff --git a/app/Http/Controllers/Users/LDAPImportController.php b/app/Http/Controllers/Users/LDAPImportController.php
index 88a6b207df..e535a171a0 100644
--- a/app/Http/Controllers/Users/LDAPImportController.php
+++ b/app/Http/Controllers/Users/LDAPImportController.php
@@ -49,15 +49,19 @@ class LDAPImportController extends Controller
{
$this->authorize('update', User::class);
// Call Artisan LDAP import command.
- $location_id = $request->input('location_id');
- Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]);
+
+ Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]);
// Collect and parse JSON summary.
$ldap_results_json = Artisan::output();
$ldap_results = json_decode($ldap_results_json, true);
+ if (!$ldap_results) {
+ return redirect()->back()->withInput()->with('error', trans('general.no_results'));
+ }
// Direct user to appropriate status page.
if ($ldap_results['error']) {
+
return redirect()->back()->withInput()->with('error', $ldap_results['error_message']);
}
diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php
index cb49396324..0b787306f9 100644
--- a/app/Http/Controllers/Users/UserFilesController.php
+++ b/app/Http/Controllers/Users/UserFilesController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Users;
+use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
@@ -135,22 +136,36 @@ class UserFilesController extends Controller
*/
public function show($userId = null, $fileId = null)
{
+
+ if (empty($fileId)) {
+ return redirect()->route('users.show')->with('error', 'Invalid file request');
+ }
+
$user = User::find($userId);
// the license is valid
if (isset($user->id)) {
+
$this->authorize('view', $user);
- $log = Actionlog::find($fileId);
- $file = $log->get_src('users');
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
- return Response::download($file); //FIXME this doesn't use the new StorageHelper yet, but it's complicated...
+ // Display the file inline
+ if (request('inline') == 'true') {
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+ return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
+ }
+
+ return Storage::download('private_uploads/users/'.$log->filename);
+ }
+
+ return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found'));
}
- // Prepare the error message
- $error = trans('admin/users/message.user_not_found', ['id' => $userId]);
- // Redirect to the licence management page
- return redirect()->route('users.index')->with('error', $error);
+ // Redirect to the user management page if the user doesn't exist
+ return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
}
}
diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php
index 25a64e5cb2..2655e50f75 100755
--- a/app/Http/Controllers/Users/UsersController.php
+++ b/app/Http/Controllers/Users/UsersController.php
@@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Controllers\UserNotFoundException;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SaveUserRequest;
+use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Group;
-use App\Models\Ldap;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\WelcomeNotification;
@@ -385,18 +385,35 @@ class UsersController extends Controller
*/
public function getRestore($id = null)
{
- $this->authorize('update', User::class);
- // Get user information
- if (! User::onlyTrashed()->find($id)) {
- return redirect()->route('users.index')->with('error', trans('admin/users/messages.user_not_found'));
+ if ($user = User::withTrashed()->find($id)) {
+ $this->authorize('delete', $user);
+
+ if ($user->deleted_at == '') {
+ return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
+ }
+
+ if ($user->restore()) {
+ $logaction = new Actionlog();
+ $logaction->item_type = User::class;
+ $logaction->item_id = $user->id;
+ $logaction->created_at = date('Y-m-d H:i:s');
+ $logaction->user_id = Auth::user()->id;
+ $logaction->logaction('restore');
+
+ // Redirect them to the deleted page if there are more, otherwise the section index
+ $deleted_users = User::onlyTrashed()->count();
+ if ($deleted_users > 0) {
+ return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
+ }
+ return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
+
+ }
+
+ // Check validation to make sure we're not restoring a user with the same username as an existing user
+ return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
}
- // Restore the user
- if (User::withTrashed()->where('id', $id)->restore()) {
- return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
- }
-
- return redirect()->route('users.index')->with('error', 'User could not be restored.');
+ return redirect()->route('users.index')->with('error', trans('admin/users/message.does_not_exist'));
}
/**
diff --git a/app/Http/Livewire/Importer.php b/app/Http/Livewire/Importer.php
index 7899182a60..07f3a9f16c 100644
--- a/app/Http/Livewire/Importer.php
+++ b/app/Http/Livewire/Importer.php
@@ -275,6 +275,7 @@ class Importer extends Component
'license_email' => trans('admin/licenses/form.to_email'),
'license_name' => trans('admin/licenses/form.to_name'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
+ 'order_number' => trans('general.order_number'),
'reassignable' => trans('admin/licenses/form.reassignable'),
'seats' => trans('admin/licenses/form.seats'),
'notes' => trans('general.notes'),
@@ -484,8 +485,17 @@ class Importer extends Component
public function selectFile($id)
{
+ $this->clearMessage();
$this->activeFile = Import::find($id);
+
+ if (!$this->activeFile) {
+ $this->message = trans('admin/hardware/message.import.file_missing');
+ $this->message_type = 'danger';
+
+ return;
+ }
+
$this->field_map = null;
foreach($this->activeFile->header_row as $element) {
if(isset($this->activeFile->field_map[$element])) {
@@ -520,6 +530,12 @@ class Importer extends Component
}
}
+ public function clearMessage()
+ {
+ $this->message = null;
+ $this->message_type = null;
+ }
+
public function render()
{
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
diff --git a/app/Http/Livewire/SlackSettingsForm.php b/app/Http/Livewire/SlackSettingsForm.php
index bb3a7bd7f6..7fc53c7818 100644
--- a/app/Http/Livewire/SlackSettingsForm.php
+++ b/app/Http/Livewire/SlackSettingsForm.php
@@ -12,7 +12,7 @@ class SlackSettingsForm extends Component
public $webhook_endpoint;
public $webhook_channel;
public $webhook_botname;
- public $isDisabled ='' ;
+ public $isDisabled ='disabled' ;
public $webhook_name;
public $webhook_link;
public $webhook_placeholder;
@@ -22,11 +22,17 @@ class SlackSettingsForm extends Component
public Setting $setting;
+ public $webhook_endpoint_rules;
+
+
protected $rules = [
- 'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
+ 'webhook_endpoint' => 'required_with:webhook_channel|starts_with:http://,https://,ftp://,irc://,https://hooks.slack.com/services/|url|nullable',
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
'webhook_botname' => 'string|nullable',
];
+ public $messages = [
+ 'webhook_endpoint.starts_with' => 'your webhook endpoint should begin with http://, https:// or other protocol.',
+ ];
public function mount() {
$this->webhook_text= [
@@ -55,9 +61,7 @@ class SlackSettingsForm extends Component
$this->webhook_botname = $this->setting->webhook_botname;
$this->webhook_options = $this->setting->webhook_selected;
- if($this->setting->webhook_selected == 'general'){
- $this->isDisabled='';
- }
+
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
$this->isDisabled= '';
}
@@ -65,9 +69,8 @@ class SlackSettingsForm extends Component
}
public function updated($field) {
- if($this->webhook_selected != 'general') {
$this->validateOnly($field, $this->rules);
- }
+
}
public function updatedWebhookSelected() {
@@ -82,7 +85,6 @@ class SlackSettingsForm extends Component
}
private function isButtonDisabled() {
- if($this->webhook_selected == 'slack') {
if (empty($this->webhook_endpoint)) {
$this->isDisabled = 'disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
@@ -91,8 +93,6 @@ class SlackSettingsForm extends Component
$this->isDisabled = 'disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
}
- }
-
}
public function render()
@@ -108,6 +108,7 @@ class SlackSettingsForm extends Component
'defaults' => [
'exceptions' => false,
],
+ 'allow_redirects' => false,
]);
$payload = json_encode(
@@ -116,18 +117,23 @@ class SlackSettingsForm extends Component
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
'username' => e($this->webhook_botname),
'icon_emoji' => ':heart:',
+
]);
try {
+ $test = $webhook->post($this->webhook_endpoint, ['body' => $payload]);
- $webhook->post($this->webhook_endpoint, ['body' => $payload]);
+ if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){
+ return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
+ }
$this->isDisabled='';
$this->save_button = trans('general.save');
- return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
+ return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
} catch (\Exception $e) {
- $this->isDisabled= 'disabled';
+ $this->isDisabled='disabled';
+ $this->save_button = trans('admin/settings/general.webhook_presave');
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
}
@@ -158,9 +164,7 @@ class SlackSettingsForm extends Component
if (Helper::isDemoMode()) {
session()->flash('error',trans('general.feature_disabled'));
} else {
- if ($this->webhook_selected != 'general') {
- $this->validate($this->rules);
- }
+ $this->validate($this->rules);
$this->setting->webhook_selected = $this->webhook_selected;
$this->setting->webhook_endpoint = $this->webhook_endpoint;
diff --git a/app/Http/Middleware/CheckLocale.php b/app/Http/Middleware/CheckLocale.php
index 75cba13261..3c525d172c 100644
--- a/app/Http/Middleware/CheckLocale.php
+++ b/app/Http/Middleware/CheckLocale.php
@@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use App\Models\Setting;
use Closure;
+use \App\Helpers\Helper;
class CheckLocale
{
@@ -18,22 +19,28 @@ class CheckLocale
*/
public function handle($request, Closure $next, $guard = null)
{
+
+ // Default app settings from config
+ $language = config('app.locale');
+
if ($settings = Setting::getSettings()) {
+
// User's preference
if (($request->user()) && ($request->user()->locale)) {
- \App::setLocale($request->user()->locale);
+ $language = $request->user()->locale;
// App setting preference
} elseif ($settings->locale != '') {
- \App::setLocale($settings->locale);
-
- // Default app setting
- } else {
- \App::setLocale(config('app.locale'));
+ $language = $settings->locale;
}
- }
- \App::setLocale(config('app.locale'));
+ }
+
+ if (config('app.locale') != Helper::mapLegacyLocale($language)) {
+ \Log::warning('Your current APP_LOCALE in your .env is set to "'.config('app.locale').'" and should be updated to be "'.Helper::mapLegacyLocale($language).'" in '.base_path().'/.env. Translations may display unexpectedly until this is updated.');
+ }
+
+ \App::setLocale(Helper::mapLegacyLocale($language));
return $next($request);
}
}
diff --git a/app/Http/Requests/CustomAssetReportRequest.php b/app/Http/Requests/CustomAssetReportRequest.php
new file mode 100644
index 0000000000..2a8fbe7fab
--- /dev/null
+++ b/app/Http/Requests/CustomAssetReportRequest.php
@@ -0,0 +1,46 @@
+ 'date|date_format:Y-m-d|nullable',
+ 'purchase_end' => 'date|date_format:Y-m-d|nullable',
+ 'created_start' => 'date|date_format:Y-m-d|nullable',
+ 'created_end' => 'date|date_format:Y-m-d|nullable',
+ 'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
+ 'checkout_date_end' => 'date|date_format:Y-m-d|nullable',
+ 'expected_checkin_start' => 'date|date_format:Y-m-d|nullable',
+ 'expected_checkin_end' => 'date|date_format:Y-m-d|nullable',
+ 'checkin_date_start' => 'date|date_format:Y-m-d|nullable',
+ 'checkin_date_end' => 'date|date_format:Y-m-d|nullable',
+ 'last_audit_start' => 'date|date_format:Y-m-d|nullable',
+ 'last_audit_end' => 'date|date_format:Y-m-d|nullable',
+ 'next_audit_start' => 'date|date_format:Y-m-d|nullable',
+ 'next_audit_end' => 'date|date_format:Y-m-d|nullable',
+ ];
+ }
+
+ public function response(array $errors)
+ {
+ return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
+ }
+}
diff --git a/app/Http/Requests/SaveUserRequest.php b/app/Http/Requests/SaveUserRequest.php
index 98e561549e..b38193c15a 100644
--- a/app/Http/Requests/SaveUserRequest.php
+++ b/app/Http/Requests/SaveUserRequest.php
@@ -32,6 +32,7 @@ class SaveUserRequest extends FormRequest
public function rules()
{
$rules = [
+ 'department_id' => 'nullable|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
];
diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php
new file mode 100644
index 0000000000..74988b6c62
--- /dev/null
+++ b/app/Http/Requests/StoreAssetRequest.php
@@ -0,0 +1,51 @@
+company_id)
+ ? Company::getIdForCurrentUser($this->company_id)
+ : $this->company_id;
+
+ $this->merge([
+ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
+ 'company_id' => $idForCurrentUser,
+ 'assigned_to' => $assigned_to ?? null,
+ ]);
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ $rules = array_merge(
+ (new Asset)->getRules(),
+ parent::rules(),
+ );
+
+ return $rules;
+ }
+}
diff --git a/app/Http/Traits/UniqueSerialTrait.php b/app/Http/Traits/UniqueSerialTrait.php
deleted file mode 100644
index b5529d7cf8..0000000000
--- a/app/Http/Traits/UniqueSerialTrait.php
+++ /dev/null
@@ -1,25 +0,0 @@
-unique_serial == '1') {
- return 'unique_undeleted:'.$this->table.','.$this->getKey();
- }
- }
- }
-}
diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php
index 7b7a7fc011..d99e570810 100644
--- a/app/Http/Transformers/ActionlogsTransformer.php
+++ b/app/Http/Transformers/ActionlogsTransformer.php
@@ -3,8 +3,18 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
+use App\Models\Asset;
+use App\Models\CustomField;
use App\Models\Setting;
+use App\Models\Statuslabel;
+use App\Models\Company;
+use App\Models\Supplier;
+use App\Models\Location;
+use App\Models\AssetModel;
use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Contracts\Encryption\DecryptException;
+use Illuminate\Support\Facades\Crypt;
+use Illuminate\Support\Facades\Gate;
class ActionlogsTransformer
{
@@ -38,21 +48,80 @@ class ActionlogsTransformer
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$icon = $actionlog->present()->icon();
+ $custom_fields = CustomField::all();
+
if ($actionlog->filename!='') {
- $icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
+ $icon = Helper::filetype_icon($actionlog->filename);
}
// This is necessary since we can't escape special characters within a JSON object
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
$meta_array = json_decode($actionlog->log_meta);
+ $clean_meta = [];
+
if ($meta_array) {
+
foreach ($meta_array as $fieldname => $fieldata) {
+
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
+
+ // this is a custom field
+ if (str_starts_with($fieldname, '_snipeit_')) {
+
+ foreach ($custom_fields as $custom_field) {
+
+ if ($custom_field->db_column == $fieldname) {
+
+ if ($custom_field->field_encrypted == '1') {
+
+ // Unset these fields. We need to decrypt them, since even if the decrypted value
+ // didn't change, their value in the DB will, so we have to compare the unencrypted version
+ // to see if the values actually did change
+ unset($clean_meta[$fieldname]);
+ unset($clean_meta[$fieldname]);
+
+ $enc_old = '';
+ $enc_new = '';
+
+ try {
+ $enc_old = \Crypt::decryptString($this->clean_field($fieldata->old));
+ } catch (\Exception $e) {
+ \Log::debug('Could not decrypt field - maybe the key changed?');
+ }
+
+ try {
+ $enc_new = \Crypt::decryptString($this->clean_field($fieldata->new));
+ } catch (\Exception $e) {
+ \Log::debug('Could not decrypt field - maybe the key changed?');
+ }
+
+ if ($enc_old != $enc_new) {
+ \Log::debug('custom fields do not match');
+ $clean_meta[$fieldname]['old'] = "************";
+ $clean_meta[$fieldname]['new'] = "************";
+
+ // Display the changes if the user is an admin or superadmin
+ if (Gate::allows('admin')) {
+ $clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
+ $clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
+ }
+
+ }
+
+
+ }
+
+ }
+
+ }
+ }
+
}
}
+ $clean_meta= $this->changedInfo($clean_meta);
}
$file_url = '';
@@ -113,11 +182,15 @@ class ActionlogsTransformer
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
+ 'remote_ip' => ($actionlog->remote_ip) ?? null,
+ 'user_agent' => ($actionlog->user_agent) ?? null,
+ 'action_source' => ($actionlog->action_source) ?? null,
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
];
- //\Log::info("Clean Meta is: ".print_r($clean_meta,true));
+// \Log::info("Clean Meta is: ".print_r($clean_meta,true));
//dd($array);
+
return $array;
}
@@ -132,6 +205,112 @@ class ActionlogsTransformer
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
+ /**
+ * This takes the ids of the changed attributes and returns the names instead for the history view of an Asset
+ *
+ * @param array $clean_meta
+ * @return array
+ */
+
+ public function changedInfo(array $clean_meta)
+ { $location = Location::withTrashed()->get();
+ $supplier = Supplier::withTrashed()->get();
+ $model = AssetModel::withTrashed()->get();
+ $status = Statuslabel::withTrashed()->get();
+ $company = Company::get();
+
+
+ if(array_key_exists('rtd_location_id',$clean_meta)) {
+
+ $oldRtd = $location->find($clean_meta['rtd_location_id']['old']);
+ $oldRtdName = $oldRtd ? e($oldRtd->name) : trans('general.deleted');
+
+ $newRtd = $location->find($clean_meta['rtd_location_id']['new']);
+ $newRtdName = $newRtd ? e($newRtd->name) : trans('general.deleted');
+
+ $clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : '';
+ $clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : '';
+ $clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
+ unset($clean_meta['rtd_location_id']);
+ }
+
+
+ if (array_key_exists('location_id', $clean_meta)) {
+
+ $oldLocation = $location->find($clean_meta['location_id']['old']);
+ $oldLocationName = $oldLocation ? e($oldLocation->name) : trans('general.deleted');
+
+ $newLocation = $location->find($clean_meta['location_id']['new']);
+ $newLocationName = $newLocation ? e($newLocation->name) : trans('general.deleted');
+
+
+ $clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : '';
+ $clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : '';
+ $clean_meta['Current Location'] = $clean_meta['location_id'];
+ unset($clean_meta['location_id']);
+ }
+
+ if(array_key_exists('model_id', $clean_meta)) {
+
+ $oldModel = $model->find($clean_meta['model_id']['old']);
+ $oldModelName = $oldModel ? e($oldModel->name) : trans('admin/models/message.deleted');
+
+ $newModel = $model->find($clean_meta['model_id']['new']);
+ $newModelName = $newModel ? e($newModel->name) : trans('admin/models/message.deleted');
+
+ $clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
+ $clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
+
+ $clean_meta['Model'] = $clean_meta['model_id'];
+ unset($clean_meta['model_id']);
+ }
+ if(array_key_exists('company_id', $clean_meta)) {
+
+ $oldCompany = $company->find($clean_meta['company_id']['old']);
+ $oldCompanyName = $oldCompany ? e($oldCompany->name) : trans('admin/company/message.deleted');
+
+ $newCompany = $company->find($clean_meta['company_id']['new']);
+ $newCompanyName = $newCompany ? e($newCompany->name) : trans('admin/company/message.deleted');
+
+ $clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
+ $clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
+ $clean_meta['Company'] = $clean_meta['company_id'];
+ unset($clean_meta['company_id']);
+ }
+ if(array_key_exists('supplier_id', $clean_meta)) {
+
+ $oldSupplier = $supplier->find($clean_meta['supplier_id']['old']);
+ $oldSupplierName = $oldSupplier ? e($oldSupplier->name) : trans('admin/suppliers/message.deleted');
+
+ $newSupplier = $supplier->find($clean_meta['supplier_id']['new']);
+ $newSupplierName = $newSupplier ? e($newSupplier->name) : trans('admin/suppliers/message.deleted');
+
+ $clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
+ $clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
+ $clean_meta['Supplier'] = $clean_meta['supplier_id'];
+ unset($clean_meta['supplier_id']);
+ }
+ if(array_key_exists('status_id', $clean_meta)) {
+
+ $oldStatus = $status->find($clean_meta['status_id']['old']);
+ $oldStatusName = $oldStatus ? e($oldStatus->name) : trans('admin/statuslabels/message.deleted_label');
+
+ $newStatus = $status->find($clean_meta['status_id']['new']);
+ $newStatusName = $newStatus ? e($newStatus->name) : trans('admin/statuslabels/message.deleted_label');
+
+ $clean_meta['status_id']['old'] = $clean_meta['status_id']['old'] ? "[id: ".$clean_meta['status_id']['old']."] ". $oldStatusName : trans('general.unassigned');
+ $clean_meta['status_id']['new'] = $clean_meta['status_id']['new'] ? "[id: ".$clean_meta['status_id']['new']."] ". $newStatusName : trans('general.unassigned');
+ $clean_meta['Status'] = $clean_meta['status_id'];
+ unset($clean_meta['status_id']);
+ }
+ if(array_key_exists('asset_eol_date', $clean_meta)) {
+ $clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
+ unset($clean_meta['asset_eol_date']);
+ }
+
+ return $clean_meta;
+
+ }
diff --git a/app/Http/Transformers/AssetMaintenancesTransformer.php b/app/Http/Transformers/AssetMaintenancesTransformer.php
index fa92640823..01ef2adb87 100644
--- a/app/Http/Transformers/AssetMaintenancesTransformer.php
+++ b/app/Http/Transformers/AssetMaintenancesTransformer.php
@@ -45,7 +45,7 @@ class AssetMaintenancesTransformer
'name'=> e($assetmaintenance->asset->location->name),
] : null,
- 'rtd_location' => ($assetmaintenance->asset->defaultLoc) ? [
+ 'rtd_location' => (($assetmaintenance->asset) && ($assetmaintenance->asset->defaultLoc)) ? [
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
'name'=> e($assetmaintenance->asset->defaultLoc->name),
] : null,
diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php
index f3a658c872..a7b1c6a49f 100644
--- a/app/Http/Transformers/AssetModelsTransformer.php
+++ b/app/Http/Transformers/AssetModelsTransformer.php
@@ -47,6 +47,7 @@ class AssetModelsTransformer
] : null,
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
'model_number' => e($assetmodel->model_number),
+ 'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
'depreciation' => ($assetmodel->depreciation) ? [
'id' => (int) $assetmodel->depreciation->id,
'name'=> e($assetmodel->depreciation->name),
@@ -72,7 +73,7 @@ class AssetModelsTransformer
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')),
- 'delete' => (Gate::allows('delete', AssetModel::class) && ($assetmodel->assets_count == 0)),
+ 'delete' => $assetmodel->isDeletable(),
'clone' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at == '')),
'restore' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at != '')),
];
diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php
index 68dc731f07..f5d5ae12b5 100644
--- a/app/Http/Transformers/AssetsTransformer.php
+++ b/app/Http/Transformers/AssetsTransformer.php
@@ -7,7 +7,8 @@ use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
-
+use Carbon\Carbon;
+use Auth;
class AssetsTransformer
{
@@ -38,7 +39,7 @@ class AssetsTransformer
'byod' => ($asset->byod ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
- 'eol' => (($asset->model) && ($asset->model->eol != '')) ? $asset->model->eol : null,
+ 'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id,
@@ -146,7 +147,7 @@ class AssetsTransformer
'clone' => Gate::allows('create', Asset::class) ? true : false,
'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false,
'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false,
- 'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class)) ? true : false,
+ 'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false,
];
@@ -231,6 +232,29 @@ class AssetsTransformer
'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id),
];
+ if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
+ $fields_array = [];
+
+ foreach ($asset->model->fieldset->fields as $field) {
+
+ // Only display this if it's allowed via the custom field setting
+ if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
+
+ $value = $asset->{$field->db_column};
+ if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) {
+ $value = Helper::getFormattedDateObject($value, 'date', false);
+ }
+
+ $fields_array[$field->db_column] = e($value);
+ }
+
+ $array['custom_fields'] = $fields_array;
+ }
+ } else {
+ $array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
+ }
+
+
$permissions_array['available_actions'] = [
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,
diff --git a/app/Http/Transformers/LabelsTransformer.php b/app/Http/Transformers/LabelsTransformer.php
index 8e0e8ca44e..0ed5a09686 100644
--- a/app/Http/Transformers/LabelsTransformer.php
+++ b/app/Http/Transformers/LabelsTransformer.php
@@ -26,8 +26,8 @@ class LabelsTransformer
'name' => $label->getName(),
'unit' => $label->getUnit(),
- 'width' => $label->getWidth(),
- 'height' => $label->getHeight(),
+ 'width' => number_format($label->getWidth(), 2),
+ 'height' => number_format($label->getHeight(), 2),
'margin_top' => $label->getMarginTop(),
'margin_bottom' => $label->getMarginBottom(),
diff --git a/app/Http/Transformers/LicenseSeatsTransformer.php b/app/Http/Transformers/LicenseSeatsTransformer.php
index f82fd3a49f..62614db4d3 100644
--- a/app/Http/Transformers/LicenseSeatsTransformer.php
+++ b/app/Http/Transformers/LicenseSeatsTransformer.php
@@ -45,6 +45,7 @@ class LicenseSeatsTransformer
'name'=> e($seat->location()->name),
] : null,
'reassignable' => (bool) $seat->license->reassignable,
+ 'notes' => e($seat->notes),
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
];
diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php
index 76c0288073..0ebaca2692 100644
--- a/app/Http/Transformers/UsersTransformer.php
+++ b/app/Http/Transformers/UsersTransformer.php
@@ -24,7 +24,7 @@ class UsersTransformer
$array = [
'id' => (int) $user->id,
'avatar' => e($user->present()->gravatar),
- 'name' => e($user->first_name).' '.e($user->last_name),
+ 'name' => e($user->getFullNameAttribute()),
'first_name' => e($user->first_name),
'last_name' => e($user->last_name),
'username' => e($user->username),
@@ -79,7 +79,7 @@ class UsersTransformer
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
- 'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
+ 'delete' => $user->isDeletable(),
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
];
diff --git a/app/Importer/AccessoryImporter.php b/app/Importer/AccessoryImporter.php
index 417075ef31..9901fb70d7 100644
--- a/app/Importer/AccessoryImporter.php
+++ b/app/Importer/AccessoryImporter.php
@@ -34,7 +34,7 @@ class AccessoryImporter extends ItemImporter
}
$this->log('Updating Accessory');
- $this->item['model_number'] = $this->findCsvMatch($row, "model_number");
+ $this->item['model_number'] = trim($this->findCsvMatch($row, "model_number"));
$accessory->update($this->sanitizeItemForUpdating($accessory));
$accessory->save();
diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php
index be19455618..cf762a8fd4 100644
--- a/app/Importer/AssetImporter.php
+++ b/app/Importer/AssetImporter.php
@@ -3,7 +3,12 @@
namespace App\Importer;
use App\Models\Asset;
+use App\Models\AssetModel;
use App\Models\Statuslabel;
+use App\Models\User;
+use App\Events\CheckoutableCheckedIn;
+use Illuminate\Support\Facades\Auth;
+use Carbon\Carbon;
class AssetImporter extends ItemImporter
{
@@ -63,6 +68,7 @@ class AssetImporter extends ItemImporter
$asset_tag = Asset::autoincrement_asset();
}
+
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($asset) {
if (! $this->updating) {
@@ -77,13 +83,13 @@ class AssetImporter extends ItemImporter
$this->log('No Matching Asset, Creating a new one');
$asset = new Asset;
}
- $this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
- $this->item['image'] = $this->findCsvMatch($row, 'image');
- $this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
+ $this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes'));
+ $this->item['image'] = trim($this->findCsvMatch($row, 'image'));
+ $this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0;
$asset->requestable = $this->item['requestable'];
- $this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
+ $this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months')));
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
- $this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
+ $this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0;
// If no status ID is found
@@ -116,12 +122,7 @@ class AssetImporter extends ItemImporter
if (isset($this->item['next_audit_date'])) {
$item['next_audit_date'] = $this->item['next_audit_date'];
}
-
- $item['asset_eol_date'] = null;
- if (isset($this->item['asset_eol_date'])) {
- $item['asset_eol_date'] = $this->item['asset_eol_date'];
- }
-
+
if ($editingAsset) {
$asset->update($item);
} else {
@@ -134,16 +135,22 @@ class AssetImporter extends ItemImporter
$asset->{$custom_field} = $val;
}
}
-
-
+
if ($asset->save()) {
+
$asset->logCreate(trans('general.importer.import_note'));
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// If we have a target to checkout to, lets do so.
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
//-- the class that needs to use it (command importer or GUI importer inside the project).
- if (isset($target)) {
+ if (isset($target) && ($target !== false)) {
+ if (!is_null($asset->assigned_to)){
+ if ($asset->assigned_to != $target->id){
+ event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s')));
+ }
+ }
+
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
}
diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php
index de3ee14d1c..71ded1b0e5 100644
--- a/app/Importer/ComponentImporter.php
+++ b/app/Importer/ComponentImporter.php
@@ -28,8 +28,8 @@ class ComponentImporter extends ItemImporter
{
$component = null;
$this->log('Creating Component');
- $component = Component::where('name', $this->item['name'])
- ->where('serial', $this->item['serial'])
+ $component = Component::where('name', trim($this->item['name']))
+ ->where('serial', trim($this->item['serial']))
->first();
if ($component) {
diff --git a/app/Importer/ConsumableImporter.php b/app/Importer/ConsumableImporter.php
index b0dea1f514..5a65514deb 100644
--- a/app/Importer/ConsumableImporter.php
+++ b/app/Importer/ConsumableImporter.php
@@ -26,7 +26,7 @@ class ConsumableImporter extends ItemImporter
*/
public function createConsumableIfNotExists($row)
{
- $consumable = Consumable::where('name', $this->item['name'])->first();
+ $consumable = Consumable::where('name', trim($this->item['name']))->first();
if ($consumable) {
if (! $this->updating) {
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
@@ -41,9 +41,9 @@ class ConsumableImporter extends ItemImporter
}
$this->log('No matching consumable, creating one');
$consumable = new Consumable();
- $this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
- $this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
- $this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
+ $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
+ $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
+ $this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
$consumable->fill($this->sanitizeItemForStoring($consumable));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$consumable->unsetEventDispatcher();
diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php
index 4c60f0beed..961f4af52c 100644
--- a/app/Importer/Importer.php
+++ b/app/Importer/Importer.php
@@ -19,22 +19,76 @@ abstract class Importer
* Id of User performing import
* @var
*/
+
protected $user_id;
/**
* Are we updating items in the import
* @var bool
*/
+
protected $updating;
+
/**
* Default Map of item fields->csv names
*
- * This has been moved into Livewire/Importer.php to be more granular.
- * @todo - remove references to this property since we don't use it anymore.
+ * This has been moved into app/Http/Livewire/Importer.php to be more granular.
+ * This private variable is ONLY used for the cli-importer.
*
+ * @todo - find a way to make this less duplicative
* @var array
*/
private $defaultFieldMap = [
-
+ 'asset_tag' => 'asset tag',
+ 'activated' => 'activated',
+ 'category' => 'category',
+ 'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already.
+ 'checkout_location' => 'checkout location',
+ 'company' => 'company',
+ 'item_name' => 'item name',
+ 'item_number' => 'item number',
+ 'image' => 'image',
+ 'expiration_date' => 'expiration date',
+ 'location' => 'location',
+ 'notes' => 'notes',
+ 'license_email' => 'licensed to email',
+ 'license_name' => 'licensed to name',
+ 'maintained' => 'maintained',
+ 'manufacturer' => 'manufacturer',
+ 'asset_model' => 'model name',
+ 'model_number' => 'model number',
+ 'order_number' => 'order number',
+ 'purchase_cost' => 'purchase cost',
+ 'purchase_date' => 'purchase date',
+ 'purchase_order' => 'purchase order',
+ 'qty' => 'quantity',
+ 'reassignable' => 'reassignable',
+ 'requestable' => 'requestable',
+ 'seats' => 'seats',
+ 'serial' => 'serial number',
+ 'status' => 'status',
+ 'supplier' => 'supplier',
+ 'termination_date' => 'termination date',
+ 'warranty_months' => 'warranty',
+ 'full_name' => 'full name',
+ 'email' => 'email',
+ 'username' => 'username',
+ 'address' => 'address',
+ 'address2' => 'address2',
+ 'city' => 'city',
+ 'state' => 'state',
+ 'country' => 'country',
+ 'zip' => 'zip',
+ 'jobtitle' => 'job title',
+ 'employee_num' => 'employee number',
+ 'phone_number' => 'phone number',
+ 'first_name' => 'first name',
+ 'last_name' => 'last name',
+ 'department' => 'department',
+ 'manager_name' => 'manager full name',
+ 'manager_username' => 'manager username',
+ 'min_amt' => 'minimum quantity',
+ 'remote' => 'remote',
+ 'vip' => 'vip',
];
/**
* Map of item fields->csv names
@@ -281,9 +335,11 @@ abstract class Importer
$user_array['email'] = User::generateEmailFromFullName($user_array['full_name']);
}
+ // Get some variables for $user_formatted_array in case we need them later
+ $user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
+
if (empty($user_array['first_name'])) {
// Get some fields for first name and last name based off of full name
- $user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
$user_array['first_name'] = $user_formatted_array['first_name'];
$user_array['last_name'] = $user_formatted_array['last_name'];
}
diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php
index 9e80cb9572..e1b0f1c289 100644
--- a/app/Importer/ItemImporter.php
+++ b/app/Importer/ItemImporter.php
@@ -10,6 +10,8 @@ use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
+use Carbon\CarbonImmutable;
+use Illuminate\Support\Facades\Log;
class ItemImporter extends Importer
{
@@ -88,8 +90,14 @@ class ItemImporter extends Importer
}
$this->item['asset_eol_date'] = null;
- if ($this->findCsvMatch($row, 'asset_eol_date') != '') {
- $this->item['asset_eol_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'asset_eol_date')));
+ if($this->findCsvMatch($row, 'asset_eol_date') != '') {
+ $csvMatch = $this->findCsvMatch($row, 'asset_eol_date');
+ try {
+ $this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d');
+ } catch (\Exception $e) {
+ Log::info($e->getMessage());
+ $this->log('Unable to parse date: '.$csvMatch);
+ }
}
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
@@ -364,7 +372,7 @@ class ItemImporter extends Importer
if (empty($asset_statuslabel_name)) {
return null;
}
- $status = Statuslabel::where(['name' => $asset_statuslabel_name])->first();
+ $status = Statuslabel::where(['name' => trim($asset_statuslabel_name)])->first();
if ($status) {
$this->log('A matching Status '.$asset_statuslabel_name.' already exists');
@@ -373,7 +381,7 @@ class ItemImporter extends Importer
}
$this->log('Creating a new status');
$status = new Statuslabel();
- $status->name = $asset_statuslabel_name;
+ $status->name = trim($asset_statuslabel_name);
$status->deployable = 1;
$status->pending = 0;
@@ -412,7 +420,7 @@ class ItemImporter extends Importer
//Otherwise create a manufacturer.
$manufacturer = new Manufacturer();
- $manufacturer->name = $item_manufacturer;
+ $manufacturer->name = trim($item_manufacturer);
$manufacturer->user_id = $this->user_id;
if ($manufacturer->save()) {
diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php
index ce07585ab5..393d00367d 100644
--- a/app/Importer/LicenseImporter.php
+++ b/app/Importer/LicenseImporter.php
@@ -55,18 +55,19 @@ class LicenseImporter extends ItemImporter
$this->log('No Matching License, Creating a new one');
$license = new License;
}
- $asset_tag = $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); // used for checkout out to an asset.
+ $asset_tag = $this->item['asset_tag'] = trim($this->findCsvMatch($row, 'asset_tag')); // used for checkout out to an asset.
$this->item["expiration_date"] = null;
if ($this->findCsvMatch($row, "expiration_date")!='') {
- $this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime($this->findCsvMatch($row, "expiration_date")));
+ $this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime(trim($this->findCsvMatch($row, "expiration_date"))));
}
- $this->item['license_email'] = $this->findCsvMatch($row, 'license_email');
- $this->item['license_name'] = $this->findCsvMatch($row, 'license_name');
- $this->item['maintained'] = $this->findCsvMatch($row, 'maintained');
- $this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order');
- $this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable');
- $this->item['manufacturer'] = $this->createOrFetchManufacturer($this->findCsvMatch($row, 'manufacturer'));
+ $this->item['license_email'] = trim($this->findCsvMatch($row, 'license_email'));
+ $this->item['license_name'] = trim($this->findCsvMatch($row, 'license_name'));
+ $this->item['maintained'] = trim($this->findCsvMatch($row, 'maintained'));
+ $this->item['purchase_order'] = trim($this->findCsvMatch($row, 'purchase_order'));
+ $this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number'));
+ $this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable'));
+ $this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer')));
if($this->item['reassignable'] == "")
{
diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php
index 25140abe00..47a157aa7f 100644
--- a/app/Importer/LocationImporter.php
+++ b/app/Importer/LocationImporter.php
@@ -53,21 +53,21 @@ class LocationImporter extends ItemImporter
}
// Pull the records from the CSV to determine their values
- $this->item['name'] = $this->findCsvMatch($row, 'name');
- $this->item['address'] = $this->findCsvMatch($row, 'address');
- $this->item['address2'] = $this->findCsvMatch($row, 'address2');
- $this->item['city'] = $this->findCsvMatch($row, 'city');
- $this->item['state'] = $this->findCsvMatch($row, 'state');
- $this->item['country'] = $this->findCsvMatch($row, 'country');
- $this->item['zip'] = $this->findCsvMatch($row, 'zip');
- $this->item['currency'] = $this->findCsvMatch($row, 'currency');
- $this->item['ldap_ou'] = $this->findCsvMatch($row, 'ldap_ou');
- $this->item['manager'] = $this->findCsvMatch($row, 'manager');
- $this->item['manager_username'] = $this->findCsvMatch($row, 'manager_username');
+ $this->item['name'] = trim($this->findCsvMatch($row, 'name'));
+ $this->item['address'] = trim($this->findCsvMatch($row, 'address'));
+ $this->item['address2'] = trim($this->findCsvMatch($row, 'address2'));
+ $this->item['city'] = trim($this->findCsvMatch($row, 'city'));
+ $this->item['state'] = trim($this->findCsvMatch($row, 'state'));
+ $this->item['country'] = trim($this->findCsvMatch($row, 'country'));
+ $this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
+ $this->item['currency'] = trim($this->findCsvMatch($row, 'currency'));
+ $this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou'));
+ $this->item['manager'] = trim($this->findCsvMatch($row, 'manager'));
+ $this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username'));
$this->item['user_id'] = \Auth::user()->id;
if ($this->findCsvMatch($row, 'parent_location')) {
- $this->item['parent_id'] = $this->createOrFetchLocation($this->findCsvMatch($row, 'parent_location'));
+ $this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location')));
}
if (!empty($this->item['manager'])) {
diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php
index e13d4c4cce..263287e3b3 100644
--- a/app/Importer/UserImporter.php
+++ b/app/Importer/UserImporter.php
@@ -42,32 +42,32 @@ class UserImporter extends ItemImporter
public function createUserIfNotExists(array $row)
{
// Pull the records from the CSV to determine their values
- $this->item['id'] = $this->findCsvMatch($row, 'id');
- $this->item['username'] = $this->findCsvMatch($row, 'username');
- $this->item['first_name'] = $this->findCsvMatch($row, 'first_name');
- $this->item['last_name'] = $this->findCsvMatch($row, 'last_name');
- $this->item['email'] = $this->findCsvMatch($row, 'email');
- $this->item['gravatar'] = $this->findCsvMatch($row, 'gravatar');
- $this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
- $this->item['website'] = $this->findCsvMatch($row, 'website');
- $this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
- $this->item['address'] = $this->findCsvMatch($row, 'address');
- $this->item['city'] = $this->findCsvMatch($row, 'city');
- $this->item['state'] = $this->findCsvMatch($row, 'state');
- $this->item['country'] = $this->findCsvMatch($row, 'country');
- $this->item['start_date'] = $this->findCsvMatch($row, 'start_date');
- $this->item['end_date'] = $this->findCsvMatch($row, 'end_date');
- $this->item['zip'] = $this->findCsvMatch($row, 'zip');
- $this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0;
- $this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
- $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
- $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
- $this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
- $this->item['vip'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0;
- $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'autoassign_licenses')) ==1 ) ? '1' : 0;
+ $this->item['id'] = trim($this->findCsvMatch($row, 'id'));
+ $this->item['username'] = trim($this->findCsvMatch($row, 'username'));
+ $this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
+ $this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
+ $this->item['email'] = trim($this->findCsvMatch($row, 'email'));
+ $this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar'));
+ $this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number'));
+ $this->item['website'] = trim($this->findCsvMatch($row, 'website'));
+ $this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle'));
+ $this->item['address'] = trim($this->findCsvMatch($row, 'address'));
+ $this->item['city'] = trim($this->findCsvMatch($row, 'city'));
+ $this->item['state'] = trim($this->findCsvMatch($row, 'state'));
+ $this->item['country'] = trim($this->findCsvMatch($row, 'country'));
+ $this->item['start_date'] = trim($this->findCsvMatch($row, 'start_date'));
+ $this->item['end_date'] = trim($this->findCsvMatch($row, 'end_date'));
+ $this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
+ $this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0;
+ $this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num'));
+ $this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department'))));
+ $this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name')));
+ $this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0;
+ $this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
+ $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
- $user_department = $this->findCsvMatch($row, 'department');
+ $user_department = trim($this->findCsvMatch($row, 'department'));
if ($this->shouldUpdateField($user_department)) {
$this->item['department_id'] = $this->createOrFetchDepartment($user_department);
}
diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php
index 09cb3ae8f2..17a8a6c1bf 100644
--- a/app/Listeners/CheckoutableListener.php
+++ b/app/Listeners/CheckoutableListener.php
@@ -18,6 +18,7 @@ use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
+use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Notification;
use Exception;
use Log;
@@ -41,14 +42,9 @@ class CheckoutableListener
/**
* Make a checkout acceptance and attach it in the notification
*/
- $acceptance = $this->getCheckoutAcceptance($event);
+ $acceptance = $this->getCheckoutAcceptance($event);
try {
- if ($this->shouldSendWebhookNotification()) {
- Notification::route('slack', Setting::getSettings()->webhook_endpoint)
- ->notify($this->getCheckoutNotification($event));
- }
-
if (! $event->checkedOutTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
$this->getNotifiables($event),
@@ -60,8 +56,15 @@ class CheckoutableListener
$this->getCheckoutNotification($event, $acceptance)
);
}
+
+ if ($this->shouldSendWebhookNotification()) {
+ Notification::route('slack', Setting::getSettings()->webhook_endpoint)
+ ->notify($this->getCheckoutNotification($event));
+ }
+ } catch (ClientException $e) {
+ Log::debug("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
- Log::error("Exception caught during checkout notification: ".$e->getMessage());
+ Log::error("Exception caught during checkout notification: " . $e->getMessage());
}
}
@@ -79,22 +82,19 @@ class CheckoutableListener
/**
* Send the appropriate notification
*/
- $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
- ->where('assigned_to_id', $event->checkedOutTo->id)
- ->get();
+ if ($event->checkedOutTo && $event->checkoutable){
+ $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
+ ->where('assigned_to_id', $event->checkedOutTo->id)
+ ->get();
- foreach($acceptances as $acceptance){
- if($acceptance->isPending()){
- $acceptance->delete();
+ foreach($acceptances as $acceptance){
+ if($acceptance->isPending()){
+ $acceptance->delete();
+ }
}
}
try {
- if ($this->shouldSendWebhookNotification()) {
- Notification::route('slack', Setting::getSettings()->webhook_endpoint)
- ->notify($this->getCheckinNotification($event));
- }
-
// Use default locale
if (! $event->checkedOutTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
@@ -107,8 +107,15 @@ class CheckoutableListener
$this->getCheckinNotification($event)
);
}
+
+ if ($this->shouldSendWebhookNotification()) {
+ Notification::route('slack', Setting::getSettings()->webhook_endpoint)
+ ->notify($this->getCheckinNotification($event));
+ }
+ } catch (ClientException $e) {
+ Log::debug("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
- Log::error("Exception caught during checkin notification: ".$e->getMessage());
+ Log::error("Exception caught during checkin notification: " . $e->getMessage());
}
}
@@ -142,9 +149,11 @@ class CheckoutableListener
$notifiables = collect();
/**
- * Notify the user who checked out the item
+ * Notify who checked out the item as long as the model can route notifications
*/
- $notifiables->push($event->checkedOutTo);
+ if (method_exists($event->checkedOutTo, 'routeNotificationFor')) {
+ $notifiables->push($event->checkedOutTo);
+ }
/**
* Notify Admin users if the settings is activated
diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php
index d14b674364..4b584c668b 100644
--- a/app/Listeners/LogListener.php
+++ b/app/Listeners/LogListener.php
@@ -33,7 +33,7 @@ class LogListener
*/
public function onCheckoutableCheckedIn(CheckoutableCheckedIn $event)
{
- $event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date);
+ $event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date, $event->originalValues);
}
/**
@@ -46,7 +46,7 @@ class LogListener
*/
public function onCheckoutableCheckedOut(CheckoutableCheckedOut $event)
{
- $event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout);
+ $event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout, $event->originalValues);
}
/**
@@ -69,7 +69,6 @@ class LogListener
$logaction->item()->associate($event->acceptance->checkoutable->license);
}
- \Log::debug('New onCheckoutAccepted Listener fired. logaction: '.print_r($logaction, true));
$logaction->save();
}
diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php
index 7576cc644f..86502dc7e7 100755
--- a/app/Models/Accessory.php
+++ b/app/Models/Accessory.php
@@ -349,6 +349,22 @@ class Accessory extends SnipeModel
return (int) $remaining;
}
+ /**
+ * Run after the checkout acceptance was declined by the user
+ *
+ * @param User $acceptedBy
+ * @param string $signature
+ */
+ public function declinedCheckout(User $declinedBy, $signature)
+ {
+ if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
+ // Redirect to the accessory management page with error
+ return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
+ }
+
+ $accessory_user->limit(1)->delete();
+ }
+
/**
* Query builder scope to order on company
*
diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php
index 7e24b839ec..bc08aa800a 100755
--- a/app/Models/Actionlog.php
+++ b/app/Models/Actionlog.php
@@ -25,7 +25,17 @@ class Actionlog extends SnipeModel
protected $table = 'action_logs';
public $timestamps = true;
- protected $fillable = ['created_at', 'item_type', 'user_id', 'item_id', 'action_type', 'note', 'target_id', 'target_type', 'stored_eula'];
+ protected $fillable = [
+ 'created_at',
+ 'item_type',
+ 'user_id',
+ 'item_id',
+ 'action_type',
+ 'note',
+ 'target_id',
+ 'target_type',
+ 'stored_eula'
+ ];
use Searchable;
@@ -34,7 +44,15 @@ class Actionlog extends SnipeModel
*
* @var array
*/
- protected $searchableAttributes = ['action_type', 'note', 'log_meta','user_id'];
+ protected $searchableAttributes = [
+ 'action_type',
+ 'note',
+ 'log_meta',
+ 'user_id',
+ 'remote_ip',
+ 'user_agent',
+ 'action_source'
+ ];
/**
* The relations and their attributes that should be included when searching the model.
@@ -248,6 +266,9 @@ class Actionlog extends SnipeModel
public function logaction($actiontype)
{
$this->action_type = $actiontype;
+ $this->remote_ip = request()->ip();
+ $this->user_agent = request()->header('User-Agent');
+ $this->action_source = $this->determineActionSource();
if ($this->save()) {
return true;
@@ -312,4 +333,29 @@ class Actionlog extends SnipeModel
->orderBy('created_at', 'asc')
->get();
}
+
+ /**
+ * Determines what the type of request is so we can log it to the action_log
+ *
+ * @author A. Gianotto
+ * @since v6.3.0
+ * @return string
+ */
+ public function determineActionSource() {
+
+ // This is an API call
+ if (((request()->header('content-type') && (request()->header('accept'))=='application/json'))
+ && (starts_with(request()->header('authorization'), 'Bearer '))) {
+ return 'api';
+ }
+
+ // This is probably NOT an API call
+ if (request()->filled('_token')) {
+ return 'gui';
+ }
+
+ // We're not sure, probably cli
+ return 'cli/unknown';
+
+ }
}
diff --git a/app/Models/Asset.php b/app/Models/Asset.php
index 95e1c3a166..abb2239dc7 100644
--- a/app/Models/Asset.php
+++ b/app/Models/Asset.php
@@ -6,7 +6,6 @@ use App\Events\AssetCheckedOut;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed;
use App\Helpers\Helper;
-use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
@@ -32,7 +31,7 @@ class Asset extends Depreciable
protected $presenter = \App\Presenters\AssetPresenter::class;
use CompanyableTrait;
- use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
+ use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
public const LOCATION = 'location';
public const ASSET = 'asset';
@@ -72,7 +71,9 @@ class Asset extends Depreciable
protected $casts = [
'purchase_date' => 'date',
+ 'eol_explicit' => 'boolean',
'last_checkout' => 'datetime',
+ 'last_checkin' => 'datetime',
'expected_checkin' => 'date',
'last_audit_date' => 'datetime',
'next_audit_date' => 'date',
@@ -82,31 +83,36 @@ class Asset extends Depreciable
'location_id' => 'integer',
'rtd_company_id' => 'integer',
'supplier_id' => 'integer',
- 'byod' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
protected $rules = [
- 'name' => 'max:255|nullable',
- 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL',
- 'status_id' => 'required|integer|exists:status_labels,id',
- 'company_id' => 'integer|nullable',
- 'warranty_months' => 'numeric|nullable|digits_between:0,240',
- 'physical' => 'numeric|max:1|nullable',
- 'last_checkout' => 'date_format:Y-m-d H:i:s|nullable',
- 'expected_checkin' => 'date|nullable',
- 'location_id' => 'exists:locations,id|nullable',
- 'rtd_location_id' => 'exists:locations,id|nullable',
- 'asset_tag' => 'required|min:1|max:255|unique_undeleted',
- 'purchase_date' => 'date|date_format:Y-m-d|nullable',
- 'serial' => 'unique_serial|nullable',
- 'purchase_cost' => 'numeric|nullable|gte:0',
- 'supplier_id' => 'exists:suppliers,id|nullable',
- 'asset_eol_date' => 'date|max:10|min:10|nullable',
+ 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array',
+ 'status_id' => 'required|integer|exists:status_labels,id',
+ 'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array',
+ 'name' => 'nullable|max:255',
+ 'company_id' => 'nullable|integer|exists:companies,id',
+ 'warranty_months' => 'nullable|numeric|digits_between:0,240',
+ 'last_checkout' => 'nullable|date_format:Y-m-d H:i:s',
+ 'expected_checkin' => 'nullable|date',
+ 'location_id' => 'nullable|exists:locations,id',
+ 'rtd_location_id' => 'nullable|exists:locations,id',
+ 'purchase_date' => 'nullable|date|date_format:Y-m-d',
+ 'serial' => 'nullable|unique_undeleted:assets,serial',
+ 'purchase_cost' => 'nullable|numeric|gte:0',
+ 'supplier_id' => 'nullable|exists:suppliers,id',
+ 'asset_eol_date' => 'nullable|date',
+ 'eol_explicit' => 'nullable|boolean',
+ 'byod' => 'nullable|boolean',
+ 'order_number' => 'nullable|string|max:191',
+ 'notes' => 'nullable|string|max:65535',
+ 'assigned_to' => 'nullable|integer',
+ 'requestable' => 'nullable|boolean',
];
+
/**
* The attributes that are mass assignable.
*
@@ -135,8 +141,10 @@ class Asset extends Depreciable
'expected_checkin',
'byod',
'asset_eol_date',
+ 'eol_explicit',
'last_audit_date',
'next_audit_date',
+ 'asset_eol_date',
];
use Searchable;
@@ -207,16 +215,16 @@ class Asset extends Depreciable
$this->rules += $model->fieldset->validation_rules();
- foreach ($this->model->fieldset->fields as $field){
- if($field->format == 'BOOLEAN'){
- $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
+ if ($this->model->fieldset){
+ foreach ($this->model->fieldset->fields as $field){
+ if($field->format == 'BOOLEAN'){
+ $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
+ }
}
}
}
}
-
-
return parent::save($params);
}
@@ -261,7 +269,7 @@ class Asset extends Depreciable
/**
* Determines if an asset is available for checkout.
- * This checks to see if the it's checked out to an invalid (deleted) user
+ * This checks to see if it's checked out to an invalid (deleted) user
* OR if the assigned_to and deleted_at fields on the asset are empty AND
* that the status is deployable
*
@@ -332,6 +340,13 @@ class Asset extends Depreciable
}
}
+ $originalValues = $this->getRawOriginal();
+
+ // attempt to detect change in value if different from today's date
+ if ($checkout_at && strpos($checkout_at, date('Y-m-d')) === false) {
+ $originalValues['action_date'] = date('Y-m-d H:i:s');
+ }
+
if ($this->save()) {
if (is_int($admin)) {
$checkedOutBy = User::findOrFail($admin);
@@ -340,7 +355,7 @@ class Asset extends Depreciable
} else {
$checkedOutBy = Auth::user();
}
- event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note));
+ event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note, $originalValues));
$this->increment('checkout_counter', 1);
@@ -448,13 +463,22 @@ class Asset extends Depreciable
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return bool
*/
- public function checkedOutToUser()
+ public function checkedOutToUser(): bool
{
return $this->assignedType() === self::USER;
}
+ public function checkedOutToLocation(): bool
+ {
+ return $this->assignedType() === self::LOCATION;
+ }
+
+ public function checkedOutToAsset(): bool
+ {
+ return $this->assignedType() === self::ASSET;
+ }
+
/**
* Get the target this asset is checked out to
*
@@ -716,7 +740,7 @@ class Asset extends Depreciable
{
$days = (is_null($days)) ? 30 : $days;
- return self::where('archived', '=', '0')
+ return self::where('archived', '=', '0') // this can stay for right now, as `archived` defaults to 0 at the db level, but should probably be replaced with assetstatus->archived?
->whereNotNull('warranty_months')
->whereNotNull('purchase_date')
->whereNull('deleted_at')
@@ -741,7 +765,7 @@ class Asset extends Depreciable
}
/**
- * Establishes the asset -> status relationship
+ * Establishes the asset -> license seats relationship
*
* @author [A. Gianotto] []
* @since [v4.0]
@@ -777,7 +801,6 @@ class Asset extends Depreciable
}
-
/**
* Get the next autoincremented asset tag
*
@@ -910,6 +933,27 @@ class Asset extends Depreciable
return $cost;
}
+ /**
+ * -----------------------------------------------
+ * BEGIN MUTATORS
+ * -----------------------------------------------
+ **/
+
+ /**
+ * This sets the requestable to a boolean 0 or 1. This accounts for forms or API calls that
+ * explicitly pass the requestable field but it has a null or empty value.
+ *
+ * This will also correctly parse a 1/0 if "true"/"false" is passed.
+ *
+ * @param $value
+ * @return void
+ */
+ public function setRequestableAttribute($value)
+ {
+ $this->attributes['requestable'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
+ }
+
+
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
@@ -940,6 +984,7 @@ class Asset extends Depreciable
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
+ ->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%')
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',
@@ -1433,6 +1478,12 @@ class Asset extends Depreciable
});
}
+ if ($fieldname == 'rtd_location') {
+ $query->whereHas('defaultLoc', function ($query) use ($search_val) {
+ $query->where('locations.name', 'LIKE', '%'.$search_val.'%');
+ });
+ }
+
if ($fieldname =='assigned_to') {
$query->whereHasMorph('assignedTo', [User::class], function ($query) use ($search_val) {
$query->where(function ($query) use ($search_val) {
diff --git a/app/Models/AssetMaintenance.php b/app/Models/AssetMaintenance.php
index 292e529571..760abf1519 100644
--- a/app/Models/AssetMaintenance.php
+++ b/app/Models/AssetMaintenance.php
@@ -20,10 +20,9 @@ class AssetMaintenance extends Model implements ICompanyableChild
use SoftDeletes;
use CompanyableChildTrait;
use ValidatingTrait;
- protected $casts = [
- 'start_date' => 'datetime',
- 'completion_date' => 'datetime',
- ];
+
+
+
protected $table = 'asset_maintenances';
protected $rules = [
'asset_id' => 'required|integer',
@@ -31,12 +30,31 @@ class AssetMaintenance extends Model implements ICompanyableChild
'asset_maintenance_type' => 'required',
'title' => 'required|max:100',
'is_warranty' => 'boolean',
- 'start_date' => 'required|date',
- 'completion_date' => 'nullable|date',
+ 'start_date' => 'required|date_format:Y-m-d',
+ 'completion_date' => 'date_format:Y-m-d|nullable',
'notes' => 'string|nullable',
'cost' => 'numeric|nullable',
];
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var array
+ */
+ protected $fillable = [
+ 'title',
+ 'asset_id',
+ 'supplier_id',
+ 'asset_maintenance_type',
+ 'is_warranty',
+ 'start_date',
+ 'completion_date',
+ 'asset_maintenance_time',
+ 'notes',
+ 'cost',
+ ];
+
use Searchable;
/**
@@ -54,6 +72,8 @@ class AssetMaintenance extends Model implements ICompanyableChild
protected $searchableRelations = [
'asset' => ['name', 'asset_tag'],
'asset.model' => ['name', 'model_number'],
+ 'asset.supplier' => ['name'],
+ 'supplier' => ['name'],
];
public function getCompanyableParents()
diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php
index e4e5ac720a..d0e47e1cf1 100755
--- a/app/Models/AssetModel.php
+++ b/app/Models/AssetModel.php
@@ -6,6 +6,7 @@ use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
@@ -29,6 +30,7 @@ class AssetModel extends SnipeModel
protected $rules = [
'name' => 'required|min:1|max:255',
'model_number' => 'max:255|nullable',
+ 'min_amt' => 'integer|min:0|nullable',
'category_id' => 'required|integer|exists:categories,id',
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
'eol' => 'integer:min:0|max:240|nullable',
@@ -65,6 +67,7 @@ class AssetModel extends SnipeModel
'fieldset_id',
'image',
'manufacturer_id',
+ 'min_amt',
'model_number',
'name',
'notes',
@@ -150,6 +153,11 @@ class AssetModel extends SnipeModel
{
return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id');
}
+
+ public function customFields()
+ {
+ return $this->fieldset()->first()->fields();
+ }
/**
* Establishes the model -> custom field default values relationship
@@ -181,6 +189,21 @@ class AssetModel extends SnipeModel
return false;
}
+
+ /**
+ * Checks if the model is deletable
+ *
+ * @author A. Gianotto
+ * @since [v6.3.4]
+ * @return bool
+ */
+ public function isDeletable()
+ {
+ return Gate::allows('delete', $this)
+ && ($this->assets_count == 0)
+ && ($this->deleted_at == '');
+ }
+
/**
* Get uploads for this model
*
@@ -284,4 +307,9 @@ class AssetModel extends SnipeModel
{
return $query->leftJoin('categories', 'models.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
}
+
+ public function scopeOrderFieldset($query, $order)
+ {
+ return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order);
+ }
}
diff --git a/app/Models/Category.php b/app/Models/Category.php
index c06ac38543..17e41da1fc 100755
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -100,7 +100,8 @@ class Category extends SnipeModel
{
return Gate::allows('delete', $this)
- && ($this->itemCount() == 0);
+ && ($this->itemCount() == 0)
+ && ($this->deleted_at == '');
}
/**
@@ -247,6 +248,26 @@ class Category extends SnipeModel
}
}
+ /**
+ * -----------------------------------------------
+ * BEGIN MUTATORS
+ * -----------------------------------------------
+ **/
+
+ /**
+ * This sets the checkin_value to a boolean 0 or 1. This accounts for forms or API calls that
+ * explicitly pass the checkin_email field but it has a null or empty value.
+ *
+ * This will also correctly parse a 1/0 if "true"/"false" is passed.
+ *
+ * @param $value
+ * @return void
+ */
+ public function setCheckinEmailAttribute($value)
+ {
+ $this->attributes['checkin_email'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
+ }
+
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php
index e7fddd1aad..c1826a94d8 100644
--- a/app/Models/CustomField.php
+++ b/app/Models/CustomField.php
@@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rule;
use Schema;
use Watson\Validating\ValidatingTrait;
-
class CustomField extends Model
{
use HasFactory;
@@ -54,6 +53,12 @@ class CustomField extends Model
'field_encrypted' => 'nullable|boolean',
'auto_add_to_fieldsets' => 'boolean',
'show_in_listview' => 'boolean',
+ 'show_in_requestable_list' => 'boolean',
+ 'show_in_email' => 'boolean',
+ ];
+
+ protected $casts = [
+ 'show_in_requestable_list' => 'boolean',
];
/**
@@ -73,7 +78,8 @@ class CustomField extends Model
'display_in_user_view',
'auto_add_to_fieldsets',
'show_in_listview',
-
+ 'show_in_email',
+ 'show_in_requestable_list',
];
/**
@@ -182,6 +188,11 @@ class CustomField extends Model
{
return $this->belongsToMany(\App\Models\CustomFieldset::class);
}
+
+ public function assetModels()
+ {
+ return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id');
+ }
/**
* Establishes the customfield -> admin user relationship
@@ -239,8 +250,6 @@ class CustomField extends Model
/**
* Gets the DB column name.
*
- * @todo figure out if this is still needed? I don't know WTF it's for.
- *
* @author [A. Gianotto] []
* @since [v3.0]
* @return string
diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php
index a2698d818c..a62f96d631 100644
--- a/app/Models/CustomFieldset.php
+++ b/app/Models/CustomFieldset.php
@@ -92,6 +92,8 @@ class CustomFieldset extends Model
array_push($rule, $field->attributes['format']);
$rules[$field->db_column_name()] = $rule;
+ //add not_array to rules for all fields
+ $rules[$field->db_column_name()][] = 'not_array';
}
return $rules;
diff --git a/app/Models/Department.php b/app/Models/Department.php
index 90fde79df4..62755d2aa0 100644
--- a/app/Models/Department.php
+++ b/app/Models/Department.php
@@ -9,6 +9,7 @@ use Watson\Validating\ValidatingTrait;
class Department extends SnipeModel
{
+ use CompanyableTrait;
use HasFactory;
/**
diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php
index 3a7dd5af1c..f06c4582f9 100644
--- a/app/Models/Labels/DefaultLabel.php
+++ b/app/Models/Labels/DefaultLabel.php
@@ -37,7 +37,7 @@ class DefaultLabel extends RectangleSheet
private int $columns;
private int $rows;
-
+
public function __construct() {
$settings = Setting::getSettings();
@@ -62,6 +62,16 @@ class DefaultLabel extends RectangleSheet
$this->columns = ($usableWidth + $this->labelSpacingH) / ($this->labelWidth + $this->labelSpacingH);
$this->rows = ($usableHeight + $this->labelSpacingV) / ($this->labelHeight + $this->labelSpacingV);
+
+ // Make sure the columns and rows are never zero, since that scenario should never happen
+ if ($this->columns == 0) {
+ $this->columns = 1;
+ }
+
+ if ($this->rows == 0) {
+ $this->rows = 1;
+ }
+
}
public function getUnit() { return 'in'; }
@@ -76,7 +86,7 @@ class DefaultLabel extends RectangleSheet
public function getColumns() { return $this->columns; }
public function getRows() { return $this->rows; }
- public function getLabelBorder() { return 0.01; }
+ public function getLabelBorder() { return 0; }
public function getLabelWidth() { return $this->labelWidth; }
public function getLabelHeight() { return $this->labelHeight; }
@@ -85,7 +95,7 @@ class DefaultLabel extends RectangleSheet
public function getLabelMarginBottom() { return 0; }
public function getLabelMarginLeft() { return 0; }
public function getLabelMarginRight() { return 0; }
-
+
public function getLabelColumnSpacing() { return $this->labelSpacingH; }
public function getLabelRowSpacing() { return $this->labelSpacingV; }
@@ -106,7 +116,7 @@ class DefaultLabel extends RectangleSheet
$textY = 0;
$textX1 = 0;
$textX2 = $this->getLabelWidth();
-
+
// 1D Barcode
if ($record->get('barcode1d')) {
static::write1DBarcode(
@@ -115,7 +125,7 @@ class DefaultLabel extends RectangleSheet
$this->getLabelWidth() - 0.1, self::BARCODE1D_SIZE
);
}
-
+
// 2D Barcode
if ($record->get('barcode2d')) {
static::write2DBarcode(
diff --git a/app/Models/Labels/Field.php b/app/Models/Labels/Field.php
index b264c7ac29..c023f54175 100644
--- a/app/Models/Labels/Field.php
+++ b/app/Models/Labels/Field.php
@@ -21,6 +21,8 @@ class Field {
public static function makeArray(Field $field, Asset $asset) {
return $field->getOptions()
+ // filter out any FieldOptions that are accidentally null
+ ->filter()
->map(fn($option) => $option->toArray($asset))
->filter(fn($result) => $result['value'] != null);
}
@@ -36,4 +38,4 @@ class Field {
->map(fn($optionString) => FieldOption::fromString($optionString));
return $field;
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/Labels/FieldOption.php b/app/Models/Labels/FieldOption.php
index 76427accaf..7e45cc0ce7 100644
--- a/app/Models/Labels/FieldOption.php
+++ b/app/Models/Labels/FieldOption.php
@@ -14,6 +14,14 @@ class FieldOption {
public function getValue(Asset $asset) {
$dataPath = collect(explode('.', $this->dataSource));
+
+ // assignedTo directly on the asset is a special case where
+ // we want to avoid returning the property directly
+ // and instead return the entity's presented name.
+ if ($dataPath[0] === 'assignedTo'){
+ return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
+ }
+
return $dataPath->reduce(function ($myValue, $path) {
try { return $myValue ? $myValue->{$path} : ${$myValue}; }
catch (\Exception $e) { return $myValue; }
@@ -46,4 +54,4 @@ class FieldOption {
return $option;
}
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php
index ff759ac544..b727c1cb18 100644
--- a/app/Models/Labels/Label.php
+++ b/app/Models/Labels/Label.php
@@ -7,6 +7,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use TCPDF;
use TCPDF_STATIC;
+use TypeError;
/**
* Model for Labels.
@@ -370,7 +371,11 @@ abstract class Label
*/
public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
if (empty($value)) return;
- $pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
+ try {
+ $pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
+ } catch (\Exception|TypeError $e) {
+ \Log::debug('The 1D barcode ' . $value . ' is not compliant with the barcode type '. $type);
+ }
}
/**
diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter.php b/app/Models/Labels/Tapes/Dymo/LabelWriter.php
new file mode 100644
index 0000000000..fa427fd213
--- /dev/null
+++ b/app/Models/Labels/Tapes/Dymo/LabelWriter.php
@@ -0,0 +1,19 @@
+getUnit()); }
+ public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); }
+ public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit());}
+ public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
+ public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php
new file mode 100644
index 0000000000..d5f0e8d122
--- /dev/null
+++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php
@@ -0,0 +1,98 @@
+getPrintableArea();
+
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+ $usableWidth = $pa->w;
+
+ $barcodeSize = $pa->h - self::TAG_SIZE;
+
+ if ($record->has('barcode2d')) {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'C',
+ $barcodeSize, self::TAG_SIZE, true, 0
+ );
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ $currentX, $currentY,
+ $barcodeSize, $barcodeSize
+ );
+ $currentX += $barcodeSize + self::BARCODE_MARGIN;
+ $usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
+ } else {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'R',
+ $usableWidth, self::TAG_SIZE, true, 0
+ );
+ }
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $currentX, $currentY,
+ 'freesans', '', self::TITLE_SIZE, 'L',
+ $usableWidth, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.3
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+
+ if ($record->has('barcode1d')) {
+ static::write1DBarcode(
+ $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
+ $currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
+ );
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/Models/Ldap.php b/app/Models/Ldap.php
index 4eb496a2ab..ae1f163dda 100644
--- a/app/Models/Ldap.php
+++ b/app/Models/Ldap.php
@@ -252,13 +252,10 @@ class Ldap extends Model
$user->last_name = $item['lastname'];
$user->username = $item['username'];
$user->email = $item['email'];
+ $user->password = $user->noPassword();
if (Setting::getSettings()->ldap_pw_sync == '1') {
-
$user->password = bcrypt($password);
- } else {
- $pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 25);
- $user->password = bcrypt($pass);
}
$user->activated = 1;
@@ -268,7 +265,7 @@ class Ldap extends Model
if ($user->save()) {
return $user;
} else {
- LOG::debug('Could not create user.'.$user->getErrors());
+ \Log::debug('Could not create user.'.$user->getErrors());
throw new Exception('Could not create user: '.$user->getErrors());
}
}
diff --git a/app/Models/License.php b/app/Models/License.php
index 162b3d662a..2ea10939fa 100755
--- a/app/Models/License.php
+++ b/app/Models/License.php
@@ -32,6 +32,7 @@ class License extends Depreciable
protected $guarded = 'id';
protected $table = 'licenses';
+
protected $casts = [
'purchase_date' => 'date',
'expiration_date' => 'date',
@@ -323,7 +324,10 @@ class License extends Depreciable
*/
public function checkin_email()
{
- return $this->category->checkin_email;
+ if ($this->category) {
+ return $this->category->checkin_email;
+ }
+ return false;
}
/**
@@ -335,7 +339,11 @@ class License extends Depreciable
*/
public function requireAcceptance()
{
- return $this->category->require_acceptance;
+ if ($this->category) {
+ return $this->category->require_acceptance;
+ }
+
+ return false;
}
/**
@@ -348,14 +356,16 @@ class License extends Depreciable
*/
public function getEula()
{
-
- if ($this->category->eula_text) {
- return Helper::parseEscapedMarkedown($this->category->eula_text);
- } elseif ($this->category->use_default_eula == '1') {
- return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
- } else {
- return false;
+ if ($this->category){
+ if ($this->category->eula_text) {
+ return Helper::parseEscapedMarkedown($this->category->eula_text);
+ } elseif ($this->category->use_default_eula == '1') {
+ return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
+ }
}
+
+ return false;
+
}
/**
diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php
index d2a99d3c56..8a51c0c9cf 100755
--- a/app/Models/LicenseSeat.php
+++ b/app/Models/LicenseSeat.php
@@ -48,7 +48,10 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
*/
public function requireAcceptance()
{
- return $this->license->category->require_acceptance;
+ if ($this->license && $this->license->category) {
+ return $this->license->category->require_acceptance;
+ }
+ return false;
}
public function getEula()
diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php
index d0bbd10733..ce3a07f159 100644
--- a/app/Models/Loggable.php
+++ b/app/Models/Loggable.php
@@ -23,7 +23,7 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
- public function logCheckout($note, $target, $action_date = null)
+ public function logCheckout($note, $target, $action_date = null, $originalValues = [])
{
$log = new Actionlog;
$log = $this->determineLogItemType($log);
@@ -62,6 +62,23 @@ trait Loggable
$log->action_date = date('Y-m-d H:i:s');
}
+ $changed = [];
+ $originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
+
+ foreach ($originalValues as $key => $value) {
+ if ($key == 'action_date' && $value != $action_date) {
+ $changed[$key]['old'] = $value;
+ $changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
+ } elseif ($value != $this->getAttributes()[$key]) {
+ $changed[$key]['old'] = $value;
+ $changed[$key]['new'] = $this->getAttributes()[$key];
+ }
+ }
+
+ if (!empty($changed)){
+ $log->log_meta = json_encode($changed);
+ }
+
$log->logaction('checkout');
return $log;
@@ -89,7 +106,7 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
- public function logCheckin($target, $note, $action_date = null)
+ public function logCheckin($target, $note, $action_date = null, $originalValues = [])
{
$settings = Setting::getSettings();
$log = new Actionlog;
@@ -114,13 +131,9 @@ trait Loggable
}
}
-
$log->location_id = null;
$log->note = $note;
$log->action_date = $action_date;
- if (! $log->action_date) {
- $log->action_date = date('Y-m-d H:i:s');
- }
if (! $log->action_date) {
$log->action_date = date('Y-m-d H:i:s');
@@ -130,6 +143,23 @@ trait Loggable
$log->user_id = Auth::user()->id;
}
+ $changed = [];
+ $originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
+
+ foreach ($originalValues as $key => $value) {
+ if ($key == 'action_date' && $value != $action_date) {
+ $changed[$key]['old'] = $value;
+ $changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
+ } elseif ($value != $this->getAttributes()[$key]) {
+ $changed[$key]['old'] = $value;
+ $changed[$key]['new'] = $this->getAttributes()[$key];
+ }
+ }
+
+ if (!empty($changed)){
+ $log->log_meta = json_encode($changed);
+ }
+
$log->logaction('checkin from');
// $params = [
diff --git a/app/Models/Manufacturer.php b/app/Models/Manufacturer.php
index da4f26b02f..5408d50c36 100755
--- a/app/Models/Manufacturer.php
+++ b/app/Models/Manufacturer.php
@@ -77,7 +77,8 @@ class Manufacturer extends SnipeModel
&& ($this->assets()->count() === 0)
&& ($this->licenses()->count() === 0)
&& ($this->consumables()->count() === 0)
- && ($this->accessories()->count() === 0);
+ && ($this->accessories()->count() === 0)
+ && ($this->deleted_at == '');
}
public function assets()
diff --git a/app/Models/SCIMUser.php b/app/Models/SCIMUser.php
index 71bd9169ae..fcf34c0d5d 100644
--- a/app/Models/SCIMUser.php
+++ b/app/Models/SCIMUser.php
@@ -9,8 +9,7 @@ class SCIMUser extends User
protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false
public function __construct(array $attributes = []) {
- $attributes['password'] = "*NO PASSWORD*";
- // $attributes['activated'] = 1;
+ $attributes['password'] = $this->noPassword();
parent::__construct($attributes);
}
}
\ No newline at end of file
diff --git a/app/Models/Setting.php b/app/Models/Setting.php
index 6c95d6b01e..caf142cbdf 100755
--- a/app/Models/Setting.php
+++ b/app/Models/Setting.php
@@ -92,6 +92,10 @@ class Setting extends Model
'google_client_secret',
];
+ protected $casts = [
+ 'label2_asset_logo' => 'boolean',
+ ];
+
/**
* Get the app settings.
* Cache is expired on Setting model saved in EventServiceProvider.
diff --git a/app/Models/SnipeSCIMConfig.php b/app/Models/SnipeSCIMConfig.php
index 7ec25645e6..f17b5cfb61 100644
--- a/app/Models/SnipeSCIMConfig.php
+++ b/app/Models/SnipeSCIMConfig.php
@@ -129,8 +129,20 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
'locale' => null, // see RFC5646
'timezone' => null, // see RFC6557
- 'active' => AttributeMapping::eloquent('activated'),
-
+ 'active' => (new AttributeMapping())->setAdd(
+ function ($value, &$object) {
+ $object->activated = $value;
+ }
+ )->setReplace(
+ function ($value, &$object) {
+ $object->activated = $value;
+ }
+ )->setRead(
+ // this works as specified.
+ function (&$object) {
+ return (bool)$object->activated;
+ }
+ ),
'password' => AttributeMapping::eloquent('password')->disableRead(),
// Multi-Valued Attributes
diff --git a/app/Models/User.php b/app/Models/User.php
index e81be484f3..72bee9d45c 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Notifications\Notifiable;
+use Illuminate\Support\Facades\Gate;
use Laravel\Passport\HasApiTokens;
use Watson\Validating\ValidatingTrait;
@@ -70,15 +71,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
];
protected $casts = [
- 'activated' => 'boolean',
'manager_id' => 'integer',
'location_id' => 'integer',
'company_id' => 'integer',
- 'vip' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
- 'autoassign_licenses' => 'boolean',
];
/**
@@ -104,6 +102,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'state' => 'min:2|max:191|nullable',
'country' => 'min:2|max:191|nullable',
'zip' => 'max:10|nullable',
+ 'vip' => 'boolean',
+ 'remote' => 'boolean',
+ 'activated' => 'boolean',
];
/**
@@ -202,6 +203,23 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->checkPermissionSection('superuser');
}
+ /**
+ * Checks if the user is deletable
+ *
+ * @author A. Gianotto
+ * @since [v6.3.4]
+ * @return bool
+ */
+ public function isDeletable()
+ {
+ return Gate::allows('delete', $this)
+ && ($this->assets()->count() === 0)
+ && ($this->licenses()->count() === 0)
+ && ($this->consumables()->count() === 0)
+ && ($this->accessories()->count() === 0)
+ && ($this->deleted_at == '');
+ }
+
/**
* Establishes the user -> company relationship
@@ -248,21 +266,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function getFullNameAttribute()
{
- return $this->first_name.' '.$this->last_name;
- }
+ $setting = Setting::getSettings();
- /**
- * Returns the complete name attribute with username
- *
- * @todo refactor this so it's less repetitive and dumb
- *
- * @author A. Gianotto
- * @since [v2.0]
- * @return string
- */
- public function getCompleteNameAttribute()
- {
- return $this->last_name.', '.$this->first_name.' ('.$this->username.')';
+ if ($setting->name_display_format=='last_first') {
+ return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name;
+ }
+ return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name;
}
@@ -476,6 +485,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
}
+ /**
+ * Set a common string when the user has been imported/synced from:
+ *
+ * - LDAP without password syncing
+ * - SCIM
+ * - CSV import where no password was provided
+ *
+ * @author A. Gianotto
+ * @since [v6.2.0]
+ * @return string
+ */
+ public function noPassword()
+ {
+ return "*** NO PASSWORD ***";
+ }
+
/**
* Query builder scope to return NOT-deleted users
@@ -770,4 +795,26 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
return $this->locale;
}
+ public function getUserTotalCost(){
+ $asset_cost= 0;
+ $license_cost= 0;
+ $accessory_cost= 0;
+ foreach ($this->assets as $asset){
+ $asset_cost += $asset->purchase_cost;
+ $this->asset_cost = $asset_cost;
+ }
+ foreach ($this->licenses as $license){
+ $license_cost += $license->purchase_cost;
+ $this->license_cost = $license_cost;
+ }
+ foreach ($this->accessories as $accessory){
+ $accessory_cost += $accessory->purchase_cost;
+ $this->accessory_cost = $accessory_cost;
+ }
+
+ $this->total_user_cost = ($asset_cost + $accessory_cost + $license_cost);
+
+
+ return $this;
+ }
}
diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php
index 53be68f58f..7735f7dc11 100644
--- a/app/Notifications/CheckinAccessoryNotification.php
+++ b/app/Notifications/CheckinAccessoryNotification.php
@@ -26,7 +26,6 @@ class CheckinAccessoryNotification extends Notification
$this->admin = $checkedInby;
$this->note = $note;
$this->settings = Setting::getSettings();
- \Log::debug('Constructor for notification fired');
}
/**
diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php
index 5389c8ddde..05e56a9619 100644
--- a/app/Notifications/CheckinAssetNotification.php
+++ b/app/Notifications/CheckinAssetNotification.php
@@ -2,6 +2,7 @@
namespace App\Notifications;
+use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php
index 84595f04b3..2b0955fde6 100644
--- a/app/Observers/AssetObserver.php
+++ b/app/Observers/AssetObserver.php
@@ -6,11 +6,12 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Setting;
use Auth;
+use Carbon\Carbon;
class AssetObserver
{
/**
- * Listen to the User created event.
+ * Listen to the Asset updating event. This fires automatically every time an existing asset is saved.
*
* @param Asset $asset
* @return void
@@ -21,6 +22,13 @@ class AssetObserver
$attributesOriginal = $asset->getRawOriginal();
$same_checkout_counter = false;
$same_checkin_counter = false;
+ $restoring_or_deleting = false;
+
+
+ // This is a gross hack to prevent the double logging when restoring an asset
+ if (array_key_exists('deleted_at', $attributes) && array_key_exists('deleted_at', $attributesOriginal)){
+ $restoring_or_deleting = (($attributes['deleted_at'] != $attributesOriginal['deleted_at']));
+ }
if (array_key_exists('checkout_counter', $attributes) && array_key_exists('checkout_counter', $attributesOriginal)){
$same_checkout_counter = (($attributes['checkout_counter'] == $attributesOriginal['checkout_counter']));
@@ -32,10 +40,10 @@ class AssetObserver
// If the asset isn't being checked out or audited, log the update.
// (Those other actions already create log entries.)
- if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
+ if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
&& ($same_checkout_counter) && ($same_checkin_counter)
&& ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null))
- && ($attributes['last_checkout'] == $attributesOriginal['last_checkout']))
+ && ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting))
{
$changed = [];
@@ -74,9 +82,9 @@ class AssetObserver
$tag = $asset->asset_tag;
$prefix = $settings->auto_increment_prefix;
$number = substr($tag, strlen($prefix));
- // IF - auto_increment_assets is on, AND (the prefix matches the start of the tag OR there is no prefix)
+ // IF - auto_increment_assets is on, AND (there is no prefix OR the prefix matches the start of the tag)
// AND the rest of the string after the prefix is all digits, THEN...
- if ($settings->auto_increment_assets && (strpos($tag, $prefix) === 0 || $prefix=='') && preg_match('/\d+/',$number) === 1) {
+ if ($settings->auto_increment_assets && ($prefix=='' || strpos($tag, $prefix) === 0) && preg_match('/\d+/',$number) === 1) {
// new way of auto-trueing-up auto_increment ID's
$next_asset_tag = intval($number, 10) + 1;
// we had to use 'intval' because the $number could be '01234' and
@@ -119,4 +127,58 @@ class AssetObserver
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
+
+ /**
+ * Listen to the Asset deleting event.
+ *
+ * @param Asset $asset
+ * @return void
+ */
+ public function restoring(Asset $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::id();
+ $logAction->logaction('restore');
+ }
+
+ /**
+ * Executes every time an asset is saved.
+ *
+ * This matters specifically because any database fields affected here MUST already exist on
+ * the assets table (and/or any related models), or related migrations WILL fail.
+ *
+ * For example, if there is a database migration that's a bit older and modifies an asset, if the save
+ * fires before a field gets created in a later migration and that field in the later migration
+ * is used in this observer, it doesn't actually exist yet and the migration will break unless we
+ * use saveQuietly() in the migration which skips this observer.
+ *
+ * @see https://github.com/snipe/snipe-it/issues/13723#issuecomment-1761315938
+ */
+ public function saving(Asset $asset)
+ {
+ // determine if calculated eol and then calculate it - this should only happen on a new asset
+ if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && ($asset->model->eol > 0)){
+ $asset->asset_eol_date = $asset->purchase_date->addMonths($asset->model->eol)->format('Y-m-d');
+ $asset->eol_explicit = false;
+ }
+
+ // determine if explicit and set eol_explicit to true
+ if (!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) {
+ if($asset->model->eol > 0) {
+ $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
+ if($months != $asset->model->eol) {
+ $asset->eol_explicit = true;
+ }
+ }
+ } elseif (!is_null($asset->asset_eol_date) && is_null($asset->purchase_date)) {
+ $asset->eol_explicit = true;
+ }
+ if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol) || ($asset->model->eol == 0))) {
+ $asset->eol_explicit = true;
+ }
+
+ }
}
diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php
new file mode 100644
index 0000000000..5c565768d3
--- /dev/null
+++ b/app/Observers/UserObserver.php
@@ -0,0 +1,149 @@
+getRawOriginal() as $key => $value) {
+
+ // Make sure the info is in the allow fields array
+ if (in_array($key, $allowed_fields)) {
+
+ // Check and see if the value changed
+ if ($user->getRawOriginal()[$key] != $user->getAttributes()[$key]) {
+
+ $changed[$key]['old'] = $user->getRawOriginal()[$key];
+ $changed[$key]['new'] = $user->getAttributes()[$key];
+
+ // Do not store the hashed password in changes
+ if ($key == 'password') {
+ $changed['password']['old'] = '*************';
+ $changed['password']['new'] = '*************';
+ }
+
+ }
+ }
+
+ }
+
+ if (count($changed) > 0) {
+ $logAction = new Actionlog();
+ $logAction->item_type = User::class;
+ $logAction->item_id = $user->id;
+ $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
+ $logAction->target_id = $user->id;
+ $logAction->created_at = date('Y-m-d H:i:s');
+ $logAction->user_id = Auth::id();
+ $logAction->log_meta = json_encode($changed);
+ $logAction->logaction('update');
+ }
+
+
+ }
+
+ /**
+ * Listen to the User created event, and increment
+ * the next_auto_tag_base value in the settings table when i
+ * a new asset is created.
+ *
+ * @param User $user
+ * @return void
+ */
+ public function created(User $user)
+ {
+ $logAction = new Actionlog();
+ $logAction->item_type = User::class; // can we instead say $logAction->item = $asset ?
+ $logAction->item_id = $user->id;
+ $logAction->created_at = date('Y-m-d H:i:s');
+ $logAction->user_id = Auth::id();
+ $logAction->logaction('create');
+ }
+
+ /**
+ * Listen to the User deleting event.
+ *
+ * @param User $user
+ * @return void
+ */
+ public function deleting(User $user)
+ {
+ $logAction = new Actionlog();
+ $logAction->item_type = User::class;
+ $logAction->item_id = $user->id;
+ $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
+ $logAction->target_id = $user->id;
+ $logAction->created_at = date('Y-m-d H:i:s');
+ $logAction->user_id = Auth::id();
+ $logAction->logaction('delete');
+ }
+
+ /**
+ * Listen to the User deleting event.
+ *
+ * @param User $user
+ * @return void
+ */
+ public function restoring(User $user)
+ {
+ $logAction = new Actionlog();
+ $logAction->item_type = User::class;
+ $logAction->item_id = $user->id;
+ $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
+ $logAction->target_id = $user->id;
+ $logAction->created_at = date('Y-m-d H:i:s');
+ $logAction->user_id = Auth::id();
+ $logAction->logaction('restore');
+ }
+
+
+}
diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php
index cd581d33ce..ddff10864e 100644
--- a/app/Presenters/ActionlogPresenter.php
+++ b/app/Presenters/ActionlogPresenter.php
@@ -38,22 +38,63 @@ class ActionlogPresenter extends Presenter
public function icon()
{
- $itemicon = 'fas fa-paperclip';
+
+ // User related icons
+ if ($this->itemType() == 'user') {
- if ($this->itemType() == 'asset') {
- return 'fas fa-barcode';
- } elseif ($this->itemType() == 'accessory') {
- return 'far fa-keyboard';
- } elseif ($this->itemType() == 'consumable') {
- return 'fas fa-tint';
- } elseif ($this->itemType() == 'license') {
- return 'far fa-save';
- } elseif ($this->itemType() == 'component') {
- return 'far fa-hdd';
- } elseif ($this->itemType() == 'user') {
- return 'fa-solid fa-people-arrows';
+ if ($this->actionType()=='create new') {
+ return 'fa-solid fa-user-plus';
+ }
+
+ if ($this->actionType()=='merged') {
+ return 'fa-solid fa-people-arrows';
+ }
+
+ if ($this->actionType()=='delete') {
+ return 'fa-solid fa-user-minus';
+ }
+
+ if ($this->actionType()=='delete') {
+ return 'fa-solid fa-user-minus';
+ }
+
+ if ($this->actionType()=='update') {
+ return 'fa-solid fa-user-pen';
+ }
+ return 'fa-solid fa-user';
}
+ // Everything else
+ if ($this->actionType()=='create new') {
+ return 'fa-solid fa-plus';
+ }
+
+ if ($this->actionType()=='delete') {
+ return 'fa-solid fa-user-xmark';
+ }
+
+ if ($this->actionType()=='update') {
+ return 'fa-solid fa-pen';
+ }
+
+ if ($this->actionType()=='restore') {
+ return 'fa-solid fa-trash-arrow-up';
+ }
+
+ if ($this->actionType()=='upload') {
+ return 'fas fa-paperclip';
+ }
+
+ if ($this->actionType()=='checkout') {
+ return 'fa-solid fa-rotate-left';
+ }
+
+ if ($this->actionType()=='checkin from') {
+ return 'fa-solid fa-rotate-right';
+ }
+
+ return 'fa-solid fa-rotate-right';
+
}
public function actionType()
diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php
index 8da192ebb2..85a0fa58ec 100644
--- a/app/Presenters/AssetModelPresenter.php
+++ b/app/Presenters/AssetModelPresenter.php
@@ -65,6 +65,14 @@ class AssetModelPresenter extends Presenter
'title' => trans('admin/models/table.modelnumber'),
'visible' => true,
],
+ [
+ 'field' => 'min_amt',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('mail.min_QTY'),
+ 'visible' => true,
+ ],
[
'field' => 'assets_count',
'searchable' => false,
@@ -96,7 +104,7 @@ class AssetModelPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'switchable' => true,
- 'title' => trans('general.eol'),
+ 'title' => trans('admin/hardware/form.eol_rate'),
'visible' => true,
],
[
diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php
index ec50933583..de7c2c7709 100644
--- a/app/Presenters/AssetPresenter.php
+++ b/app/Presenters/AssetPresenter.php
@@ -3,6 +3,7 @@
namespace App\Presenters;
use App\Models\CustomField;
+use Carbon\CarbonImmutable;
use DateTime;
/**
@@ -142,8 +143,8 @@ class AssetPresenter extends Presenter
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'age',
- 'searchable' => true,
- 'sortable' => true,
+ 'searchable' => false,
+ 'sortable' => false,
'visible' => false,
'title' => trans('general.age'),
], [
@@ -172,7 +173,7 @@ class AssetPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'visible' => false,
- 'title' => trans('general.eol'),
+ 'title' => trans('admin/hardware/form.eol_rate'),
],
[
'field' => 'asset_eol_date',
@@ -429,10 +430,7 @@ class AssetPresenter extends Presenter
public function eol_date()
{
if (($this->purchase_date) && ($this->model->model) && ($this->model->model->eol)) {
- $date = date_create($this->purchase_date);
- date_add($date, date_interval_create_from_date_string($this->model->model->eol.' months'));
-
- return date_format($date, 'Y-m-d');
+ return CarbonImmutable::parse($this->purchase_date)->addMonths($this->model->model->eol)->format('Y-m-d');
}
}
@@ -548,8 +546,10 @@ class AssetPresenter extends Presenter
public function dynamicWarrantyUrl()
{
$warranty_lookup_url = $this->model->model->manufacturer->warranty_lookup_url;
- $url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale,$warranty_lookup_url));
- $url = (str_replace('{SERIAL}',$this->model->serial,$url));
+ $url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale, $warranty_lookup_url));
+ $url = (str_replace('{SERIAL}', urlencode($this->model->serial), $url));
+ $url = (str_replace('{MODEL_NAME}', urlencode($this->model->model->name), $url));
+ $url = (str_replace('{MODEL_NUMBER}', urlencode($this->model->model->model_number), $url));
return $url;
}
diff --git a/app/Presenters/LabelPresenter.php b/app/Presenters/LabelPresenter.php
index 5ff95d2c4d..db919e659a 100644
--- a/app/Presenters/LabelPresenter.php
+++ b/app/Presenters/LabelPresenter.php
@@ -21,7 +21,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'name',
'searchable' => true,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('general.name'),
'visible' => true,
@@ -44,14 +44,14 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_fields',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_fields'),
'visible' => true
], [
'field' => 'support_asset_tag',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_asset_tag'),
'visible' => true,
@@ -59,7 +59,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_1d_barcode',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_1d_barcode'),
'visible' => true,
@@ -67,7 +67,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_2d_barcode',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_2d_barcode'),
'visible' => true,
@@ -75,7 +75,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_logo',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_logo'),
'visible' => true,
@@ -83,7 +83,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_title',
'searchable' => false,
- 'sortable' => true,
+ 'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_title'),
'visible' => true,
diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php
index f2d54549e1..e76c9152cb 100644
--- a/app/Presenters/LicensePresenter.php
+++ b/app/Presenters/LicensePresenter.php
@@ -254,6 +254,14 @@ class LicensePresenter extends Presenter
'visible' => true,
'formatter' => 'locationsLinkObjFormatter',
],
+ [
+ 'field' => 'notes',
+ 'searchable' => false,
+ 'sortable' => false,
+ 'visible' => false,
+ 'title' => trans('general.notes'),
+ 'formatter' => 'notesFormatter'
+ ],
[
'field' => 'checkincheckout',
'searchable' => false,
diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php
index bf4ee27bba..86e82c1220 100644
--- a/app/Presenters/LocationPresenter.php
+++ b/app/Presenters/LocationPresenter.php
@@ -106,7 +106,7 @@ class LocationPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'switchable' => true,
- 'title' => trans('admin/locations/table.address'),
+ 'title' => trans('admin/locations/table.address2'),
'visible' => false,
],
[
diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php
index 080a2d10e9..f70ddf8af6 100644
--- a/app/Presenters/UserPresenter.php
+++ b/app/Presenters/UserPresenter.php
@@ -433,7 +433,7 @@ class UserPresenter extends Presenter
*/
public function nameUrl()
{
- return (string) link_to_route('users.show', $this->fullName(), $this->id);
+ return (string) link_to_route('users.show', $this->getFullNameAttribute(), $this->id);
}
/**
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 607d206a67..dcd8d01275 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -7,10 +7,12 @@ use App\Models\Asset;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
+use App\Models\User;
use App\Models\Setting;
use App\Models\SnipeSCIMConfig;
use App\Observers\AccessoryObserver;
use App\Observers\AssetObserver;
+use App\Observers\UserObserver;
use App\Observers\ComponentObserver;
use App\Observers\ConsumableObserver;
use App\Observers\LicenseObserver;
@@ -58,6 +60,7 @@ class AppServiceProvider extends ServiceProvider
Schema::defaultStringLength(191);
Asset::observe(AssetObserver::class);
+ User::observe(UserObserver::class);
Accessory::observe(AccessoryObserver::class);
Component::observe(ComponentObserver::class);
Consumable::observe(ConsumableObserver::class);
@@ -75,12 +78,7 @@ class AppServiceProvider extends ServiceProvider
// Only load rollbar if there is a rollbar key and the app is in production
if (($this->app->environment('production')) && (config('logging.channels.rollbar.access_token'))) {
$this->app->register(\Rollbar\Laravel\RollbarServiceProvider::class);
- }
-
- // Only load dusk's service provider if the app is in local or develop mode
- if ($this->app->environment(['local', 'develop'])) {
- $this->app->register(\Laravel\Dusk\DuskServiceProvider::class);
- }
+ }
$this->app->singleton('ArieTimmerman\Laravel\SCIMServer\SCIMConfig', SnipeSCIMConfig::class); // this overrides the default SCIM configuration with our own
diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php
index 371fc234de..41dd80b4fc 100644
--- a/app/Providers/SettingsServiceProvider.php
+++ b/app/Providers/SettingsServiceProvider.php
@@ -33,18 +33,33 @@ class SettingsServiceProvider extends ServiceProvider
// Make sure the limit is actually set, is an integer and does not exceed system limits
\App::singleton('api_limit_value', function () {
$limit = config('app.max_results');
+ $int_limit = intval(request('limit'));
- if ((abs(intval(request('limit'))) > 0) && (abs(request('limit')) <= config('app.max_results'))) {
- $limit = abs(request('limit'));
+ if ((abs($int_limit) > 0) && ($int_limit <= config('app.max_results'))) {
+ $limit = abs($int_limit);
}
- \Log::debug('Max in env: '.config('app.max_results'));
- \Log::debug('Original requested limit: '.request('limit'));
- \Log::debug('Modified limit: '.$limit);
- \Log::debug('------------------------------');
+
+// \Log::debug('Max in env: '.config('app.max_results'));
+// \Log::debug('Original requested limit: '.request('limit'));
+// \Log::debug('Int limit: '.$int_limit);
+// \Log::debug('Modified limit: '.$limit);
+// \Log::debug('------------------------------');
+
return $limit;
});
+ // Make sure the offset is actually set and is an integer
+ \App::singleton('api_offset_value', function () {
+ $offset = intval(request('offset'));
+// \Log::debug('Original requested offset: '.request('offset'));
+// \Log::debug('Modified offset: '.$offset);
+// \Log::debug('------------------------------');
+
+
+ return $offset;
+ });
+
/**
* Set some common variables so that they're globally available.
diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php
index d7a3c03778..50468c8d72 100644
--- a/app/Providers/ValidationServiceProvider.php
+++ b/app/Providers/ValidationServiceProvider.php
@@ -3,6 +3,7 @@
namespace App\Providers;
use App\Models\Department;
+use App\Models\Setting;
use DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rule;
@@ -45,30 +46,87 @@ class ValidationServiceProvider extends ServiceProvider
return $validator->passes();
});
- // Unique only if undeleted
- // This works around the use case where multiple deleted items have the same unique attribute.
- // (I think this is a bug in Laravel's validator?)
+
+ /**
+ * Unique only if undeleted.
+ *
+ * This works around the use case where multiple deleted items have the same unique attribute.
+ * (I think this is a bug in Laravel's validator?)
+ *
+ * $attribute is the FIELDNAME you're checking against
+ * $value is the VALUE of the item you're checking against the existing values in the fieldname
+ * $parameters[0] is the TABLE NAME you're querying
+ * $parameters[1] is the ID of the item you're querying - this makes it work on saving, checkout, etc,
+ * since it defaults to 0 if there is no item created yet (new item), but populates the ID if editing
+ *
+ * The UniqueUndeletedTrait prefills these parameters, so you can just use
+ * `unique_undeleted:table,fieldname` in your rules out of the box
+ */
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
+
if (count($parameters)) {
- $count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
+
+ // This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable
+ if (($parameters[0]=='assets') && ($attribute == 'serial') && (Setting::getSettings()->unique_serial != '1')) {
+ return true;
+ }
+
+ $count = DB::table($parameters[0])
+ ->select('id')
+ ->where($attribute, '=', $value)
+ ->whereNull('deleted_at')
+ ->where('id', '!=', $parameters[1])->count();
+
+ return $count < 1;
+ }
+ });
+
+ /**
+ * Unique if undeleted for two columns
+ *
+ * Same as unique_undeleted but taking the combination of two columns as unique constrain.
+ * This uses the Validator::replacer('two_column_unique_undeleted') below for nicer translations.
+ *
+ * $parameters[0] - the name of the first table we're looking at
+ * $parameters[1] - the ID (this will be 0 on new creations)
+ * $parameters[2] - the name of the second table we're looking at
+ * $parameters[3] - the value that the request is passing for the second table we're
+ * checking for uniqueness across
+ *
+ */
+ Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
+ if (count($parameters)) {
+ $count = DB::table($parameters[0])
+ ->select('id')->where($attribute, '=', $value)
+ ->whereNull('deleted_at')
+ ->where('id', '!=', $parameters[1])
+ ->where($parameters[2], $parameters[3])->count();
return $count < 1;
}
});
- // Unique if undeleted for two columns
- // Same as unique_undeleted but taking the combination of two columns as unique constrain.
- Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
- if (count($parameters)) {
- $count = DB::table($parameters[0])
- ->select('id')->where($attribute, '=', $value)
- ->whereNull('deleted_at')
- ->where('id', '!=', $parameters[1])
- ->where($parameters[2], $parameters[3])->count();
- return $count < 1;
- }
- });
+ /**
+ * This is the validator replace static method that allows us to pass the $parameters of the table names
+ * into the translation string in validation.two_column_unique_undeleted for two_column_unique_undeleted
+ * validation messages.
+ *
+ * This is invoked automatically by Validator::extend('two_column_unique_undeleted') above and
+ * produces a translation like: "The name value must be unique across categories and category type."
+ *
+ * The $parameters passed coincide with the ones the two_column_unique_undeleted custom validator above
+ * uses, so $parameter[0] is the first table and so $parameter[2] is the second table.
+ */
+ Validator::replacer('two_column_unique_undeleted', function($message, $attribute, $rule, $parameters) {
+ $message = str_replace(':table1', $parameters[0], $message);
+ $message = str_replace(':table2', $parameters[2], $message);
+
+ // Change underscores to spaces for a friendlier display
+ $message = str_replace('_', ' ', $message);
+ return $message;
+ });
+
// Prevent circular references
//
@@ -232,6 +290,10 @@ class ValidationServiceProvider extends ServiceProvider
return true;
}
});
+
+ Validator::extend('not_array', function ($attribute, $value, $parameters, $validator) {
+ return !is_array($value);
+ });
}
/**
diff --git a/app/View/Label.php b/app/View/Label.php
index 99da449827..83184e4b04 100644
--- a/app/View/Label.php
+++ b/app/View/Label.php
@@ -39,7 +39,7 @@ class Label implements View
$assets = $this->data->get('assets');
$offset = $this->data->get('offset');
$template = $this->data->get('template');
-
+
// If disabled, pass to legacy view
if ((!$settings->label2_enable) && (!$template)) {
return view('hardware/labels')
@@ -49,8 +49,12 @@ class Label implements View
->with('count', $this->data->get('count'));
}
- if (empty($template)) $template = LabelModel::find($settings->label2_template);
- elseif (is_string($template)) $template = LabelModel::find($template);
+ // If a specific template was set, use it, otherwise fall back to default
+ if (empty($template)) {
+ $template = LabelModel::find($settings->label2_template);
+ } elseif (is_string($template)) {
+ $template = LabelModel::find($template);
+ }
$template->validate();
@@ -87,25 +91,29 @@ class Label implements View
$assetData->put('tag', $asset->asset_tag);
if ($template->getSupportTitle()) {
- $title = !empty($settings->label2_title) ?
- str_ireplace('{COMPANY}', $asset->company->name, $settings->label2_title) :
+
+ if ($asset->company && !empty($settings->label2_title)) {
+ $title = str_replace('{COMPANY}', $asset->company->name, $settings->label2_title);
$settings->qr_text;
- if (!empty($title)) $assetData->put('title', $title);
+ $assetData->put('title', $title);
+ }
}
if ($template->getSupportLogo()) {
- $logo = $settings->label2_asset_logo ?
- (
- !empty($asset->company->image) ?
- Storage::disk('public')->path('companies/'.e($asset->company->image)) :
- null
- ) :
- (
- !empty($settings->label_logo) ?
- Storage::disk('public')->path(''.e($settings->label_logo)) :
- null
- );
- if (!empty($logo)) $assetData->put('logo', $logo);
+
+ $logo = null;
+
+ // Should we use the assets assigned company logo? (A.K.A. "Is `Labels > Use Asset Logo` enabled?"), and do we have a company logo?
+ if ($settings->label2_asset_logo && $asset->company && $asset->company->image!='') {
+ $logo = Storage::disk('public')->path('companies/'.e($asset->company->image));
+ } elseif (!empty($settings->label_logo)) {
+ // Use the general site label logo, if available
+ $logo = Storage::disk('public')->path('/'.e($settings->label_logo));
+ }
+
+ if (!empty($logo)) {
+ $assetData->put('logo', $logo);
+ }
}
if ($template->getSupport1DBarcode()) {
diff --git a/composer.json b/composer.json
index 20d76a1d91..2a456999e9 100644
--- a/composer.json
+++ b/composer.json
@@ -74,13 +74,16 @@
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^6.1"
},
+ "suggest": {
+ "ext-ldap": "*"
+ },
"require-dev": {
"brianium/paratest": "^6.6",
"fakerphp/faker": "^1.16",
- "laravel/dusk": "^6.25",
"mockery/mockery": "^1.4",
"nunomaduro/larastan": "^1.0",
"nunomaduro/phpinsights": "^2.7",
+ "php-mock/php-mock-phpunit": "^2.8",
"phpunit/php-token-stream": "^3.1",
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5",
@@ -107,7 +110,6 @@
},
"autoload-dev": {
"classmap": [
- "tests/DuskTestCase.php",
"tests/TestCase.php"
],
"psr-4": {
diff --git a/composer.lock b/composer.lock
index 6221937a3d..2d5c6c677e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f30d1bebf56af36eb55a56d093b54650",
+ "content-hash": "f4f3b6b02d044ed3e54cdd509b01c3dc",
"packages": [
{
"name": "alek13/slack",
@@ -78,25 +78,25 @@
"source": {
"type": "git",
"url": "https://github.com/grokability/laravel-scim-server.git",
- "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419"
+ "reference": "dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419",
- "reference": "9e8dd2d3958d3c3c05d0a99fe6475361ad9e9419",
+ "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d",
+ "reference": "dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d",
"shasum": ""
},
"require": {
- "illuminate/console": "^6.0|^7.0|^8.0|^9.0",
- "illuminate/database": "^6.0|^7.0|^8.0|^9.0",
- "illuminate/support": "^6.0|^7.0|^8.0|^9.0",
+ "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0",
+ "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0",
+ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0",
"php": "^7.0|^8.0",
"tmilos/scim-filter-parser": "^1.3",
"tmilos/scim-schema": "^0.1.0"
},
"require-dev": {
"laravel/legacy-factories": "*",
- "orchestra/testbench": "^5.0|^6.0"
+ "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0"
},
"default-branch": true,
"type": "library",
@@ -133,7 +133,7 @@
"support": {
"source": "https://github.com/grokability/laravel-scim-server/tree/master"
},
- "time": "2023-01-12T00:32:07+00:00"
+ "time": "2023-09-07T16:45:26+00:00"
},
{
"name": "asm89/stack-cors",
@@ -193,23 +193,27 @@
},
{
"name": "aws/aws-crt-php",
- "version": "v1.0.2",
+ "version": "v1.2.4",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
- "reference": "3942776a8c99209908ee0b287746263725685732"
+ "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732",
- "reference": "3942776a8c99209908ee0b287746263725685732",
+ "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2",
+ "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35|^5.4.3"
+ "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
+ "yoast/phpunit-polyfills": "^1.0"
+ },
+ "suggest": {
+ "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
},
"type": "library",
"autoload": {
@@ -228,7 +232,7 @@
}
],
"description": "AWS Common Runtime for PHP",
- "homepage": "http://aws.amazon.com/sdkforphp",
+ "homepage": "https://github.com/awslabs/aws-crt-php",
"keywords": [
"amazon",
"aws",
@@ -237,40 +241,42 @@
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
- "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2"
+ "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4"
},
- "time": "2021-09-03T22:57:30+00:00"
+ "time": "2023-11-08T00:42:13+00:00"
},
{
"name": "aws/aws-sdk-php",
- "version": "3.231.12",
+ "version": "3.295.4",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "8f8742caa42b260950320c98ddc5da4926e2373d"
+ "reference": "2372661db989fe4229abd95f4434b37252076d58"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8f8742caa42b260950320c98ddc5da4926e2373d",
- "reference": "8f8742caa42b260950320c98ddc5da4926e2373d",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2372661db989fe4229abd95f4434b37252076d58",
+ "reference": "2372661db989fe4229abd95f4434b37252076d58",
"shasum": ""
},
"require": {
- "aws/aws-crt-php": "^1.0.2",
+ "aws/aws-crt-php": "^1.2.3",
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
- "guzzlehttp/promises": "^1.4.0",
- "guzzlehttp/psr7": "^1.8.5 || ^2.3",
+ "guzzlehttp/promises": "^1.4.0 || ^2.0",
+ "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
"mtdowling/jmespath.php": "^2.6",
- "php": ">=5.5"
+ "php": ">=7.2.5",
+ "psr/http-message": "^1.0 || ^2.0"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"composer/composer": "^1.10.22",
+ "dms/phpunit-arraysubset-asserts": "^0.4.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
@@ -278,10 +284,11 @@
"ext-sockets": "*",
"nette/neon": "^2.3",
"paragonie/random_compat": ">= 2",
- "phpunit/phpunit": "^4.8.35 || ^5.6.3",
+ "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0",
- "sebastian/comparator": "^1.2.3"
+ "sebastian/comparator": "^1.2.3 || ^4.0",
+ "yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
@@ -329,9 +336,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.231.12"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.295.4"
},
- "time": "2022-07-22T18:19:47+00:00"
+ "time": "2023-12-29T19:07:49+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -1810,16 +1817,16 @@
},
{
"name": "dompdf/dompdf",
- "version": "v2.0.3",
+ "version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
- "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85"
+ "reference": "093f2d9739cec57428e39ddadedfd4f3ae862c0f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/e8d2d5e37e8b0b30f0732a011295ab80680d7e85",
- "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85",
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/093f2d9739cec57428e39ddadedfd4f3ae862c0f",
+ "reference": "093f2d9739cec57428e39ddadedfd4f3ae862c0f",
"shasum": ""
},
"require": {
@@ -1866,9 +1873,9 @@
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
- "source": "https://github.com/dompdf/dompdf/tree/v2.0.3"
+ "source": "https://github.com/dompdf/dompdf/tree/v2.0.4"
},
- "time": "2023-02-07T12:51:48+00:00"
+ "time": "2023-12-12T20:19:39+00:00"
},
{
"name": "dragonmantank/cron-expression",
@@ -2740,22 +2747,22 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.4.5",
+ "version": "7.8.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82"
+ "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
- "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
+ "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.5",
- "guzzlehttp/psr7": "^1.9 || ^2.4",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
+ "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -2764,10 +2771,11 @@
"psr/http-client-implementation": "1.0"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.4.1",
+ "bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
- "php-http/client-integration-tests": "^3.0",
- "phpunit/phpunit": "^8.5.5 || ^9.3.5",
+ "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+ "php-http/message-factory": "^1.1",
+ "phpunit/phpunit": "^8.5.36 || ^9.6.15",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
@@ -2777,8 +2785,9 @@
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "7.4-dev"
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
}
},
"autoload": {
@@ -2844,7 +2853,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.4.5"
+ "source": "https://github.com/guzzle/guzzle/tree/7.8.1"
},
"funding": [
{
@@ -2860,38 +2869,37 @@
"type": "tidelift"
}
],
- "time": "2022-06-20T22:16:13+00:00"
+ "time": "2023-12-03T20:35:24+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "1.5.1",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
+ "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
- "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
+ "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
"shasum": ""
},
"require": {
- "php": ">=5.5"
+ "php": "^7.2.5 || ^8.0"
},
"require-dev": {
- "symfony/phpunit-bridge": "^4.4 || ^5.1"
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.36 || ^9.6.15"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "1.5-dev"
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
}
},
"autoload": {
- "files": [
- "src/functions_include.php"
- ],
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
@@ -2928,7 +2936,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/1.5.1"
+ "source": "https://github.com/guzzle/promises/tree/2.0.2"
},
"funding": [
{
@@ -2944,26 +2952,26 @@
"type": "tidelift"
}
],
- "time": "2021-10-22T20:56:57+00:00"
+ "time": "2023-12-03T20:19:20+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "2.4.0",
+ "version": "2.6.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "13388f00956b1503577598873fffb5ae994b5737"
+ "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737",
- "reference": "13388f00956b1503577598873fffb5ae994b5737",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
+ "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
- "psr/http-message": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
@@ -2971,17 +2979,18 @@
"psr/http-message-implementation": "1.0"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.4.1",
+ "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9",
- "phpunit/phpunit": "^8.5.8 || ^9.3.10"
+ "phpunit/phpunit": "^8.5.36 || ^9.6.15"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "2.4-dev"
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
}
},
"autoload": {
@@ -3043,7 +3052,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.4.0"
+ "source": "https://github.com/guzzle/psr7/tree/2.6.2"
},
"funding": [
{
@@ -3059,7 +3068,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-20T21:43:11+00:00"
+ "time": "2023-12-03T20:05:35+00:00"
},
{
"name": "intervention/image",
@@ -5274,26 +5283,24 @@
},
{
"name": "masterminds/html5",
- "version": "2.7.6",
+ "version": "2.8.1",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
- "reference": "897eb517a343a2281f11bc5556d6548db7d93947"
+ "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947",
- "reference": "897eb517a343a2281f11bc5556d6548db7d93947",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf",
+ "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf",
"shasum": ""
},
"require": {
- "ext-ctype": "*",
"ext-dom": "*",
- "ext-libxml": "*",
"php": ">=5.3.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8"
},
"type": "library",
"extra": {
@@ -5337,9 +5344,9 @@
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
- "source": "https://github.com/Masterminds/html5-php/tree/2.7.6"
+ "source": "https://github.com/Masterminds/html5-php/tree/2.8.1"
},
- "time": "2022-08-18T16:18:26+00:00"
+ "time": "2023-05-10T11:58:31+00:00"
},
{
"name": "maximebf/debugbar",
@@ -5576,25 +5583,25 @@
},
{
"name": "mtdowling/jmespath.php",
- "version": "2.6.1",
+ "version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
- "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb"
+ "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb",
- "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb",
+ "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b",
+ "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b",
"shasum": ""
},
"require": {
- "php": "^5.4 || ^7.0 || ^8.0",
+ "php": "^7.2.5 || ^8.0",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
- "composer/xdebug-handler": "^1.4 || ^2.0",
- "phpunit/phpunit": "^4.8.36 || ^7.5.15"
+ "composer/xdebug-handler": "^3.0.3",
+ "phpunit/phpunit": "^8.5.33"
},
"bin": [
"bin/jp.php"
@@ -5602,7 +5609,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.6-dev"
+ "dev-master": "2.7-dev"
}
},
"autoload": {
@@ -5618,6 +5625,11 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
@@ -5631,9 +5643,9 @@
],
"support": {
"issues": "https://github.com/jmespath/jmespath.php/issues",
- "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1"
+ "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0"
},
- "time": "2021-06-14T00:11:39+00:00"
+ "time": "2023-08-25T10:54:48+00:00"
},
{
"name": "myclabs/php-enum",
@@ -6130,16 +6142,16 @@
},
{
"name": "nyholm/psr7",
- "version": "1.5.1",
+ "version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
- "reference": "f734364e38a876a23be4d906a2a089e1315be18a"
+ "reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Nyholm/psr7/zipball/f734364e38a876a23be4d906a2a089e1315be18a",
- "reference": "f734364e38a876a23be4d906a2a089e1315be18a",
+ "url": "https://api.github.com/repos/Nyholm/psr7/zipball/e874c8c4286a1e010fb4f385f3a55ac56a05cc93",
+ "reference": "e874c8c4286a1e010fb4f385f3a55ac56a05cc93",
"shasum": ""
},
"require": {
@@ -6149,6 +6161,7 @@
"psr/http-message": "^1.0"
},
"provide": {
+ "php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
@@ -6161,7 +6174,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "1.6-dev"
}
},
"autoload": {
@@ -6191,7 +6204,7 @@
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
- "source": "https://github.com/Nyholm/psr7/tree/1.5.1"
+ "source": "https://github.com/Nyholm/psr7/tree/1.6.1"
},
"funding": [
{
@@ -6203,7 +6216,7 @@
"type": "github"
}
],
- "time": "2022-06-22T07:13:36+00:00"
+ "time": "2023-04-17T16:03:48+00:00"
},
{
"name": "onelogin/php-saml",
@@ -6574,16 +6587,16 @@
},
{
"name": "phenx/php-svg-lib",
- "version": "0.5.0",
+ "version": "0.5.1",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
- "reference": "76876c6cf3080bcb6f249d7d59705108166a6685"
+ "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685",
- "reference": "76876c6cf3080bcb6f249d7d59705108166a6685",
+ "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8a8a1ebcf6aea861ef30197999f096f7bd4b4456",
+ "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456",
"shasum": ""
},
"require": {
@@ -6614,9 +6627,9 @@
"homepage": "https://github.com/PhenX/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
- "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0"
+ "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.1"
},
- "time": "2022-09-06T12:16:56+00:00"
+ "time": "2023-12-11T20:56:08+00:00"
},
{
"name": "php-http/message-factory",
@@ -6670,6 +6683,7 @@
"issues": "https://github.com/php-http/message-factory/issues",
"source": "https://github.com/php-http/message-factory/tree/master"
},
+ "abandoned": "psr/http-factory",
"time": "2015-12-19T14:08:53+00:00"
},
{
@@ -7009,16 +7023,16 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.14",
+ "version": "3.0.34",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef"
+ "reference": "56c79f16a6ae17e42089c06a2144467acc35348a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2f0b7af658cbea265cbb4a791d6c29a6613f98ef",
- "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a",
+ "reference": "56c79f16a6ae17e42089c06a2144467acc35348a",
"shasum": ""
},
"require": {
@@ -7030,6 +7044,7 @@
"phpunit/phpunit": "*"
},
"suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
@@ -7098,7 +7113,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.14"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34"
},
"funding": [
{
@@ -7114,7 +7129,7 @@
"type": "tidelift"
}
],
- "time": "2022-04-04T05:15:45+00:00"
+ "time": "2023-11-27T11:13:31+00:00"
},
{
"name": "phpspec/prophecy",
@@ -7521,21 +7536,21 @@
},
{
"name": "psr/http-client",
- "version": "1.0.1",
+ "version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
- "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
- "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
- "psr/http-message": "^1.0"
+ "psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
@@ -7555,7 +7570,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
@@ -7567,27 +7582,27 @@
"psr-18"
],
"support": {
- "source": "https://github.com/php-fig/http-client/tree/master"
+ "source": "https://github.com/php-fig/http-client"
},
- "time": "2020-06-29T06:28:15+00:00"
+ "time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
- "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+ "reference": "e616d01114759c4c489f93b099585439f795fe35"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
- "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
+ "reference": "e616d01114759c4c489f93b099585439f795fe35",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
- "psr/http-message": "^1.0"
+ "psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
@@ -7607,7 +7622,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
@@ -7622,31 +7637,31 @@
"response"
],
"support": {
- "source": "https://github.com/php-fig/http-factory/tree/master"
+ "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
},
- "time": "2019-04-30T12:38:16+00:00"
+ "time": "2023-04-10T20:10:41+00:00"
},
{
"name": "psr/http-message",
- "version": "1.0.1",
+ "version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.1.x-dev"
}
},
"autoload": {
@@ -7675,9 +7690,9 @@
"response"
],
"support": {
- "source": "https://github.com/php-fig/http-message/tree/master"
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
},
- "time": "2016-08-06T14:39:51+00:00"
+ "time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/log",
@@ -8794,12 +8809,12 @@
"version": "3.7.1",
"source": {
"type": "git",
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
"shasum": ""
},
@@ -8843,6 +8858,20 @@
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ }
+ ],
"time": "2022-06-18T07:21:10+00:00"
},
{
@@ -8923,16 +8952,16 @@
},
{
"name": "symfony/console",
- "version": "v5.4.10",
+ "version": "v5.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000"
+ "reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000",
- "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000",
+ "url": "https://api.github.com/repos/symfony/console/zipball/c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
+ "reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
"shasum": ""
},
"require": {
@@ -8997,12 +9026,12 @@
"homepage": "https://symfony.com",
"keywords": [
"cli",
- "command line",
+ "command-line",
"console",
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.4.10"
+ "source": "https://github.com/symfony/console/tree/v5.4.32"
},
"funding": [
{
@@ -9018,7 +9047,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-26T13:00:04+00:00"
+ "time": "2023-11-18T18:23:04+00:00"
},
{
"name": "symfony/css-selector",
@@ -9390,16 +9419,16 @@
},
{
"name": "symfony/finder",
- "version": "v5.4.8",
+ "version": "v5.4.27",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9"
+ "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9",
- "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d",
+ "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d",
"shasum": ""
},
"require": {
@@ -9433,7 +9462,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v5.4.8"
+ "source": "https://github.com/symfony/finder/tree/v5.4.27"
},
"funding": [
{
@@ -9449,7 +9478,7 @@
"type": "tidelift"
}
],
- "time": "2022-04-15T08:07:45+00:00"
+ "time": "2023-07-31T08:02:31+00:00"
},
{
"name": "symfony/http-foundation",
@@ -9721,16 +9750,16 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
- "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
@@ -9745,7 +9774,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -9783,7 +9812,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
@@ -9799,7 +9828,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-iconv",
@@ -9886,16 +9915,16 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "433d05519ce6990bf3530fba6957499d327395c2"
+ "reference": "875e90aeea2777b6f135677f618529449334a612"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
- "reference": "433d05519ce6990bf3530fba6957499d327395c2",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
+ "reference": "875e90aeea2777b6f135677f618529449334a612",
"shasum": ""
},
"require": {
@@ -9907,7 +9936,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -9947,7 +9976,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
},
"funding": [
{
@@ -9963,7 +9992,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
@@ -10054,16 +10083,16 @@
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
- "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": ""
},
"require": {
@@ -10075,7 +10104,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -10118,7 +10147,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
},
"funding": [
{
@@ -10134,20 +10163,20 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
+ "reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
- "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+ "reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
@@ -10162,7 +10191,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -10201,7 +10230,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
@@ -10217,7 +10246,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-07-28T09:04:16+00:00"
},
{
"name": "symfony/polyfill-php72",
@@ -10297,16 +10326,16 @@
},
{
"name": "symfony/polyfill-php73",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
- "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85"
+ "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85",
- "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5",
+ "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5",
"shasum": ""
},
"require": {
@@ -10315,7 +10344,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -10356,7 +10385,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0"
},
"funding": [
{
@@ -10372,20 +10401,20 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
+ "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
- "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
+ "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"shasum": ""
},
"require": {
@@ -10394,7 +10423,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -10439,7 +10468,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
},
"funding": [
{
@@ -10455,20 +10484,20 @@
"type": "tidelift"
}
],
- "time": "2022-05-10T07:21:04+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-php81",
- "version": "v1.26.0",
+ "version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
- "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1"
+ "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1",
- "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b",
+ "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b",
"shasum": ""
},
"require": {
@@ -10477,7 +10506,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.26-dev"
+ "dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -10518,7 +10547,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0"
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0"
},
"funding": [
{
@@ -10534,20 +10563,20 @@
"type": "tidelift"
}
],
- "time": "2022-05-24T11:49:31+00:00"
+ "time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/process",
- "version": "v5.4.8",
+ "version": "v5.4.28",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3"
+ "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
- "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
+ "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
+ "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
"shasum": ""
},
"require": {
@@ -10580,7 +10609,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v5.4.8"
+ "source": "https://github.com/symfony/process/tree/v5.4.28"
},
"funding": [
{
@@ -10596,7 +10625,7 @@
"type": "tidelift"
}
],
- "time": "2022-04-08T05:07:18+00:00"
+ "time": "2023-08-07T10:36:04+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -10861,16 +10890,16 @@
},
{
"name": "symfony/string",
- "version": "v5.4.10",
+ "version": "v5.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097"
+ "reference": "91bf4453d65d8231688a04376c3a40efe0770f04"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097",
- "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097",
+ "url": "https://api.github.com/repos/symfony/string/zipball/91bf4453d65d8231688a04376c3a40efe0770f04",
+ "reference": "91bf4453d65d8231688a04376c3a40efe0770f04",
"shasum": ""
},
"require": {
@@ -10927,7 +10956,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.10"
+ "source": "https://github.com/symfony/string/tree/v5.4.32"
},
"funding": [
{
@@ -10943,7 +10972,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-26T15:57:47+00:00"
+ "time": "2023-11-26T13:43:46+00:00"
},
{
"name": "symfony/translation",
@@ -12355,16 +12384,16 @@
},
{
"name": "composer/ca-bundle",
- "version": "1.3.6",
+ "version": "1.3.7",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "90d087e988ff194065333d16bc5cf649872d9cdb"
+ "reference": "76e46335014860eec1aa5a724799a00a2e47cc85"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb",
- "reference": "90d087e988ff194065333d16bc5cf649872d9cdb",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85",
+ "reference": "76e46335014860eec1aa5a724799a00a2e47cc85",
"shasum": ""
},
"require": {
@@ -12411,7 +12440,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
- "source": "https://github.com/composer/ca-bundle/tree/1.3.6"
+ "source": "https://github.com/composer/ca-bundle/tree/1.3.7"
},
"funding": [
{
@@ -12427,49 +12456,125 @@
"type": "tidelift"
}
],
- "time": "2023-06-06T12:02:59+00:00"
+ "time": "2023-08-30T09:31:38+00:00"
},
{
- "name": "composer/composer",
- "version": "2.3.10",
+ "name": "composer/class-map-generator",
+ "version": "1.1.0",
"source": {
"type": "git",
- "url": "https://github.com/composer/composer.git",
- "reference": "ebac357c0a41359f3981098729042ed6dedc97ba"
+ "url": "https://github.com/composer/class-map-generator.git",
+ "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/composer/zipball/ebac357c0a41359f3981098729042ed6dedc97ba",
- "reference": "ebac357c0a41359f3981098729042ed6dedc97ba",
+ "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9",
+ "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^2.1 || ^3.1",
+ "php": "^7.2 || ^8.0",
+ "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.6",
+ "phpstan/phpstan-deprecation-rules": "^1",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "symfony/filesystem": "^5.4 || ^6",
+ "symfony/phpunit-bridge": "^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\ClassMapGenerator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "description": "Utilities to scan PHP code and generate class maps.",
+ "keywords": [
+ "classmap"
+ ],
+ "support": {
+ "issues": "https://github.com/composer/class-map-generator/issues",
+ "source": "https://github.com/composer/class-map-generator/tree/1.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-06-30T13:58:57+00:00"
+ },
+ {
+ "name": "composer/composer",
+ "version": "2.6.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/composer.git",
+ "reference": "683557bd2466072777309d039534bb1332d0dda5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/composer/zipball/683557bd2466072777309d039534bb1332d0dda5",
+ "reference": "683557bd2466072777309d039534bb1332d0dda5",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.0",
+ "composer/class-map-generator": "^1.0",
"composer/metadata-minifier": "^1.0",
- "composer/pcre": "^2 || ^3",
- "composer/semver": "^3.0",
- "composer/spdx-licenses": "^1.2",
+ "composer/pcre": "^2.1 || ^3.1",
+ "composer/semver": "^3.2.5",
+ "composer/spdx-licenses": "^1.5.7",
"composer/xdebug-handler": "^2.0.2 || ^3.0.3",
"justinrainbow/json-schema": "^5.2.11",
"php": "^7.2.5 || ^8.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
- "react/promise": "^2.8",
+ "react/promise": "^2.8 || ^3",
"seld/jsonlint": "^1.4",
"seld/phar-utils": "^1.2",
- "symfony/console": "^5.4.7 || ^6.0.7",
- "symfony/filesystem": "^5.4 || ^6.0",
- "symfony/finder": "^5.4 || ^6.0",
+ "seld/signal-handler": "^2.0",
+ "symfony/console": "^5.4.11 || ^6.0.11",
+ "symfony/filesystem": "^5.4 || ^6.0 || ^7",
+ "symfony/finder": "^5.4 || ^6.0 || ^7",
"symfony/polyfill-php73": "^1.24",
"symfony/polyfill-php80": "^1.24",
- "symfony/process": "^5.4 || ^6.0"
+ "symfony/polyfill-php81": "^1.24",
+ "symfony/process": "^5.4 || ^6.0 || ^7"
},
"require-dev": {
- "phpstan/phpstan": "^1.4.1",
+ "phpstan/phpstan": "^1.9.3",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1",
- "phpstan/phpstan-symfony": "^1.1",
- "symfony/phpunit-bridge": "^6.0"
+ "phpstan/phpstan-symfony": "^1.2.10",
+ "symfony/phpunit-bridge": "^6.0 || ^7"
},
"suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
@@ -12482,7 +12587,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "2.3-dev"
+ "dev-main": "2.6-dev"
},
"phpstan": {
"includes": [
@@ -12492,7 +12597,7 @@
},
"autoload": {
"psr-4": {
- "Composer\\": "src/Composer"
+ "Composer\\": "src/Composer/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -12521,7 +12626,8 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/composer/issues",
- "source": "https://github.com/composer/composer/tree/2.3.10"
+ "security": "https://github.com/composer/composer/security/policy",
+ "source": "https://github.com/composer/composer/tree/2.6.6"
},
"funding": [
{
@@ -12537,7 +12643,7 @@
"type": "tidelift"
}
],
- "time": "2022-07-13T13:48:23+00:00"
+ "time": "2023-12-08T17:32:26+00:00"
},
{
"name": "composer/metadata-minifier",
@@ -12610,16 +12716,16 @@
},
{
"name": "composer/pcre",
- "version": "3.1.0",
+ "version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
- "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2"
+ "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
- "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
+ "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
"shasum": ""
},
"require": {
@@ -12661,7 +12767,7 @@
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
- "source": "https://github.com/composer/pcre/tree/3.1.0"
+ "source": "https://github.com/composer/pcre/tree/3.1.1"
},
"funding": [
{
@@ -12677,20 +12783,20 @@
"type": "tidelift"
}
],
- "time": "2022-11-17T09:50:14+00:00"
+ "time": "2023-10-11T07:11:09+00:00"
},
{
"name": "composer/semver",
- "version": "3.3.2",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
- "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
- "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
+ "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
+ "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"shasum": ""
},
"require": {
@@ -12740,9 +12846,9 @@
"versioning"
],
"support": {
- "irc": "irc://irc.freenode.org/composer",
+ "irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
- "source": "https://github.com/composer/semver/tree/3.3.2"
+ "source": "https://github.com/composer/semver/tree/3.4.0"
},
"funding": [
{
@@ -12758,20 +12864,20 @@
"type": "tidelift"
}
],
- "time": "2022-04-01T19:23:25+00:00"
+ "time": "2023-08-31T09:50:34+00:00"
},
{
"name": "composer/spdx-licenses",
- "version": "1.5.7",
+ "version": "1.5.8",
"source": {
"type": "git",
"url": "https://github.com/composer/spdx-licenses.git",
- "reference": "c848241796da2abf65837d51dce1fae55a960149"
+ "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149",
- "reference": "c848241796da2abf65837d51dce1fae55a960149",
+ "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a",
+ "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a",
"shasum": ""
},
"require": {
@@ -12820,9 +12926,9 @@
"validator"
],
"support": {
- "irc": "irc://irc.freenode.org/composer",
+ "irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/spdx-licenses/issues",
- "source": "https://github.com/composer/spdx-licenses/tree/1.5.7"
+ "source": "https://github.com/composer/spdx-licenses/tree/1.5.8"
},
"funding": [
{
@@ -12838,7 +12944,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-23T07:37:50+00:00"
+ "time": "2023-11-20T07:44:33+00:00"
},
{
"name": "composer/xdebug-handler",
@@ -13451,16 +13557,16 @@
},
{
"name": "justinrainbow/json-schema",
- "version": "5.2.12",
+ "version": "v5.2.13",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
- "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60"
+ "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
- "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60",
+ "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793",
+ "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793",
"shasum": ""
},
"require": {
@@ -13515,82 +13621,9 @@
],
"support": {
"issues": "https://github.com/justinrainbow/json-schema/issues",
- "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12"
+ "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13"
},
- "time": "2022-04-13T08:02:27+00:00"
- },
- {
- "name": "laravel/dusk",
- "version": "v6.25.2",
- "source": {
- "type": "git",
- "url": "https://github.com/laravel/dusk.git",
- "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
- "reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "ext-zip": "*",
- "illuminate/console": "^6.0|^7.0|^8.0|^9.0",
- "illuminate/support": "^6.0|^7.0|^8.0|^9.0",
- "nesbot/carbon": "^2.0",
- "php": "^7.2|^8.0",
- "php-webdriver/webdriver": "^1.9.0",
- "symfony/console": "^4.3|^5.0|^6.0",
- "symfony/finder": "^4.3|^5.0|^6.0",
- "symfony/process": "^4.3|^5.0|^6.0",
- "vlucas/phpdotenv": "^3.0|^4.0|^5.2"
- },
- "require-dev": {
- "mockery/mockery": "^1.0",
- "orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0",
- "phpunit/phpunit": "^7.5.15|^8.4|^9.0"
- },
- "suggest": {
- "ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "6.x-dev"
- },
- "laravel": {
- "providers": [
- "Laravel\\Dusk\\DuskServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Laravel\\Dusk\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylor@laravel.com"
- }
- ],
- "description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
- "keywords": [
- "laravel",
- "testing",
- "webdriver"
- ],
- "support": {
- "issues": "https://github.com/laravel/dusk/issues",
- "source": "https://github.com/laravel/dusk/tree/v6.25.2"
- },
- "time": "2022-09-29T09:37:07+00:00"
+ "time": "2023-09-26T02:20:38+00:00"
},
{
"name": "league/container",
@@ -13861,12 +13894,12 @@
"version": "1.0.4",
"source": {
"type": "git",
- "url": "https://github.com/nunomaduro/larastan.git",
+ "url": "https://github.com/larastan/larastan.git",
"reference": "769bc6346a6cce3b823c30eaace33d9c3a0dd40e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/769bc6346a6cce3b823c30eaace33d9c3a0dd40e",
+ "url": "https://api.github.com/repos/larastan/larastan/zipball/769bc6346a6cce3b823c30eaace33d9c3a0dd40e",
"reference": "769bc6346a6cce3b823c30eaace33d9c3a0dd40e",
"shasum": ""
},
@@ -13931,8 +13964,8 @@
"static analysis"
],
"support": {
- "issues": "https://github.com/nunomaduro/larastan/issues",
- "source": "https://github.com/nunomaduro/larastan/tree/1.0.4"
+ "issues": "https://github.com/larastan/larastan/issues",
+ "source": "https://github.com/larastan/larastan/tree/1.0.4"
},
"funding": [
{
@@ -13952,6 +13985,7 @@
"type": "patreon"
}
],
+ "abandoned": "larastan/larastan",
"time": "2022-11-09T09:09:31+00:00"
},
{
@@ -14171,6 +14205,213 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
+ {
+ "name": "php-mock/php-mock",
+ "version": "2.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-mock/php-mock.git",
+ "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-mock/php-mock/zipball/6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d",
+ "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0 || ^8.0",
+ "phpunit/php-text-template": "^1 || ^2 || ^3"
+ },
+ "replace": {
+ "malkusch/php-mock": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "suggest": {
+ "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "autoload.php"
+ ],
+ "psr-4": {
+ "phpmock\\": [
+ "classes/",
+ "tests/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "WTFPL"
+ ],
+ "authors": [
+ {
+ "name": "Markus Malkusch",
+ "email": "markus@malkusch.de",
+ "homepage": "http://markus.malkusch.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.",
+ "homepage": "https://github.com/php-mock/php-mock",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "function",
+ "mock",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/php-mock/php-mock/issues",
+ "source": "https://github.com/php-mock/php-mock/tree/2.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/michalbundyra",
+ "type": "github"
+ }
+ ],
+ "time": "2023-06-12T20:48:52+00:00"
+ },
+ {
+ "name": "php-mock/php-mock-integration",
+ "version": "2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-mock/php-mock-integration.git",
+ "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/04f4a8d5442ca457b102b5204673f77323e3edb5",
+ "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6",
+ "php-mock/php-mock": "^2.4",
+ "phpunit/php-text-template": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "phpmock\\integration\\": "classes/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "WTFPL"
+ ],
+ "authors": [
+ {
+ "name": "Markus Malkusch",
+ "email": "markus@malkusch.de",
+ "homepage": "http://markus.malkusch.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Integration package for PHP-Mock",
+ "homepage": "https://github.com/php-mock/php-mock-integration",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "function",
+ "mock",
+ "stub",
+ "test",
+ "test double"
+ ],
+ "support": {
+ "issues": "https://github.com/php-mock/php-mock-integration/issues",
+ "source": "https://github.com/php-mock/php-mock-integration/tree/2.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/michalbundyra",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-13T09:51:29+00:00"
+ },
+ {
+ "name": "php-mock/php-mock-phpunit",
+ "version": "2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-mock/php-mock-phpunit.git",
+ "reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/56edee85ad3232caa0202f98f2a3c899ab16bdb7",
+ "reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7",
+ "php-mock/php-mock-integration": "^2.2.1",
+ "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "autoload.php"
+ ],
+ "psr-4": {
+ "phpmock\\phpunit\\": "classes/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "WTFPL"
+ ],
+ "authors": [
+ {
+ "name": "Markus Malkusch",
+ "email": "markus@malkusch.de",
+ "homepage": "http://markus.malkusch.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.",
+ "homepage": "https://github.com/php-mock/php-mock-phpunit",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "function",
+ "mock",
+ "phpunit",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/php-mock/php-mock-phpunit/issues",
+ "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/michalbundyra",
+ "type": "github"
+ }
+ ],
+ "time": "2023-10-30T07:06:12+00:00"
+ },
{
"name": "php-parallel-lint/php-parallel-lint",
"version": "v1.3.2",
@@ -14228,71 +14469,6 @@
},
"time": "2022-02-21T12:50:22+00:00"
},
- {
- "name": "php-webdriver/webdriver",
- "version": "1.12.1",
- "source": {
- "type": "git",
- "url": "https://github.com/php-webdriver/php-webdriver.git",
- "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a",
- "reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a",
- "shasum": ""
- },
- "require": {
- "ext-curl": "*",
- "ext-json": "*",
- "ext-zip": "*",
- "php": "^5.6 || ~7.0 || ^8.0",
- "symfony/polyfill-mbstring": "^1.12",
- "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0"
- },
- "replace": {
- "facebook/webdriver": "*"
- },
- "require-dev": {
- "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0",
- "php-coveralls/php-coveralls": "^2.4",
- "php-mock/php-mock-phpunit": "^1.1 || ^2.0",
- "php-parallel-lint/php-parallel-lint": "^1.2",
- "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9",
- "squizlabs/php_codesniffer": "^3.5",
- "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0"
- },
- "suggest": {
- "ext-SimpleXML": "For Firefox profile creation"
- },
- "type": "library",
- "autoload": {
- "files": [
- "lib/Exception/TimeoutException.php"
- ],
- "psr-4": {
- "Facebook\\WebDriver\\": "lib/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
- "homepage": "https://github.com/php-webdriver/php-webdriver",
- "keywords": [
- "Chromedriver",
- "geckodriver",
- "php",
- "selenium",
- "webdriver"
- ],
- "support": {
- "issues": "https://github.com/php-webdriver/php-webdriver/issues",
- "source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.1"
- },
- "time": "2022-05-03T12:16:34+00:00"
- },
{
"name": "phpstan/phpdoc-parser",
"version": "1.22.1",
@@ -14881,23 +15057,24 @@
},
{
"name": "react/promise",
- "version": "v2.10.0",
+ "version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
- "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38"
+ "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
- "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c",
+ "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c",
"shasum": ""
},
"require": {
- "php": ">=5.4.0"
+ "php": ">=7.1.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36"
+ "phpstan/phpstan": "1.10.39 || 1.4.10",
+ "phpunit/phpunit": "^9.6 || ^7.5"
},
"type": "library",
"autoload": {
@@ -14941,7 +15118,7 @@
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
- "source": "https://github.com/reactphp/promise/tree/v2.10.0"
+ "source": "https://github.com/reactphp/promise/tree/v3.1.0"
},
"funding": [
{
@@ -14949,7 +15126,7 @@
"type": "open_collective"
}
],
- "time": "2023-05-02T15:15:43+00:00"
+ "time": "2023-11-16T16:21:57+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -15747,6 +15924,67 @@
},
"time": "2022-08-31T10:31:18+00:00"
},
+ {
+ "name": "seld/signal-handler",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/signal-handler.git",
+ "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98",
+ "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpstan/phpstan-strict-rules": "^1.3",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Seld\\Signal\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development",
+ "keywords": [
+ "posix",
+ "sigint",
+ "signal",
+ "sigterm",
+ "unix"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/signal-handler/issues",
+ "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2"
+ },
+ "time": "2023-09-03T09:24:00+00:00"
+ },
{
"name": "slevomat/coding-standard",
"version": "8.13.1",
@@ -16736,5 +16974,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.0.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/config/app.php b/config/app.php
index 2559b8012c..1b4f45e45b 100755
--- a/config/app.php
+++ b/config/app.php
@@ -99,7 +99,7 @@ return [
|
*/
- 'locale' => env('APP_LOCALE', 'en'),
+ 'locale' => env('APP_LOCALE', 'en-US'),
/*
|--------------------------------------------------------------------------
@@ -112,7 +112,7 @@ return [
|
*/
- 'fallback_locale' => 'en',
+ 'fallback_locale' => 'en-US',
/*
|--------------------------------------------------------------------------
@@ -239,7 +239,7 @@ return [
|
*/
- 'min_php' => '7.2.5',
+ 'min_php' => '7.4.0',
/*
diff --git a/config/backup.php b/config/backup.php
index 479b1db6aa..b95129b7a6 100644
--- a/config/backup.php
+++ b/config/backup.php
@@ -35,6 +35,13 @@ return [
'files' => [
+ /*
+ * This path is used to make directories in resulting zip-file relative
+ * Set to false to include complete absolute path
+ * Example: base_path()
+ */
+ 'relative_path' => base_path(),
+
/*
* The list of directories and files that will be included in the backup.
*/
diff --git a/config/database.php b/config/database.php
index 36440b2127..1b4feeca93 100755
--- a/config/database.php
+++ b/config/database.php
@@ -69,7 +69,7 @@ return [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
- 'port' => env('DB_PORT', '3306'),
+ 'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
@@ -155,7 +155,7 @@ return [
'host' => env('REDIS_HOST', 'localhost'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
- 'database' => 0,
+ 'database' => env('REDIS_DATABASE', 0),
],
],
diff --git a/config/version.php b/config/version.php
index ba0dd3bace..c420df65ba 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,10 +1,10 @@
'v6.1.2',
- 'full_app_version' => 'v6.1.2 - build 10938-g32747cafd',
- 'build_version' => '10938',
+ 'app_version' => 'v6.2.4-pre',
+ 'full_app_version' => 'v6.2.4-pre - build 12343-gb23ce6cfc',
+ 'build_version' => '12343',
'prerelease_version' => '',
- 'hash_version' => 'g32747cafd',
- 'full_hash' => 'v6.1.2-89-g32747cafd',
+ 'hash_version' => 'gb23ce6cfc',
+ 'full_hash' => 'v6.2.4-pre-582-gb23ce6cfc',
'branch' => 'develop',
);
\ No newline at end of file
diff --git a/crowdin.yml b/crowdin.yml
index 7abe441e4f..24873b4a05 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -2,7 +2,7 @@
"files": [
{
- "source" : "/resources/lang/en/**/*.php",
+ "source" : "/resources/lang/en-US/**/*.php",
# https://developer.crowdin.com/configuration-file/#placeholders
"translation" : "/resources/lang/%locale%/**/%original_file_name%"
}
diff --git a/database/factories/ActionlogFactory.php b/database/factories/ActionlogFactory.php
index c25fdcc70a..1a4007888c 100644
--- a/database/factories/ActionlogFactory.php
+++ b/database/factories/ActionlogFactory.php
@@ -38,7 +38,7 @@ class ActionlogFactory extends Factory
{
return $this->state(function () {
$target = User::inRandomOrder()->first();
- $asset = Asset::RTD()->inRandomOrder()->first();
+ $asset = Asset::inRandomOrder()->RTD()->first();
$asset->update(
[
diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php
index 0e0c3931d8..432495bcc7 100644
--- a/database/factories/AssetFactory.php
+++ b/database/factories/AssetFactory.php
@@ -8,6 +8,8 @@ use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
+use Carbon\Carbon;
+use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\Factory;
class AssetFactory extends Factory
@@ -39,7 +41,7 @@ class AssetFactory extends Factory
'notes' => 'Created by DB seeder',
'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'),
'purchase_cost' => $this->faker->randomFloat(2, '299.99', '2999.99'),
- 'order_number' => $this->faker->numberBetween(1000000, 50000000),
+ 'order_number' => (string) $this->faker->numberBetween(1000000, 50000000),
'supplier_id' => Supplier::factory(),
'requestable' => $this->faker->boolean(),
'assigned_to' => null,
@@ -48,6 +50,18 @@ class AssetFactory extends Factory
'last_checkout' => null,
];
}
+
+
+ public function configure()
+ {
+ return $this->afterMaking(function (Asset $asset) {
+ // calculates the EOL date most of the time, but sometimes sets a random date so we have some explicits
+ // the explicit boolean gets set in the saving() method on the observer
+ $asset->asset_eol_date = $this->faker->boolean(5)
+ ? CarbonImmutable::parse($asset->purchase_date)->addMonths(rand(0, 20))->format('Y-m-d')
+ : CarbonImmutable::parse($asset->purchase_date)->addMonths($asset->model->eol)->format('Y-m-d');
+ });
+ }
public function laptopMbp()
{
diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php
index bf4b7ce242..607822fef1 100644
--- a/database/factories/CompanyFactory.php
+++ b/database/factories/CompanyFactory.php
@@ -22,7 +22,7 @@ class CompanyFactory extends Factory
public function definition()
{
return [
- 'name' => $this->faker->company(),
+ 'name' => $this->faker->unique()->company(),
];
}
}
diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php
index adcca9cae1..44ab0707e0 100644
--- a/database/factories/CustomFieldFactory.php
+++ b/database/factories/CustomFieldFactory.php
@@ -22,10 +22,11 @@ class CustomFieldFactory extends Factory
public function definition()
{
return [
- 'name' => $this->faker->catchPhrase(),
+ 'name' => $this->faker->unique()->catchPhrase(),
'format' => '',
'element' => 'text',
'auto_add_to_fieldsets' => '0',
+ 'show_in_requestable_list' => '0',
];
}
@@ -66,6 +67,7 @@ class CustomFieldFactory extends Factory
return [
'name' => 'CPU',
'help_text' => 'The speed of the processor on this device.',
+ 'show_in_requestable_list' => '1',
];
});
}
@@ -79,4 +81,40 @@ class CustomFieldFactory extends Factory
];
});
}
+
+ public function testEncrypted()
+ {
+ return $this->state(function () {
+ return [
+ 'name' => 'Test Encrypted',
+ 'field_encrypted' => '1',
+ 'help_text' => 'This is a sample encrypted field.',
+ ];
+ });
+ }
+
+ public function testCheckbox()
+ {
+ return $this->state(function () {
+ return [
+ 'name' => 'Test Checkbox',
+ 'help_text' => 'This is a sample checkbox.',
+ 'field_values' => "One\r\nTwo\r\nThree",
+ 'element' => 'checkbox',
+ ];
+ });
+ }
+
+ public function testRadio()
+ {
+ return $this->state(function () {
+ return [
+ 'name' => 'Test Radio',
+ 'help_text' => 'This is a sample radio.',
+ 'field_values' => "One\r\nTwo\r\nThree",
+ 'element' => 'radio',
+ ];
+ });
+ }
+
}
diff --git a/database/factories/CustomFieldsetFactory.php b/database/factories/CustomFieldsetFactory.php
index f369121c8f..e651b5c8d3 100644
--- a/database/factories/CustomFieldsetFactory.php
+++ b/database/factories/CustomFieldsetFactory.php
@@ -22,7 +22,7 @@ class CustomFieldsetFactory extends Factory
public function definition()
{
return [
- 'name' => $this->faker->catchPhrase(),
+ 'name' => $this->faker->unique()->catchPhrase(),
];
}
diff --git a/database/factories/DepreciationFactory.php b/database/factories/DepreciationFactory.php
index 6a648d7ade..6359e2326b 100644
--- a/database/factories/DepreciationFactory.php
+++ b/database/factories/DepreciationFactory.php
@@ -23,7 +23,7 @@ class DepreciationFactory extends Factory
public function definition()
{
return [
- 'name' => $this->faker->catchPhrase(),
+ 'name' => $this->faker->unique()->catchPhrase(),
'user_id' => User::factory()->superuser(),
'months' => 36,
];
diff --git a/database/factories/ManufacturerFactory.php b/database/factories/ManufacturerFactory.php
index 4e736b8d8a..7d6892426d 100644
--- a/database/factories/ManufacturerFactory.php
+++ b/database/factories/ManufacturerFactory.php
@@ -23,7 +23,7 @@ class ManufacturerFactory extends Factory
public function definition()
{
return [
- 'name' => $this->faker->company(),
+ 'name' => $this->faker->unique()->company(),
'user_id' => User::factory()->superuser(),
'support_phone' => $this->faker->phoneNumber(),
'url' => $this->faker->url(),
diff --git a/database/factories/SettingFactory.php b/database/factories/SettingFactory.php
index 1655bd3350..0b18450cbc 100644
--- a/database/factories/SettingFactory.php
+++ b/database/factories/SettingFactory.php
@@ -29,9 +29,9 @@ class SettingFactory extends Factory
'alerts_enabled' => true,
'brand' => 1,
'default_currency' => $this->faker->currencyCode(),
- 'locale' => 'en',
+ 'locale' => 'en-US',
'pwd_secure_min' => 10, // Match web setup
- 'email_domain' => 'test.com',
+ 'email_domain' => 'example.org',
];
}
}
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
index 1416ec461f..cb1ccd89b5 100644
--- a/database/factories/UserFactory.php
+++ b/database/factories/UserFactory.php
@@ -27,17 +27,17 @@ class UserFactory extends Factory
'first_name' => $this->faker->firstName(),
'jobtitle' => $this->faker->jobTitle(),
'last_name' => $this->faker->lastName(),
- 'locale' => 'en',
+ 'locale' => 'en-US',
'notes' => 'Created by DB seeder',
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'permissions' => '{}',
'phone' => $this->faker->phoneNumber(),
'state' => $this->faker->stateAbbr(),
- 'username' => $this->faker->username(),
+ 'username' => $this->faker->unique()->username(),
'zip' => $this->faker->postcode(),
];
}
-
+
public function firstAdmin()
{
return $this->state(function () {
diff --git a/database/migrations/2015_11_05_183749_add_image_to_assets.php b/database/migrations/2015_11_05_183749_add_image_to_assets.php
new file mode 100644
index 0000000000..598f71aa5b
--- /dev/null
+++ b/database/migrations/2015_11_05_183749_add_image_to_assets.php
@@ -0,0 +1,45 @@
+text('image')->after('notes')->nullable()->default(null);
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+
+ /**
+ * I'm leaving this one out, since it could destroy data that was already long-existing.
+ */
+ }
+}
diff --git a/database/migrations/2015_11_05_183749_image.php b/database/migrations/2015_11_05_183749_image.php
deleted file mode 100644
index 0582b852aa..0000000000
--- a/database/migrations/2015_11_05_183749_image.php
+++ /dev/null
@@ -1,32 +0,0 @@
-text('image')->after('notes')->nullable()->default(null);
- //
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::table('assets', function ($table) {
- $table->dropColumn('image');
- });
- }
-}
diff --git a/database/migrations/2022_10_25_215520_add_label2_settings.php b/database/migrations/2022_10_25_215520_add_label2_settings.php
index 692e8440db..2b07651e67 100644
--- a/database/migrations/2022_10_25_215520_add_label2_settings.php
+++ b/database/migrations/2022_10_25_215520_add_label2_settings.php
@@ -21,11 +21,7 @@ class AddLabel2Settings extends Migration
$table->string('label2_1d_type')->default('default');
$table->string('label2_2d_type')->default('default');
$table->string('label2_2d_target')->default('hardware_id');
- $table->string('label2_fields')->default(
- trans('admin/hardware/form.name').'=name;'.
- trans('admin/hardware/form.serial').'=serial;'.
- trans('admin/hardware/form.model').'=model.name;'
- );
+ $table->string('label2_fields')->default('name=name;serial=serial;model=model.name;');
});
}
diff --git a/database/migrations/2023_01_21_225350_add_eol_date_on_assets_table.php b/database/migrations/2023_01_21_225350_add_eol_date_on_assets_table.php
index 9f5c5aa1e5..aa9086a7c7 100644
--- a/database/migrations/2023_01_21_225350_add_eol_date_on_assets_table.php
+++ b/database/migrations/2023_01_21_225350_add_eol_date_on_assets_table.php
@@ -17,19 +17,30 @@ class AddEolDateOnAssetsTable extends Migration
{
Schema::table('assets', function (Blueprint $table) {
+
if (!Schema::hasColumn('assets', 'asset_eol_date')) {
$table->date('asset_eol_date')->after('purchase_date')->nullable()->default(null);
}
+
+ // This is a temporary shim so we don't have to modify the asset observer for migrations where
+ // there is a large version difference. (See the AssetObserver notes). This column gets created
+ // later in 2023_07_13_052204_denormalized_eol_and_add_column_for_explicit_date_to_assets.php
+ // but we have to temporarily create it now so the save method below doesn't break
+ if (!Schema::hasColumn('assets', 'eol_explicit')) {
+ $table->boolean('eol_explicit')->default(false)->after('asset_eol_date');
+ }
});
// Chunk the model query to get the models that do have an EOL date
+ // We use saveQuietly() here to skip the AssetObserver, since it modifies fields
+ // that do not yet exist on the assets table.
AssetModel::whereNotNull('eol')->chunk(10, function ($models) {
foreach ($models as $model) {
foreach ($model->assets as $asset) {
if ($asset->purchase_date!='') {
$asset->asset_eol_date = $asset->present()->eol_date();
- $asset->save();
+ $asset->saveQuietly();
}
}
diff --git a/database/migrations/2023_07_13_052204_denormalized_eol_and_add_column_for_explicit_date_to_assets.php b/database/migrations/2023_07_13_052204_denormalized_eol_and_add_column_for_explicit_date_to_assets.php
new file mode 100644
index 0000000000..146e5b2d71
--- /dev/null
+++ b/database/migrations/2023_07_13_052204_denormalized_eol_and_add_column_for_explicit_date_to_assets.php
@@ -0,0 +1,90 @@
+boolean('eol_explicit')->default(false)->after('asset_eol_date');
+ }
+ });
+
+
+ // Update the eol_explicit column with the value from asset_eol_date if it exists and is different from the calculated value
+ Asset::whereNotNull('asset_eol_date')->with('model')->chunkById(500, function ($assetsWithEolDates) {
+ foreach ($assetsWithEolDates as $asset) {
+ if ($asset->asset_eol_date && $asset->purchase_date) {
+ try {
+ $months = CarbonImmutable::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
+ } catch (\Exception $e) {
+ Log::info('asset_eol_date invalid for asset ' . $asset->id);
+ }
+ if ($asset->model->eol) {
+ if ($months != $asset->model->eol) {
+ DB::table('assets')->where('id', $asset->id)->update(['eol_explicit' => true]);
+ }
+ }
+ // if there is NO model eol, but there is a purchase date and an asset_eol_date (which is what is left over) the asset_eol_date has still been explicitly set
+ else {
+ DB::table('assets')->where('id', $asset->id)->update(['eol_explicit' => true]);
+ }
+ }
+ }
+ });
+
+ DB::table('assets')
+ ->whereNull('asset_eol_date')
+ ->whereNotNull('purchase_date')
+ ->whereNotNull('model_id')
+ ->join('models', 'assets.model_id', '=', 'models.id')
+ ->update([
+ 'asset_eol_date' => $this->eolUpdateExpression(),
+ ]);
+ }
+
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('assets', function (Blueprint $table) {
+ $table->dropColumn('eol_explicit');
+ });
+ }
+
+ /**
+ * This method returns the correct database expression for either
+ * mysql, postgres, or sqlite depending on the driver being used.
+ */
+ private function eolUpdateExpression(): Expression
+ {
+ if (DB::getDriverName() === 'sqlite') {
+ return DB::raw("DATE(purchase_date, '+' || (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id) || ' months')");
+ }
+
+ if (DB::getDriverName() === 'pgsql') {
+ return DB::raw("date(purchase_date + interval '1 month' * (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id))");
+ }
+
+ // Default to MySQL's method
+ return DB::raw('DATE_ADD(purchase_date, INTERVAL ' . DB::getTablePrefix() . 'models.eol MONTH)');
+ }
+}
diff --git a/database/migrations/2023_08_17_202638_add_last_checkin_to_assets.php b/database/migrations/2023_08_17_202638_add_last_checkin_to_assets.php
new file mode 100644
index 0000000000..74048ce941
--- /dev/null
+++ b/database/migrations/2023_08_17_202638_add_last_checkin_to_assets.php
@@ -0,0 +1,36 @@
+dateTime('last_checkin')->after('last_checkout')->nullable();
+ });
+
+ DB::statement(
+ "UPDATE " . DB::getTablePrefix() . "assets SET last_checkin=(SELECT MAX(" . DB::getTablePrefix() . "action_logs.action_date) FROM " . DB::getTablePrefix() . "action_logs WHERE item_type='App\\\Models\\\Asset' AND " . DB::getTablePrefix() . "action_logs.item_id=" . DB::getTablePrefix() . "assets.id AND " . DB::getTablePrefix() . "action_logs.action_type='checkin from')"
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('assets', function (Blueprint $table) {
+ $table->dropColumn('last_checkin');
+ });
+ }
+}
diff --git a/database/migrations/2023_08_21_064609_add_name_ordering_to_settings.php b/database/migrations/2023_08_21_064609_add_name_ordering_to_settings.php
new file mode 100644
index 0000000000..7a0afc5456
--- /dev/null
+++ b/database/migrations/2023_08_21_064609_add_name_ordering_to_settings.php
@@ -0,0 +1,32 @@
+string('name_display_format', 10)->after('alert_threshold')->nullable()->default('first_last');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('settings', function (Blueprint $table) {
+ $table->dropColumn('name_display_format');
+ });
+ }
+}
diff --git a/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php b/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php
new file mode 100644
index 0000000000..5f0bb85f9b
--- /dev/null
+++ b/database/migrations/2023_08_21_181742_add_min_amt_to_models_table.php
@@ -0,0 +1,32 @@
+integer('min_amt')->after('model_number')->default(null);;
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('models', function (Blueprint $table) {
+ $table->dropColumn('min_amt');
+ });
+ }
+}
diff --git a/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php b/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php
new file mode 100644
index 0000000000..c3b3d1e0d7
--- /dev/null
+++ b/database/migrations/2023_09_13_200913_fix_asset_model_min_qty_nullability.php
@@ -0,0 +1,32 @@
+integer('min_amt')->nullable()->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('models', function (Blueprint $table) {
+ $table->integer('min_amt')->nullable(false)->change();
+ });
+ }
+}
diff --git a/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php b/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php
new file mode 100644
index 0000000000..710a56e819
--- /dev/null
+++ b/database/migrations/2023_10_25_064324_add_show_in_requestable_to_custom_fields.php
@@ -0,0 +1,34 @@
+boolean('show_in_requestable_list')->after('show_in_email')->nullable()->default(0);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('custom_fields', function (Blueprint $table) {
+ if (Schema::hasColumn('custom_fields', 'show_in_requestable_list')) {
+ $table->dropColumn('show_in_requestable_list');
+ }
+ });
+ }
+}
diff --git a/database/migrations/2023_12_14_032522_add_remote_ip_and_action_source_to_action_logs.php b/database/migrations/2023_12_14_032522_add_remote_ip_and_action_source_to_action_logs.php
new file mode 100644
index 0000000000..70616b9f7f
--- /dev/null
+++ b/database/migrations/2023_12_14_032522_add_remote_ip_and_action_source_to_action_logs.php
@@ -0,0 +1,42 @@
+string('action_source')->nullable()->default(null);
+ $table->ipAddress('remote_ip')->nullable()->default(null);
+ $table->string('user_agent')->nullable()->default(null);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('action_logs', function (Blueprint $table) {
+ if (Schema::hasColumn('action_logs', 'action_source')) {
+ $table->dropColumn('action_source');
+ }
+ if (Schema::hasColumn('action_logs', 'remote_ip')) {
+ $table->dropColumn('remote_ip');
+ }
+ if (Schema::hasColumn('action_logs', 'user_agent')) {
+ $table->dropColumn('user_agent');
+ }
+ });
+ }
+}
diff --git a/database/migrations/2023_12_15_024643_add_indexes_to_new_activity_report_fields.php b/database/migrations/2023_12_15_024643_add_indexes_to_new_activity_report_fields.php
new file mode 100644
index 0000000000..1410430592
--- /dev/null
+++ b/database/migrations/2023_12_15_024643_add_indexes_to_new_activity_report_fields.php
@@ -0,0 +1,34 @@
+index('action_type');
+ $table->index('remote_ip');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('action_logs', function (Blueprint $table) {
+ $table->dropIndex(['action_type']);
+ $table->dropIndex(['remote_ip']);
+ });
+ }
+}
diff --git a/database/migrations/2023_12_19_081112_fix_language_dirs.php b/database/migrations/2023_12_19_081112_fix_language_dirs.php
new file mode 100644
index 0000000000..64b9598048
--- /dev/null
+++ b/database/migrations/2023_12_19_081112_fix_language_dirs.php
@@ -0,0 +1,60 @@
+locale != '')) {
+ DB::table('settings')->update(['locale' => Helper::mapLegacyLocale($settings->locale)]);
+ }
+
+ /**
+ * Update the users table
+ */
+ $users = User::whereNotNull('locale')->get();
+ // Skip the model in case the validation rules have changed
+ foreach ($users as $user) {
+ DB::table('users')->where('id', $user->id)->update(['locale' => Helper::mapLegacyLocale($user->locale)]);
+ }
+
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ $settings = Setting::getSettings();
+ if (($settings) && ($settings->locale != '')) {
+ DB::table('settings')->update(['locale' => Helper::mapBackToLegacyLocale($settings->locale)]);
+ }
+
+ /**
+ * Update the users table
+ */
+ $users = User::whereNotNull('locale')->whereNull('deleted_at')->get();
+ // Skip the model in case the validation rules have changed
+ foreach ($users as $user) {
+ DB::table('users')->where('id', $user->id)->update(['locale' => Helper::mapBackToLegacyLocale($user->locale)]);
+ }
+
+ }
+}
diff --git a/database/seeders/CustomFieldSeeder.php b/database/seeders/CustomFieldSeeder.php
index 551e05f40f..1c50ac806d 100644
--- a/database/seeders/CustomFieldSeeder.php
+++ b/database/seeders/CustomFieldSeeder.php
@@ -33,6 +33,10 @@ class CustomFieldSeeder extends Seeder
CustomField::factory()->count(1)->ram()->create();
CustomField::factory()->count(1)->cpu()->create();
CustomField::factory()->count(1)->macAddress()->create();
+ CustomField::factory()->count(1)->testEncrypted()->create();
+ CustomField::factory()->count(1)->testCheckbox()->create();
+ CustomField::factory()->count(1)->testRadio()->create();
+
DB::table('custom_field_custom_fieldset')->insert([
[
@@ -66,6 +70,46 @@ class CustomFieldSeeder extends Seeder
'required' => 0,
],
+ [
+ 'custom_field_id' => '6',
+ 'custom_fieldset_id' => '2',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+
+ [
+ 'custom_field_id' => '6',
+ 'custom_fieldset_id' => '1',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+
+ [
+ 'custom_field_id' => '7',
+ 'custom_fieldset_id' => '2',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+ [
+ 'custom_field_id' => '7',
+ 'custom_fieldset_id' => '1',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+
+ [
+ 'custom_field_id' => '8',
+ 'custom_fieldset_id' => '2',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+ [
+ 'custom_field_id' => '8',
+ 'custom_fieldset_id' => '1',
+ 'order' => 0,
+ 'required' => 0,
+ ],
+
]);
}
}
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 1429604139..5e26a9a257 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -38,12 +38,13 @@ class DatabaseSeeder extends Seeder
$this->call(DepreciationSeeder::class);
$this->call(StatuslabelSeeder::class);
$this->call(AccessorySeeder::class);
+ $this->call(CustomFieldSeeder::class);
$this->call(AssetSeeder::class);
$this->call(LicenseSeeder::class);
$this->call(ComponentSeeder::class);
$this->call(ConsumableSeeder::class);
$this->call(ActionlogSeeder::class);
- $this->call(CustomFieldSeeder::class);
+
Artisan::call('snipeit:sync-asset-locations', ['--output' => 'all']);
$output = Artisan::output();
diff --git a/database/seeders/SettingsSeeder.php b/database/seeders/SettingsSeeder.php
index 9cbdf79f94..c90ce52d07 100644
--- a/database/seeders/SettingsSeeder.php
+++ b/database/seeders/SettingsSeeder.php
@@ -32,14 +32,14 @@ class SettingsSeeder extends Seeder
$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 = 'on';
$settings->pwd_secure_min = '8';
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
- $user->locale = 'en';
+ $user->locale = 'en-US';
$user->save();
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 101c15d3b5..15272ce5c5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
ports:
- "8000:80"
volumes:
- - ./logs:/var/www/html/storage/logs
+ - ./storage/logs:/var/www/html/storage/logs
depends_on:
- mariadb
- redis
diff --git a/package-lock.json b/package-lock.json
index 649581f940..957d510248 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1329,9 +1329,9 @@
"dev": true
},
"@fortawesome/fontawesome-free": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
- "integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ=="
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz",
+ "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw=="
},
"@jridgewell/gen-mapping": {
"version": "0.1.1",
@@ -1565,27 +1565,27 @@
}
},
"@types/eslint": {
- "version": "8.44.2",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
- "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.1.tgz",
+ "integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==",
"requires": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"@types/eslint-scope": {
- "version": "3.7.4",
- "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
- "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"requires": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"@types/estree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
- "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"@types/express": {
"version": "4.17.13",
@@ -1883,6 +1883,19 @@
}
}
},
+ "@vue/reactivity": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
+ "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
+ "requires": {
+ "@vue/shared": "3.1.5"
+ }
+ },
+ "@vue/shared": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
+ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
+ },
"@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@@ -2065,9 +2078,9 @@
}
},
"acorn": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
- "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw=="
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
},
"acorn-import-assertions": {
"version": "1.9.0",
@@ -2187,6 +2200,172 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
},
+ "all-contributors-cli": {
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz",
+ "integrity": "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.7.6",
+ "async": "^3.1.0",
+ "chalk": "^4.0.0",
+ "didyoumean": "^1.2.1",
+ "inquirer": "^7.3.3",
+ "json-fixer": "^1.6.8",
+ "lodash": "^4.11.2",
+ "node-fetch": "^2.6.0",
+ "pify": "^5.0.0",
+ "prettier": "^2",
+ "yargs": "^15.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "pify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
+ "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
+ "dev": true
+ },
+ "prettier": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+ "dev": true,
+ "optional": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "alpinejs": {
+ "version": "3.13.3",
+ "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
+ "integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
+ "requires": {
+ "@vue/reactivity": "~3.1.1"
+ }
+ },
+ "ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.21.3"
+ }
+ },
"ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
@@ -2282,6 +2461,12 @@
}
}
},
+ "async": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
+ "dev": true
+ },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3526,9 +3711,10 @@
}
},
"camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
},
"caniuse-api": {
"version": "3.0.0",
@@ -3599,6 +3785,12 @@
"supports-color": "^2.0.0"
}
},
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
+ },
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
@@ -3693,6 +3885,15 @@
}
}
},
+ "cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^3.1.0"
+ }
+ },
"cli-table3": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz",
@@ -3703,6 +3904,22 @@
"string-width": "^4.2.0"
}
},
+ "cli-width": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+ "dev": true
+ },
+ "clipboard": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+ "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+ "requires": {
+ "good-listener": "^1.2.2",
+ "select": "^1.1.2",
+ "tiny-emitter": "^2.0.0"
+ }
+ },
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -3964,11 +4181,11 @@
"dev": true
},
"copy-anything": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.3.tgz",
- "integrity": "sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
+ "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"requires": {
- "is-what": "^3.12.0"
+ "is-what": "^3.14.1"
}
},
"core-js": {
@@ -4144,24 +4361,27 @@
}
},
"css-loader": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz",
- "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==",
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz",
+ "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==",
"requires": {
- "camelcase": "^6.0.0",
- "cssesc": "^3.0.0",
- "icss-utils": "^4.1.1",
+ "icss-utils": "^5.1.0",
"loader-utils": "^2.0.0",
- "postcss": "^7.0.32",
- "postcss-modules-extract-imports": "^2.0.0",
- "postcss-modules-local-by-default": "^3.0.3",
- "postcss-modules-scope": "^2.2.0",
- "postcss-modules-values": "^3.0.0",
+ "postcss": "^8.2.15",
+ "postcss-modules-extract-imports": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.0",
+ "postcss-modules-scope": "^3.0.0",
+ "postcss-modules-values": "^4.0.0",
"postcss-value-parser": "^4.1.0",
- "schema-utils": "^2.7.1",
- "semver": "^7.3.2"
+ "schema-utils": "^3.0.0",
+ "semver": "^7.3.5"
},
"dependencies": {
+ "@types/json-schema": {
+ "version": "7.0.13",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
+ "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ=="
+ },
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
@@ -4172,32 +4392,23 @@
"json5": "^2.1.2"
}
},
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
+ "schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
}
},
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"requires": {
"lru-cache": "^6.0.0"
}
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
@@ -4343,6 +4554,12 @@
"ms": "2.0.0"
}
},
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true
+ },
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -4508,6 +4725,11 @@
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true
},
+ "delegate": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+ "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+ },
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -4561,6 +4783,12 @@
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
},
+ "didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -4860,9 +5088,9 @@
}
},
"es-module-lexer": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz",
- "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
+ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"es-to-primitive": {
"version": "1.2.1",
@@ -5035,6 +5263,17 @@
}
}
},
+ "external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ }
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5095,6 +5334,15 @@
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
},
+ "figures": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
"file-loader": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
@@ -14858,6 +15106,14 @@
}
}
},
+ "good-listener": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+ "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+ "requires": {
+ "delegate": "^3.1.2"
+ }
+ },
"gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -15278,38 +15534,15 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"icss-utils": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
- "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
- "requires": {
- "postcss": "^7.0.14"
- },
- "dependencies": {
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
- }
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="
},
"ieee754": {
"version": "1.2.1",
@@ -15409,6 +15642,87 @@
"resolved": "https://registry.npmjs.org/inputmask/-/inputmask-3.3.11.tgz",
"integrity": "sha1-FCHJSuKMPc0bTSYze1CLs0mY4tg="
},
+ "inquirer": {
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
+ "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^3.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash": "^4.17.19",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.4.0",
+ "rxjs": "^6.6.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"insert-module-globals": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz",
@@ -15881,11 +16195,6 @@
"jquery": ">=1.8.0 <4.0.0"
}
},
- "jquery-ui-bundle": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/jquery-ui-bundle/-/jquery-ui-bundle-1.12.1.tgz",
- "integrity": "sha1-1r4uTDd0lOI3ixyuKSCpHRGC2MQ="
- },
"jquery.iframe-transport": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jquery.iframe-transport/-/jquery.iframe-transport-1.0.0.tgz",
@@ -15908,6 +16217,77 @@
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
+ "json-fixer": {
+ "version": "1.6.15",
+ "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.15.tgz",
+ "integrity": "sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.18.9",
+ "chalk": "^4.1.2",
+ "pegjs": "^0.10.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.23.8",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz",
+ "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -15962,9 +16342,9 @@
}
},
"jspdf-autotable": {
- "version": "3.5.31",
- "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.31.tgz",
- "integrity": "sha512-Lc1KuLGDQWW/5t57Z/+c2E94XQV3jV2QVU3xMRiwvcm/nMx79aCkpPCsxLzJZVFneZvz4XoA8+egQR1QajYiWw=="
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.1.tgz",
+ "integrity": "sha512-UjJqo80Z3/WUzDi4JipTGp0pAvNvR3Gsm38inJ5ZnwsJH0Lw4pEbssRSH6zMWAhR1ZkTrsDpQo5p6rZk987/AQ=="
},
"junk": {
"version": "3.1.0",
@@ -16312,9 +16692,9 @@
}
},
"less": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/less/-/less-4.1.2.tgz",
- "integrity": "sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
+ "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
"requires": {
"copy-anything": "^2.0.1",
"errno": "^0.1.1",
@@ -16322,34 +16702,12 @@
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
- "needle": "^2.5.2",
+ "needle": "^3.1.0",
"parse-node-version": "^1.0.1",
"source-map": "~0.6.0",
"tslib": "^2.3.0"
},
"dependencies": {
- "make-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
- "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
- "optional": true,
- "requires": {
- "pify": "^4.0.1",
- "semver": "^5.6.0"
- }
- },
- "pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "optional": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "optional": true
- },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -16500,6 +16858,24 @@
"yallist": "^4.0.0"
}
},
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "optional": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "optional": true
+ }
+ }
+ },
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -16792,37 +17168,35 @@
"thunky": "^1.0.2"
}
},
+ "mute-stream": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+ "dev": true
+ },
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
- "dev": true
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
},
"needle": {
- "version": "2.9.1",
- "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
- "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
+ "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"optional": true,
"requires": {
- "debug": "^3.2.6",
- "iconv-lite": "^0.4.4",
+ "iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"dependencies": {
- "debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"requires": {
- "ms": "^2.1.1"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
}
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "optional": true
}
}
},
@@ -16847,6 +17221,15 @@
"tslib": "^2.0.3"
}
},
+ "node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
+ },
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -17162,6 +17545,12 @@
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
"integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc="
},
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "dev": true
+ },
"p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
@@ -17389,6 +17778,12 @@
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="
},
+ "pegjs": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
+ "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==",
+ "dev": true
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -17405,6 +17800,12 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "optional": true
+ },
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -17423,7 +17824,6 @@
"version": "8.4.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
- "dev": true,
"requires": {
"nanoid": "^3.1.30",
"picocolors": "^1.0.0",
@@ -17701,124 +18101,34 @@
}
},
"postcss-modules-extract-imports": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
- "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
- "requires": {
- "postcss": "^7.0.5"
- },
- "dependencies": {
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
- }
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
+ "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw=="
},
"postcss-modules-local-by-default": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz",
- "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz",
+ "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==",
"requires": {
- "icss-utils": "^4.1.1",
- "postcss": "^7.0.32",
+ "icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
- },
- "dependencies": {
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
}
},
"postcss-modules-scope": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz",
- "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
+ "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"requires": {
- "postcss": "^7.0.6",
- "postcss-selector-parser": "^6.0.0"
- },
- "dependencies": {
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
+ "postcss-selector-parser": "^6.0.4"
}
},
"postcss-modules-values": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz",
- "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+ "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"requires": {
- "icss-utils": "^4.0.0",
- "postcss": "^7.0.6"
- },
- "dependencies": {
- "picocolors": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
- "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
- },
- "postcss": {
- "version": "7.0.39",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
- "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
- "requires": {
- "picocolors": "^0.2.1",
- "source-map": "^0.6.1"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- }
+ "icss-utils": "^5.0.0"
}
},
"postcss-normalize-charset": {
@@ -18430,6 +18740,12 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -18481,6 +18797,16 @@
"lowercase-keys": "^2.0.0"
}
},
+ "restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "requires": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
"restructure": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz",
@@ -18521,6 +18847,12 @@
"inherits": "^2.0.1"
}
},
+ "run-async": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+ "dev": true
+ },
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -18529,6 +18861,23 @@
"queue-microtask": "^1.2.2"
}
},
+ "rxjs": {
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ }
+ }
+ },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -18548,12 +18897,18 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+ "dev": true,
"requires": {
"@types/json-schema": "^7.0.5",
"ajv": "^6.12.4",
"ajv-keywords": "^3.5.2"
}
},
+ "select": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+ "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
+ },
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -18683,6 +19038,12 @@
"send": "0.18.0"
}
},
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true
+ },
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -18814,8 +19175,7 @@
"source-map-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
- "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
- "dev": true
+ "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA=="
},
"source-map-support": {
"version": "0.5.21",
@@ -19380,11 +19740,25 @@
"process": "~0.11.0"
}
},
+ "tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+ },
"tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
"to-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
@@ -19420,6 +19794,12 @@
"ieee754": "^1.2.1"
}
},
+ "tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
@@ -19430,6 +19810,12 @@
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
"integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw=="
},
+ "type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true
+ },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -19704,10 +20090,16 @@
"minimalistic-assert": "^1.0.0"
}
},
+ "webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
"webpack": {
- "version": "5.88.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
- "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
+ "version": "5.89.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
+ "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"requires": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
@@ -19746,18 +20138,18 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@jridgewell/trace-mapping": {
- "version": "0.3.19",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
- "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
+ "version": "0.3.20",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
+ "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"@types/json-schema": {
- "version": "7.0.12",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
- "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"commander": {
"version": "2.20.3",
@@ -19788,9 +20180,9 @@
}
},
"terser": {
- "version": "5.19.2",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
- "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
+ "version": "5.26.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz",
+ "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==",
"requires": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -19799,15 +20191,15 @@
}
},
"terser-webpack-plugin": {
- "version": "5.3.9",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
- "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
+ "version": "5.3.10",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
+ "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"requires": {
- "@jridgewell/trace-mapping": "^0.3.17",
+ "@jridgewell/trace-mapping": "^0.3.20",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.1",
- "terser": "^5.16.8"
+ "terser": "^5.26.0"
}
},
"webpack-sources": {
@@ -20110,6 +20502,16 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
+ "whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "requires": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -20142,6 +20544,12 @@
"is-weakset": "^2.0.1"
}
},
+ "which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "dev": true
+ },
"which-typed-array": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz",
diff --git a/package.json b/package.json
index 7a497786e3..c57348dc68 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,9 @@
{
"private": true,
"scripts": {
+ "contributors:add": "all-contributors add",
+ "contributors:check": "all-contributors check",
+ "contributors:generate": "all-contributors generate",
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
@@ -13,6 +16,7 @@
"node": ">=0.12"
},
"devDependencies": {
+ "all-contributors-cli": "^6.26.1",
"axios": "^0.27.2",
"babel-preset-latest": "^6.24.1",
"jquery": "<3.6.0",
@@ -24,12 +28,12 @@
"vue-template-compiler": "2.4.4"
},
"dependencies": {
- "@fortawesome/fontawesome-free": "^6.4.0",
- "acorn": "^8.9.0",
+ "@fortawesome/fontawesome-free": "^6.5.0",
+ "acorn": "^8.11.2",
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
"ajv": "^6.12.6",
- "alpinejs": "^3.10.5",
+ "alpinejs": "^3.13.3",
"blueimp-file-upload": "^9.34.0",
"bootstrap": "^3.4.1",
"bootstrap-colorpicker": "^2.5.3",
@@ -37,16 +41,16 @@
"bootstrap-less": "^3.3.8",
"bootstrap-table": "1.22.1",
"chart.js": "^2.9.4",
- "css-loader": "^4.0.0",
+ "clipboard": "^2.0.11",
+ "css-loader": "^5.0.0",
"ekko-lightbox": "^5.1.1",
"imagemin": "^8.0.1",
"jquery-form-validator": "^2.3.79",
"jquery-slimscroll": "^1.3.8",
"jquery-ui": "^1.13.2",
- "jquery-ui-bundle": "^1.12.1",
"jquery.iframe-transport": "^1.0.0",
- "jspdf-autotable": "^3.5.30",
- "less": "^4.1.2",
+ "jspdf-autotable": "^3.8.0",
+ "less": "^4.2.0",
"less-loader": "^5.0",
"list.js": "^1.5.0",
"papaparse": "^4.3.3",
@@ -55,6 +59,6 @@
"tableexport.jquery.plugin": "1.28.0",
"tether": "^1.4.0",
"vue-resource": "^1.5.2",
- "webpack": "^5.88.2"
+ "webpack": "^5.89.0"
}
}
diff --git a/public/css/build/app.css b/public/css/build/app.css
index c8fc025597..23a455db61 100644
Binary files a/public/css/build/app.css and b/public/css/build/app.css differ
diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css
index 5111695ac7..1630ba1df9 100644
Binary files a/public/css/build/overrides.css and b/public/css/build/overrides.css differ
diff --git a/public/css/dist/all.css b/public/css/dist/all.css
index 6a556a42bf..53e604b8a8 100644
Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ
diff --git a/public/css/dist/bootstrap-table.css b/public/css/dist/bootstrap-table.css
index 301c592a5d..b3304f6aeb 100644
Binary files a/public/css/dist/bootstrap-table.css and b/public/css/dist/bootstrap-table.css differ
diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css
index 3e17735603..f12332df6d 100644
Binary files a/public/css/dist/skins/skin-black-dark.css and b/public/css/dist/skins/skin-black-dark.css differ
diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css
index 3e17735603..f12332df6d 100644
Binary files a/public/css/dist/skins/skin-black-dark.min.css and b/public/css/dist/skins/skin-black-dark.min.css differ
diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css
index 8e5430e5cd..e024040c4e 100644
Binary files a/public/css/dist/skins/skin-green-dark.css and b/public/css/dist/skins/skin-green-dark.css differ
diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css
index 8e5430e5cd..e024040c4e 100644
Binary files a/public/css/dist/skins/skin-green-dark.min.css and b/public/css/dist/skins/skin-green-dark.min.css differ
diff --git a/public/css/webfonts/fa-brands-400.ttf b/public/css/webfonts/fa-brands-400.ttf
index 774d51ac4b..5efb1d4f96 100644
Binary files a/public/css/webfonts/fa-brands-400.ttf and b/public/css/webfonts/fa-brands-400.ttf differ
diff --git a/public/css/webfonts/fa-brands-400.woff2 b/public/css/webfonts/fa-brands-400.woff2
index 71e3185268..36fbda7d33 100644
Binary files a/public/css/webfonts/fa-brands-400.woff2 and b/public/css/webfonts/fa-brands-400.woff2 differ
diff --git a/public/css/webfonts/fa-regular-400.ttf b/public/css/webfonts/fa-regular-400.ttf
index 8a9d6344d1..838b4e2cfe 100644
Binary files a/public/css/webfonts/fa-regular-400.ttf and b/public/css/webfonts/fa-regular-400.ttf differ
diff --git a/public/css/webfonts/fa-regular-400.woff2 b/public/css/webfonts/fa-regular-400.woff2
index 7f021680b9..b6cabbacb6 100644
Binary files a/public/css/webfonts/fa-regular-400.woff2 and b/public/css/webfonts/fa-regular-400.woff2 differ
diff --git a/public/css/webfonts/fa-solid-900.ttf b/public/css/webfonts/fa-solid-900.ttf
index 993dbe1f95..ec24749db9 100644
Binary files a/public/css/webfonts/fa-solid-900.ttf and b/public/css/webfonts/fa-solid-900.ttf differ
diff --git a/public/css/webfonts/fa-solid-900.woff2 b/public/css/webfonts/fa-solid-900.woff2
index 5c16cd3e8a..824d518eb4 100644
Binary files a/public/css/webfonts/fa-solid-900.woff2 and b/public/css/webfonts/fa-solid-900.woff2 differ
diff --git a/public/css/webfonts/fa-v4compatibility.ttf b/public/css/webfonts/fa-v4compatibility.ttf
index ab6ae22482..b175aa8ece 100644
Binary files a/public/css/webfonts/fa-v4compatibility.ttf and b/public/css/webfonts/fa-v4compatibility.ttf differ
diff --git a/public/css/webfonts/fa-v4compatibility.woff2 b/public/css/webfonts/fa-v4compatibility.woff2
index 9027e38bcd..e09b5a5500 100644
Binary files a/public/css/webfonts/fa-v4compatibility.woff2 and b/public/css/webfonts/fa-v4compatibility.woff2 differ
diff --git a/public/js/build/app.js b/public/js/build/app.js
index cfbed799e6..2332ec657f 100644
Binary files a/public/js/build/app.js and b/public/js/build/app.js differ
diff --git a/public/js/build/vendor.js b/public/js/build/vendor.js
index 9c591006f2..f65d4fdd87 100644
Binary files a/public/js/build/vendor.js and b/public/js/build/vendor.js differ
diff --git a/public/js/dist/all-defer.js b/public/js/dist/all-defer.js
index 3eac3595a9..53e56a8a67 100644
Binary files a/public/js/dist/all-defer.js and b/public/js/dist/all-defer.js differ
diff --git a/public/js/dist/all.js b/public/js/dist/all.js
index 4d5e9ac818..80168a1a64 100644
Binary files a/public/js/dist/all.js and b/public/js/dist/all.js differ
diff --git a/public/js/dist/bootstrap-table.js b/public/js/dist/bootstrap-table.js
index c01fba3e19..ede82d13f7 100644
Binary files a/public/js/dist/bootstrap-table.js and b/public/js/dist/bootstrap-table.js differ
diff --git a/public/mix-manifest.json b/public/mix-manifest.json
index 28549f366a..fcc3223276 100644
--- a/public/mix-manifest.json
+++ b/public/mix-manifest.json
@@ -1,8 +1,8 @@
{
- "/js/build/app.js": "/js/build/app.js?id=82ddaa718cced6fb0be6e8db6f16a0c0",
+ "/js/build/app.js": "/js/build/app.js?id=41293fc7aa00ece89fd524e1e0e31a68",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374",
- "/css/build/overrides.css": "/css/build/overrides.css?id=5166569ede5a36802eadeea982686ca6",
- "/css/build/app.css": "/css/build/app.css?id=b612c48e78fece2e1e8042ae24cc5f1e",
+ "/css/build/overrides.css": "/css/build/overrides.css?id=9a69d65b9f2b35e6d17c6a72e76424d5",
+ "/css/build/app.css": "/css/build/app.css?id=5a516232bc9f1488514577cf3c8108eb",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e6e53eef152bba01a4c666a4d8b01117",
@@ -12,32 +12,32 @@
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7d92dea45d94be7e1d4e427c728d335d",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=8ca888bbc050d9680cbb65021382acba",
- "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=bdfc704731682c67645a2248b0b8d2d7",
+ "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=b061bb141af3bdb6280c6ee772cf8f4f",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
- "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=e36e83c2aa3c3afdbb8ebe2c0309e91d",
+ "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=d419cb63a12dc175d71645c876bfc2ab",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
- "/css/dist/all.css": "/css/dist/all.css?id=8b29a53b11e372ceb332b1a2f7026632",
+ "/css/dist/all.css": "/css/dist/all.css?id=672c12fc9cd418d80133a246b24b828d",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
- "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=e2e2b1797606a266ed55549f5bb5a179",
- "/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=fe912d1c4a7e0e1db87a64eb7e54c945",
- "/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=31a6b5ecfc8d018d0e3a294f0c80e9e9",
- "/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=bf8eabe300a00a3adb0293596987abc4",
- "/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=cd687455c6d6c058e2e9f84f409e2965",
- "/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=eea38615e7b5dbbaf88c263f2230cc32",
- "/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=6ebbf5afc34f54463abc2b81ca637364",
- "/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=67b8a78b7e80e805cfa4ee0421895ba4",
- "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=281bcfe26549412d128f695234961081",
- "/js/build/vendor.js": "/js/build/vendor.js?id=3592e07ae9a6d1805a4ea3bd3c034aef",
- "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=df78f0c4cc93c29c02a41144590f6350",
- "/js/dist/all.js": "/js/dist/all.js?id=ba07d399f23b294f7c4983030b757423",
- "/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7d1b362ddda912bce36f333cd492191b",
+ "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
+ "/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=189b85e9c72c6f75e464c3f58a6707cf",
+ "/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=ed4c23399d1013809882e90bfe396d1b",
+ "/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=be75b1958ae0da55e1eed562d9b7713d",
+ "/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=dfdc7801582dd0d20ea75faa3b96c296",
+ "/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
+ "/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
+ "/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f",
+ "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=2bd29fa7f9d666800c246a52ce708633",
+ "/js/build/vendor.js": "/js/build/vendor.js?id=a2b971da417306a63385c8098acfe4af",
+ "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=1f678160a05960c3087fb8263168ff41",
+ "/js/dist/all.js": "/js/dist/all.js?id=7588c5db6df57ae2c6bb6d7ac2ac5b55",
+ "/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7f9a130eda6916eaa32a0a57e81918f3",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
- "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=e36e83c2aa3c3afdbb8ebe2c0309e91d",
+ "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=d419cb63a12dc175d71645c876bfc2ab",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb",
- "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=bdfc704731682c67645a2248b0b8d2d7",
+ "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=b061bb141af3bdb6280c6ee772cf8f4f",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=07273f6ca3c698a39e8fc2075af4fa07",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
diff --git a/resources/assets/css/dragtable.css b/resources/assets/css/dragtable.css
new file mode 100644
index 0000000000..d81c29bfcd
--- /dev/null
+++ b/resources/assets/css/dragtable.css
@@ -0,0 +1,40 @@
+/*
+ * dragtable
+ *
+ * @Version 2.0.15
+ *
+ * default css
+ *
+ */
+/*##### the dragtable stuff #####*/
+.dragtable-sortable {
+ list-style-type: none; margin: 0; padding: 0; -moz-user-select: none;
+}
+.dragtable-sortable li {
+ margin: 0; padding: 0; float: left; font-size: 1em; background: white;
+}
+
+.dragtable-sortable th, .dragtable-sortable td{
+ border-left: 0px;
+}
+
+.dragtable-sortable li:first-child th, .dragtable-sortable li:first-child td {
+ border-left: 1px solid #CCC;
+}
+
+.ui-sortable-helper {
+ opacity: 0.7;filter: alpha(opacity=70);
+}
+.ui-sortable-placeholder {
+ -moz-box-shadow: 4px 5px 4px #C6C6C6 inset;
+ -webkit-box-shadow: 4px 5px 4px #C6C6C6 inset;
+ box-shadow: 4px 5px 4px #C6C6C6 inset;
+ border-bottom: 1px solid #CCCCCC;
+ border-top: 1px solid #CCCCCC;
+ visibility: visible !important;
+ background: #EFEFEF !important;
+ visibility: visible !important;
+}
+.ui-sortable-placeholder * {
+ opacity: 0.0; visibility: hidden;
+}
\ No newline at end of file
diff --git a/resources/assets/js/bootstrap-table-reorder-columns.js b/resources/assets/js/bootstrap-table-reorder-columns.js
new file mode 100644
index 0000000000..a9b29d4a94
--- /dev/null
+++ b/resources/assets/js/bootstrap-table-reorder-columns.js
@@ -0,0 +1,2131 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
+ typeof define === 'function' && define.amd ? define(['jquery'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.jQuery));
+})(this, (function ($$6) { 'use strict';
+
+ function _iterableToArrayLimit(arr, i) {
+ var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
+ if (null != _i) {
+ var _s,
+ _e,
+ _x,
+ _r,
+ _arr = [],
+ _n = !0,
+ _d = !1;
+ try {
+ if (_x = (_i = _i.call(arr)).next, 0 === i) {
+ if (Object(_i) !== _i) return;
+ _n = !1;
+ } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
+ } catch (err) {
+ _d = !0, _e = err;
+ } finally {
+ try {
+ if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
+ } finally {
+ if (_d) throw _e;
+ }
+ }
+ return _arr;
+ }
+ }
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
+ }
+ }
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ Object.defineProperty(Constructor, "prototype", {
+ writable: false
+ });
+ return Constructor;
+ }
+ function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ writable: true,
+ configurable: true
+ }
+ });
+ Object.defineProperty(subClass, "prototype", {
+ writable: false
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+ }
+ function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+ }
+ function _setPrototypeOf(o, p) {
+ _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+ }
+ function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+ try {
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+ return self;
+ }
+ function _possibleConstructorReturn(self, call) {
+ if (call && (typeof call === "object" || typeof call === "function")) {
+ return call;
+ } else if (call !== void 0) {
+ throw new TypeError("Derived constructors may only return object or undefined");
+ }
+ return _assertThisInitialized(self);
+ }
+ function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+ return _possibleConstructorReturn(this, result);
+ };
+ }
+ function _superPropBase(object, property) {
+ while (!Object.prototype.hasOwnProperty.call(object, property)) {
+ object = _getPrototypeOf(object);
+ if (object === null) break;
+ }
+ return object;
+ }
+ function _get() {
+ if (typeof Reflect !== "undefined" && Reflect.get) {
+ _get = Reflect.get.bind();
+ } else {
+ _get = function _get(target, property, receiver) {
+ var base = _superPropBase(target, property);
+ if (!base) return;
+ var desc = Object.getOwnPropertyDescriptor(base, property);
+ if (desc.get) {
+ return desc.get.call(arguments.length < 3 ? target : receiver);
+ }
+ return desc.value;
+ };
+ }
+ return _get.apply(this, arguments);
+ }
+ function _slicedToArray(arr, i) {
+ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
+ }
+ function _arrayWithHoles(arr) {
+ if (Array.isArray(arr)) return arr;
+ }
+ function _unsupportedIterableToArray(o, minLen) {
+ if (!o) return;
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+ var n = Object.prototype.toString.call(o).slice(8, -1);
+ if (n === "Object" && o.constructor) n = o.constructor.name;
+ if (n === "Map" || n === "Set") return Array.from(o);
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+ }
+ function _arrayLikeToArray(arr, len) {
+ if (len == null || len > arr.length) len = arr.length;
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+ return arr2;
+ }
+ function _nonIterableRest() {
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+ }
+ function _toPrimitive(input, hint) {
+ if (typeof input !== "object" || input === null) return input;
+ var prim = input[Symbol.toPrimitive];
+ if (prim !== undefined) {
+ var res = prim.call(input, hint || "default");
+ if (typeof res !== "object") return res;
+ throw new TypeError("@@toPrimitive must return a primitive value.");
+ }
+ return (hint === "string" ? String : Number)(input);
+ }
+ function _toPropertyKey(arg) {
+ var key = _toPrimitive(arg, "string");
+ return typeof key === "symbol" ? key : String(key);
+ }
+
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+ var check = function (it) {
+ return it && it.Math == Math && it;
+ };
+
+ // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
+ var global$b =
+ // eslint-disable-next-line es/no-global-this -- safe
+ check(typeof globalThis == 'object' && globalThis) ||
+ check(typeof window == 'object' && window) ||
+ // eslint-disable-next-line no-restricted-globals -- safe
+ check(typeof self == 'object' && self) ||
+ check(typeof commonjsGlobal == 'object' && commonjsGlobal) ||
+ // eslint-disable-next-line no-new-func -- fallback
+ (function () { return this; })() || Function('return this')();
+
+ var objectGetOwnPropertyDescriptor = {};
+
+ var fails$d = function (exec) {
+ try {
+ return !!exec();
+ } catch (error) {
+ return true;
+ }
+ };
+
+ var fails$c = fails$d;
+
+ // Detect IE8's incomplete defineProperty implementation
+ var descriptors = !fails$c(function () {
+ // eslint-disable-next-line es/no-object-defineproperty -- required for testing
+ return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7;
+ });
+
+ var fails$b = fails$d;
+
+ var functionBindNative = !fails$b(function () {
+ // eslint-disable-next-line es/no-function-prototype-bind -- safe
+ var test = (function () { /* empty */ }).bind();
+ // eslint-disable-next-line no-prototype-builtins -- safe
+ return typeof test != 'function' || test.hasOwnProperty('prototype');
+ });
+
+ var NATIVE_BIND$2 = functionBindNative;
+
+ var call$5 = Function.prototype.call;
+
+ var functionCall = NATIVE_BIND$2 ? call$5.bind(call$5) : function () {
+ return call$5.apply(call$5, arguments);
+ };
+
+ var objectPropertyIsEnumerable = {};
+
+ var $propertyIsEnumerable$1 = {}.propertyIsEnumerable;
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ var getOwnPropertyDescriptor$1 = Object.getOwnPropertyDescriptor;
+
+ // Nashorn ~ JDK8 bug
+ var NASHORN_BUG = getOwnPropertyDescriptor$1 && !$propertyIsEnumerable$1.call({ 1: 2 }, 1);
+
+ // `Object.prototype.propertyIsEnumerable` method implementation
+ // https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
+ objectPropertyIsEnumerable.f = NASHORN_BUG ? function propertyIsEnumerable(V) {
+ var descriptor = getOwnPropertyDescriptor$1(this, V);
+ return !!descriptor && descriptor.enumerable;
+ } : $propertyIsEnumerable$1;
+
+ var createPropertyDescriptor$3 = function (bitmap, value) {
+ return {
+ enumerable: !(bitmap & 1),
+ configurable: !(bitmap & 2),
+ writable: !(bitmap & 4),
+ value: value
+ };
+ };
+
+ var NATIVE_BIND$1 = functionBindNative;
+
+ var FunctionPrototype$1 = Function.prototype;
+ var call$4 = FunctionPrototype$1.call;
+ var uncurryThisWithBind = NATIVE_BIND$1 && FunctionPrototype$1.bind.bind(call$4, call$4);
+
+ var functionUncurryThis = NATIVE_BIND$1 ? uncurryThisWithBind : function (fn) {
+ return function () {
+ return call$4.apply(fn, arguments);
+ };
+ };
+
+ var uncurryThis$g = functionUncurryThis;
+
+ var toString$5 = uncurryThis$g({}.toString);
+ var stringSlice$1 = uncurryThis$g(''.slice);
+
+ var classofRaw$2 = function (it) {
+ return stringSlice$1(toString$5(it), 8, -1);
+ };
+
+ var uncurryThis$f = functionUncurryThis;
+ var fails$a = fails$d;
+ var classof$5 = classofRaw$2;
+
+ var $Object$3 = Object;
+ var split = uncurryThis$f(''.split);
+
+ // fallback for non-array-like ES3 and non-enumerable old V8 strings
+ var indexedObject = fails$a(function () {
+ // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346
+ // eslint-disable-next-line no-prototype-builtins -- safe
+ return !$Object$3('z').propertyIsEnumerable(0);
+ }) ? function (it) {
+ return classof$5(it) == 'String' ? split(it, '') : $Object$3(it);
+ } : $Object$3;
+
+ // we can't use just `it == null` since of `document.all` special case
+ // https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-aec
+ var isNullOrUndefined$2 = function (it) {
+ return it === null || it === undefined;
+ };
+
+ var isNullOrUndefined$1 = isNullOrUndefined$2;
+
+ var $TypeError$6 = TypeError;
+
+ // `RequireObjectCoercible` abstract operation
+ // https://tc39.es/ecma262/#sec-requireobjectcoercible
+ var requireObjectCoercible$3 = function (it) {
+ if (isNullOrUndefined$1(it)) throw $TypeError$6("Can't call method on " + it);
+ return it;
+ };
+
+ // toObject with fallback for non-array-like ES3 strings
+ var IndexedObject$2 = indexedObject;
+ var requireObjectCoercible$2 = requireObjectCoercible$3;
+
+ var toIndexedObject$5 = function (it) {
+ return IndexedObject$2(requireObjectCoercible$2(it));
+ };
+
+ var documentAll$2 = typeof document == 'object' && document.all;
+
+ // https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
+ // eslint-disable-next-line unicorn/no-typeof-undefined -- required for testing
+ var IS_HTMLDDA = typeof documentAll$2 == 'undefined' && documentAll$2 !== undefined;
+
+ var documentAll_1 = {
+ all: documentAll$2,
+ IS_HTMLDDA: IS_HTMLDDA
+ };
+
+ var $documentAll$1 = documentAll_1;
+
+ var documentAll$1 = $documentAll$1.all;
+
+ // `IsCallable` abstract operation
+ // https://tc39.es/ecma262/#sec-iscallable
+ var isCallable$c = $documentAll$1.IS_HTMLDDA ? function (argument) {
+ return typeof argument == 'function' || argument === documentAll$1;
+ } : function (argument) {
+ return typeof argument == 'function';
+ };
+
+ var isCallable$b = isCallable$c;
+ var $documentAll = documentAll_1;
+
+ var documentAll = $documentAll.all;
+
+ var isObject$7 = $documentAll.IS_HTMLDDA ? function (it) {
+ return typeof it == 'object' ? it !== null : isCallable$b(it) || it === documentAll;
+ } : function (it) {
+ return typeof it == 'object' ? it !== null : isCallable$b(it);
+ };
+
+ var global$a = global$b;
+ var isCallable$a = isCallable$c;
+
+ var aFunction = function (argument) {
+ return isCallable$a(argument) ? argument : undefined;
+ };
+
+ var getBuiltIn$4 = function (namespace, method) {
+ return arguments.length < 2 ? aFunction(global$a[namespace]) : global$a[namespace] && global$a[namespace][method];
+ };
+
+ var uncurryThis$e = functionUncurryThis;
+
+ var objectIsPrototypeOf = uncurryThis$e({}.isPrototypeOf);
+
+ var engineUserAgent = typeof navigator != 'undefined' && String(navigator.userAgent) || '';
+
+ var global$9 = global$b;
+ var userAgent = engineUserAgent;
+
+ var process = global$9.process;
+ var Deno = global$9.Deno;
+ var versions = process && process.versions || Deno && Deno.version;
+ var v8 = versions && versions.v8;
+ var match, version;
+
+ if (v8) {
+ match = v8.split('.');
+ // in old Chrome, versions of V8 isn't V8 = Chrome / 10
+ // but their correct versions are not interesting for us
+ version = match[0] > 0 && match[0] < 4 ? 1 : +(match[0] + match[1]);
+ }
+
+ // BrowserFS NodeJS `process` polyfill incorrectly set `.v8` to `0.0`
+ // so check `userAgent` even if `.v8` exists, but 0
+ if (!version && userAgent) {
+ match = userAgent.match(/Edge\/(\d+)/);
+ if (!match || match[1] >= 74) {
+ match = userAgent.match(/Chrome\/(\d+)/);
+ if (match) version = +match[1];
+ }
+ }
+
+ var engineV8Version = version;
+
+ /* eslint-disable es/no-symbol -- required for testing */
+
+ var V8_VERSION$2 = engineV8Version;
+ var fails$9 = fails$d;
+
+ // eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing
+ var symbolConstructorDetection = !!Object.getOwnPropertySymbols && !fails$9(function () {
+ var symbol = Symbol();
+ // Chrome 38 Symbol has incorrect toString conversion
+ // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances
+ return !String(symbol) || !(Object(symbol) instanceof Symbol) ||
+ // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances
+ !Symbol.sham && V8_VERSION$2 && V8_VERSION$2 < 41;
+ });
+
+ /* eslint-disable es/no-symbol -- required for testing */
+
+ var NATIVE_SYMBOL$1 = symbolConstructorDetection;
+
+ var useSymbolAsUid = NATIVE_SYMBOL$1
+ && !Symbol.sham
+ && typeof Symbol.iterator == 'symbol';
+
+ var getBuiltIn$3 = getBuiltIn$4;
+ var isCallable$9 = isCallable$c;
+ var isPrototypeOf = objectIsPrototypeOf;
+ var USE_SYMBOL_AS_UID$1 = useSymbolAsUid;
+
+ var $Object$2 = Object;
+
+ var isSymbol$2 = USE_SYMBOL_AS_UID$1 ? function (it) {
+ return typeof it == 'symbol';
+ } : function (it) {
+ var $Symbol = getBuiltIn$3('Symbol');
+ return isCallable$9($Symbol) && isPrototypeOf($Symbol.prototype, $Object$2(it));
+ };
+
+ var $String$3 = String;
+
+ var tryToString$1 = function (argument) {
+ try {
+ return $String$3(argument);
+ } catch (error) {
+ return 'Object';
+ }
+ };
+
+ var isCallable$8 = isCallable$c;
+ var tryToString = tryToString$1;
+
+ var $TypeError$5 = TypeError;
+
+ // `Assert: IsCallable(argument) is true`
+ var aCallable$2 = function (argument) {
+ if (isCallable$8(argument)) return argument;
+ throw $TypeError$5(tryToString(argument) + ' is not a function');
+ };
+
+ var aCallable$1 = aCallable$2;
+ var isNullOrUndefined = isNullOrUndefined$2;
+
+ // `GetMethod` abstract operation
+ // https://tc39.es/ecma262/#sec-getmethod
+ var getMethod$1 = function (V, P) {
+ var func = V[P];
+ return isNullOrUndefined(func) ? undefined : aCallable$1(func);
+ };
+
+ var call$3 = functionCall;
+ var isCallable$7 = isCallable$c;
+ var isObject$6 = isObject$7;
+
+ var $TypeError$4 = TypeError;
+
+ // `OrdinaryToPrimitive` abstract operation
+ // https://tc39.es/ecma262/#sec-ordinarytoprimitive
+ var ordinaryToPrimitive$1 = function (input, pref) {
+ var fn, val;
+ if (pref === 'string' && isCallable$7(fn = input.toString) && !isObject$6(val = call$3(fn, input))) return val;
+ if (isCallable$7(fn = input.valueOf) && !isObject$6(val = call$3(fn, input))) return val;
+ if (pref !== 'string' && isCallable$7(fn = input.toString) && !isObject$6(val = call$3(fn, input))) return val;
+ throw $TypeError$4("Can't convert object to primitive value");
+ };
+
+ var sharedExports = {};
+ var shared$3 = {
+ get exports(){ return sharedExports; },
+ set exports(v){ sharedExports = v; },
+ };
+
+ var global$8 = global$b;
+
+ // eslint-disable-next-line es/no-object-defineproperty -- safe
+ var defineProperty$3 = Object.defineProperty;
+
+ var defineGlobalProperty$3 = function (key, value) {
+ try {
+ defineProperty$3(global$8, key, { value: value, configurable: true, writable: true });
+ } catch (error) {
+ global$8[key] = value;
+ } return value;
+ };
+
+ var global$7 = global$b;
+ var defineGlobalProperty$2 = defineGlobalProperty$3;
+
+ var SHARED = '__core-js_shared__';
+ var store$3 = global$7[SHARED] || defineGlobalProperty$2(SHARED, {});
+
+ var sharedStore = store$3;
+
+ var store$2 = sharedStore;
+
+ (shared$3.exports = function (key, value) {
+ return store$2[key] || (store$2[key] = value !== undefined ? value : {});
+ })('versions', []).push({
+ version: '3.29.0',
+ mode: 'global',
+ copyright: '© 2014-2023 Denis Pushkarev (zloirock.ru)',
+ license: 'https://github.com/zloirock/core-js/blob/v3.29.0/LICENSE',
+ source: 'https://github.com/zloirock/core-js'
+ });
+
+ var requireObjectCoercible$1 = requireObjectCoercible$3;
+
+ var $Object$1 = Object;
+
+ // `ToObject` abstract operation
+ // https://tc39.es/ecma262/#sec-toobject
+ var toObject$4 = function (argument) {
+ return $Object$1(requireObjectCoercible$1(argument));
+ };
+
+ var uncurryThis$d = functionUncurryThis;
+ var toObject$3 = toObject$4;
+
+ var hasOwnProperty = uncurryThis$d({}.hasOwnProperty);
+
+ // `HasOwnProperty` abstract operation
+ // https://tc39.es/ecma262/#sec-hasownproperty
+ // eslint-disable-next-line es/no-object-hasown -- safe
+ var hasOwnProperty_1 = Object.hasOwn || function hasOwn(it, key) {
+ return hasOwnProperty(toObject$3(it), key);
+ };
+
+ var uncurryThis$c = functionUncurryThis;
+
+ var id = 0;
+ var postfix = Math.random();
+ var toString$4 = uncurryThis$c(1.0.toString);
+
+ var uid$2 = function (key) {
+ return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString$4(++id + postfix, 36);
+ };
+
+ var global$6 = global$b;
+ var shared$2 = sharedExports;
+ var hasOwn$6 = hasOwnProperty_1;
+ var uid$1 = uid$2;
+ var NATIVE_SYMBOL = symbolConstructorDetection;
+ var USE_SYMBOL_AS_UID = useSymbolAsUid;
+
+ var Symbol$2 = global$6.Symbol;
+ var WellKnownSymbolsStore = shared$2('wks');
+ var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol$2['for'] || Symbol$2 : Symbol$2 && Symbol$2.withoutSetter || uid$1;
+
+ var wellKnownSymbol$7 = function (name) {
+ if (!hasOwn$6(WellKnownSymbolsStore, name)) {
+ WellKnownSymbolsStore[name] = NATIVE_SYMBOL && hasOwn$6(Symbol$2, name)
+ ? Symbol$2[name]
+ : createWellKnownSymbol('Symbol.' + name);
+ } return WellKnownSymbolsStore[name];
+ };
+
+ var call$2 = functionCall;
+ var isObject$5 = isObject$7;
+ var isSymbol$1 = isSymbol$2;
+ var getMethod = getMethod$1;
+ var ordinaryToPrimitive = ordinaryToPrimitive$1;
+ var wellKnownSymbol$6 = wellKnownSymbol$7;
+
+ var $TypeError$3 = TypeError;
+ var TO_PRIMITIVE = wellKnownSymbol$6('toPrimitive');
+
+ // `ToPrimitive` abstract operation
+ // https://tc39.es/ecma262/#sec-toprimitive
+ var toPrimitive$1 = function (input, pref) {
+ if (!isObject$5(input) || isSymbol$1(input)) return input;
+ var exoticToPrim = getMethod(input, TO_PRIMITIVE);
+ var result;
+ if (exoticToPrim) {
+ if (pref === undefined) pref = 'default';
+ result = call$2(exoticToPrim, input, pref);
+ if (!isObject$5(result) || isSymbol$1(result)) return result;
+ throw $TypeError$3("Can't convert object to primitive value");
+ }
+ if (pref === undefined) pref = 'number';
+ return ordinaryToPrimitive(input, pref);
+ };
+
+ var toPrimitive = toPrimitive$1;
+ var isSymbol = isSymbol$2;
+
+ // `ToPropertyKey` abstract operation
+ // https://tc39.es/ecma262/#sec-topropertykey
+ var toPropertyKey$3 = function (argument) {
+ var key = toPrimitive(argument, 'string');
+ return isSymbol(key) ? key : key + '';
+ };
+
+ var global$5 = global$b;
+ var isObject$4 = isObject$7;
+
+ var document$1 = global$5.document;
+ // typeof document.createElement is 'object' in old IE
+ var EXISTS$1 = isObject$4(document$1) && isObject$4(document$1.createElement);
+
+ var documentCreateElement$1 = function (it) {
+ return EXISTS$1 ? document$1.createElement(it) : {};
+ };
+
+ var DESCRIPTORS$9 = descriptors;
+ var fails$8 = fails$d;
+ var createElement = documentCreateElement$1;
+
+ // Thanks to IE8 for its funny defineProperty
+ var ie8DomDefine = !DESCRIPTORS$9 && !fails$8(function () {
+ // eslint-disable-next-line es/no-object-defineproperty -- required for testing
+ return Object.defineProperty(createElement('div'), 'a', {
+ get: function () { return 7; }
+ }).a != 7;
+ });
+
+ var DESCRIPTORS$8 = descriptors;
+ var call$1 = functionCall;
+ var propertyIsEnumerableModule$1 = objectPropertyIsEnumerable;
+ var createPropertyDescriptor$2 = createPropertyDescriptor$3;
+ var toIndexedObject$4 = toIndexedObject$5;
+ var toPropertyKey$2 = toPropertyKey$3;
+ var hasOwn$5 = hasOwnProperty_1;
+ var IE8_DOM_DEFINE$1 = ie8DomDefine;
+
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ var $getOwnPropertyDescriptor$1 = Object.getOwnPropertyDescriptor;
+
+ // `Object.getOwnPropertyDescriptor` method
+ // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
+ objectGetOwnPropertyDescriptor.f = DESCRIPTORS$8 ? $getOwnPropertyDescriptor$1 : function getOwnPropertyDescriptor(O, P) {
+ O = toIndexedObject$4(O);
+ P = toPropertyKey$2(P);
+ if (IE8_DOM_DEFINE$1) try {
+ return $getOwnPropertyDescriptor$1(O, P);
+ } catch (error) { /* empty */ }
+ if (hasOwn$5(O, P)) return createPropertyDescriptor$2(!call$1(propertyIsEnumerableModule$1.f, O, P), O[P]);
+ };
+
+ var objectDefineProperty = {};
+
+ var DESCRIPTORS$7 = descriptors;
+ var fails$7 = fails$d;
+
+ // V8 ~ Chrome 36-
+ // https://bugs.chromium.org/p/v8/issues/detail?id=3334
+ var v8PrototypeDefineBug = DESCRIPTORS$7 && fails$7(function () {
+ // eslint-disable-next-line es/no-object-defineproperty -- required for testing
+ return Object.defineProperty(function () { /* empty */ }, 'prototype', {
+ value: 42,
+ writable: false
+ }).prototype != 42;
+ });
+
+ var isObject$3 = isObject$7;
+
+ var $String$2 = String;
+ var $TypeError$2 = TypeError;
+
+ // `Assert: Type(argument) is Object`
+ var anObject$4 = function (argument) {
+ if (isObject$3(argument)) return argument;
+ throw $TypeError$2($String$2(argument) + ' is not an object');
+ };
+
+ var DESCRIPTORS$6 = descriptors;
+ var IE8_DOM_DEFINE = ie8DomDefine;
+ var V8_PROTOTYPE_DEFINE_BUG$1 = v8PrototypeDefineBug;
+ var anObject$3 = anObject$4;
+ var toPropertyKey$1 = toPropertyKey$3;
+
+ var $TypeError$1 = TypeError;
+ // eslint-disable-next-line es/no-object-defineproperty -- safe
+ var $defineProperty = Object.defineProperty;
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+ var ENUMERABLE = 'enumerable';
+ var CONFIGURABLE$1 = 'configurable';
+ var WRITABLE = 'writable';
+
+ // `Object.defineProperty` method
+ // https://tc39.es/ecma262/#sec-object.defineproperty
+ objectDefineProperty.f = DESCRIPTORS$6 ? V8_PROTOTYPE_DEFINE_BUG$1 ? function defineProperty(O, P, Attributes) {
+ anObject$3(O);
+ P = toPropertyKey$1(P);
+ anObject$3(Attributes);
+ if (typeof O === 'function' && P === 'prototype' && 'value' in Attributes && WRITABLE in Attributes && !Attributes[WRITABLE]) {
+ var current = $getOwnPropertyDescriptor(O, P);
+ if (current && current[WRITABLE]) {
+ O[P] = Attributes.value;
+ Attributes = {
+ configurable: CONFIGURABLE$1 in Attributes ? Attributes[CONFIGURABLE$1] : current[CONFIGURABLE$1],
+ enumerable: ENUMERABLE in Attributes ? Attributes[ENUMERABLE] : current[ENUMERABLE],
+ writable: false
+ };
+ }
+ } return $defineProperty(O, P, Attributes);
+ } : $defineProperty : function defineProperty(O, P, Attributes) {
+ anObject$3(O);
+ P = toPropertyKey$1(P);
+ anObject$3(Attributes);
+ if (IE8_DOM_DEFINE) try {
+ return $defineProperty(O, P, Attributes);
+ } catch (error) { /* empty */ }
+ if ('get' in Attributes || 'set' in Attributes) throw $TypeError$1('Accessors not supported');
+ if ('value' in Attributes) O[P] = Attributes.value;
+ return O;
+ };
+
+ var DESCRIPTORS$5 = descriptors;
+ var definePropertyModule$4 = objectDefineProperty;
+ var createPropertyDescriptor$1 = createPropertyDescriptor$3;
+
+ var createNonEnumerableProperty$2 = DESCRIPTORS$5 ? function (object, key, value) {
+ return definePropertyModule$4.f(object, key, createPropertyDescriptor$1(1, value));
+ } : function (object, key, value) {
+ object[key] = value;
+ return object;
+ };
+
+ var makeBuiltInExports = {};
+ var makeBuiltIn$2 = {
+ get exports(){ return makeBuiltInExports; },
+ set exports(v){ makeBuiltInExports = v; },
+ };
+
+ var DESCRIPTORS$4 = descriptors;
+ var hasOwn$4 = hasOwnProperty_1;
+
+ var FunctionPrototype = Function.prototype;
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ var getDescriptor = DESCRIPTORS$4 && Object.getOwnPropertyDescriptor;
+
+ var EXISTS = hasOwn$4(FunctionPrototype, 'name');
+ // additional protection from minified / mangled / dropped function names
+ var PROPER = EXISTS && (function something() { /* empty */ }).name === 'something';
+ var CONFIGURABLE = EXISTS && (!DESCRIPTORS$4 || (DESCRIPTORS$4 && getDescriptor(FunctionPrototype, 'name').configurable));
+
+ var functionName = {
+ EXISTS: EXISTS,
+ PROPER: PROPER,
+ CONFIGURABLE: CONFIGURABLE
+ };
+
+ var uncurryThis$b = functionUncurryThis;
+ var isCallable$6 = isCallable$c;
+ var store$1 = sharedStore;
+
+ var functionToString = uncurryThis$b(Function.toString);
+
+ // this helper broken in `core-js@3.4.1-3.4.4`, so we can't use `shared` helper
+ if (!isCallable$6(store$1.inspectSource)) {
+ store$1.inspectSource = function (it) {
+ return functionToString(it);
+ };
+ }
+
+ var inspectSource$2 = store$1.inspectSource;
+
+ var global$4 = global$b;
+ var isCallable$5 = isCallable$c;
+
+ var WeakMap$1 = global$4.WeakMap;
+
+ var weakMapBasicDetection = isCallable$5(WeakMap$1) && /native code/.test(String(WeakMap$1));
+
+ var shared$1 = sharedExports;
+ var uid = uid$2;
+
+ var keys = shared$1('keys');
+
+ var sharedKey$2 = function (key) {
+ return keys[key] || (keys[key] = uid(key));
+ };
+
+ var hiddenKeys$4 = {};
+
+ var NATIVE_WEAK_MAP = weakMapBasicDetection;
+ var global$3 = global$b;
+ var isObject$2 = isObject$7;
+ var createNonEnumerableProperty$1 = createNonEnumerableProperty$2;
+ var hasOwn$3 = hasOwnProperty_1;
+ var shared = sharedStore;
+ var sharedKey$1 = sharedKey$2;
+ var hiddenKeys$3 = hiddenKeys$4;
+
+ var OBJECT_ALREADY_INITIALIZED = 'Object already initialized';
+ var TypeError$1 = global$3.TypeError;
+ var WeakMap = global$3.WeakMap;
+ var set, get, has;
+
+ var enforce = function (it) {
+ return has(it) ? get(it) : set(it, {});
+ };
+
+ var getterFor = function (TYPE) {
+ return function (it) {
+ var state;
+ if (!isObject$2(it) || (state = get(it)).type !== TYPE) {
+ throw TypeError$1('Incompatible receiver, ' + TYPE + ' required');
+ } return state;
+ };
+ };
+
+ if (NATIVE_WEAK_MAP || shared.state) {
+ var store = shared.state || (shared.state = new WeakMap());
+ /* eslint-disable no-self-assign -- prototype methods protection */
+ store.get = store.get;
+ store.has = store.has;
+ store.set = store.set;
+ /* eslint-enable no-self-assign -- prototype methods protection */
+ set = function (it, metadata) {
+ if (store.has(it)) throw TypeError$1(OBJECT_ALREADY_INITIALIZED);
+ metadata.facade = it;
+ store.set(it, metadata);
+ return metadata;
+ };
+ get = function (it) {
+ return store.get(it) || {};
+ };
+ has = function (it) {
+ return store.has(it);
+ };
+ } else {
+ var STATE = sharedKey$1('state');
+ hiddenKeys$3[STATE] = true;
+ set = function (it, metadata) {
+ if (hasOwn$3(it, STATE)) throw TypeError$1(OBJECT_ALREADY_INITIALIZED);
+ metadata.facade = it;
+ createNonEnumerableProperty$1(it, STATE, metadata);
+ return metadata;
+ };
+ get = function (it) {
+ return hasOwn$3(it, STATE) ? it[STATE] : {};
+ };
+ has = function (it) {
+ return hasOwn$3(it, STATE);
+ };
+ }
+
+ var internalState = {
+ set: set,
+ get: get,
+ has: has,
+ enforce: enforce,
+ getterFor: getterFor
+ };
+
+ var uncurryThis$a = functionUncurryThis;
+ var fails$6 = fails$d;
+ var isCallable$4 = isCallable$c;
+ var hasOwn$2 = hasOwnProperty_1;
+ var DESCRIPTORS$3 = descriptors;
+ var CONFIGURABLE_FUNCTION_NAME = functionName.CONFIGURABLE;
+ var inspectSource$1 = inspectSource$2;
+ var InternalStateModule = internalState;
+
+ var enforceInternalState = InternalStateModule.enforce;
+ var getInternalState = InternalStateModule.get;
+ var $String$1 = String;
+ // eslint-disable-next-line es/no-object-defineproperty -- safe
+ var defineProperty$2 = Object.defineProperty;
+ var stringSlice = uncurryThis$a(''.slice);
+ var replace$1 = uncurryThis$a(''.replace);
+ var join = uncurryThis$a([].join);
+
+ var CONFIGURABLE_LENGTH = DESCRIPTORS$3 && !fails$6(function () {
+ return defineProperty$2(function () { /* empty */ }, 'length', { value: 8 }).length !== 8;
+ });
+
+ var TEMPLATE = String(String).split('String');
+
+ var makeBuiltIn$1 = makeBuiltIn$2.exports = function (value, name, options) {
+ if (stringSlice($String$1(name), 0, 7) === 'Symbol(') {
+ name = '[' + replace$1($String$1(name), /^Symbol\(([^)]*)\)/, '$1') + ']';
+ }
+ if (options && options.getter) name = 'get ' + name;
+ if (options && options.setter) name = 'set ' + name;
+ if (!hasOwn$2(value, 'name') || (CONFIGURABLE_FUNCTION_NAME && value.name !== name)) {
+ if (DESCRIPTORS$3) defineProperty$2(value, 'name', { value: name, configurable: true });
+ else value.name = name;
+ }
+ if (CONFIGURABLE_LENGTH && options && hasOwn$2(options, 'arity') && value.length !== options.arity) {
+ defineProperty$2(value, 'length', { value: options.arity });
+ }
+ try {
+ if (options && hasOwn$2(options, 'constructor') && options.constructor) {
+ if (DESCRIPTORS$3) defineProperty$2(value, 'prototype', { writable: false });
+ // in V8 ~ Chrome 53, prototypes of some methods, like `Array.prototype.values`, are non-writable
+ } else if (value.prototype) value.prototype = undefined;
+ } catch (error) { /* empty */ }
+ var state = enforceInternalState(value);
+ if (!hasOwn$2(state, 'source')) {
+ state.source = join(TEMPLATE, typeof name == 'string' ? name : '');
+ } return value;
+ };
+
+ // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
+ // eslint-disable-next-line no-extend-native -- required
+ Function.prototype.toString = makeBuiltIn$1(function toString() {
+ return isCallable$4(this) && getInternalState(this).source || inspectSource$1(this);
+ }, 'toString');
+
+ var isCallable$3 = isCallable$c;
+ var definePropertyModule$3 = objectDefineProperty;
+ var makeBuiltIn = makeBuiltInExports;
+ var defineGlobalProperty$1 = defineGlobalProperty$3;
+
+ var defineBuiltIn$2 = function (O, key, value, options) {
+ if (!options) options = {};
+ var simple = options.enumerable;
+ var name = options.name !== undefined ? options.name : key;
+ if (isCallable$3(value)) makeBuiltIn(value, name, options);
+ if (options.global) {
+ if (simple) O[key] = value;
+ else defineGlobalProperty$1(key, value);
+ } else {
+ try {
+ if (!options.unsafe) delete O[key];
+ else if (O[key]) simple = true;
+ } catch (error) { /* empty */ }
+ if (simple) O[key] = value;
+ else definePropertyModule$3.f(O, key, {
+ value: value,
+ enumerable: false,
+ configurable: !options.nonConfigurable,
+ writable: !options.nonWritable
+ });
+ } return O;
+ };
+
+ var objectGetOwnPropertyNames = {};
+
+ var ceil = Math.ceil;
+ var floor = Math.floor;
+
+ // `Math.trunc` method
+ // https://tc39.es/ecma262/#sec-math.trunc
+ // eslint-disable-next-line es/no-math-trunc -- safe
+ var mathTrunc = Math.trunc || function trunc(x) {
+ var n = +x;
+ return (n > 0 ? floor : ceil)(n);
+ };
+
+ var trunc = mathTrunc;
+
+ // `ToIntegerOrInfinity` abstract operation
+ // https://tc39.es/ecma262/#sec-tointegerorinfinity
+ var toIntegerOrInfinity$2 = function (argument) {
+ var number = +argument;
+ // eslint-disable-next-line no-self-compare -- NaN check
+ return number !== number || number === 0 ? 0 : trunc(number);
+ };
+
+ var toIntegerOrInfinity$1 = toIntegerOrInfinity$2;
+
+ var max = Math.max;
+ var min$1 = Math.min;
+
+ // Helper for a popular repeating case of the spec:
+ // Let integer be ? ToInteger(index).
+ // If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length).
+ var toAbsoluteIndex$1 = function (index, length) {
+ var integer = toIntegerOrInfinity$1(index);
+ return integer < 0 ? max(integer + length, 0) : min$1(integer, length);
+ };
+
+ var toIntegerOrInfinity = toIntegerOrInfinity$2;
+
+ var min = Math.min;
+
+ // `ToLength` abstract operation
+ // https://tc39.es/ecma262/#sec-tolength
+ var toLength$1 = function (argument) {
+ return argument > 0 ? min(toIntegerOrInfinity(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991
+ };
+
+ var toLength = toLength$1;
+
+ // `LengthOfArrayLike` abstract operation
+ // https://tc39.es/ecma262/#sec-lengthofarraylike
+ var lengthOfArrayLike$3 = function (obj) {
+ return toLength(obj.length);
+ };
+
+ var toIndexedObject$3 = toIndexedObject$5;
+ var toAbsoluteIndex = toAbsoluteIndex$1;
+ var lengthOfArrayLike$2 = lengthOfArrayLike$3;
+
+ // `Array.prototype.{ indexOf, includes }` methods implementation
+ var createMethod$3 = function (IS_INCLUDES) {
+ return function ($this, el, fromIndex) {
+ var O = toIndexedObject$3($this);
+ var length = lengthOfArrayLike$2(O);
+ var index = toAbsoluteIndex(fromIndex, length);
+ var value;
+ // Array#includes uses SameValueZero equality algorithm
+ // eslint-disable-next-line no-self-compare -- NaN check
+ if (IS_INCLUDES && el != el) while (length > index) {
+ value = O[index++];
+ // eslint-disable-next-line no-self-compare -- NaN check
+ if (value != value) return true;
+ // Array#indexOf ignores holes, Array#includes - not
+ } else for (;length > index; index++) {
+ if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0;
+ } return !IS_INCLUDES && -1;
+ };
+ };
+
+ var arrayIncludes = {
+ // `Array.prototype.includes` method
+ // https://tc39.es/ecma262/#sec-array.prototype.includes
+ includes: createMethod$3(true),
+ // `Array.prototype.indexOf` method
+ // https://tc39.es/ecma262/#sec-array.prototype.indexof
+ indexOf: createMethod$3(false)
+ };
+
+ var uncurryThis$9 = functionUncurryThis;
+ var hasOwn$1 = hasOwnProperty_1;
+ var toIndexedObject$2 = toIndexedObject$5;
+ var indexOf = arrayIncludes.indexOf;
+ var hiddenKeys$2 = hiddenKeys$4;
+
+ var push$2 = uncurryThis$9([].push);
+
+ var objectKeysInternal = function (object, names) {
+ var O = toIndexedObject$2(object);
+ var i = 0;
+ var result = [];
+ var key;
+ for (key in O) !hasOwn$1(hiddenKeys$2, key) && hasOwn$1(O, key) && push$2(result, key);
+ // Don't enum bug & hidden keys
+ while (names.length > i) if (hasOwn$1(O, key = names[i++])) {
+ ~indexOf(result, key) || push$2(result, key);
+ }
+ return result;
+ };
+
+ // IE8- don't enum bug keys
+ var enumBugKeys$3 = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toLocaleString',
+ 'toString',
+ 'valueOf'
+ ];
+
+ var internalObjectKeys$1 = objectKeysInternal;
+ var enumBugKeys$2 = enumBugKeys$3;
+
+ var hiddenKeys$1 = enumBugKeys$2.concat('length', 'prototype');
+
+ // `Object.getOwnPropertyNames` method
+ // https://tc39.es/ecma262/#sec-object.getownpropertynames
+ // eslint-disable-next-line es/no-object-getownpropertynames -- safe
+ objectGetOwnPropertyNames.f = Object.getOwnPropertyNames || function getOwnPropertyNames(O) {
+ return internalObjectKeys$1(O, hiddenKeys$1);
+ };
+
+ var objectGetOwnPropertySymbols = {};
+
+ // eslint-disable-next-line es/no-object-getownpropertysymbols -- safe
+ objectGetOwnPropertySymbols.f = Object.getOwnPropertySymbols;
+
+ var getBuiltIn$2 = getBuiltIn$4;
+ var uncurryThis$8 = functionUncurryThis;
+ var getOwnPropertyNamesModule = objectGetOwnPropertyNames;
+ var getOwnPropertySymbolsModule$1 = objectGetOwnPropertySymbols;
+ var anObject$2 = anObject$4;
+
+ var concat$1 = uncurryThis$8([].concat);
+
+ // all object keys, includes non-enumerable and symbols
+ var ownKeys$1 = getBuiltIn$2('Reflect', 'ownKeys') || function ownKeys(it) {
+ var keys = getOwnPropertyNamesModule.f(anObject$2(it));
+ var getOwnPropertySymbols = getOwnPropertySymbolsModule$1.f;
+ return getOwnPropertySymbols ? concat$1(keys, getOwnPropertySymbols(it)) : keys;
+ };
+
+ var hasOwn = hasOwnProperty_1;
+ var ownKeys = ownKeys$1;
+ var getOwnPropertyDescriptorModule = objectGetOwnPropertyDescriptor;
+ var definePropertyModule$2 = objectDefineProperty;
+
+ var copyConstructorProperties$1 = function (target, source, exceptions) {
+ var keys = ownKeys(source);
+ var defineProperty = definePropertyModule$2.f;
+ var getOwnPropertyDescriptor = getOwnPropertyDescriptorModule.f;
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (!hasOwn(target, key) && !(exceptions && hasOwn(exceptions, key))) {
+ defineProperty(target, key, getOwnPropertyDescriptor(source, key));
+ }
+ }
+ };
+
+ var fails$5 = fails$d;
+ var isCallable$2 = isCallable$c;
+
+ var replacement = /#|\.prototype\./;
+
+ var isForced$1 = function (feature, detection) {
+ var value = data[normalize(feature)];
+ return value == POLYFILL ? true
+ : value == NATIVE ? false
+ : isCallable$2(detection) ? fails$5(detection)
+ : !!detection;
+ };
+
+ var normalize = isForced$1.normalize = function (string) {
+ return String(string).replace(replacement, '.').toLowerCase();
+ };
+
+ var data = isForced$1.data = {};
+ var NATIVE = isForced$1.NATIVE = 'N';
+ var POLYFILL = isForced$1.POLYFILL = 'P';
+
+ var isForced_1 = isForced$1;
+
+ var global$2 = global$b;
+ var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
+ var createNonEnumerableProperty = createNonEnumerableProperty$2;
+ var defineBuiltIn$1 = defineBuiltIn$2;
+ var defineGlobalProperty = defineGlobalProperty$3;
+ var copyConstructorProperties = copyConstructorProperties$1;
+ var isForced = isForced_1;
+
+ /*
+ options.target - name of the target object
+ options.global - target is the global object
+ options.stat - export as static methods of target
+ options.proto - export as prototype methods of target
+ options.real - real prototype method for the `pure` version
+ options.forced - export even if the native feature is available
+ options.bind - bind methods to the target, required for the `pure` version
+ options.wrap - wrap constructors to preventing global pollution, required for the `pure` version
+ options.unsafe - use the simple assignment of property instead of delete + defineProperty
+ options.sham - add a flag to not completely full polyfills
+ options.enumerable - export as enumerable property
+ options.dontCallGetSet - prevent calling a getter on target
+ options.name - the .name of the function if it does not match the key
+ */
+ var _export = function (options, source) {
+ var TARGET = options.target;
+ var GLOBAL = options.global;
+ var STATIC = options.stat;
+ var FORCED, target, key, targetProperty, sourceProperty, descriptor;
+ if (GLOBAL) {
+ target = global$2;
+ } else if (STATIC) {
+ target = global$2[TARGET] || defineGlobalProperty(TARGET, {});
+ } else {
+ target = (global$2[TARGET] || {}).prototype;
+ }
+ if (target) for (key in source) {
+ sourceProperty = source[key];
+ if (options.dontCallGetSet) {
+ descriptor = getOwnPropertyDescriptor(target, key);
+ targetProperty = descriptor && descriptor.value;
+ } else targetProperty = target[key];
+ FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);
+ // contained in target
+ if (!FORCED && targetProperty !== undefined) {
+ if (typeof sourceProperty == typeof targetProperty) continue;
+ copyConstructorProperties(sourceProperty, targetProperty);
+ }
+ // add a flag to not completely full polyfills
+ if (options.sham || (targetProperty && targetProperty.sham)) {
+ createNonEnumerableProperty(sourceProperty, 'sham', true);
+ }
+ defineBuiltIn$1(target, key, sourceProperty, options);
+ }
+ };
+
+ var internalObjectKeys = objectKeysInternal;
+ var enumBugKeys$1 = enumBugKeys$3;
+
+ // `Object.keys` method
+ // https://tc39.es/ecma262/#sec-object.keys
+ // eslint-disable-next-line es/no-object-keys -- safe
+ var objectKeys$3 = Object.keys || function keys(O) {
+ return internalObjectKeys(O, enumBugKeys$1);
+ };
+
+ var DESCRIPTORS$2 = descriptors;
+ var uncurryThis$7 = functionUncurryThis;
+ var objectKeys$2 = objectKeys$3;
+ var toIndexedObject$1 = toIndexedObject$5;
+ var $propertyIsEnumerable = objectPropertyIsEnumerable.f;
+
+ var propertyIsEnumerable = uncurryThis$7($propertyIsEnumerable);
+ var push$1 = uncurryThis$7([].push);
+
+ // `Object.{ entries, values }` methods implementation
+ var createMethod$2 = function (TO_ENTRIES) {
+ return function (it) {
+ var O = toIndexedObject$1(it);
+ var keys = objectKeys$2(O);
+ var length = keys.length;
+ var i = 0;
+ var result = [];
+ var key;
+ while (length > i) {
+ key = keys[i++];
+ if (!DESCRIPTORS$2 || propertyIsEnumerable(O, key)) {
+ push$1(result, TO_ENTRIES ? [key, O[key]] : O[key]);
+ }
+ }
+ return result;
+ };
+ };
+
+ var objectToArray = {
+ // `Object.entries` method
+ // https://tc39.es/ecma262/#sec-object.entries
+ entries: createMethod$2(true),
+ // `Object.values` method
+ // https://tc39.es/ecma262/#sec-object.values
+ values: createMethod$2(false)
+ };
+
+ var $$5 = _export;
+ var $entries = objectToArray.entries;
+
+ // `Object.entries` method
+ // https://tc39.es/ecma262/#sec-object.entries
+ $$5({ target: 'Object', stat: true }, {
+ entries: function entries(O) {
+ return $entries(O);
+ }
+ });
+
+ var classofRaw$1 = classofRaw$2;
+ var uncurryThis$6 = functionUncurryThis;
+
+ var functionUncurryThisClause = function (fn) {
+ // Nashorn bug:
+ // https://github.com/zloirock/core-js/issues/1128
+ // https://github.com/zloirock/core-js/issues/1130
+ if (classofRaw$1(fn) === 'Function') return uncurryThis$6(fn);
+ };
+
+ var uncurryThis$5 = functionUncurryThisClause;
+ var aCallable = aCallable$2;
+ var NATIVE_BIND = functionBindNative;
+
+ var bind$1 = uncurryThis$5(uncurryThis$5.bind);
+
+ // optional / simple context binding
+ var functionBindContext = function (fn, that) {
+ aCallable(fn);
+ return that === undefined ? fn : NATIVE_BIND ? bind$1(fn, that) : function (/* ...args */) {
+ return fn.apply(that, arguments);
+ };
+ };
+
+ var classof$4 = classofRaw$2;
+
+ // `IsArray` abstract operation
+ // https://tc39.es/ecma262/#sec-isarray
+ // eslint-disable-next-line es/no-array-isarray -- safe
+ var isArray$2 = Array.isArray || function isArray(argument) {
+ return classof$4(argument) == 'Array';
+ };
+
+ var wellKnownSymbol$5 = wellKnownSymbol$7;
+
+ var TO_STRING_TAG$1 = wellKnownSymbol$5('toStringTag');
+ var test = {};
+
+ test[TO_STRING_TAG$1] = 'z';
+
+ var toStringTagSupport = String(test) === '[object z]';
+
+ var TO_STRING_TAG_SUPPORT$2 = toStringTagSupport;
+ var isCallable$1 = isCallable$c;
+ var classofRaw = classofRaw$2;
+ var wellKnownSymbol$4 = wellKnownSymbol$7;
+
+ var TO_STRING_TAG = wellKnownSymbol$4('toStringTag');
+ var $Object = Object;
+
+ // ES3 wrong here
+ var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments';
+
+ // fallback for IE11 Script Access Denied error
+ var tryGet = function (it, key) {
+ try {
+ return it[key];
+ } catch (error) { /* empty */ }
+ };
+
+ // getting tag from ES6+ `Object.prototype.toString`
+ var classof$3 = TO_STRING_TAG_SUPPORT$2 ? classofRaw : function (it) {
+ var O, tag, result;
+ return it === undefined ? 'Undefined' : it === null ? 'Null'
+ // @@toStringTag case
+ : typeof (tag = tryGet(O = $Object(it), TO_STRING_TAG)) == 'string' ? tag
+ // builtinTag case
+ : CORRECT_ARGUMENTS ? classofRaw(O)
+ // ES3 arguments fallback
+ : (result = classofRaw(O)) == 'Object' && isCallable$1(O.callee) ? 'Arguments' : result;
+ };
+
+ var uncurryThis$4 = functionUncurryThis;
+ var fails$4 = fails$d;
+ var isCallable = isCallable$c;
+ var classof$2 = classof$3;
+ var getBuiltIn$1 = getBuiltIn$4;
+ var inspectSource = inspectSource$2;
+
+ var noop = function () { /* empty */ };
+ var empty = [];
+ var construct = getBuiltIn$1('Reflect', 'construct');
+ var constructorRegExp = /^\s*(?:class|function)\b/;
+ var exec$1 = uncurryThis$4(constructorRegExp.exec);
+ var INCORRECT_TO_STRING = !constructorRegExp.exec(noop);
+
+ var isConstructorModern = function isConstructor(argument) {
+ if (!isCallable(argument)) return false;
+ try {
+ construct(noop, empty, argument);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ };
+
+ var isConstructorLegacy = function isConstructor(argument) {
+ if (!isCallable(argument)) return false;
+ switch (classof$2(argument)) {
+ case 'AsyncFunction':
+ case 'GeneratorFunction':
+ case 'AsyncGeneratorFunction': return false;
+ }
+ try {
+ // we can't check .prototype since constructors produced by .bind haven't it
+ // `Function#toString` throws on some built-it function in some legacy engines
+ // (for example, `DOMQuad` and similar in FF41-)
+ return INCORRECT_TO_STRING || !!exec$1(constructorRegExp, inspectSource(argument));
+ } catch (error) {
+ return true;
+ }
+ };
+
+ isConstructorLegacy.sham = true;
+
+ // `IsConstructor` abstract operation
+ // https://tc39.es/ecma262/#sec-isconstructor
+ var isConstructor$1 = !construct || fails$4(function () {
+ var called;
+ return isConstructorModern(isConstructorModern.call)
+ || !isConstructorModern(Object)
+ || !isConstructorModern(function () { called = true; })
+ || called;
+ }) ? isConstructorLegacy : isConstructorModern;
+
+ var isArray$1 = isArray$2;
+ var isConstructor = isConstructor$1;
+ var isObject$1 = isObject$7;
+ var wellKnownSymbol$3 = wellKnownSymbol$7;
+
+ var SPECIES$1 = wellKnownSymbol$3('species');
+ var $Array = Array;
+
+ // a part of `ArraySpeciesCreate` abstract operation
+ // https://tc39.es/ecma262/#sec-arrayspeciescreate
+ var arraySpeciesConstructor$1 = function (originalArray) {
+ var C;
+ if (isArray$1(originalArray)) {
+ C = originalArray.constructor;
+ // cross-realm fallback
+ if (isConstructor(C) && (C === $Array || isArray$1(C.prototype))) C = undefined;
+ else if (isObject$1(C)) {
+ C = C[SPECIES$1];
+ if (C === null) C = undefined;
+ }
+ } return C === undefined ? $Array : C;
+ };
+
+ var arraySpeciesConstructor = arraySpeciesConstructor$1;
+
+ // `ArraySpeciesCreate` abstract operation
+ // https://tc39.es/ecma262/#sec-arrayspeciescreate
+ var arraySpeciesCreate$2 = function (originalArray, length) {
+ return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length);
+ };
+
+ var bind = functionBindContext;
+ var uncurryThis$3 = functionUncurryThis;
+ var IndexedObject$1 = indexedObject;
+ var toObject$2 = toObject$4;
+ var lengthOfArrayLike$1 = lengthOfArrayLike$3;
+ var arraySpeciesCreate$1 = arraySpeciesCreate$2;
+
+ var push = uncurryThis$3([].push);
+
+ // `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation
+ var createMethod$1 = function (TYPE) {
+ var IS_MAP = TYPE == 1;
+ var IS_FILTER = TYPE == 2;
+ var IS_SOME = TYPE == 3;
+ var IS_EVERY = TYPE == 4;
+ var IS_FIND_INDEX = TYPE == 6;
+ var IS_FILTER_REJECT = TYPE == 7;
+ var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;
+ return function ($this, callbackfn, that, specificCreate) {
+ var O = toObject$2($this);
+ var self = IndexedObject$1(O);
+ var boundFunction = bind(callbackfn, that);
+ var length = lengthOfArrayLike$1(self);
+ var index = 0;
+ var create = specificCreate || arraySpeciesCreate$1;
+ var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined;
+ var value, result;
+ for (;length > index; index++) if (NO_HOLES || index in self) {
+ value = self[index];
+ result = boundFunction(value, index, O);
+ if (TYPE) {
+ if (IS_MAP) target[index] = result; // map
+ else if (result) switch (TYPE) {
+ case 3: return true; // some
+ case 5: return value; // find
+ case 6: return index; // findIndex
+ case 2: push(target, value); // filter
+ } else switch (TYPE) {
+ case 4: return false; // every
+ case 7: push(target, value); // filterReject
+ }
+ }
+ }
+ return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target;
+ };
+ };
+
+ var arrayIteration = {
+ // `Array.prototype.forEach` method
+ // https://tc39.es/ecma262/#sec-array.prototype.foreach
+ forEach: createMethod$1(0),
+ // `Array.prototype.map` method
+ // https://tc39.es/ecma262/#sec-array.prototype.map
+ map: createMethod$1(1),
+ // `Array.prototype.filter` method
+ // https://tc39.es/ecma262/#sec-array.prototype.filter
+ filter: createMethod$1(2),
+ // `Array.prototype.some` method
+ // https://tc39.es/ecma262/#sec-array.prototype.some
+ some: createMethod$1(3),
+ // `Array.prototype.every` method
+ // https://tc39.es/ecma262/#sec-array.prototype.every
+ every: createMethod$1(4),
+ // `Array.prototype.find` method
+ // https://tc39.es/ecma262/#sec-array.prototype.find
+ find: createMethod$1(5),
+ // `Array.prototype.findIndex` method
+ // https://tc39.es/ecma262/#sec-array.prototype.findIndex
+ findIndex: createMethod$1(6),
+ // `Array.prototype.filterReject` method
+ // https://github.com/tc39/proposal-array-filtering
+ filterReject: createMethod$1(7)
+ };
+
+ var objectDefineProperties = {};
+
+ var DESCRIPTORS$1 = descriptors;
+ var V8_PROTOTYPE_DEFINE_BUG = v8PrototypeDefineBug;
+ var definePropertyModule$1 = objectDefineProperty;
+ var anObject$1 = anObject$4;
+ var toIndexedObject = toIndexedObject$5;
+ var objectKeys$1 = objectKeys$3;
+
+ // `Object.defineProperties` method
+ // https://tc39.es/ecma262/#sec-object.defineproperties
+ // eslint-disable-next-line es/no-object-defineproperties -- safe
+ objectDefineProperties.f = DESCRIPTORS$1 && !V8_PROTOTYPE_DEFINE_BUG ? Object.defineProperties : function defineProperties(O, Properties) {
+ anObject$1(O);
+ var props = toIndexedObject(Properties);
+ var keys = objectKeys$1(Properties);
+ var length = keys.length;
+ var index = 0;
+ var key;
+ while (length > index) definePropertyModule$1.f(O, key = keys[index++], props[key]);
+ return O;
+ };
+
+ var getBuiltIn = getBuiltIn$4;
+
+ var html$1 = getBuiltIn('document', 'documentElement');
+
+ /* global ActiveXObject -- old IE, WSH */
+
+ var anObject = anObject$4;
+ var definePropertiesModule = objectDefineProperties;
+ var enumBugKeys = enumBugKeys$3;
+ var hiddenKeys = hiddenKeys$4;
+ var html = html$1;
+ var documentCreateElement = documentCreateElement$1;
+ var sharedKey = sharedKey$2;
+
+ var GT = '>';
+ var LT = '<';
+ var PROTOTYPE = 'prototype';
+ var SCRIPT = 'script';
+ var IE_PROTO = sharedKey('IE_PROTO');
+
+ var EmptyConstructor = function () { /* empty */ };
+
+ var scriptTag = function (content) {
+ return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT;
+ };
+
+ // Create object with fake `null` prototype: use ActiveX Object with cleared prototype
+ var NullProtoObjectViaActiveX = function (activeXDocument) {
+ activeXDocument.write(scriptTag(''));
+ activeXDocument.close();
+ var temp = activeXDocument.parentWindow.Object;
+ activeXDocument = null; // avoid memory leak
+ return temp;
+ };
+
+ // Create object with fake `null` prototype: use iframe Object with cleared prototype
+ var NullProtoObjectViaIFrame = function () {
+ // Thrash, waste and sodomy: IE GC bug
+ var iframe = documentCreateElement('iframe');
+ var JS = 'java' + SCRIPT + ':';
+ var iframeDocument;
+ iframe.style.display = 'none';
+ html.appendChild(iframe);
+ // https://github.com/zloirock/core-js/issues/475
+ iframe.src = String(JS);
+ iframeDocument = iframe.contentWindow.document;
+ iframeDocument.open();
+ iframeDocument.write(scriptTag('document.F=Object'));
+ iframeDocument.close();
+ return iframeDocument.F;
+ };
+
+ // Check for document.domain and active x support
+ // No need to use active x approach when document.domain is not set
+ // see https://github.com/es-shims/es5-shim/issues/150
+ // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346
+ // avoid IE GC bug
+ var activeXDocument;
+ var NullProtoObject = function () {
+ try {
+ activeXDocument = new ActiveXObject('htmlfile');
+ } catch (error) { /* ignore */ }
+ NullProtoObject = typeof document != 'undefined'
+ ? document.domain && activeXDocument
+ ? NullProtoObjectViaActiveX(activeXDocument) // old IE
+ : NullProtoObjectViaIFrame()
+ : NullProtoObjectViaActiveX(activeXDocument); // WSH
+ var length = enumBugKeys.length;
+ while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]];
+ return NullProtoObject();
+ };
+
+ hiddenKeys[IE_PROTO] = true;
+
+ // `Object.create` method
+ // https://tc39.es/ecma262/#sec-object.create
+ // eslint-disable-next-line es/no-object-create -- safe
+ var objectCreate = Object.create || function create(O, Properties) {
+ var result;
+ if (O !== null) {
+ EmptyConstructor[PROTOTYPE] = anObject(O);
+ result = new EmptyConstructor();
+ EmptyConstructor[PROTOTYPE] = null;
+ // add "__proto__" for Object.getPrototypeOf polyfill
+ result[IE_PROTO] = O;
+ } else result = NullProtoObject();
+ return Properties === undefined ? result : definePropertiesModule.f(result, Properties);
+ };
+
+ var wellKnownSymbol$2 = wellKnownSymbol$7;
+ var create = objectCreate;
+ var defineProperty$1 = objectDefineProperty.f;
+
+ var UNSCOPABLES = wellKnownSymbol$2('unscopables');
+ var ArrayPrototype = Array.prototype;
+
+ // Array.prototype[@@unscopables]
+ // https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
+ if (ArrayPrototype[UNSCOPABLES] == undefined) {
+ defineProperty$1(ArrayPrototype, UNSCOPABLES, {
+ configurable: true,
+ value: create(null)
+ });
+ }
+
+ // add a key to Array.prototype[@@unscopables]
+ var addToUnscopables$1 = function (key) {
+ ArrayPrototype[UNSCOPABLES][key] = true;
+ };
+
+ var $$4 = _export;
+ var $find = arrayIteration.find;
+ var addToUnscopables = addToUnscopables$1;
+
+ var FIND = 'find';
+ var SKIPS_HOLES = true;
+
+ // Shouldn't skip holes
+ if (FIND in []) Array(1)[FIND](function () { SKIPS_HOLES = false; });
+
+ // `Array.prototype.find` method
+ // https://tc39.es/ecma262/#sec-array.prototype.find
+ $$4({ target: 'Array', proto: true, forced: SKIPS_HOLES }, {
+ find: function find(callbackfn /* , that = undefined */) {
+ return $find(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
+ }
+ });
+
+ // https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
+ addToUnscopables(FIND);
+
+ var TO_STRING_TAG_SUPPORT$1 = toStringTagSupport;
+ var classof$1 = classof$3;
+
+ // `Object.prototype.toString` method implementation
+ // https://tc39.es/ecma262/#sec-object.prototype.tostring
+ var objectToString = TO_STRING_TAG_SUPPORT$1 ? {}.toString : function toString() {
+ return '[object ' + classof$1(this) + ']';
+ };
+
+ var TO_STRING_TAG_SUPPORT = toStringTagSupport;
+ var defineBuiltIn = defineBuiltIn$2;
+ var toString$3 = objectToString;
+
+ // `Object.prototype.toString` method
+ // https://tc39.es/ecma262/#sec-object.prototype.tostring
+ if (!TO_STRING_TAG_SUPPORT) {
+ defineBuiltIn(Object.prototype, 'toString', toString$3, { unsafe: true });
+ }
+
+ var classof = classof$3;
+
+ var $String = String;
+
+ var toString$2 = function (argument) {
+ if (classof(argument) === 'Symbol') throw TypeError('Cannot convert a Symbol value to a string');
+ return $String(argument);
+ };
+
+ // a string of all valid unicode whitespaces
+ var whitespaces$2 = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' +
+ '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF';
+
+ var uncurryThis$2 = functionUncurryThis;
+ var requireObjectCoercible = requireObjectCoercible$3;
+ var toString$1 = toString$2;
+ var whitespaces$1 = whitespaces$2;
+
+ var replace = uncurryThis$2(''.replace);
+ var ltrim = RegExp('^[' + whitespaces$1 + ']+');
+ var rtrim = RegExp('(^|[^' + whitespaces$1 + '])[' + whitespaces$1 + ']+$');
+
+ // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation
+ var createMethod = function (TYPE) {
+ return function ($this) {
+ var string = toString$1(requireObjectCoercible($this));
+ if (TYPE & 1) string = replace(string, ltrim, '');
+ if (TYPE & 2) string = replace(string, rtrim, '$1');
+ return string;
+ };
+ };
+
+ var stringTrim = {
+ // `String.prototype.{ trimLeft, trimStart }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimstart
+ start: createMethod(1),
+ // `String.prototype.{ trimRight, trimEnd }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimend
+ end: createMethod(2),
+ // `String.prototype.trim` method
+ // https://tc39.es/ecma262/#sec-string.prototype.trim
+ trim: createMethod(3)
+ };
+
+ var global$1 = global$b;
+ var fails$3 = fails$d;
+ var uncurryThis$1 = functionUncurryThis;
+ var toString = toString$2;
+ var trim = stringTrim.trim;
+ var whitespaces = whitespaces$2;
+
+ var $parseInt$1 = global$1.parseInt;
+ var Symbol$1 = global$1.Symbol;
+ var ITERATOR = Symbol$1 && Symbol$1.iterator;
+ var hex = /^[+-]?0x/i;
+ var exec = uncurryThis$1(hex.exec);
+ var FORCED$1 = $parseInt$1(whitespaces + '08') !== 8 || $parseInt$1(whitespaces + '0x16') !== 22
+ // MS Edge 18- broken with boxed symbols
+ || (ITERATOR && !fails$3(function () { $parseInt$1(Object(ITERATOR)); }));
+
+ // `parseInt` method
+ // https://tc39.es/ecma262/#sec-parseint-string-radix
+ var numberParseInt = FORCED$1 ? function parseInt(string, radix) {
+ var S = trim(toString(string));
+ return $parseInt$1(S, (radix >>> 0) || (exec(hex, S) ? 16 : 10));
+ } : $parseInt$1;
+
+ var $$3 = _export;
+ var $parseInt = numberParseInt;
+
+ // `parseInt` method
+ // https://tc39.es/ecma262/#sec-parseint-string-radix
+ $$3({ global: true, forced: parseInt != $parseInt }, {
+ parseInt: $parseInt
+ });
+
+ var fails$2 = fails$d;
+ var wellKnownSymbol$1 = wellKnownSymbol$7;
+ var V8_VERSION$1 = engineV8Version;
+
+ var SPECIES = wellKnownSymbol$1('species');
+
+ var arrayMethodHasSpeciesSupport$2 = function (METHOD_NAME) {
+ // We can't use this feature detection in V8 since it causes
+ // deoptimization and serious performance degradation
+ // https://github.com/zloirock/core-js/issues/677
+ return V8_VERSION$1 >= 51 || !fails$2(function () {
+ var array = [];
+ var constructor = array.constructor = {};
+ constructor[SPECIES] = function () {
+ return { foo: 1 };
+ };
+ return array[METHOD_NAME](Boolean).foo !== 1;
+ });
+ };
+
+ var $$2 = _export;
+ var $filter = arrayIteration.filter;
+ var arrayMethodHasSpeciesSupport$1 = arrayMethodHasSpeciesSupport$2;
+
+ var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport$1('filter');
+
+ // `Array.prototype.filter` method
+ // https://tc39.es/ecma262/#sec-array.prototype.filter
+ // with adding support of @@species
+ $$2({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, {
+ filter: function filter(callbackfn /* , thisArg */) {
+ return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
+ }
+ });
+
+ var DESCRIPTORS = descriptors;
+ var uncurryThis = functionUncurryThis;
+ var call = functionCall;
+ var fails$1 = fails$d;
+ var objectKeys = objectKeys$3;
+ var getOwnPropertySymbolsModule = objectGetOwnPropertySymbols;
+ var propertyIsEnumerableModule = objectPropertyIsEnumerable;
+ var toObject$1 = toObject$4;
+ var IndexedObject = indexedObject;
+
+ // eslint-disable-next-line es/no-object-assign -- safe
+ var $assign = Object.assign;
+ // eslint-disable-next-line es/no-object-defineproperty -- required for testing
+ var defineProperty = Object.defineProperty;
+ var concat = uncurryThis([].concat);
+
+ // `Object.assign` method
+ // https://tc39.es/ecma262/#sec-object.assign
+ var objectAssign = !$assign || fails$1(function () {
+ // should have correct order of operations (Edge bug)
+ if (DESCRIPTORS && $assign({ b: 1 }, $assign(defineProperty({}, 'a', {
+ enumerable: true,
+ get: function () {
+ defineProperty(this, 'b', {
+ value: 3,
+ enumerable: false
+ });
+ }
+ }), { b: 2 })).b !== 1) return true;
+ // should work with symbols and should have deterministic property order (V8 bug)
+ var A = {};
+ var B = {};
+ // eslint-disable-next-line es/no-symbol -- safe
+ var symbol = Symbol();
+ var alphabet = 'abcdefghijklmnopqrst';
+ A[symbol] = 7;
+ alphabet.split('').forEach(function (chr) { B[chr] = chr; });
+ return $assign({}, A)[symbol] != 7 || objectKeys($assign({}, B)).join('') != alphabet;
+ }) ? function assign(target, source) { // eslint-disable-line no-unused-vars -- required for `.length`
+ var T = toObject$1(target);
+ var argumentsLength = arguments.length;
+ var index = 1;
+ var getOwnPropertySymbols = getOwnPropertySymbolsModule.f;
+ var propertyIsEnumerable = propertyIsEnumerableModule.f;
+ while (argumentsLength > index) {
+ var S = IndexedObject(arguments[index++]);
+ var keys = getOwnPropertySymbols ? concat(objectKeys(S), getOwnPropertySymbols(S)) : objectKeys(S);
+ var length = keys.length;
+ var j = 0;
+ var key;
+ while (length > j) {
+ key = keys[j++];
+ if (!DESCRIPTORS || call(propertyIsEnumerable, S, key)) T[key] = S[key];
+ }
+ } return T;
+ } : $assign;
+
+ var $$1 = _export;
+ var assign = objectAssign;
+
+ // `Object.assign` method
+ // https://tc39.es/ecma262/#sec-object.assign
+ // eslint-disable-next-line es/no-object-assign -- required for testing
+ $$1({ target: 'Object', stat: true, arity: 2, forced: Object.assign !== assign }, {
+ assign: assign
+ });
+
+ var $TypeError = TypeError;
+ var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF; // 2 ** 53 - 1 == 9007199254740991
+
+ var doesNotExceedSafeInteger$1 = function (it) {
+ if (it > MAX_SAFE_INTEGER) throw $TypeError('Maximum allowed index exceeded');
+ return it;
+ };
+
+ var toPropertyKey = toPropertyKey$3;
+ var definePropertyModule = objectDefineProperty;
+ var createPropertyDescriptor = createPropertyDescriptor$3;
+
+ var createProperty$1 = function (object, key, value) {
+ var propertyKey = toPropertyKey(key);
+ if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor(0, value));
+ else object[propertyKey] = value;
+ };
+
+ var $ = _export;
+ var fails = fails$d;
+ var isArray = isArray$2;
+ var isObject = isObject$7;
+ var toObject = toObject$4;
+ var lengthOfArrayLike = lengthOfArrayLike$3;
+ var doesNotExceedSafeInteger = doesNotExceedSafeInteger$1;
+ var createProperty = createProperty$1;
+ var arraySpeciesCreate = arraySpeciesCreate$2;
+ var arrayMethodHasSpeciesSupport = arrayMethodHasSpeciesSupport$2;
+ var wellKnownSymbol = wellKnownSymbol$7;
+ var V8_VERSION = engineV8Version;
+
+ var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable');
+
+ // We can't use this feature detection in V8 since it causes
+ // deoptimization and serious performance degradation
+ // https://github.com/zloirock/core-js/issues/679
+ var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails(function () {
+ var array = [];
+ array[IS_CONCAT_SPREADABLE] = false;
+ return array.concat()[0] !== array;
+ });
+
+ var isConcatSpreadable = function (O) {
+ if (!isObject(O)) return false;
+ var spreadable = O[IS_CONCAT_SPREADABLE];
+ return spreadable !== undefined ? !!spreadable : isArray(O);
+ };
+
+ var FORCED = !IS_CONCAT_SPREADABLE_SUPPORT || !arrayMethodHasSpeciesSupport('concat');
+
+ // `Array.prototype.concat` method
+ // https://tc39.es/ecma262/#sec-array.prototype.concat
+ // with adding support of @@isConcatSpreadable and @@species
+ $({ target: 'Array', proto: true, arity: 1, forced: FORCED }, {
+ // eslint-disable-next-line no-unused-vars -- required for `.length`
+ concat: function concat(arg) {
+ var O = toObject(this);
+ var A = arraySpeciesCreate(O, 0);
+ var n = 0;
+ var i, k, length, len, E;
+ for (i = -1, length = arguments.length; i < length; i++) {
+ E = i === -1 ? O : arguments[i];
+ if (isConcatSpreadable(E)) {
+ len = lengthOfArrayLike(E);
+ doesNotExceedSafeInteger(n + len);
+ for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]);
+ } else {
+ doesNotExceedSafeInteger(n + 1);
+ createProperty(A, n++, E);
+ }
+ }
+ A.length = n;
+ return A;
+ }
+ });
+
+ /**
+ * @author: Dennis Hernández
+ * @update: https://github.com/wenzhixin
+ * @version: v1.2.0
+ */
+
+ $$6.akottr.dragtable.prototype._restoreState = function (persistObj) {
+ var i = 0;
+ for (var _i = 0, _Object$entries = Object.entries(persistObj); _i < _Object$entries.length; _i++) {
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
+ field = _Object$entries$_i[0],
+ value = _Object$entries$_i[1];
+ var $th = this.originalTable.el.find("th[data-field=\"".concat(field, "\"]"));
+ if (!$th.length) {
+ i++;
+ continue;
+ }
+ this.originalTable.startIndex = $th.prevAll().length + 1;
+ this.originalTable.endIndex = parseInt(value, 10) + 1 - i;
+ this._bubbleCols();
+ }
+ };
+
+ // From MDN site, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
+ var filterFn = function filterFn() {
+ if (!Array.prototype.filter) {
+ Array.prototype.filter = function (fun /* , thisArg*/) {
+ if (this === undefined || this === null) {
+ throw new TypeError();
+ }
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== 'function') {
+ throw new TypeError();
+ }
+ var res = [];
+ var thisArg = arguments.length >= 2 ? arguments[1] : undefined;
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ var val = t[i];
+
+ // NOTE: Technically this should Object.defineProperty at
+ // the next index, as push can be affected by
+ // properties on Object.prototype and Array.prototype.
+ // But this method's new, and collisions should be
+ // rare, so use the more-compatible alternative.
+ if (fun.call(thisArg, val, i, t)) {
+ res.push(val);
+ }
+ }
+ }
+ return res;
+ };
+ }
+ };
+ Object.assign($$6.fn.bootstrapTable.defaults, {
+ reorderableColumns: false,
+ maxMovingRows: 10,
+ // eslint-disable-next-line no-unused-vars
+ onReorderColumn: function onReorderColumn(headerFields) {
+ return false;
+ },
+ dragaccept: null
+ });
+ Object.assign($$6.fn.bootstrapTable.events, {
+ 'reorder-column.bs.table': 'onReorderColumn'
+ });
+ $$6.fn.bootstrapTable.methods.push('orderColumns');
+ $$6.BootstrapTable = /*#__PURE__*/function (_$$BootstrapTable) {
+ _inherits(_class, _$$BootstrapTable);
+ var _super = _createSuper(_class);
+ function _class() {
+ _classCallCheck(this, _class);
+ return _super.apply(this, arguments);
+ }
+ _createClass(_class, [{
+ key: "initHeader",
+ value: function initHeader() {
+ var _get2;
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ (_get2 = _get(_getPrototypeOf(_class.prototype), "initHeader", this)).call.apply(_get2, [this].concat(args));
+ if (!this.options.reorderableColumns) {
+ return;
+ }
+ this.makeColumnsReorderable();
+ }
+ }, {
+ key: "_toggleColumn",
+ value: function _toggleColumn() {
+ var _get3;
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+ (_get3 = _get(_getPrototypeOf(_class.prototype), "_toggleColumn", this)).call.apply(_get3, [this].concat(args));
+ if (!this.options.reorderableColumns) {
+ return;
+ }
+ this.makeColumnsReorderable();
+ }
+ }, {
+ key: "toggleView",
+ value: function toggleView() {
+ var _get4;
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ args[_key3] = arguments[_key3];
+ }
+ (_get4 = _get(_getPrototypeOf(_class.prototype), "toggleView", this)).call.apply(_get4, [this].concat(args));
+ if (!this.options.reorderableColumns) {
+ return;
+ }
+ if (this.options.cardView) {
+ return;
+ }
+ this.makeColumnsReorderable();
+ }
+ }, {
+ key: "resetView",
+ value: function resetView() {
+ var _get5;
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+ (_get5 = _get(_getPrototypeOf(_class.prototype), "resetView", this)).call.apply(_get5, [this].concat(args));
+ if (!this.options.reorderableColumns) {
+ return;
+ }
+ this.makeColumnsReorderable();
+ }
+ }, {
+ key: "makeColumnsReorderable",
+ value: function makeColumnsReorderable() {
+ var _this = this;
+ var order = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ try {
+ $$6(this.$el).dragtable('destroy');
+ } catch (e) {
+ // do nothing
+ }
+ $$6(this.$el).dragtable({
+ maxMovingRows: this.options.maxMovingRows,
+ dragaccept: this.options.dragaccept,
+ clickDelay: 200,
+ dragHandle: '.th-inner',
+ restoreState: order ? order : this.columnsSortOrder,
+ beforeStop: function beforeStop(table) {
+ var sortOrder = {};
+ table.el.find('th').each(function (i, el) {
+ sortOrder[$$6(el).data('field')] = i;
+ });
+ _this.columnsSortOrder = sortOrder;
+ if (_this.options.cookie) {
+ _this.persistReorderColumnsState(_this);
+ }
+ var ths = [];
+ var formatters = [];
+ var columns = [];
+ var columnsHidden = [];
+ var columnIndex = -1;
+ var optionsColumns = [];
+ _this.$header.find('th:not(.detail)').each(function (i, el) {
+ ths.push($$6(el).data('field'));
+ formatters.push($$6(el).data('formatter'));
+ });
+
+ // Exist columns not shown
+ if (ths.length < _this.columns.length) {
+ columnsHidden = _this.columns.filter(function (column) {
+ return !column.visible;
+ });
+ for (var i = 0; i < columnsHidden.length; i++) {
+ ths.push(columnsHidden[i].field);
+ formatters.push(columnsHidden[i].formatter);
+ }
+ }
+ for (var _i2 = 0; _i2 < ths.length; _i2++) {
+ columnIndex = _this.fieldsColumnsIndex[ths[_i2]];
+ if (columnIndex !== -1) {
+ _this.fieldsColumnsIndex[ths[_i2]] = _i2;
+ _this.columns[columnIndex].fieldIndex = _i2;
+ columns.push(_this.columns[columnIndex]);
+ }
+ }
+ _this.columns = columns;
+ filterFn(); // Support only header. 40 row should be enough, the rest is usually not in the viewport
+ excludeFooter: false, // excludes the footer row(s) while moving other columns. Make sense if there is a footer with a colspan. */
+ onlyHeaderThreshold: 100, // TODO: not implemented yet, switch automatically between entire col moving / only header moving
+ dragaccept: null, // draggable cols -> default all
+ persistState: null, // url or function -> plug in your custom persistState function right here. function call is persistState(originalTable)
+ restoreState: null, // JSON-Object or function: some kind of experimental aka Quick-Hack TODO: do it better
+ exact: true, // removes pixels, so that the overlay table width fits exactly the original table width
+ clickDelay: 10, // ms to wait before rendering sortable list and delegating click event
+ containment: null, // @see http://api.jqueryui.com/sortable/#option-containment, use it if you want to move in 2 dimesnions (together with axis: null)
+ cursor: 'move', // @see http://api.jqueryui.com/sortable/#option-cursor
+ cursorAt: false, // @see http://api.jqueryui.com/sortable/#option-cursorAt
+ distance: 0, // @see http://api.jqueryui.com/sortable/#option-distance, for immediate feedback use "0"
+ tolerance: 'pointer', // @see http://api.jqueryui.com/sortable/#option-tolerance
+ axis: 'x', // @see http://api.jqueryui.com/sortable/#option-axis, Only vertical moving is allowed. Use 'x' or null. Use this in conjunction with the 'containment' setting
+ beforeStart: $.noop, // returning FALSE will stop the execution chain.
+ beforeMoving: $.noop,
+ beforeReorganize: $.noop,
+ beforeStop: $.noop
+ },
+ originalTable: {
+ el: null,
+ selectedHandle: null,
+ sortOrder: null,
+ startIndex: 0,
+ endIndex: 0
+ },
+ sortableTable: {
+ el: $(),
+ selectedHandle: $(),
+ movingRow: $()
+ },
+ persistState: function() {
+ var _this = this;
+ this.originalTable.el.find('th').each(function(i) {
+ if (this.id !== '') {
+ _this.originalTable.sortOrder[this.id] = i;
+ }
+ });
+ $.ajax({
+ url: this.options.persistState,
+ data: this.originalTable.sortOrder
+ });
+ },
+ /*
+ * persistObj looks like
+ * {'id1':'2','id3':'3','id2':'1'}
+ * table looks like
+ * | id2 | id1 | id3 |
+ */
+ _restoreState: function(persistObj) {
+ for (var n in persistObj) {
+ this.originalTable.startIndex = $('#' + n).closest('th').prevAll().length + 1;
+ this.originalTable.endIndex = parseInt(persistObj[n], 10) + 1;
+ this._bubbleCols();
+ }
+ },
+ // bubble the moved col left or right
+ _bubbleCols: function() {
+ var i, j, col1, col2;
+ var from = this.originalTable.startIndex;
+ var to = this.originalTable.endIndex;
+ /* Find children thead and tbody.
+ * Only to process the immediate tr-children. Bugfix for inner tables
+ */
+ var thtb = this.originalTable.el.children();
+ if (this.options.excludeFooter) {
+ thtb = thtb.not('tfoot');
+ }
+ if (from < to) {
+ for (i = from; i < to; i++) {
+ col1 = thtb.find('> tr > td:nth-child(' + i + ')')
+ .add(thtb.find('> tr > th:nth-child(' + i + ')'));
+ col2 = thtb.find('> tr > td:nth-child(' + (i + 1) + ')')
+ .add(thtb.find('> tr > th:nth-child(' + (i + 1) + ')'));
+ for (j = 0; j < col1.length; j++) {
+ swapNodes(col1[j], col2[j]);
+ }
+ }
+ } else {
+ for (i = from; i > to; i--) {
+ col1 = thtb.find('> tr > td:nth-child(' + i + ')')
+ .add(thtb.find('> tr > th:nth-child(' + i + ')'));
+ col2 = thtb.find('> tr > td:nth-child(' + (i - 1) + ')')
+ .add(thtb.find('> tr > th:nth-child(' + (i - 1) + ')'));
+ for (j = 0; j < col1.length; j++) {
+ swapNodes(col1[j], col2[j]);
+ }
+ }
+ }
+ },
+ _rearrangeTableBackroundProcessing: function() {
+ var _this = this;
+ return function() {
+ _this._bubbleCols();
+ _this.options.beforeStop(_this.originalTable);
+ _this.sortableTable.el.remove();
+ restoreTextSelection();
+ // persist state if necessary
+ if (_this.options.persistState !== null) {
+ $.isFunction(_this.options.persistState) ? _this.options.persistState(_this.originalTable) : _this.persistState();
+ }
+ };
+ },
+ _rearrangeTable: function() {
+ var _this = this;
+ return function() {
+ // remove handler-class -> handler is now finished
+ _this.originalTable.selectedHandle.removeClass('dragtable-handle-selected');
+ // add disabled class -> reorgorganisation starts soon
+ _this.sortableTable.el.sortable("disable");
+ _this.sortableTable.el.addClass('dragtable-disabled');
+ _this.options.beforeReorganize(_this.originalTable, _this.sortableTable);
+ // do reorganisation asynchronous
+ // for chrome a little bit more than 1 ms because we want to force a rerender
+ _this.originalTable.endIndex = _this.sortableTable.movingRow.prevAll().length + 1;
+ setTimeout(_this._rearrangeTableBackroundProcessing(), 50);
+ };
+ },
+ /*
+ * Disrupts the table. The original table stays the same.
+ * But on a layer above the original table we are constructing a list (ul > li)
+ * each li with a separate table representig a single col of the original table.
+ */
+ _generateSortable: function(e) {
+ !e.cancelBubble && (e.cancelBubble = true);
+ var _this = this;
+ // table attributes
+ var attrs = this.originalTable.el[0].attributes;
+ var attrsString = '';
+ for (var i = 0; i < attrs.length; i++) {
+ if (attrs[i].nodeValue && attrs[i].nodeName != 'id' && attrs[i].nodeName != 'width') {
+ attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue + '" ';
+ }
+ }
+
+ // row attributes
+ var rowAttrsArr = [];
+ //compute height, special handling for ie needed :-(
+ var heightArr = [];
+ this.originalTable.el.find('tr').slice(0, this.options.maxMovingRows).each(function(i, v) {
+ // row attributes
+ var attrs = this.attributes;
+ var attrsString = "";
+ for (var j = 0; j < attrs.length; j++) {
+ if (attrs[j].nodeValue && attrs[j].nodeName != 'id') {
+ attrsString += " " + attrs[j].nodeName + '="' + attrs[j].nodeValue + '"';
+ }
+ }
+ rowAttrsArr.push(attrsString);
+ heightArr.push($(this).height());
+ });
+
+ // compute width, no special handling for ie needed :-)
+ var widthArr = [];
+ // compute total width, needed for not wrapping around after the screen ends (floating)
+ var totalWidth = 0;
+ /* Find children thead and tbody.
+ * Only to process the immediate tr-children. Bugfix for inner tables
+ */
+ var thtb = _this.originalTable.el.children();
+ if (this.options.excludeFooter) {
+ thtb = thtb.not('tfoot');
+ }
+ thtb.find('> tr > th').each(function(i, v) {
+ var w = $(this).is(':visible') ? $(this).outerWidth() : 0;
+ widthArr.push(w);
+ totalWidth += w;
+ });
+ if(_this.options.exact) {
+ var difference = totalWidth - _this.originalTable.el.outerWidth();
+ widthArr[0] -= difference;
+ }
+ // one extra px on right and left side
+ totalWidth += 2
+
+ var sortableHtml = '';
+ // assemble the needed html
+ thtb.find('> tr > th').each(function(i, v) {
+ var width_li = $(this).is(':visible') ? $(this).outerWidth() : 0;
+ sortableHtml += '- ';
+ sortableHtml += '
';
+ var row = thtb.find('> tr > th:nth-child(' + (i + 1) + ')');
+ if (_this.options.maxMovingRows > 1) {
+ row = row.add(thtb.find('> tr > td:nth-child(' + (i + 1) + ')').slice(0, _this.options.maxMovingRows - 1));
+ }
+ row.each(function(j) {
+ // TODO: May cause duplicate style-Attribute
+ var row_content = $(this).clone().wrap('').parent().html();
+ if (row_content.toLowerCase().indexOf('";
+ sortableHtml += ' | ';
+ sortableHtml += row_content;
+ if (row_content.toLowerCase().indexOf('";
+ sortableHtml += ' |
';
+ });
+ sortableHtml += '
';
+ sortableHtml += ' ';
+ });
+ sortableHtml += '
';
+ this.sortableTable.el = this.originalTable.el.before(sortableHtml).prev();
+ // set width if necessary
+ this.sortableTable.el.find('> li > table').each(function(i, v) {
+ $(this).css('width', widthArr[i] + 'px');
+ });
+
+ // assign this.sortableTable.selectedHandle
+ this.sortableTable.selectedHandle = this.sortableTable.el.find('th .dragtable-handle-selected');
+
+ var items = !this.options.dragaccept ? 'li' : 'li:has(' + this.options.dragaccept + ')';
+ this.sortableTable.el.sortable({
+ items: items,
+ stop: this._rearrangeTable(),
+ // pass thru options for sortable widget
+ revert: this.options.revert,
+ tolerance: this.options.tolerance,
+ containment: this.options.containment,
+ cursor: this.options.cursor,
+ cursorAt: this.options.cursorAt,
+ distance: this.options.distance,
+ axis: this.options.axis
+ });
+
+ // assign start index
+ this.originalTable.startIndex = $(e.target).closest('th').prevAll().length + 1;
+
+ this.options.beforeMoving(this.originalTable, this.sortableTable);
+ // Start moving by delegating the original event to the new sortable table
+ this.sortableTable.movingRow = this.sortableTable.el.find('> li:nth-child(' + this.originalTable.startIndex + ')');
+
+ // prevent the user from drag selecting "highlighting" surrounding page elements
+ disableTextSelection();
+ // clone the initial event and trigger the sort with it
+ this.sortableTable.movingRow.trigger($.extend($.Event(e.type), {
+ which: 1,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ screenX: e.screenX,
+ screenY: e.screenY
+ }));
+
+ // Some inner divs to deliver the posibillity to style the placeholder more sophisticated
+ var placeholder = this.sortableTable.el.find('.ui-sortable-placeholder');
+ if(!placeholder.height() <= 0) {
+ placeholder.css('height', this.sortableTable.el.find('.ui-sortable-helper').height());
+ }
+
+ placeholder.html('');
+ },
+ bindTo: {},
+ _create: function() {
+ this.originalTable = {
+ el: this.element,
+ selectedHandle: $(),
+ sortOrder: {},
+ startIndex: 0,
+ endIndex: 0
+ };
+ // bind draggable to 'th' by default
+ this.bindTo = this.originalTable.el.find('th');
+ // filter only the cols that are accepted
+ if (this.options.dragaccept) {
+ this.bindTo = this.bindTo.filter(this.options.dragaccept);
+ }
+ // bind draggable to handle if exists
+ if (this.bindTo.find(this.options.dragHandle).length > 0) {
+ this.bindTo = this.bindTo.find(this.options.dragHandle);
+ }
+ // restore state if necessary
+ if (this.options.restoreState !== null) {
+ $.isFunction(this.options.restoreState) ? this.options.restoreState(this.originalTable) : this._restoreState(this.options.restoreState);
+ }
+ var _this = this;
+ this.bindTo.mousedown(function(evt) {
+ // listen only to left mouse click
+ if(evt.which!==1) return;
+ if (_this.options.beforeStart(_this.originalTable) === false) {
+ return;
+ }
+ clearTimeout(this.downTimer);
+ this.downTimer = setTimeout(function() {
+ _this.originalTable.selectedHandle = $(this);
+ _this.originalTable.selectedHandle.addClass('dragtable-handle-selected');
+ _this._generateSortable(evt);
+ }, _this.options.clickDelay);
+ }).mouseup(function(evt) {
+ clearTimeout(this.downTimer);
+ });
+ },
+ redraw: function(){
+ this.destroy();
+ this._create();
+ },
+ destroy: function() {
+ this.bindTo.unbind('mousedown');
+ $.Widget.prototype.destroy.apply(this, arguments); // default destroy
+ // now do other stuff particular to this widget
+ }
+ });
+
+ /** closure-scoped "private" functions **/
+
+ var body_onselectstart_save = $(document.body).attr('onselectstart'),
+ body_unselectable_save = $(document.body).attr('unselectable');
+
+ // css properties to disable user-select on the body tag by appending a ');
+ $(document.head).append($style);
+ $(document.body).attr('onselectstart', 'return false;').attr('unselectable', 'on');
+ if (window.getSelection) {
+ window.getSelection().removeAllRanges();
+ } else {
+ document.selection.empty(); // MSIE http://msdn.microsoft.com/en-us/library/ms535869%28v=VS.85%29.aspx
+ }
+ }
+
+ // remove the