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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
snipe
snipe

💻 🚇 📖 ⚠️ 🐛 🎨 👀
Brady Wetherington
Brady Wetherington

💻 📖 🚇 👀
Daniel Meltzer
Daniel Meltzer

💻 ⚠️ 📖
Michael T
Michael T

💻
madd15
madd15

📖 💬
Vincent Sposato
Vincent Sposato

💻
Andrea Bergamasco
Andrea Bergamasco

💻
Karol
Karol

🌍 💻
morph027
morph027

💻
fvleminckx
fvleminckx

🚇
itsupportcmsukorg
itsupportcmsukorg

💻 🐛
Frank
Frank

💻
Deleted user
Deleted user

🌍 💻
tiagom62
tiagom62

💻 🚇
Ryan Stafford
Ryan Stafford

💻
Eammon Hanlon
Eammon Hanlon

💻
zjean
zjean

💻
Matthias Frei
Matthias Frei

💻
opsydev
opsydev

💻
Daniel Dreier
Daniel Dreier

💻
Nikolai Prokoschenko
Nikolai Prokoschenko

💻
Drew
Drew

💻
Walter
Walter

💻
Petr Baloun
Petr Baloun

💻
reidblomquist
reidblomquist

📖
Mathieu Kooiman
Mathieu Kooiman

💻
csayre
csayre

📖
Adam Dunson
Adam Dunson

💻
Hereward
Hereward

💻
swoopdk
swoopdk

💻
Abdullah Alansari
Abdullah Alansari

💻
Micael Rodrigues
Micael Rodrigues

💻
Patrick Gallagher
Patrick Gallagher

📖
Miliamber
Miliamber

💻
hawk554
hawk554

💻
Justin Kerr
Justin Kerr

💻
Ira W. Snyder
Ira W. Snyder

📖
Aladin Alaily
Aladin Alaily

💻
Chase Hansen
Chase Hansen

💻 💬 🐛
IDM Helpdesk
IDM Helpdesk

💻
Kai
Kai

💻
Michael Daniels
Michael Daniels

💻
Tom Castleman
Tom Castleman

💻
Daniel Nemanic
Daniel Nemanic

💻
SouthWolf
SouthWolf

💻
Ivar Nesje
Ivar Nesje

💻
Jérémy Benoist
Jérémy Benoist

📖
Chris Leathley
Chris Leathley

🚇
splaer
splaer

🐛 💻
Joe Ferguson
Joe Ferguson

💻
diwanicki
diwanicki

💻 📖
Lee Thoong Ching
Lee Thoong Ching

📖 💻
Marek Šuppa
Marek Šuppa

💻
Juan J. Martinez
Juan J. Martinez

🌍
R Ryan Dial
R Ryan Dial

🌍
Andrej Manduch
Andrej Manduch

📖
Jay Richards
Jay Richards

💻
Alexander Innes
Alexander Innes

💻
Danny Garcia
Danny Garcia

💻
archpoint
archpoint

💻
Jake McGraw
Jake McGraw

💻
FleischKarussel
FleischKarussel

📖
Dylan Yi
Dylan Yi

💻
Gil Rutkowski
Gil Rutkowski

💻
Desmond Morris
Desmond Morris

💻
Nick Peelman
Nick Peelman

💻
Abraham Vegh
Abraham Vegh

💻
Mohamed Rashid
Mohamed Rashid

📖
Kasey
Kasey

💻
Brett
Brett

⚠️
Jason Spriggs
Jason Spriggs

💻
Nate Felton
Nate Felton

💻
Manasses Ferreira
Manasses Ferreira

💻
Steve
Steve

⚠️
matc
matc

⚠️
Cole R. Davis
Cole R. Davis

⚠️
gibsonjoshua55
gibsonjoshua55

💻
Robin Temme
Robin Temme

💻
Iman
Iman

💻
Richard Hofman
Richard Hofman

💻
gizzmojr
gizzmojr

💻
Jenny Li
Jenny Li

📖
Geoff Young
Geoff Young

💻
Elliot Blackburn
Elliot Blackburn

📖
Tõnis Ormisson
Tõnis Ormisson

💻
Nicolai Essig
Nicolai Essig

💻
Danielle
Danielle

📖
Lawrence
Lawrence

⚠️ 🐛
uknzaeinozpas
uknzaeinozpas

⚠️ 💻
Ryan
Ryan

📖
vcordes79
vcordes79

💻
fordster78
fordster78

💻
CronKz
CronKz

💻 🌍
Tim Bishop
Tim Bishop

💻
Sean McIlvenna
Sean McIlvenna

💻
cepacs
cepacs

🐛 📖
lea-mink
lea-mink

💻
Hannah Tinkler
Hannah Tinkler

💻
Doeke Zanstra
Doeke Zanstra

💻
Djamon Staal
Djamon Staal

💻
Earl Ramirez
Earl Ramirez

💻
Richard Ray Thomas
Richard Ray Thomas

💻
Ryan Kuba
Ryan Kuba

💻
Brian Monroe
Brian Monroe

💻
plexorama
plexorama

💻
Till Deeke
Till Deeke

💻
5quirrel
5quirrel

💻
Jason
Jason

💻
Antti
Antti

💻
DeusMaximus
DeusMaximus

💻
a-royal
a-royal

🌍
Alberto Aldrigo
Alberto Aldrigo

🌍
Alex Stanev
Alex Stanev

🌍
Andreas Rehm
Andreas Rehm

🌍
Andreas Erhard
Andreas Erhard

🌍
Andrés Vanegas Jiménez
Andrés Vanegas Jiménez

🌍
Antonio Schiavon
Antonio Schiavon

🌍
benunter
benunter

🌍
Borys Żmuda
Borys Żmuda

🌍
chibacityblues
chibacityblues

🌍
Chien Wei Lin
Chien Wei Lin

🌍
Christian Schuster
Christian Schuster

🌍
Christian Stefanus
Christian Stefanus

🌍
wxcafé
wxcafé

🌍
dpyroc
dpyroc

🌍
Daniel Friedlmaier
Daniel Friedlmaier

🌍
Daniel Heene
Daniel Heene

🌍
danielcb
danielcb

🌍
Dominik Senti
Dominik Senti

🌍
Eric Gautheron
Eric Gautheron

🌍
Erlend Pilø
Erlend Pilø

🌍
Fabio Rapposelli
Fabio Rapposelli

🌍
Felipe Barros
Felipe Barros

🌍
Fernando Possebon
Fernando Possebon

🌍
gdraque
gdraque

🌍
Georg Wallisch
Georg Wallisch

🌍
Gerardo Robles
Gerardo Robles

🌍
Gluek
Gluek

🌍
AdnanAbuShahad
AdnanAbuShahad

🌍
Hafidzi My
Hafidzi My

🌍
Harim Park
Harim Park

🌍
Henrik Kentsson
Henrik Kentsson

🌍
Husnul Yaqien
Husnul Yaqien

🌍
Ibrahim
Ibrahim

🌍
igolman
igolman

🌍
itangiang
itangiang

🌍
jarby1211
jarby1211

🌍
Jhonn Willker
Jhonn Willker

🌍
Jose
Jose

🌍
laopangzi
laopangzi

🌍
Lars Strojny
Lars Strojny

🌍
MarcosBL
MarcosBL

🌍
marie joy cajes
marie joy cajes

🌍
Mark S. Johansen
Mark S. Johansen

🌍
Martin Stub
Martin Stub

🌍
Meyer Flavio
Meyer Flavio

🌍
Micael Rodrigues
Micael Rodrigues

🌍
Mikael Rasmussen
Mikael Rasmussen

🌍
IxFail
IxFail

🌍
Mohammed Fota
Mohammed Fota

🌍
Moayad Alserihi
Moayad Alserihi

🌍
saymd
saymd

🌍
Patrik Larsson
Patrik Larsson

🌍
drcryo
drcryo

🌍
pawel1615
pawel1615

🌍
bodrovics
bodrovics

🌍
priatna
priatna

🌍
Fan Jiang
Fan Jiang

🌍
ragnarcx
ragnarcx

🌍
Rein van Haaren
Rein van Haaren

🌍
Teguh Dwicaksana
Teguh Dwicaksana

🌍
fraccie
fraccie

🌍
vinzruzell
vinzruzell

🌍
Kevin Austin
Kevin Austin

🌍
Wira Sandy
Wira Sandy

🌍
Илья
Илья

🌍
GodUseVPN
GodUseVPN

🌍
周周
周周

🌍
Sam
Sam

💻
Azerothian
Azerothian

💻
Wes Hulette
Wes Hulette

💻
patrict
patrict

💻
Dmitriy Minaev
Dmitriy Minaev

💻
liquidhorse
liquidhorse

💻
Jordi Boggiano
Jordi Boggiano

💻
Ivan Nieto
Ivan Nieto

💻
Ben RUBSON
Ben RUBSON

💻
NMathar
NMathar

💻
Steffen
Steffen

💻
Sxderp
Sxderp

💻
fanta8897
fanta8897

💻
Andrey Bolonin
Andrey Bolonin

💻
shinayoshi
shinayoshi

💻
Hubert
Hubert

💻
KeenRivals
KeenRivals

💻
omyno
omyno

💻
Evgeny
Evgeny

💻
Colin Campbell
Colin Campbell

💻
Ľubomír Kučera
Ľubomír Kučera

💻
Martin Meredith
Martin Meredith

💻
Tim Farmer
Tim Farmer

💻
Marián Skrip
Marián Skrip

💻
Godfrey Martinez
Godfrey Martinez

💻
bigtreeEdo
bigtreeEdo

💻
Colin  McNeil
Colin McNeil

💻
JoKneeMo
JoKneeMo

💻
Joshi
Joshi

💻
Anthony Burns
Anthony Burns

💻
johnson-yi
johnson-yi

💻
Sanjay Govind
Sanjay Govind

💻
Peter Upfold
Peter Upfold

💻
Jared Biel
Jared Biel

💻
Dampfklon
Dampfklon

💻
Charles Hamilton
Charles Hamilton

💻
Giuseppe Iannello
Giuseppe Iannello

💻
Peter Dave Hello
Peter Dave Hello

💻
sigmoidal
sigmoidal

💻
Vincent Lainé
Vincent Lainé

💻
Lucas Pleß
Lucas Pleß

💻
Ian Littman
Ian Littman

💻
João Paulo
João Paulo

💻
ThoBur
ThoBur

💻
Alexander Chibrikin
Alexander Chibrikin

💻
Anthony Winstanley
Anthony Winstanley

💻
Folke
Folke

💻
Bennett Blodinger
Bennett Blodinger

💻
NMC
NMC

💻
andres-baller
andres-baller

💻
sean-borg
sean-borg

💻
EDVLeer
EDVLeer

💻
Kurokat
Kurokat

💻
Kevin Köllmann
Kevin Köllmann

💻
sw-mreyes
sw-mreyes

💻
Joel Pittet
Joel Pittet

💻
Eli Young
Eli Young

💻
Raell Dottin
Raell Dottin

💻
Tom Misilo
Tom Misilo

💻
David Davenne
David Davenne

💻
Mark Stenglein
Mark Stenglein

💻
ajsy
ajsy

💻
Jan Kiesewetter
Jan Kiesewetter

💻
Tetrachloromethane250
Tetrachloromethane250

💻
Lars Kajes
Lars Kajes

💻
Joly0
Joly0

💻
theburger
theburger

💻
David Valin Alonso
David Valin Alonso

💻
andreaci
andreaci

💻
Jelle Sebreghts
Jelle Sebreghts

💻
Michael Pietsch
Michael Pietsch

Masudul Haque Shihab
Masudul Haque Shihab

💻
Supapong Areeprasertkul
Supapong Areeprasertkul

💻
Peter Sarossy
Peter Sarossy

💻
Renee Margaret McConahy
Renee Margaret McConahy

💻
JohnnyPicnic
JohnnyPicnic

💻
markbrule
markbrule

💻
Mike Campbell
Mike Campbell

💻
tbrconnect
tbrconnect

💻
kcoyo
kcoyo

💻
Travis Miller
Travis Miller

💻
Evan Taylor
Evan Taylor

💻
Petri Asikainen
Petri Asikainen

💻
derdeagle
derdeagle

💻
Mike Frysinger
Mike Frysinger

💻
ALPHA
ALPHA

💻
FliegenKLATSCH
FliegenKLATSCH

💻
Jeremy Price
Jeremy Price

💻
Toreg87
Toreg87

💻
Matthew Nickson
Matthew Nickson

💻
Jethro Nederhof
Jethro Nederhof

💻
Oskar Stenberg
Oskar Stenberg

💻
Robert-Azelis
Robert-Azelis

💻
Alexander William Smith
Alexander William Smith

💻
LEITWERK AG
LEITWERK AG

💻
Adam
Adam

💻
Ian
Ian

💻
Shao Yu-Lung (Allen)
Shao Yu-Lung (Allen)

💻
Haxatron
Haxatron

💻
PlaneNuts
PlaneNuts

💻
Bradley Coudriet
Bradley Coudriet

💻
Dalton Durst
Dalton Durst

💻
Alex Janes
Alex Janes

💻
Nuraeil
Nuraeil

💻
TenOfTens
TenOfTens

💻
waffle
waffle

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Achmad Fienan Rahardianto
Achmad Fienan Rahardianto

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Christian Weirich
Christian Weirich

💻
denzfarid
denzfarid

ntbutler-nbcs
ntbutler-nbcs

💻
Naveen
Naveen

💻
Mike Roquemore
Mike Roquemore

💻
Daniel Reeder
Daniel Reeder

🌍 🌍 💻
vickyjaura183
vickyjaura183

💻
Peace
Peace

💻
Kyle Gordon
Kyle Gordon

💻
Katharina Drexel
Katharina Drexel

💻
David Sferruzza
David Sferruzza

💻
Rick Nelson
Rick Nelson

💻
BasO12
BasO12

💻
Vautia
Vautia

💻
Chris Hartjes
Chris Hartjes

💻
geo-chen
geo-chen

💻
Phan Nguyen
Phan Nguyen

💻
Iisakki Jaakkola
Iisakki Jaakkola

💻
Ikko Ashimine
Ikko Ashimine

💻
Lukas Fehling
Lukas Fehling

💻
Fernando Almeida
Fernando Almeida

💻
akemidx
akemidx

💻
Oguz Bilgic
Oguz Bilgic

💻
Scooter Crawford
Scooter Crawford

💻
subdriven
subdriven

💻
Andrew Savinykh
Andrew Savinykh

💻
Tadayuki Onishi
Tadayuki Onishi

💻
Florian
Florian

💻
Spencer Long
Spencer Long

💻
Marcus Moore
Marcus Moore

💻
Martin Meredith
Martin Meredith

dboth
dboth

💻
Zachary Fleck
Zachary Fleck

💻
VIKAAS-A
VIKAAS-A

💻
Abdul Kareem
Abdul Kareem

💻
NojoudAlshehri
NojoudAlshehri

💻
Stefan Stidl
Stefan Stidl

💻
Quentin Aymard
Quentin Aymard

💻
Grant Le Roux
Grant Le Roux

💻
Bogdan
Bogdan

💻
mmanjos
mmanjos

💻
Abdelaziz Faki
Abdelaziz Faki

💻
bilias
bilias

💻
coach1988
coach1988

💻
MrM
MrM

💻
+ + + + + + +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 @@ -![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) -[![All Contributors](https://img.shields.io/badge/all_contributors-326-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) +![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) + +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) +[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](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 += row_content; + if (row_content.toLowerCase().indexOf(' 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 + @@ -260,11 +256,13 @@ trans('admin/settings/general.show_archived_in_list')) }}
    - {{ Form::checkbox('show_archived_in_list', '1', Request::old('show_archived_in_list', $setting->show_archived_in_list),array('class' => 'minimal')) }} - {{ trans('admin/settings/general.show_archived_in_list_text') }} - {!! $errors->first('show_archived_in_list', '') !!} -
    + + {!! $errors->first('show_archived_in_list', '') !!} + @@ -274,8 +272,10 @@ trans('admin/settings/general.show_assigned_assets')) }}
    +

    {{ trans('admin/settings/general.show_assigned_assets_help') }}

    {!! $errors->first('show_assigned_assets', ':message') !!}
    diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index fd1e9b630a..9ff9c94f71 100755 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -54,7 +54,7 @@ } - +
    @@ -346,7 +346,7 @@
    - + @@ -358,19 +358,19 @@

    {{ trans('admin/settings/general.system') }}

    -
    +
    - {{ trans('admin/settings/general.snipe_version') }} + {{ trans('admin/settings/general.snipe_version') }}:
    {{ config('version.app_version') }} build {{ config('version.build_version') }} ({{ config('version.hash_version') }})
    - {{ trans('admin/settings/general.license') }} + {{ trans('admin/settings/general.license') }}:
    AGPL3 @@ -381,19 +381,68 @@
    - {{ trans('admin/settings/general.php') }} + {{ trans('admin/settings/general.php') }}:
    {{ phpversion() }}
    - {{ trans('admin/settings/general.laravel') }} + {{ trans('admin/settings/general.laravel') }}:
    {{ $snipeSettings->lar_ver() }}
    +
    + +
    +
    + {{ trans('admin/settings/general.timezone') }}: +
    +
    + {{ config('app.timezone') }} +
    + +
    + {{ trans('admin/settings/general.database_driver') }}: +
    +
    + {{ config('database.default') }} +
    +
    + + +
    +
    + {{ trans('admin/settings/general.mail_from') }}: +
    +
    + {{ config('mail.from.name') }} + <{{ config('mail.from.address') }}> +
    + +
    + {{ trans('admin/settings/general.mail_reply_to') }}: +
    +
    + {{ config('mail.reply_to.name') }} + <{{ config('mail.reply_to.address') }}> +
    +
    + + +
    +
    + {{ trans('admin/settings/general.bs_table_storage') }}: +
    +
    + {{ config('session.bs_table_storage') }} +
    + +
    + +
    @@ -402,6 +451,9 @@
    + + + @section('moar_scripts') diff --git a/routes/api.php b/routes/api.php index 4b8866dc2f..d10d90fbee 100644 --- a/routes/api.php +++ b/routes/api.php @@ -280,17 +280,9 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi Api\ConsumablesController::class, 'getDataView' ] - )->name('api.consumables.showUsers'); + )->name('api.consumables.show.users'); - // This is LEGACY endpoint URL and should be removed in the next major release - Route::get('view/{id}/users', - [ - Api\ConsumablesController::class, - 'getDataView' - ] - )->name('api.consumables.showUsers'); - Route::post('{consumable}/checkout', [ Api\ConsumablesController::class, @@ -714,6 +706,13 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi ] )->name('api.manufacturers.selectlist'); + Route::post('{id}/restore', + [ + Api\ManufacturersController::class, + 'restore' + ] + )->name('api.manufacturers.restore'); + }); Route::resource('manufacturers', @@ -750,6 +749,13 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi ] )->name('api.models.assets'); + Route::post('{id}/restore', + [ + Api\AssetModelsController::class, + 'restore' + ] + )->name('api.models.restore'); + }); Route::resource('models', diff --git a/routes/web.php b/routes/web.php index 8386a0132c..b0000669b6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -225,6 +225,11 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser [SettingsController::class, 'postUploadBackup'] )->name('settings.backups.upload'); + // Handle redirect from after POST request from backup restore + Route::get('/restore/{filename?}', function () { + return redirect(route('settings.backups.index')); + }); + Route::get('/', [SettingsController::class, 'getBackups'])->name('settings.backups.index'); }); @@ -375,8 +380,8 @@ Route::group(['middleware' => ['auth']], function () { 'reports/unaccepted_assets/{deleted?}', [ReportsController::class, 'getAssetAcceptanceReport'] )->name('reports/unaccepted_assets'); - Route::get( - 'reports/unaccepted_assets/{acceptanceId}/sent_reminder', + Route::post( + 'reports/unaccepted_assets/sent_reminder', [ReportsController::class, 'sentAssetAcceptanceReminder'] )->name('reports/unaccepted_assets_sent_reminder'); Route::delete( diff --git a/routes/web/hardware.php b/routes/web/hardware.php index 1643e5794e..d4f2892281 100644 --- a/routes/web/hardware.php +++ b/routes/web/hardware.php @@ -122,9 +122,10 @@ Route::group( [AssetCheckinController::class, 'store'] )->name('hardware.checkin.store'); - Route::get('{assetId}/view', - [AssetsController::class, 'show'] - )->name('hardware.view'); + // Redirect old legacy /asset_id/view urls to the resource route version + Route::get('{assetId}/view', function ($assetId) { + return redirect()->route('hardware.show', ['hardware' => $assetId]); + }); Route::get('{assetId}/qr_code', [AssetsController::class, 'getQrCode'] @@ -178,13 +179,17 @@ Route::group( Route::post('bulkcheckout', [BulkAssetsController::class, 'storeCheckout'] )->name('hardware.bulkcheckout.store'); + }); Route::resource('hardware', AssetsController::class, [ 'middleware' => ['auth'], - 'parameters' => ['asset' => 'asset_id' + 'parameters' => ['asset' => 'asset_id', + 'names' => [ + 'show' => 'view', + ], ], ]); diff --git a/sample_csvs/assets-sample.csv b/sample_csvs/assets-sample.csv index 329c76d5eb..c17cac3bc8 100644 --- a/sample_csvs/assets-sample.csv +++ b/sample_csvs/assets-sample.csv @@ -1,151 +1,151 @@ -Company,Name,Asset Tag,Category,Supplier,Manufacturer,Location,Order Number,Model,Model Notes,Model Number,Asset Notes,Purchase Date,Purchase Cost,Checkout Type,Checked Out To: Username,Checked Out To: First Name,Checked Out To: Last Name,Checked Out To: Email,Checked Out To: Location -Abshire and Sons,Backhoe,ICC-2065556,Ornamental Railings,"Kunde, Doyle and Kozey",Berge Inc,"Wilkinson, Waters and Kerluke",3271901481,Nial,,1786VM80X07,at nulla suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis,2023-01-23,2266.13,,"","","","","" -"Quitzon, Oberbrunner and Dibbert",Dragline,WBH-2841795,Structural and Misc Steel (Fabrication),Krajcik LLC,"Botsford, Boyle and Herzog",Lindgren-Marquardt,5504512275,Chase,ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam,9351IS25A51,aliquam convallis nunc proin at turpis a pede posuere nonummy integer,2022-11-14,1292.94,User,gmccrackem2a,Gage,McCrackem,gmccrackem2a@bing.com,"" -Boyer and Sons,Excavator,NNH-3656031,Soft Flooring and Base,"Heaney, Altenwerth and Emmerich",Pollich LLC,Pacocha-Kiehn,4861125177,Chase,,9929FR08W85,,2023-03-01,2300.71,Location,"","","","",Pacocha-Kiehn -Hayes-Rippin,Trencher,BOL-0305383,Prefabricated Aluminum Metal Canopies,"Botsford, Boyle and Herzog",Walker-Towne,Fritsch-Abernathy,2416994639,Mabelle,neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus,9139KQ78G81,,2022-10-26,1777.56,User,ksennett6,Katerina,Sennett,ksennett6@ibm.com,"" -Romaguera-Flatley,Compactor,YVN-3440973,"Temp Fencing, Decorative Fencing and Gates",Ankunding-Ledner,Berge Inc,Roberts-Anderson,6080904229,Sumner,turpis adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede,0910VB28Q61,,2022-07-24,2967.97,,"","","","","" -Auer LLC,Bulldozer,YOO-5936907,Electrical,Berge Inc,"Heaney, Altenwerth and Emmerich",Roberts-Anderson,8204459090,Mabelle,,7375EM02N97,proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem,2022-10-01,3819.73,,"","","","","" -Olson Group,Skid-Steer,EJS-7488052,Roofing (Metal),"Fritsch, Sauer and Conn","Romaguera, Goldner and Crooks",Lindgren-Marquardt,1252634576,Flo,blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae,,rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis,2022-06-25,2711.89,Location,"","","","","McGlynn, Hagenes and Bruen" -"Powlowski, Monahan and Reichel",Bulldozer,HNY-7340937,Ornamental Railings,"Botsford, Boyle and Herzog",Watsica LLC,Roberts-Anderson,7294510907,Ward,sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in,2828XJ28E95,erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales,2022-07-24,1833.42,User,mhasley21,Marion,Hasley,mhasley21@clickbank.net,"" -Harris LLC,Grader,JHN-0598394,Curb & Gutter,"Heaney, Altenwerth and Emmerich","Fritsch, Sauer and Conn",Waters LLC,0709494209,Nial,orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur,2534KR53Y73,vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere,2023-04-11,3263.79,User,mbaudy2b,Mickie,Baudy,mbaudy2b@intel.com,"" -Boyer-Okuneva,Backhoe,GEX-7216431,Prefabricated Aluminum Metal Canopies,Pollich LLC,"Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",4030738107,Jere,velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat,7055KO63K62,,2022-08-25,2448.17,Location,"","","","",Lynch and Sons -Ebert-Reilly,Scraper,DWM-3752227,Construction Clean and Final Clean,Watsica LLC,Berge Inc,"Mills, Gleichner and Schamberger",9381884673,Nobe,sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit,0535QM19F37,rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper,2022-12-23,2721.12,User,sbucknall28,Saidee,Bucknall,sbucknall28@cbsnews.com,"" -"Mitchell, Ward and Hettinger",Dragline,OLM-0226994,Structural and Misc Steel (Fabrication),Walker-Towne,"Kutch, Johnson and Olson",Frami and Sons,7504201033,Nial,curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non,7520IT24T23,amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique,2023-05-14,3178.59,Location,"","","","","O'Conner, Nitzsche and Aufderhar" -Prosacco-Ledner,Scraper,LYO-8134459,Roofing (Asphalt),Mosciski Inc,Mosciski Inc,Waters LLC,9994825740,Bale,molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci,2427CL03K39,,2023-05-07,3322.34,,"","","","","" -Harvey and Sons,Compactor,HLK-1645158,Structural and Misc Steel (Fabrication),"Romaguera, Goldner and Crooks",Mosciski Inc,Treutel Inc,8322817966,Karry,egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend,1075JB25N09,,2022-09-03,1823.95,User,flewins10,Filip,Lewins,flewins10@amazonaws.com,"" -"Lebsack, Roob and Streich",Scraper,AZT-4280937,Marlite Panels (FED),Okuneva Group,"Upton, Feil and Jast",Lynch and Sons,8146943004,Tommy,,9621CJ23Y35,,2022-12-17,1614.68,Location,"","","","",Roberts-Anderson -Abbott-Nikolaus,Dump Truck,IAB-5332824,Structural & Misc Steel Erection,"Upton, Feil and Jast","Romaguera, Goldner and Crooks",Lynch and Sons,3906729636,Rolland,dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce,2315CN41G71,,2022-11-02,1444.69,Location,"","","","",Fritsch-Abernathy -Nolan-Wisoky,Trencher,OID-4455781,HVAC,Ankunding-Ledner,Abernathy-Stamm,Schinner Group,8981616853,Kizzee,maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus,6080UE59E09,,2022-09-07,3637.94,User,bmctrustrie1c,Balduin,McTrustrie,bmctrustrie1c@free.fr,"" -"Miller, Morissette and Kihn",Trencher,UQY-2172679,Curb & Gutter,"Legros, Paucek and Collier",Mosciski Inc,"O'Conner, Nitzsche and Aufderhar",3983411009,Jeana,,5505YF23M46,,2023-02-10,4253.88,User,kkubanek1t,Kalinda,Kubanek,kkubanek1t@umich.edu,"" -Erdman and Sons,Grader,HRI-2262410,Granite Surfaces,Berge Inc,Walker-Towne,Ledner-Barrows,9311141848,Sumner,,8673QP30R80,,2022-06-20,1784.66,Location,"","","","",Lynch and Sons -"Rogahn, Cormier and Ruecker",Bulldozer,BBK-5960598,Drywall & Acoustical (MOB),Abernathy-Stamm,"Heaney, Altenwerth and Emmerich",Frami and Sons,5157837617,Mabelle,nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium,9088XV67Q94,,2022-12-15,668.43,Location,"","","","",Russel Group -Pagac-Feeney,Compactor,RRO-8557470,Structural and Misc Steel (Fabrication),Mosciski Inc,Okuneva Group,Russel Group,2776102414,Illa,quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis,,,2022-12-27,3391.42,,"","","","","" -Toy-Daniel,Compactor,SUQ-5159067,Fire Protection,"Botsford, Boyle and Herzog",Berge Inc,"Jenkins, Goldner and Cruickshank",0313117719,Robby,,2830MI42B80,,2022-06-24,4402.74,User,hbucknall29,Hillie,Bucknall,hbucknall29@webnode.com,"" -"Rau, O'Kon and Predovic",Grader,KUW-8075173,Hard Tile & Stone,"Kunde, Doyle and Kozey",Abernathy-Stamm,"Torp, Kautzer and Rodriguez",3235657209,Tommy,,8752PQ41C20,,2023-04-16,2428.95,User,wtomblin2r,Waiter,Tomblin,wtomblin2r@cisco.com,"" -"Rodriguez, Rippin and Maggio",Backhoe,KGD-2478881,Epoxy Flooring,"Upton, Feil and Jast",Krajcik LLC,Roberts-Anderson,9968615213,Sumner,,,interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor,2022-11-14,2353.49,,"","","","","" -Collins-Langworth,Backhoe,NLM-1912680,Glass & Glazing,Walker-Towne,"Kunde, Doyle and Kozey","Nitzsche, Gislason and Douglas",2439578863,Paloma,quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum,4241FY30X65,aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate justo,2022-05-29,413.99,User,esargersonc,El,Sargerson,esargersonc@moonfruit.com,"" -"Tillman, Rippin and Robel",Backhoe,HKF-2889015,Framing (Steel),Rippin-Schiller,"Upton, Feil and Jast","Nitzsche, Gislason and Douglas",1249482779,Mabelle,,7737HG97C81,nibh in hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante,2022-09-27,174.79,User,lloosely2i,Leia,Loosely,lloosely2i@ebay.co.uk,"" -Bogisich-Gerhold,Excavator,HIN-5577764,HVAC,Ankunding-Ledner,"Kutch, Johnson and Olson",Treutel Inc,5300366684,Robby,,,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia,2023-04-09,4662.35,User,bogg1s,Blakeley,Ogg,bogg1s@examiner.com,"" -DuBuque-Jones,Excavator,RSF-1042461,Roofing (Asphalt),Abernathy-Stamm,"Stokes, Daniel and Johnson",Frami and Sons,8526994711,Jere,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean,,,2022-11-25,2356.93,,"","","","","" -Runolfsson-McCullough,Scraper,DAK-0644656,Framing (Wood),Mosciski Inc,"Legros, Paucek and Collier",Lynch and Sons,5644034134,Mabelle,,3664GP06B10,molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu,2022-10-05,3912.18,User,mnollet1,Maury,Nollet,mnollet1@ow.ly,"" -Hilpert-Vandervort,Crawler,XWJ-0341580,Roofing (Asphalt),Ankunding-Ledner,Abernathy-Stamm,"Mills, Gleichner and Schamberger",0635168064,Chase,,9438FZ85N89,dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien,2022-08-28,4426.46,User,nhumburton1m,Noelle,Humburton,nhumburton1m@ow.ly,"" -"Schmitt, Kuhlman and Gusikowski",Excavator,UQF-6263661,Electrical,"Upton, Feil and Jast","Kunde, Doyle and Kozey",Fritsch-Abernathy,6394370767,Ward,,0642OF31I56,adipiscing elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis,2022-12-04,3182.1,Location,"","","","",Powlowski LLC -Klein Inc,Excavator,IOC-4424021,Elevator,Watsica LLC,Pacocha-Goodwin,Treutel Inc,9408196701,Beverly,,4124VH09F17,,2022-09-12,3321.66,,"","","","","" -"Runte, Feeney and Lueilwitz",Compactor,TBU-9704770,Drilled Shafts,"Kunde, Doyle and Kozey",Rippin-Schiller,"Nitzsche, Gislason and Douglas",5300662174,Jere,consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in,,,2022-07-19,1573.26,User,tdoumerquef,Tobe,Doumerque,tdoumerquef@twitpic.com,"" -Reinger LLC,Dump Truck,TTW-1274810,Masonry,"Heaney, Altenwerth and Emmerich",Abernathy-Stamm,"Torp, Kautzer and Rodriguez",6161734867,Karry,,,,2023-03-05,2677.79,User,rtitterrell0,Rora,Titterrell,rtitterrell0@prnewswire.com,"" -"Breitenberg, Crooks and Goldner",Grader,RIU-2781610,Drilled Shafts,Pollich LLC,"Kunde, Doyle and Kozey","Nitzsche, Gislason and Douglas",1424455064,Nial,,6982GT82L84,,2023-03-22,4085.21,User,otaverner7,Odey,Taverner,otaverner7@discuz.net,"" -"Rolfson, Pollich and Kertzmann",Compactor,BFO-5970934,"Doors, Frames & Hardware","Romaguera, Goldner and Crooks",Pacocha-Goodwin,Pacocha-Kiehn,9193131620,Robby,donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium,3185PP20K43,gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras,2022-05-31,1819.48,,"","","","","" -Luettgen Inc,Grader,QEY-4583797,Painting & Vinyl Wall Covering,"Upton, Feil and Jast","Kunde, Doyle and Kozey","O'Conner, Nitzsche and Aufderhar",6610625137,Debbi,,4971UO73K02,,2022-07-07,4635.63,User,glippitt22,Gregorius,Lippitt,glippitt22@yandex.ru,"" -"Hermann, Cremin and Crona",Dragline,OPB-2390560,Ornamental Railings,"Fritsch, Sauer and Conn",Ankunding-Ledner,Treutel Inc,7358467570,Beverly,,7467PI87D35,nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros,2023-04-09,2089.47,User,dhailwood18,Duffy,Hailwood,dhailwood18@ask.com,"" -Cruickshank-Blanda,Grader,CUD-4868409,Electrical and Fire Alarm,Watsica LLC,Ankunding-Ledner,Treutel Inc,7859007380,Kizzee,,0709PI47U54,,2023-01-19,484.12,,"","","","","" -"Reilly, Yundt and Keeling",Bulldozer,ORL-2640580,RF Shielding,"Legros, Paucek and Collier",Berge Inc,Schinner Group,8731356307,Bondon,,6643DS43O77,,2023-03-29,254.99,,"","","","","" -"Franecki, Nolan and Swift",Trencher,GFV-9868944,RF Shielding,"Kunde, Doyle and Kozey","Kunde, Doyle and Kozey",Lynch and Sons,8841110804,Karry,,1465WJ98O96,,2022-08-27,1191.44,User,acharplinge,Andreana,Charpling,acharplinge@dell.com,"" -"Lind, Reinger and Grant",Dump Truck,KFR-9464842,Epoxy Flooring,"Botsford, Boyle and Herzog","Upton, Feil and Jast",Erdman-West,5382312507,Nial,,7440CN27Q04,amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in,2022-08-19,430.07,,"","","","","" -Quigley Inc,Dragline,XQS-0788077,Casework,Ankunding-Ledner,"Fritsch, Sauer and Conn",Lynch and Sons,5163086523,Rolland,ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel,1663OK46D26,,2022-12-25,1308.3,User,ahugonnet1f,Alvis,Hugonnet,ahugonnet1f@vinaora.com,"" -Hudson-Graham,Dragline,APW-0602098,Waterproofing & Caulking,"Romaguera, Goldner and Crooks",Berge Inc,Waters LLC,1473905199,Mabelle,urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat,,,2022-08-18,2037.76,Location,"","","","","Mills, Gleichner and Schamberger" -"Schmeler, Dietrich and Buckridge",Crawler,RFX-9706298,Drilled Shafts,Krajcik LLC,Krajcik LLC,Roberts-Anderson,2238031407,Robby,ornare consequat lectus in est risus auctor sed tristique in,7387DP63Q90,tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh,2023-02-13,3395.33,Location,"","","","",Erdman-West -"Jast, Cassin and Hane",Grader,EQQ-2912281,Wall Protection,"Fritsch, Sauer and Conn",Krajcik LLC,Lynch and Sons,6313951394,Mabelle,sagittis sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum,4541TF76I78,tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum primis,2022-05-25,4787.17,Location,"","","","",Schinner Group -Bergstrom-Block,Excavator,IEG-8406586,Masonry,Mosciski Inc,Mosciski Inc,Roberts-Anderson,6040029438,Bondon,cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit,4324BJ82Z56,,2022-09-09,2371.3,Location,"","","","",Powlowski LLC -"Powlowski, Lowe and Streich",Dragline,ZCB-5287486,Roofing (Asphalt),Watsica LLC,Watsica LLC,"McGlynn, Hagenes and Bruen",6324836270,Debbi,,7369JZ83X28,pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim,2022-05-30,3261.46,User,ohynson1y,Olivia,Hynson,ohynson1y@ask.com,"" -"Purdy, Leannon and Boyer",Crawler,VVZ-9417930,Retaining Wall and Brick Pavers,"Botsford, Boyle and Herzog","Legros, Paucek and Collier",Schinner Group,1391767652,Bale,,,nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla,2022-06-22,807.74,User,vgunning1z,Vince,Gunning,vgunning1z@arstechnica.com,"" -"Schowalter, Grady and Stracke",Backhoe,LHC-9749327,Fire Protection,Walker-Towne,Ankunding-Ledner,Schinner Group,3460146131,Flo,,4031MX62O09,aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in,2023-02-14,2051.25,User,akneath13,Arron,Kneath,akneath13@sphinn.com,"" -Schulist-Marks,Dragline,HUM-7311338,Sitework & Site Utilities,Berge Inc,Okuneva Group,Roberts-Anderson,4146242985,Bondon,,0468AG53C12,,2022-05-20,2788.78,Location,"","","","","O'Conner, Nitzsche and Aufderhar" -Cole-Feeney,Trencher,LHK-1308041,"Temp Fencing, Decorative Fencing and Gates","Upton, Feil and Jast","Stokes, Daniel and Johnson",Lindgren-Marquardt,7648736489,Robby,,,massa donec dapibus duis at velit eu est congue elementum in hac habitasse,2022-11-02,2755.2,User,dkent23,Debbi,Kent,dkent23@mtv.com,"" -O'Hara Inc,Backhoe,CIC-1996687,"Temp Fencing, Decorative Fencing and Gates","Romaguera, Goldner and Crooks","Romaguera, Goldner and Crooks",Frami and Sons,5059462108,Bale,,5053ZY27R14,placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi,2023-01-17,2100.84,User,kephson2f,Kimberly,Ephson,kephson2f@sina.com.cn,"" -Rolfson-Kulas,Dump Truck,YBW-8150273,EIFS,Krajcik LLC,"Upton, Feil and Jast","Wilkinson, Waters and Kerluke",5490351215,Mabelle,,9162YL93W88,ullamcorper augue a suscipit nulla elit ac nulla sed vel enim,2023-01-14,877.57,Location,"","","","",Fritsch-Abernathy -Lehner-Effertz,Excavator,OFB-4987804,Casework,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson",Waters LLC,2712763720,Robby,,0270YA01J87,sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus,2022-09-03,3428.56,User,acansdell14,Adela,Cansdell,acansdell14@cbc.ca,"" -"Dietrich, Von and Mante",Excavator,RXG-3730482,Elevator,Rippin-Schiller,"Botsford, Boyle and Herzog",Lindgren-Marquardt,0868073143,Jere,,8281VU07P52,,2023-01-25,713.89,User,pwippers,Peirce,Wipper,pwippers@liveinternet.ru,"" -Predovic-Roob,Bulldozer,ATA-2489950,EIFS,"Botsford, Boyle and Herzog","Romaguera, Goldner and Crooks",Frami and Sons,8905034852,Chase,platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut,,,2023-02-07,1138.85,User,odella1g,Osgood,Della,odella1g@hc360.com,"" -Adams-Larkin,Grader,SFI-7496689,"Doors, Frames & Hardware","Kutch, Johnson and Olson",Abernathy-Stamm,Lynch and Sons,3092970929,Flo,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo,0649BJ66S09,at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac,2022-09-10,278.59,,"","","","","" -"Beier, Grant and Thiel",Grader,GSX-1706311,Masonry,"Heaney, Altenwerth and Emmerich",Mosciski Inc,Ledner-Barrows,5141273631,Mabelle,convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget,6967FJ73D06,sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam,2022-07-18,1732.28,Location,"","","","",Fritsch-Abernathy -Armstrong Group,Dump Truck,LXV-5626274,Plumbing & Medical Gas,Rippin-Schiller,Ankunding-Ledner,"Wilkinson, Waters and Kerluke",3202695143,Jeana,,5970GY37L93,purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus,2022-10-11,1981.53,User,cwinfreyl,Casper,Winfrey,cwinfreyl@adobe.com,"" -Rodriguez-Schultz,Excavator,BRF-0336967,Structural and Misc Steel (Fabrication),Abernathy-Stamm,"Botsford, Boyle and Herzog","Torp, Kautzer and Rodriguez",8211664431,Nial,nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum,,ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi,2022-12-30,4924.25,User,mgooday1q,Mariellen,Gooday,mgooday1q@alexa.com,"" -"Huels, Satterfield and Kautzer",Backhoe,CVH-2946076,Painting & Vinyl Wall Covering,Ankunding-Ledner,"Upton, Feil and Jast",Frami and Sons,0835287084,Paloma,justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus,,,2022-07-15,2406.57,,"","","","","" -"Lueilwitz, Herzog and Runte",Skid-Steer,OTH-2531332,HVAC,Mosciski Inc,Ankunding-Ledner,Ledner-Barrows,7517530461,Paloma,,1256WA18C94,,2023-01-14,531.46,,"","","","","" -Funk LLC,Grader,ZMB-4808007,Fire Sprinkler System,"Upton, Feil and Jast",Ankunding-Ledner,"Wilkinson, Waters and Kerluke",4467089765,Jeana,,0663SC93W29,quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu,2022-07-19,4741.85,,"","","","","" -"Doyle, Kulas and Mosciski",Dump Truck,XES-9665196,Masonry & Precast,"Botsford, Boyle and Herzog",Pacocha-Goodwin,Ledner-Barrows,4220776022,Sumner,,0099EC66L98,in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis,2022-08-06,276.15,,"","","","","" -"Robel, Johnston and Crist",Excavator,KUJ-7232314,Construction Clean and Final Clean,Ankunding-Ledner,"Romaguera, Goldner and Crooks",Fritsch-Abernathy,5757955088,Paloma,,4270AX61I99,,2022-09-21,3749.65,Location,"","","","","Wilkinson, Waters and Kerluke" -Wiza-Mante,Grader,UNN-0016298,Roofing (Metal),Watsica LLC,Ankunding-Ledner,Frami and Sons,6746036070,Tommy,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie,9540RE21E79,est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat,2022-08-08,4600.22,Location,"","","","",Roberts-Anderson -Jacobi Group,Bulldozer,JRP-9799959,Roofing (Metal),Rippin-Schiller,"Stokes, Daniel and Johnson",Russel Group,1090875903,Nobe,,5159PT28N59,,2023-05-02,3255.63,Location,"","","","",Pacocha-Kiehn -Murazik-Cormier,Crawler,GTI-0627775,Marlite Panels (FED),"Kutch, Johnson and Olson","Fritsch, Sauer and Conn",Powlowski LLC,7650476155,Jeana,,9055CM63Z89,,2022-06-24,1295.04,Location,"","","","","Jenkins, Goldner and Cruickshank" -"Simonis, Schulist and Volkman",Compactor,WEL-4481760,RF Shielding,Mosciski Inc,Rippin-Schiller,Powlowski LLC,6908782578,Bale,curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat,3712NE31P89,,2022-08-02,2038.79,User,ksennett6,Katerina,Sennett,ksennett6@ibm.com,"" -Conroy-Abshire,Grader,VKB-3777629,Masonry & Precast,Krajcik LLC,Pollich LLC,Erdman-West,6651233484,Illa,duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec,,in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris,2023-04-28,4988.38,,"","","","","" -Mueller and Sons,Bulldozer,YFK-3812221,Waterproofing & Caulking,Rippin-Schiller,Berge Inc,"Mills, Gleichner and Schamberger",3679743009,Illa,sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum,2077OM84Z52,,2022-09-18,3085.39,User,moscullya,Maurice,O'Scully,moscullya@engadget.com,"" -"Turner, Schulist and Hodkiewicz",Compactor,ENN-1432777,Ornamental Railings,"Romaguera, Goldner and Crooks","Kunde, Doyle and Kozey","Mills, Gleichner and Schamberger",7184133416,Robby,,6321OY95W96,,2022-09-25,4517.54,User,lloosely2i,Leia,Loosely,lloosely2i@ebay.co.uk,"" -Dicki-Hartmann,Skid-Steer,JNS-3537485,Epoxy Flooring,Pacocha-Goodwin,Mosciski Inc,Pacocha-Kiehn,2137927703,Mabelle,erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam,2809IW22A70,consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in,2022-09-12,4293.51,,"","","","","" -Schoen and Sons,Dump Truck,RAA-2200532,Soft Flooring and Base,Walker-Towne,"Upton, Feil and Jast",Waters LLC,5479047972,Beverly,maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra,,,2023-01-19,1760.88,User,rsapeyb,Raquela,Sapey,rsapeyb@furl.net,"" -"Rolfson, Champlin and Cole",Bulldozer,GDB-9523081,EIFS,"Heaney, Altenwerth and Emmerich","Upton, Feil and Jast",Frami and Sons,6544577007,Rolland,,,,2022-11-29,2769.18,,"","","","","" -"Maggio, Wolff and Friesen",Excavator,IOH-2003935,Structural and Misc Steel (Fabrication),Okuneva Group,Abernathy-Stamm,Lindgren-Marquardt,1508089431,Flo,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu,2753UJ02X37,porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus,2022-08-05,3709.55,,"","","","","" -Brekke-Hoeger,Backhoe,UPX-0286727,Plumbing & Medical Gas,"Upton, Feil and Jast",Rippin-Schiller,Lynch and Sons,6217027186,Bondon,,6387KP57B15,vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec,2023-03-21,4138.24,,"","","","","" -Konopelski Inc,Dump Truck,NZE-5809675,Casework,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson",Erdman-West,8486277027,Mabelle,dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus,4732FS58G78,,2023-05-11,114.69,,"","","","","" -"Nolan, Kertzmann and Rippin",Bulldozer,YXY-3875893,RF Shielding,"Romaguera, Goldner and Crooks",Abernathy-Stamm,Frami and Sons,7979699903,Bondon,habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt,4378XF72E06,,2023-04-29,1781.53,,"","","","","" -Stroman and Sons,Skid-Steer,BDD-0117730,"Temp Fencing, Decorative Fencing and Gates",Okuneva Group,Mosciski Inc,Frami and Sons,1053653041,Karry,,0484GG78J91,odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat,2022-09-12,4854.6,User,mbaudy2b,Mickie,Baudy,mbaudy2b@intel.com,"" -Howe-Volkman,Crawler,YMB-2902862,Masonry & Precast,"Kutch, Johnson and Olson",Krajcik LLC,Roberts-Anderson,0269899359,Nial,,2685ZN23G49,,2022-08-27,133.33,User,jdriffill2,Jeanne,Driffill,jdriffill2@google.fr,"" -Heathcote and Sons,Skid-Steer,LWA-2897309,Exterior Signage,"Heaney, Altenwerth and Emmerich",Okuneva Group,Schinner Group,1268560442,Paloma,,8414GM02P21,,2022-12-27,4524.09,,"","","","","" -Borer-Aufderhar,Scraper,OOY-9898294,Electrical,Pacocha-Goodwin,Berge Inc,"Nitzsche, Gislason and Douglas",4556315148,Karry,,0187NT56E91,,2022-08-21,390.85,,"","","","","" -"Hauck, Bogisich and King",Dragline,XHB-6046299,Sitework & Site Utilities,"Fritsch, Sauer and Conn","Romaguera, Goldner and Crooks","Jenkins, Goldner and Cruickshank",9343736607,Debbi,aliquam erat volutpat in congue etiam justo etiam pretium iaculis,8559BT54S22,in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate,2022-05-25,1439.23,Location,"","","","",Ledner-Barrows -"Upton, Luettgen and Mayer",Grader,JVB-8844609,Structural & Misc Steel Erection,Pollich LLC,"Kunde, Doyle and Kozey",Treutel Inc,6715000024,Karry,nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper,0088HF56H55,,2022-12-17,4849.95,Location,"","","","","McGlynn, Hagenes and Bruen" -Fadel Inc,Skid-Steer,VAA-1096481,Hard Tile & Stone,Mosciski Inc,Krajcik LLC,Schinner Group,2384395201,Nobe,parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id,,tortor duis mattis egestas metus aenean fermentum donec ut mauris,2022-07-15,1190.44,User,tphysick3,Tiphanie,Physick,tphysick3@umn.edu,"" -Dickens and Sons,Skid-Steer,OAC-1765208,Ornamental Railings,Ankunding-Ledner,Rippin-Schiller,Lindgren-Marquardt,2119388227,Ward,non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta,9895LS02S32,,2022-09-03,1868.26,Location,"","","","","Jenkins, Goldner and Cruickshank" -Cassin Group,Bulldozer,CPK-3286546,Structural & Misc Steel Erection,Berge Inc,"Botsford, Boyle and Herzog",Frami and Sons,6044719518,Chase,,5420GG86P87,,2022-07-22,1463.24,,"","","","","" -"Wolf, Wiza and Pacocha",Excavator,ZHU-6798344,Electrical,"Kutch, Johnson and Olson",Abernathy-Stamm,Frami and Sons,6111751517,Tommy,odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat,5462TF70C36,amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim,2023-02-26,1268.92,User,pshirleyi,Pen,Shirley,pshirleyi@theguardian.com,"" -"Davis, Klein and Kiehn",Backhoe,WHR-8533403,Plumbing & Medical Gas,"Botsford, Boyle and Herzog",Mosciski Inc,Treutel Inc,7632050235,Tommy,,5478JY13D60,,2022-09-11,4908.48,Location,"","","","",Pacocha-Kiehn -"Gorczany, Lindgren and Hand",Compactor,FAC-2072179,Framing (Steel),Berge Inc,"Legros, Paucek and Collier",Ledner-Barrows,8755015421,Rolland,,2138TK23O47,massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede,2022-09-19,2249.9,User,kgrealish16,Krystalle,Grealish,kgrealish16@sciencedirect.com,"" -Romaguera-Ondricka,Grader,KON-1821691,Curb & Gutter,"Kutch, Johnson and Olson",Okuneva Group,"O'Conner, Nitzsche and Aufderhar",4908345668,Chase,,5414US21B26,pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti,2022-11-14,1545.41,User,ewarriner2o,Ellene,Warriner,ewarriner2o@wikimedia.org,"" -"Anderson, Goldner and Bailey",Compactor,NDF-4324420,Roofing (Metal),Mosciski Inc,"Kunde, Doyle and Kozey","McGlynn, Hagenes and Bruen",3212882737,Nial,,0754DG10B38,dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo,2023-05-15,4423.19,,"","","","","" -Mraz-Windler,Trencher,DLM-4710838,Plumbing & Medical Gas,"Kunde, Doyle and Kozey",Walker-Towne,"Jenkins, Goldner and Cruickshank",1315337182,Debbi,,2430PB13D95,,2022-07-11,2708.18,User,fmcguiness1k,Fredrika,McGuiness,fmcguiness1k@woothemes.com,"" -Volkman Group,Compactor,MLJ-9313486,Electrical and Fire Alarm,"Romaguera, Goldner and Crooks","Botsford, Boyle and Herzog",Fritsch-Abernathy,2738670737,Illa,,,,2022-10-09,1404.85,Location,"","","","",Pacocha-Kiehn -Schulist-Kulas,Compactor,XBE-4236649,Drywall & Acoustical (MOB),Abernathy-Stamm,Berge Inc,"Nitzsche, Gislason and Douglas",7811285882,Kizzee,,2875KP71D22,,2022-11-21,271.5,Location,"","","","","Wilkinson, Waters and Kerluke" -Hammes Inc,Crawler,NRV-3022526,Epoxy Flooring,Pollich LLC,"Upton, Feil and Jast",Powlowski LLC,8015369811,Ward,,6485BK61G70,facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros,2023-01-25,1757.26,,"","","","","" -"Kemmer, Beier and Hegmann",Skid-Steer,EXG-6125653,Glass & Glazing,"Upton, Feil and Jast",Pacocha-Goodwin,Erdman-West,9025824302,Rolland,,8500KT88G80,,2022-09-27,3697.22,User,aweemsj,Antoinette,Weems,aweemsj@who.int,"" -"Padberg, Kozey and Morar",Compactor,XYK-7848357,RF Shielding,Berge Inc,Abernathy-Stamm,Russel Group,2116700211,Robby,,,,2022-11-05,4059.57,Location,"","","","",Lindgren-Marquardt -Pfannerstill and Sons,Skid-Steer,GZW-6229249,Waterproofing & Caulking,Pollich LLC,"Kutch, Johnson and Olson","Torp, Kautzer and Rodriguez",4909263041,Debbi,,,lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in,2022-08-11,1697.74,,"","","","","" -Kunde-Hermiston,Compactor,VZD-7424699,Marlite Panels (FED),Mosciski Inc,"Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",5611381559,Nial,,8991VD43T11,cras mi pede malesuada in imperdiet et commodo vulputate justo in blandit,2022-09-17,4957.55,Location,"","","","",Powlowski LLC -"Treutel, Schmidt and Koss",Compactor,RZO-4616134,Retaining Wall and Brick Pavers,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson","O'Conner, Nitzsche and Aufderhar",0833202169,Nial,,6036SV40U93,,2023-04-18,3218.61,User,bvandalen5,Barbabas,Van Dalen,bvandalen5@smugmug.com,"" -Nader Inc,Scraper,NXG-1697279,RF Shielding,"Botsford, Boyle and Herzog","Fritsch, Sauer and Conn",Waters LLC,7094568704,Robby,,7964JY42Z54,,2022-08-26,3628.55,Location,"","","","","McGlynn, Hagenes and Bruen" -"Cremin, Conroy and Kuphal",Crawler,OGN-4843235,Glass & Glazing,Okuneva Group,"Heaney, Altenwerth and Emmerich",Pacocha-Kiehn,6424257173,Beverly,,2880LU86I74,sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci,2022-09-22,4163.07,,"","","","","" -"Douglas, Considine and Gerhold",Backhoe,BXI-1028705,Structural and Misc Steel (Fabrication),"Romaguera, Goldner and Crooks",Ankunding-Ledner,Roberts-Anderson,5388101251,Karry,,,,2023-02-20,976.02,,"","","","","" -Emard Group,Scraper,MWK-5482358,Ornamental Railings,Mosciski Inc,"Heaney, Altenwerth and Emmerich","Mills, Gleichner and Schamberger",5509346090,Nobe,,8183RH74P65,donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum,2022-06-28,1441.26,Location,"","","","",Fritsch-Abernathy -Runolfsdottir-Schulist,Compactor,ZSS-2076422,EIFS,Rippin-Schiller,"Upton, Feil and Jast",Ledner-Barrows,4163465013,Karry,dui luctus rutrum nulla tellus in sagittis dui vel nisl,,,2022-11-11,3688.09,User,rrappa27,Rabi,Rappa,rrappa27@cyberchimps.com,"" -Stoltenberg-Cormier,Trencher,XNO-4048888,Roofing (Metal),Watsica LLC,Pollich LLC,Russel Group,3593822457,Robby,morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed,1583JR98T50,,2022-12-04,1780.96,,"","","","","" -"Hand, Bogan and Cassin",Skid-Steer,SWP-7721219,Roofing (Metal),"Kunde, Doyle and Kozey",Krajcik LLC,Lindgren-Marquardt,1185234640,Nial,,4091TS60T07,,2023-05-09,1528.53,User,mskellingtonu,Marrilee,Skellington,mskellingtonu@abc.net.au,"" -Kling-Homenick,Dragline,HGX-6549969,Asphalt Paving,Walker-Towne,"Fritsch, Sauer and Conn","Jenkins, Goldner and Cruickshank",2726185990,Bale,,9573TJ14G71,,2023-01-31,4930.43,User,asyne1a,Angela,Syne,asyne1a@icio.us,"" -"Kilback, Towne and Stehr",Skid-Steer,ZTD-2850379,Drilled Shafts,Krajcik LLC,"Stokes, Daniel and Johnson","O'Conner, Nitzsche and Aufderhar",7192145952,Paloma,,,ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci,2022-12-25,2947.93,,"","","","","" -Harvey-Moore,Dump Truck,QKC-1710236,Fire Protection,Berge Inc,Okuneva Group,Lynch and Sons,7337073255,Kizzee,,7010OW07S33,,2023-02-01,1617.55,Location,"","","","",Ledner-Barrows -Maggio-Hoppe,Excavator,LHL-3384861,Termite Control,Pacocha-Goodwin,Abernathy-Stamm,Lindgren-Marquardt,5642326493,Ward,amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum,0489GD18P85,,2022-07-29,4682.12,User,nscroggs2d,Naoma,Scroggs,nscroggs2d@yelp.com,"" -Muller LLC,Skid-Steer,VYY-4278214,Rebar & Wire Mesh Install,"Kunde, Doyle and Kozey",Okuneva Group,Schinner Group,0946092591,Paloma,,,felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis,2022-09-07,4400.17,Location,"","","","",Lynch and Sons -Kerluke Inc,Backhoe,HRP-6178185,Retaining Wall and Brick Pavers,Mosciski Inc,Berge Inc,Treutel Inc,6532044804,Nial,,,,2022-09-07,1345.45,Location,"","","","",Lindgren-Marquardt -"Harvey, Schulist and Kemmer",Trencher,QND-9017996,Painting & Vinyl Wall Covering,Abernathy-Stamm,Pollich LLC,Fritsch-Abernathy,4977996097,Paloma,est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce,2497DB20W09,habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo,2023-01-27,317.08,,"","","","","" -Schimmel and Sons,Dragline,DAX-0085828,Drywall & Acoustical (MOB),Okuneva Group,Watsica LLC,"Jenkins, Goldner and Cruickshank",7739403397,Flo,,,,2022-12-28,4521.28,User,pwippers,Peirce,Wipper,pwippers@liveinternet.ru,"" -Grimes-Lowe,Dragline,VED-7099927,Masonry,Rippin-Schiller,"Stokes, Daniel and Johnson",Lynch and Sons,3351670325,Karry,,,ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at,2023-02-04,2620.69,,"","","","","" -Krajcik-Cruickshank,Bulldozer,ZHU-0211288,Painting & Vinyl Wall Covering,"Fritsch, Sauer and Conn",Pacocha-Goodwin,Lindgren-Marquardt,5042401893,Paloma,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo,0511VF33D78,proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem,2022-09-27,923.99,,"","","","","" -Donnelly-Maggio,Compactor,HFG-7624506,Electrical and Fire Alarm,Pollich LLC,"Fritsch, Sauer and Conn","Torp, Kautzer and Rodriguez",3157087051,Illa,libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim,5980WY24F50,tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non,2022-12-05,4670.18,Location,"","","","","Jenkins, Goldner and Cruickshank" -Pfeffer-Zieme,Trencher,AZY-1682920,Wall Protection,Abernathy-Stamm,Mosciski Inc,"Torp, Kautzer and Rodriguez",5186444169,Beverly,consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim,6620HZ14T83,aliquam convallis nunc proin at turpis a pede posuere nonummy,2022-06-27,1674.34,User,cstamper1h,Celie,Stamper,cstamper1h@usda.gov,"" -"Cormier, Glover and Hickle",Compactor,EYP-3561396,Termite Control,Rippin-Schiller,"Kunde, Doyle and Kozey","Torp, Kautzer and Rodriguez",4964027369,Paloma,quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus,4352FH58S52,,2022-11-21,136.12,,"","","","","" -Rau Group,Skid-Steer,DYU-7769483,Marlite Panels (FED),Krajcik LLC,Krajcik LLC,Fritsch-Abernathy,2457705388,Sumner,,2286KF82X10,,2023-05-14,1147.8,User,flewins10,Filip,Lewins,flewins10@amazonaws.com,"" -"Shanahan, Bradtke and Rowe",Backhoe,JYC-0713874,Hard Tile & Stone,"Upton, Feil and Jast",Ankunding-Ledner,Waters LLC,0372144769,Rolland,porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in,7775EM65V29,phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu,2022-05-27,1691.36,,"","","","","" -Bogisich LLC,Bulldozer,MIQ-6169091,Soft Flooring and Base,"Romaguera, Goldner and Crooks",Mosciski Inc,Powlowski LLC,8984702329,Ward,,,justo eu massa donec dapibus duis at velit eu est,2022-10-15,1578.57,User,ndubery1e,Neda,Dubery,ndubery1e@fotki.com,"" -Ondricka-Schuster,Dump Truck,KEN-1184794,"Doors, Frames & Hardware",Walker-Towne,Watsica LLC,Pacocha-Kiehn,4745674969,Mabelle,ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat,,,2022-12-10,4298.98,,"","","","","" -Mayer-Abernathy,Scraper,ZMD-6316371,Rebar & Wire Mesh Install,"Stokes, Daniel and Johnson",Abernathy-Stamm,Powlowski LLC,2526471701,Flo,,1918JS43H32,,2023-03-24,1171.9,User,gbassingdenm,Godwin,Bassingden,gbassingdenm@dedecms.com,"" -Towne-Adams,Scraper,QCU-6826093,Prefabricated Aluminum Metal Canopies,Abernathy-Stamm,Rippin-Schiller,Waters LLC,4899233400,Debbi,arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst,6487ZI24B86,nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas,2023-02-22,602.41,Location,"","","","","McGlynn, Hagenes and Bruen" -Kunde Group,Compactor,ITE-9907827,Sitework & Site Utilities,Mosciski Inc,"Romaguera, Goldner and Crooks",Treutel Inc,4671828697,Kizzee,,3508XP22O98,sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est,2023-02-18,3119.76,Location,"","","","",Fritsch-Abernathy -Welch Group,Compactor,GXW-3680693,Drywall & Acoustical (MOB),Okuneva Group,Watsica LLC,"Nitzsche, Gislason and Douglas",4413339149,Karry,mi integer ac neque duis bibendum morbi non quam nec,,,2023-02-16,1570.25,User,ewhitebrook1r,Emmalyn,Whitebrook,ewhitebrook1r@webnode.com,"" -"Zieme, Metz and Schamberger",Grader,GDQ-7108837,Masonry,"Heaney, Altenwerth and Emmerich","Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",8560447476,Chase,,4743VU30T06,nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non,2022-08-09,1530.46,User,pwoollons2q,Pyotr,Woollons,pwoollons2q@nps.gov,"" -"Kuhlman, Walker and Denesik",Dragline,QZO-1370026,Asphalt Paving,Krajcik LLC,"Botsford, Boyle and Herzog",Erdman-West,5748443707,Mabelle,,2693SH27I09,accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis,2022-08-23,2694.26,,"","","","","" -Cormier Inc,Bulldozer,AQW-7617737,RF Shielding,"Heaney, Altenwerth and Emmerich",Watsica LLC,"Mills, Gleichner and Schamberger",1502376915,Rolland,,8461WN86M04,sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis,2023-03-04,1055.33,,"","","","","" -Beier LLC,Dragline,NDV-6470913,Masonry,Mosciski Inc,"Kutch, Johnson and Olson",Lynch and Sons,3437983455,Ward,volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus,9621QF22B21,,2022-07-27,1251.77,User,rrappa27,Rabi,Rappa,rrappa27@cyberchimps.com,"" -Skiles and Sons,Compactor,FOR-0865018,Exterior Signage,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson","Torp, Kautzer and Rodriguez",3955766875,Tommy,,6385GQ84X66,quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere,2023-04-26,1109.44,Location,"","","","",Frami and Sons -"Corkery, Kunde and Schulist",Crawler,SCK-0860481,Rebar & Wire Mesh Install,"Fritsch, Sauer and Conn",Walker-Towne,Lynch and Sons,5146662358,Karry,amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu,,,2022-10-05,2029.54,Location,"","","","","Torp, Kautzer and Rodriguez" -Brakus LLC,Backhoe,DAI-7864625,Casework,Pollich LLC,"Kunde, Doyle and Kozey","Jenkins, Goldner and Cruickshank",3421666270,Mabelle,,3209SC49Y83,vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis,2022-06-01,3611.16,User,tcicutto1l,Thornie,Cicutto,tcicutto1l@irs.gov,"" -"Donnelly, Daniel and Kuhn",Skid-Steer,AXA-8500000,Glass & Glazing,Pacocha-Goodwin,"Upton, Feil and Jast",Powlowski LLC,5545256559,Chase,,5792EV06A79,,2023-01-19,3029.64,,"","","","","" -Wehner-Wyman,Compactor,MGX-6732943,Fire Sprinkler System,Berge Inc,Mosciski Inc,Ledner-Barrows,3758714940,Jeana,ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur,,,2023-01-09,2587.65,User,dhailwood18,Duffy,Hailwood,dhailwood18@ask.com,"" -Hauck-Bosco,Bulldozer,NIS-2897343,Electrical,"Kunde, Doyle and Kozey","Botsford, Boyle and Herzog","Jenkins, Goldner and Cruickshank",8342227248,Illa,,0783AR26Y44,,2023-01-11,904.38,Location,"","","","",Pacocha-Kiehn -Shields Inc,Bulldozer,QZL-7700638,Construction Clean and Final Clean,Okuneva Group,Ankunding-Ledner,Pacocha-Kiehn,3955442162,Mabelle,et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi,2293HV69Q96,,2022-05-30,2920.56,Location,"","","","",Erdman-West -"Willms, O'Connell and Cormier",Grader,RJS-7752630,Fire Sprinkler System,Ankunding-Ledner,Ankunding-Ledner,Treutel Inc,1717119122,Bondon,,2846FL33H31,mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui,2022-11-12,3998.15,,"","","","","" -Schinner-Price,Compactor,MDX-2483852,Construction Clean and Final Clean,Abernathy-Stamm,"Kutch, Johnson and Olson",Lindgren-Marquardt,9849455784,Bondon,,8475JD14T05,in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel,2023-04-21,2625.55,Location,"","","","","Wilkinson, Waters and Kerluke" -"Treutel, Berge and Batz",Grader,FZI-9158744,Marlite Panels (FED),"Kutch, Johnson and Olson","Kutch, Johnson and Olson",Russel Group,7516475364,Nial,,2620DL01N52,,2023-02-02,4272.01,Location,"","","","","Jenkins, Goldner and Cruickshank" -"McDermott, Koch and Skiles",Skid-Steer,RDJ-1659181,Marlite Panels (FED),Walker-Towne,"Romaguera, Goldner and Crooks","Jenkins, Goldner and Cruickshank",2002850756,Robby,congue eget semper rutrum nulla nunc purus phasellus in felis donec semper sapien a libero nam dui proin leo,4676YV46O78,,2022-07-24,3087.45,User,kharbage1w,Kinny,Harbage,kharbage1w@wordpress.org,"" -Tremblay-Collier,Compactor,XCQ-5970453,Drilled Shafts,"Legros, Paucek and Collier",Mosciski Inc,Roberts-Anderson,7468627157,Kizzee,turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem duis,2895CV95N88,velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam,2022-10-23,2726.04,,"","","","","" -"Shanahan, Bergnaum and Buckridge",Trencher,IRM-4172620,Drywall & Acoustical (MOB),"Romaguera, Goldner and Crooks","Fritsch, Sauer and Conn","Mills, Gleichner and Schamberger",8440065950,Debbi,habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante,4296XD75M64,,2023-05-18,2627.55,User,pwoollons2q,Pyotr,Woollons,pwoollons2q@nps.gov,"" -"Dickens, Huel and Reilly",Compactor,DPP-2739545,Sitework & Site Utilities,"Botsford, Boyle and Herzog",Okuneva Group,Russel Group,1933518954,Debbi,sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus,4147HN29Y26,,2023-02-01,4571.33,,"","","","","" -Shields Inc,Scraper,EBH-1609775,Roofing (Asphalt),Watsica LLC,Pollich LLC,"Nitzsche, Gislason and Douglas",3751836527,Debbi,,,,2023-01-16,3195.45,User,ahugonnet1f,Alvis,Hugonnet,ahugonnet1f@vinaora.com,"" +Company,Name,Asset Tag,Category,Supplier,Manufacturer,Location,Order Number,Model,Model Notes,Model Number,Asset Notes,Purchase Date,Purchase Cost,Checkout Type,Checked Out To: Username,Checked Out To: First Name,Checked Out To: Last Name,Checked Out To: Email,Checked Out To: Location,Asset EOL Date +Abshire and Sons,Backhoe,ICC-2065556,Ornamental Railings,"Kunde, Doyle and Kozey",Berge Inc,"Wilkinson, Waters and Kerluke",3271901481,"Macbook Pro 13""",,1786VM80X07,at nulla suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis,2023-01-23,2266.13,,,,,,,2028-10-27 +"Quitzon, Oberbrunner and Dibbert",Dragline,WBH-2841795,Structural and Misc Steel (Fabrication),Krajcik LLC,"Botsford, Boyle and Herzog",Lindgren-Marquardt,5504512275,"Macbook Pro 13""",ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam,9351IS25A51,aliquam convallis nunc proin at turpis a pede posuere nonummy integer,2022-11-14,1292.94,User,gmccrackem2a,Gage,McCrackem,gmccrackem2a@bing.com,,2028-10-27 +Boyer and Sons,Excavator,NNH-3656031,Soft Flooring and Base,"Heaney, Altenwerth and Emmerich",Pollich LLC,Pacocha-Kiehn,4861125177,"Macbook Pro 13""",,9929FR08W85,,2023-03-01,2300.71,Location,,,,,Pacocha-Kiehn,2028-10-27 +Hayes-Rippin,Trencher,BOL-0305383,Prefabricated Aluminum Metal Canopies,"Botsford, Boyle and Herzog",Walker-Towne,Fritsch-Abernathy,2416994639,"Macbook Pro 13""",neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus,9139KQ78G81,,2022-10-26,1777.56,User,ksennett6,Katerina,Sennett,ksennett6@ibm.com,,2028-10-27 +Romaguera-Flatley,Compactor,YVN-3440973,"Temp Fencing, Decorative Fencing and Gates",Ankunding-Ledner,Berge Inc,Roberts-Anderson,6080904229,"Macbook Pro 13""",turpis adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede,0910VB28Q61,,2022-07-24,2967.97,,,,,,,2028-10-27 +Auer LLC,Bulldozer,YOO-5936907,Electrical,Berge Inc,"Heaney, Altenwerth and Emmerich",Roberts-Anderson,8204459090,"Macbook Pro 13""",,7375EM02N97,proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem,2022-10-01,3819.73,,,,,,,2028-10-27 +Olson Group,Skid-Steer,EJS-7488052,Roofing (Metal),"Fritsch, Sauer and Conn","Romaguera, Goldner and Crooks",Lindgren-Marquardt,1252634576,"Macbook Pro 13""",blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae,,rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis,2022-06-25,2711.89,Location,,,,,"McGlynn, Hagenes and Bruen",2028-10-27 +"Powlowski, Monahan and Reichel",Bulldozer,HNY-7340937,Ornamental Railings,"Botsford, Boyle and Herzog",Watsica LLC,Roberts-Anderson,7294510907,"Macbook Pro 13""",sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in,"",erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales,2022-07-24,1833.42,User,mhasley21,Marion,Hasley,mhasley21@clickbank.net,,2028-10-27 +Harris LLC,Grader,JHN-0598394,Curb & Gutter,"Heaney, Altenwerth and Emmerich","Fritsch, Sauer and Conn",Waters LLC,0709494209,"Macbook Pro 13""",orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur,"",vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere,2023-04-11,3263.79,User,mbaudy2b,Mickie,Baudy,mbaudy2b@intel.com,,2028-10-27 +Boyer-Okuneva,Backhoe,GEX-7216431,Prefabricated Aluminum Metal Canopies,Pollich LLC,"Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",4030738107,"Macbook Pro 13""",velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat,"",,2022-08-25,2448.17,Location,,,,,Lynch and Sons,2028-10-27 +Ebert-Reilly,Scraper,DWM-3752227,Construction Clean and Final Clean,Watsica LLC,Berge Inc,"Mills, Gleichner and Schamberger",9381884673,"Macbook Pro 13""",sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit,"",rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper,2022-12-23,2721.12,User,sbucknall28,Saidee,Bucknall,sbucknall28@cbsnews.com,,"" +"Mitchell, Ward and Hettinger",Dragline,OLM-0226994,Structural and Misc Steel (Fabrication),Walker-Towne,"Kutch, Johnson and Olson",Frami and Sons,7504201033,"Macbook Pro 13""",curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non,"",amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique,2023-05-14,3178.59,Location,,,,,"O'Conner, Nitzsche and Aufderhar","" +Prosacco-Ledner,Scraper,LYO-8134459,Roofing (Asphalt),Mosciski Inc,Mosciski Inc,Waters LLC,9994825740,"Macbook Pro 13""",molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci,"",,2023-05-07,3322.34,,,,,,,"" +Harvey and Sons,Compactor,HLK-1645158,Structural and Misc Steel (Fabrication),"Romaguera, Goldner and Crooks",Mosciski Inc,Treutel Inc,8322817966,"Macbook Pro 13""",egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend,"",,2022-09-03,1823.95,User,flewins10,Filip,Lewins,flewins10@amazonaws.com,,"" +"Lebsack, Roob and Streich",Scraper,AZT-4280937,Marlite Panels (FED),Okuneva Group,"Upton, Feil and Jast",Lynch and Sons,8146943004,"Macbook Pro 13""",,"",,2022-12-17,1614.68,Location,,,,,Roberts-Anderson,"" +Abbott-Nikolaus,Dump Truck,IAB-5332824,Structural & Misc Steel Erection,"Upton, Feil and Jast","Romaguera, Goldner and Crooks",Lynch and Sons,3906729636,"Macbook Pro 13""",dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce,2315CN41G71,,2022-11-02,1444.69,Location,,,,,Fritsch-Abernathy,"" +Nolan-Wisoky,Trencher,OID-4455781,HVAC,Ankunding-Ledner,Abernathy-Stamm,Schinner Group,8981616853,"Macbook Pro 13""",maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus,6080UE59E09,,2022-09-07,3637.94,User,bmctrustrie1c,Balduin,McTrustrie,bmctrustrie1c@free.fr,,"" +"Miller, Morissette and Kihn",Trencher,UQY-2172679,Curb & Gutter,"Legros, Paucek and Collier",Mosciski Inc,"O'Conner, Nitzsche and Aufderhar",3983411009,"Macbook Pro 13""",,5505YF23M46,,2023-02-10,4253.88,User,kkubanek1t,Kalinda,Kubanek,kkubanek1t@umich.edu,,"" +Erdman and Sons,Grader,HRI-2262410,Granite Surfaces,Berge Inc,Walker-Towne,Ledner-Barrows,9311141848,"Macbook Pro 13""",,8673QP30R80,,2022-06-20,1784.66,Location,,,,,Lynch and Sons,"" +"Rogahn, Cormier and Ruecker",Bulldozer,BBK-5960598,Drywall & Acoustical (MOB),Abernathy-Stamm,"Heaney, Altenwerth and Emmerich",Frami and Sons,5157837617,"Macbook Pro 13""",nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium,9088XV67Q94,,2022-12-15,668.43,Location,,,,,Russel Group,"" +Pagac-Feeney,Compactor,RRO-8557470,Structural and Misc Steel (Fabrication),Mosciski Inc,Okuneva Group,Russel Group,2776102414,"Macbook Pro 13""",quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis,,,2022-12-27,3391.42,,,,,,,"" +Toy-Daniel,Compactor,SUQ-5159067,Fire Protection,"Botsford, Boyle and Herzog",Berge Inc,"Jenkins, Goldner and Cruickshank",0313117719,Robby,,2830MI42B80,,2022-06-24,4402.74,User,hbucknall29,Hillie,Bucknall,hbucknall29@webnode.com,,2028-10-27 +"Rau, O'Kon and Predovic",Grader,KUW-8075173,Hard Tile & Stone,"Kunde, Doyle and Kozey",Abernathy-Stamm,"Torp, Kautzer and Rodriguez",3235657209,Tommy,,8752PQ41C20,,2023-04-16,2428.95,User,wtomblin2r,Waiter,Tomblin,wtomblin2r@cisco.com,,2028-10-27 +"Rodriguez, Rippin and Maggio",Backhoe,KGD-2478881,Epoxy Flooring,"Upton, Feil and Jast",Krajcik LLC,Roberts-Anderson,9968615213,Sumner,,,interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor,2022-11-14,2353.49,,,,,,,2028-10-27 +Collins-Langworth,Backhoe,NLM-1912680,Glass & Glazing,Walker-Towne,"Kunde, Doyle and Kozey","Nitzsche, Gislason and Douglas",2439578863,Paloma,quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum,4241FY30X65,aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate justo,2022-05-29,413.99,User,esargersonc,El,Sargerson,esargersonc@moonfruit.com,,2028-10-27 +"Tillman, Rippin and Robel",Backhoe,HKF-2889015,Framing (Steel),Rippin-Schiller,"Upton, Feil and Jast","Nitzsche, Gislason and Douglas",1249482779,Mabelle,,7737HG97C81,nibh in hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante,2022-09-27,174.79,User,lloosely2i,Leia,Loosely,lloosely2i@ebay.co.uk,,2028-10-27 +Bogisich-Gerhold,Excavator,HIN-5577764,HVAC,Ankunding-Ledner,"Kutch, Johnson and Olson",Treutel Inc,5300366684,Robby,,,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia,2023-04-09,4662.35,User,bogg1s,Blakeley,Ogg,bogg1s@examiner.com,,2028-10-27 +DuBuque-Jones,Excavator,RSF-1042461,Roofing (Asphalt),Abernathy-Stamm,"Stokes, Daniel and Johnson",Frami and Sons,8526994711,Jere,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean,,,2022-11-25,2356.93,,,,,,,2028-10-27 +Runolfsson-McCullough,Scraper,DAK-0644656,Framing (Wood),Mosciski Inc,"Legros, Paucek and Collier",Lynch and Sons,5644034134,Mabelle,,3664GP06B10,molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu,2022-10-05,3912.18,User,mnollet1,Maury,Nollet,mnollet1@ow.ly,,2028-10-27 +Hilpert-Vandervort,Crawler,XWJ-0341580,Roofing (Asphalt),Ankunding-Ledner,Abernathy-Stamm,"Mills, Gleichner and Schamberger",0635168064,Chase,,9438FZ85N89,dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien,2022-08-28,4426.46,User,nhumburton1m,Noelle,Humburton,nhumburton1m@ow.ly,,2028-10-27 +"Schmitt, Kuhlman and Gusikowski",Excavator,UQF-6263661,Electrical,"Upton, Feil and Jast","Kunde, Doyle and Kozey",Fritsch-Abernathy,6394370767,Ward,,0642OF31I56,adipiscing elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis,2022-12-04,3182.1,Location,,,,,Powlowski LLC,2028-10-27 +Klein Inc,Excavator,IOC-4424021,Elevator,Watsica LLC,Pacocha-Goodwin,Treutel Inc,9408196701,Beverly,,4124VH09F17,,2022-09-12,3321.66,,,,,,,2028-10-27 +"Runte, Feeney and Lueilwitz",Compactor,TBU-9704770,Drilled Shafts,"Kunde, Doyle and Kozey",Rippin-Schiller,"Nitzsche, Gislason and Douglas",5300662174,Jere,consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in,,,2022-07-19,1573.26,User,tdoumerquef,Tobe,Doumerque,tdoumerquef@twitpic.com,,2028-10-27 +Reinger LLC,Dump Truck,TTW-1274810,Masonry,"Heaney, Altenwerth and Emmerich",Abernathy-Stamm,"Torp, Kautzer and Rodriguez",6161734867,Karry,,,,2023-03-05,2677.79,User,rtitterrell0,Rora,Titterrell,rtitterrell0@prnewswire.com,,2028-10-27 +"Breitenberg, Crooks and Goldner",Grader,RIU-2781610,Drilled Shafts,Pollich LLC,"Kunde, Doyle and Kozey","Nitzsche, Gislason and Douglas",1424455064,Nial,,6982GT82L84,,2023-03-22,4085.21,User,otaverner7,Odey,Taverner,otaverner7@discuz.net,,2028-10-27 +"Rolfson, Pollich and Kertzmann",Compactor,BFO-5970934,"Doors, Frames & Hardware","Romaguera, Goldner and Crooks",Pacocha-Goodwin,Pacocha-Kiehn,9193131620,Robby,donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium,3185PP20K43,gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras,2022-05-31,1819.48,,,,,,,2028-10-27 +Luettgen Inc,Grader,QEY-4583797,Painting & Vinyl Wall Covering,"Upton, Feil and Jast","Kunde, Doyle and Kozey","O'Conner, Nitzsche and Aufderhar",6610625137,Debbi,,4971UO73K02,,2022-07-07,4635.63,User,glippitt22,Gregorius,Lippitt,glippitt22@yandex.ru,,2028-10-27 +"Hermann, Cremin and Crona",Dragline,OPB-2390560,Ornamental Railings,"Fritsch, Sauer and Conn",Ankunding-Ledner,Treutel Inc,7358467570,Beverly,,7467PI87D35,nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros,2023-04-09,2089.47,User,dhailwood18,Duffy,Hailwood,dhailwood18@ask.com,,2028-10-27 +Cruickshank-Blanda,Grader,CUD-4868409,Electrical and Fire Alarm,Watsica LLC,Ankunding-Ledner,Treutel Inc,7859007380,Kizzee,,0709PI47U54,,2023-01-19,484.12,,,,,,,2028-10-27 +"Reilly, Yundt and Keeling",Bulldozer,ORL-2640580,RF Shielding,"Legros, Paucek and Collier",Berge Inc,Schinner Group,8731356307,Bondon,,6643DS43O77,,2023-03-29,254.99,,,,,,,2028-10-27 +"Franecki, Nolan and Swift",Trencher,GFV-9868944,RF Shielding,"Kunde, Doyle and Kozey","Kunde, Doyle and Kozey",Lynch and Sons,8841110804,Karry,,1465WJ98O96,,2022-08-27,1191.44,User,acharplinge,Andreana,Charpling,acharplinge@dell.com,,2028-10-27 +"Lind, Reinger and Grant",Dump Truck,KFR-9464842,Epoxy Flooring,"Botsford, Boyle and Herzog","Upton, Feil and Jast",Erdman-West,5382312507,Nial,,7440CN27Q04,amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in,2022-08-19,430.07,,,,,,,2028-10-27 +Quigley Inc,Dragline,XQS-0788077,Casework,Ankunding-Ledner,"Fritsch, Sauer and Conn",Lynch and Sons,5163086523,Rolland,ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel,1663OK46D26,,2022-12-25,1308.3,User,ahugonnet1f,Alvis,Hugonnet,ahugonnet1f@vinaora.com,,2028-10-27 +Hudson-Graham,Dragline,APW-0602098,Waterproofing & Caulking,"Romaguera, Goldner and Crooks",Berge Inc,Waters LLC,1473905199,Mabelle,urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat,,,2022-08-18,2037.76,Location,,,,,"Mills, Gleichner and Schamberger",2028-10-27 +"Schmeler, Dietrich and Buckridge",Crawler,RFX-9706298,Drilled Shafts,Krajcik LLC,Krajcik LLC,Roberts-Anderson,2238031407,Robby,ornare consequat lectus in est risus auctor sed tristique in,7387DP63Q90,tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh,2023-02-13,3395.33,Location,,,,,Erdman-West,2028-10-27 +"Jast, Cassin and Hane",Grader,EQQ-2912281,Wall Protection,"Fritsch, Sauer and Conn",Krajcik LLC,Lynch and Sons,6313951394,Mabelle,sagittis sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum,4541TF76I78,tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum primis,2022-05-25,4787.17,Location,,,,,Schinner Group,2028-10-27 +Bergstrom-Block,Excavator,IEG-8406586,Masonry,Mosciski Inc,Mosciski Inc,Roberts-Anderson,6040029438,Bondon,cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit,4324BJ82Z56,,2022-09-09,2371.3,Location,,,,,Powlowski LLC,2028-10-27 +"Powlowski, Lowe and Streich",Dragline,ZCB-5287486,Roofing (Asphalt),Watsica LLC,Watsica LLC,"McGlynn, Hagenes and Bruen",6324836270,Debbi,,7369JZ83X28,pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim,2022-05-30,3261.46,User,ohynson1y,Olivia,Hynson,ohynson1y@ask.com,,2028-10-27 +"Purdy, Leannon and Boyer",Crawler,VVZ-9417930,Retaining Wall and Brick Pavers,"Botsford, Boyle and Herzog","Legros, Paucek and Collier",Schinner Group,1391767652,Bale,,,nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla,2022-06-22,807.74,User,vgunning1z,Vince,Gunning,vgunning1z@arstechnica.com,,2028-10-27 +"Schowalter, Grady and Stracke",Backhoe,LHC-9749327,Fire Protection,Walker-Towne,Ankunding-Ledner,Schinner Group,3460146131,Flo,,4031MX62O09,aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in,2023-02-14,2051.25,User,akneath13,Arron,Kneath,akneath13@sphinn.com,,2028-10-27 +Schulist-Marks,Dragline,HUM-7311338,Sitework & Site Utilities,Berge Inc,Okuneva Group,Roberts-Anderson,4146242985,Bondon,,0468AG53C12,,2022-05-20,2788.78,Location,,,,,"O'Conner, Nitzsche and Aufderhar",2028-10-27 +Cole-Feeney,Trencher,LHK-1308041,"Temp Fencing, Decorative Fencing and Gates","Upton, Feil and Jast","Stokes, Daniel and Johnson",Lindgren-Marquardt,7648736489,Robby,,,massa donec dapibus duis at velit eu est congue elementum in hac habitasse,2022-11-02,2755.2,User,dkent23,Debbi,Kent,dkent23@mtv.com,,2028-10-27 +O'Hara Inc,Backhoe,CIC-1996687,"Temp Fencing, Decorative Fencing and Gates","Romaguera, Goldner and Crooks","Romaguera, Goldner and Crooks",Frami and Sons,5059462108,Bale,,5053ZY27R14,placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi,2023-01-17,2100.84,User,kephson2f,Kimberly,Ephson,kephson2f@sina.com.cn,,2028-10-27 +Rolfson-Kulas,Dump Truck,YBW-8150273,EIFS,Krajcik LLC,"Upton, Feil and Jast","Wilkinson, Waters and Kerluke",5490351215,Mabelle,,9162YL93W88,ullamcorper augue a suscipit nulla elit ac nulla sed vel enim,2023-01-14,877.57,Location,,,,,Fritsch-Abernathy,2028-10-27 +Lehner-Effertz,Excavator,OFB-4987804,Casework,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson",Waters LLC,2712763720,Robby,,0270YA01J87,sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus,2022-09-03,3428.56,User,acansdell14,Adela,Cansdell,acansdell14@cbc.ca,,2028-10-27 +"Dietrich, Von and Mante",Excavator,RXG-3730482,Elevator,Rippin-Schiller,"Botsford, Boyle and Herzog",Lindgren-Marquardt,0868073143,Jere,,8281VU07P52,,2023-01-25,713.89,User,pwippers,Peirce,Wipper,pwippers@liveinternet.ru,,2028-10-27 +Predovic-Roob,Bulldozer,ATA-2489950,EIFS,"Botsford, Boyle and Herzog","Romaguera, Goldner and Crooks",Frami and Sons,8905034852,Chase,platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut,,,2023-02-07,1138.85,User,odella1g,Osgood,Della,odella1g@hc360.com,,2028-10-27 +Adams-Larkin,Grader,SFI-7496689,"Doors, Frames & Hardware","Kutch, Johnson and Olson",Abernathy-Stamm,Lynch and Sons,3092970929,Flo,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo,0649BJ66S09,at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac,2022-09-10,278.59,,,,,,,2028-10-27 +"Beier, Grant and Thiel",Grader,GSX-1706311,Masonry,"Heaney, Altenwerth and Emmerich",Mosciski Inc,Ledner-Barrows,5141273631,Mabelle,convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget,6967FJ73D06,sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam,2022-07-18,1732.28,Location,,,,,Fritsch-Abernathy,2028-10-27 +Armstrong Group,Dump Truck,LXV-5626274,Plumbing & Medical Gas,Rippin-Schiller,Ankunding-Ledner,"Wilkinson, Waters and Kerluke",3202695143,Jeana,,5970GY37L93,purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus,2022-10-11,1981.53,User,cwinfreyl,Casper,Winfrey,cwinfreyl@adobe.com,,2028-10-27 +Rodriguez-Schultz,Excavator,BRF-0336967,Structural and Misc Steel (Fabrication),Abernathy-Stamm,"Botsford, Boyle and Herzog","Torp, Kautzer and Rodriguez",8211664431,Nial,nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum,,ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi,2022-12-30,4924.25,User,mgooday1q,Mariellen,Gooday,mgooday1q@alexa.com,,2028-10-27 +"Huels, Satterfield and Kautzer",Backhoe,CVH-2946076,Painting & Vinyl Wall Covering,Ankunding-Ledner,"Upton, Feil and Jast",Frami and Sons,0835287084,Paloma,justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus,,,2022-07-15,2406.57,,,,,,,2028-10-27 +"Lueilwitz, Herzog and Runte",Skid-Steer,OTH-2531332,HVAC,Mosciski Inc,Ankunding-Ledner,Ledner-Barrows,7517530461,Paloma,,1256WA18C94,,2023-01-14,531.46,,,,,,,2028-10-27 +Funk LLC,Grader,ZMB-4808007,Fire Sprinkler System,"Upton, Feil and Jast",Ankunding-Ledner,"Wilkinson, Waters and Kerluke",4467089765,Jeana,,0663SC93W29,quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu,2022-07-19,4741.85,,,,,,,2028-10-27 +"Doyle, Kulas and Mosciski",Dump Truck,XES-9665196,Masonry & Precast,"Botsford, Boyle and Herzog",Pacocha-Goodwin,Ledner-Barrows,4220776022,Sumner,,0099EC66L98,in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis,2022-08-06,276.15,,,,,,,2028-10-27 +"Robel, Johnston and Crist",Excavator,KUJ-7232314,Construction Clean and Final Clean,Ankunding-Ledner,"Romaguera, Goldner and Crooks",Fritsch-Abernathy,5757955088,Paloma,,4270AX61I99,,2022-09-21,3749.65,Location,,,,,"Wilkinson, Waters and Kerluke",2028-10-27 +Wiza-Mante,Grader,UNN-0016298,Roofing (Metal),Watsica LLC,Ankunding-Ledner,Frami and Sons,6746036070,Tommy,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie,9540RE21E79,est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat,2022-08-08,4600.22,Location,,,,,Roberts-Anderson,2028-10-27 +Jacobi Group,Bulldozer,JRP-9799959,Roofing (Metal),Rippin-Schiller,"Stokes, Daniel and Johnson",Russel Group,1090875903,Nobe,,5159PT28N59,,2023-05-02,3255.63,Location,,,,,Pacocha-Kiehn,2028-10-27 +Murazik-Cormier,Crawler,GTI-0627775,Marlite Panels (FED),"Kutch, Johnson and Olson","Fritsch, Sauer and Conn",Powlowski LLC,7650476155,Jeana,,9055CM63Z89,,2022-06-24,1295.04,Location,,,,,"Jenkins, Goldner and Cruickshank",2028-10-27 +"Simonis, Schulist and Volkman",Compactor,WEL-4481760,RF Shielding,Mosciski Inc,Rippin-Schiller,Powlowski LLC,6908782578,Bale,curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat,3712NE31P89,,2022-08-02,2038.79,User,ksennett6,Katerina,Sennett,ksennett6@ibm.com,,2028-10-27 +Conroy-Abshire,Grader,VKB-3777629,Masonry & Precast,Krajcik LLC,Pollich LLC,Erdman-West,6651233484,Illa,duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec,,in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris,2023-04-28,4988.38,,,,,,,2028-10-27 +Mueller and Sons,Bulldozer,YFK-3812221,Waterproofing & Caulking,Rippin-Schiller,Berge Inc,"Mills, Gleichner and Schamberger",3679743009,Illa,sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum,2077OM84Z52,,2022-09-18,3085.39,User,moscullya,Maurice,O'Scully,moscullya@engadget.com,,2028-10-27 +"Turner, Schulist and Hodkiewicz",Compactor,ENN-1432777,Ornamental Railings,"Romaguera, Goldner and Crooks","Kunde, Doyle and Kozey","Mills, Gleichner and Schamberger",7184133416,Robby,,6321OY95W96,,2022-09-25,4517.54,User,lloosely2i,Leia,Loosely,lloosely2i@ebay.co.uk,,2028-10-27 +Dicki-Hartmann,Skid-Steer,JNS-3537485,Epoxy Flooring,Pacocha-Goodwin,Mosciski Inc,Pacocha-Kiehn,2137927703,Mabelle,erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam,2809IW22A70,consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in,2022-09-12,4293.51,,,,,,,2028-10-27 +Schoen and Sons,Dump Truck,RAA-2200532,Soft Flooring and Base,Walker-Towne,"Upton, Feil and Jast",Waters LLC,5479047972,Beverly,maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra,,,2023-01-19,1760.88,User,rsapeyb,Raquela,Sapey,rsapeyb@furl.net,,2028-10-27 +"Rolfson, Champlin and Cole",Bulldozer,GDB-9523081,EIFS,"Heaney, Altenwerth and Emmerich","Upton, Feil and Jast",Frami and Sons,6544577007,Rolland,,,,2022-11-29,2769.18,,,,,,,2028-10-27 +"Maggio, Wolff and Friesen",Excavator,IOH-2003935,Structural and Misc Steel (Fabrication),Okuneva Group,Abernathy-Stamm,Lindgren-Marquardt,1508089431,Flo,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu,2753UJ02X37,porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus,2022-08-05,3709.55,,,,,,,2028-10-27 +Brekke-Hoeger,Backhoe,UPX-0286727,Plumbing & Medical Gas,"Upton, Feil and Jast",Rippin-Schiller,Lynch and Sons,6217027186,Bondon,,6387KP57B15,vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec,2023-03-21,4138.24,,,,,,,2028-10-27 +Konopelski Inc,Dump Truck,NZE-5809675,Casework,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson",Erdman-West,8486277027,Mabelle,dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus,4732FS58G78,,2023-05-11,114.69,,,,,,,2028-10-27 +"Nolan, Kertzmann and Rippin",Bulldozer,YXY-3875893,RF Shielding,"Romaguera, Goldner and Crooks",Abernathy-Stamm,Frami and Sons,7979699903,Bondon,habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt,4378XF72E06,,2023-04-29,1781.53,,,,,,,2028-10-27 +Stroman and Sons,Skid-Steer,BDD-0117730,"Temp Fencing, Decorative Fencing and Gates",Okuneva Group,Mosciski Inc,Frami and Sons,1053653041,Karry,,0484GG78J91,odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat,2022-09-12,4854.6,User,mbaudy2b,Mickie,Baudy,mbaudy2b@intel.com,,2028-10-27 +Howe-Volkman,Crawler,YMB-2902862,Masonry & Precast,"Kutch, Johnson and Olson",Krajcik LLC,Roberts-Anderson,0269899359,Nial,,2685ZN23G49,,2022-08-27,133.33,User,jdriffill2,Jeanne,Driffill,jdriffill2@google.fr,,2028-10-27 +Heathcote and Sons,Skid-Steer,LWA-2897309,Exterior Signage,"Heaney, Altenwerth and Emmerich",Okuneva Group,Schinner Group,1268560442,Paloma,,8414GM02P21,,2022-12-27,4524.09,,,,,,,2028-10-27 +Borer-Aufderhar,Scraper,OOY-9898294,Electrical,Pacocha-Goodwin,Berge Inc,"Nitzsche, Gislason and Douglas",4556315148,Karry,,0187NT56E91,,2022-08-21,390.85,,,,,,,2028-10-27 +"Hauck, Bogisich and King",Dragline,XHB-6046299,Sitework & Site Utilities,"Fritsch, Sauer and Conn","Romaguera, Goldner and Crooks","Jenkins, Goldner and Cruickshank",9343736607,Debbi,aliquam erat volutpat in congue etiam justo etiam pretium iaculis,8559BT54S22,in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate,2022-05-25,1439.23,Location,,,,,Ledner-Barrows, +"Upton, Luettgen and Mayer",Grader,JVB-8844609,Structural & Misc Steel Erection,Pollich LLC,"Kunde, Doyle and Kozey",Treutel Inc,6715000024,Karry,nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper,0088HF56H55,,2022-12-17,4849.95,Location,,,,,"McGlynn, Hagenes and Bruen", +Fadel Inc,Skid-Steer,VAA-1096481,Hard Tile & Stone,Mosciski Inc,Krajcik LLC,Schinner Group,2384395201,Nobe,parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id,,tortor duis mattis egestas metus aenean fermentum donec ut mauris,2022-07-15,1190.44,User,tphysick3,Tiphanie,Physick,tphysick3@umn.edu,, +Dickens and Sons,Skid-Steer,OAC-1765208,Ornamental Railings,Ankunding-Ledner,Rippin-Schiller,Lindgren-Marquardt,2119388227,Ward,non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta,9895LS02S32,,2022-09-03,1868.26,Location,,,,,"Jenkins, Goldner and Cruickshank", +Cassin Group,Bulldozer,CPK-3286546,Structural & Misc Steel Erection,Berge Inc,"Botsford, Boyle and Herzog",Frami and Sons,6044719518,Chase,,5420GG86P87,,2022-07-22,1463.24,,,,,,, +"Wolf, Wiza and Pacocha",Excavator,ZHU-6798344,Electrical,"Kutch, Johnson and Olson",Abernathy-Stamm,Frami and Sons,6111751517,Tommy,odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat,5462TF70C36,amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim,2023-02-26,1268.92,User,pshirleyi,Pen,Shirley,pshirleyi@theguardian.com,, +"Davis, Klein and Kiehn",Backhoe,WHR-8533403,Plumbing & Medical Gas,"Botsford, Boyle and Herzog",Mosciski Inc,Treutel Inc,7632050235,Tommy,,5478JY13D60,,2022-09-11,4908.48,Location,,,,,Pacocha-Kiehn, +"Gorczany, Lindgren and Hand",Compactor,FAC-2072179,Framing (Steel),Berge Inc,"Legros, Paucek and Collier",Ledner-Barrows,8755015421,Rolland,,2138TK23O47,massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede,2022-09-19,2249.9,User,kgrealish16,Krystalle,Grealish,kgrealish16@sciencedirect.com,, +Romaguera-Ondricka,Grader,KON-1821691,Curb & Gutter,"Kutch, Johnson and Olson",Okuneva Group,"O'Conner, Nitzsche and Aufderhar",4908345668,Chase,,5414US21B26,pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti,2022-11-14,1545.41,User,ewarriner2o,Ellene,Warriner,ewarriner2o@wikimedia.org,, +"Anderson, Goldner and Bailey",Compactor,NDF-4324420,Roofing (Metal),Mosciski Inc,"Kunde, Doyle and Kozey","McGlynn, Hagenes and Bruen",3212882737,Nial,,0754DG10B38,dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo,2023-05-15,4423.19,,,,,,, +Mraz-Windler,Trencher,DLM-4710838,Plumbing & Medical Gas,"Kunde, Doyle and Kozey",Walker-Towne,"Jenkins, Goldner and Cruickshank",1315337182,Debbi,,2430PB13D95,,2022-07-11,2708.18,User,fmcguiness1k,Fredrika,McGuiness,fmcguiness1k@woothemes.com,, +Volkman Group,Compactor,MLJ-9313486,Electrical and Fire Alarm,"Romaguera, Goldner and Crooks","Botsford, Boyle and Herzog",Fritsch-Abernathy,2738670737,Illa,,,,2022-10-09,1404.85,Location,,,,,Pacocha-Kiehn, +Schulist-Kulas,Compactor,XBE-4236649,Drywall & Acoustical (MOB),Abernathy-Stamm,Berge Inc,"Nitzsche, Gislason and Douglas",7811285882,Kizzee,,2875KP71D22,,2022-11-21,271.5,Location,,,,,"Wilkinson, Waters and Kerluke", +Hammes Inc,Crawler,NRV-3022526,Epoxy Flooring,Pollich LLC,"Upton, Feil and Jast",Powlowski LLC,8015369811,Ward,,6485BK61G70,facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros,2023-01-25,1757.26,,,,,,, +"Kemmer, Beier and Hegmann",Skid-Steer,EXG-6125653,Glass & Glazing,"Upton, Feil and Jast",Pacocha-Goodwin,Erdman-West,9025824302,Rolland,,8500KT88G80,,2022-09-27,3697.22,User,aweemsj,Antoinette,Weems,aweemsj@who.int,, +"Padberg, Kozey and Morar",Compactor,XYK-7848357,RF Shielding,Berge Inc,Abernathy-Stamm,Russel Group,2116700211,Robby,,,,2022-11-05,4059.57,Location,,,,,Lindgren-Marquardt, +Pfannerstill and Sons,Skid-Steer,GZW-6229249,Waterproofing & Caulking,Pollich LLC,"Kutch, Johnson and Olson","Torp, Kautzer and Rodriguez",4909263041,Debbi,,,lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in,2022-08-11,1697.74,,,,,,, +Kunde-Hermiston,Compactor,VZD-7424699,Marlite Panels (FED),Mosciski Inc,"Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",5611381559,Nial,,8991VD43T11,cras mi pede malesuada in imperdiet et commodo vulputate justo in blandit,2022-09-17,4957.55,Location,,,,,Powlowski LLC, +"Treutel, Schmidt and Koss",Compactor,RZO-4616134,Retaining Wall and Brick Pavers,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson","O'Conner, Nitzsche and Aufderhar",0833202169,Nial,,6036SV40U93,,2023-04-18,3218.61,User,bvandalen5,Barbabas,Van Dalen,bvandalen5@smugmug.com,, +Nader Inc,Scraper,NXG-1697279,RF Shielding,"Botsford, Boyle and Herzog","Fritsch, Sauer and Conn",Waters LLC,7094568704,Robby,,7964JY42Z54,,2022-08-26,3628.55,Location,,,,,"McGlynn, Hagenes and Bruen", +"Cremin, Conroy and Kuphal",Crawler,OGN-4843235,Glass & Glazing,Okuneva Group,"Heaney, Altenwerth and Emmerich",Pacocha-Kiehn,6424257173,Beverly,,2880LU86I74,sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci,2022-09-22,4163.07,,,,,,, +"Douglas, Considine and Gerhold",Backhoe,BXI-1028705,Structural and Misc Steel (Fabrication),"Romaguera, Goldner and Crooks",Ankunding-Ledner,Roberts-Anderson,5388101251,Karry,,,,2023-02-20,976.02,,,,,,, +Emard Group,Scraper,MWK-5482358,Ornamental Railings,Mosciski Inc,"Heaney, Altenwerth and Emmerich","Mills, Gleichner and Schamberger",5509346090,Nobe,,8183RH74P65,donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum,2022-06-28,1441.26,Location,,,,,Fritsch-Abernathy, +Runolfsdottir-Schulist,Compactor,ZSS-2076422,EIFS,Rippin-Schiller,"Upton, Feil and Jast",Ledner-Barrows,4163465013,Karry,dui luctus rutrum nulla tellus in sagittis dui vel nisl,,,2022-11-11,3688.09,User,rrappa27,Rabi,Rappa,rrappa27@cyberchimps.com,, +Stoltenberg-Cormier,Trencher,XNO-4048888,Roofing (Metal),Watsica LLC,Pollich LLC,Russel Group,3593822457,Robby,morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed,1583JR98T50,,2022-12-04,1780.96,,,,,,, +"Hand, Bogan and Cassin",Skid-Steer,SWP-7721219,Roofing (Metal),"Kunde, Doyle and Kozey",Krajcik LLC,Lindgren-Marquardt,1185234640,Nial,,4091TS60T07,,2023-05-09,1528.53,User,mskellingtonu,Marrilee,Skellington,mskellingtonu@abc.net.au,, +Kling-Homenick,Dragline,HGX-6549969,Asphalt Paving,Walker-Towne,"Fritsch, Sauer and Conn","Jenkins, Goldner and Cruickshank",2726185990,Bale,,9573TJ14G71,,2023-01-31,4930.43,User,asyne1a,Angela,Syne,asyne1a@icio.us,, +"Kilback, Towne and Stehr",Skid-Steer,ZTD-2850379,Drilled Shafts,Krajcik LLC,"Stokes, Daniel and Johnson","O'Conner, Nitzsche and Aufderhar",7192145952,Paloma,,,ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci,2022-12-25,2947.93,,,,,,, +Harvey-Moore,Dump Truck,QKC-1710236,Fire Protection,Berge Inc,Okuneva Group,Lynch and Sons,7337073255,Kizzee,,7010OW07S33,,2023-02-01,1617.55,Location,,,,,Ledner-Barrows, +Maggio-Hoppe,Excavator,LHL-3384861,Termite Control,Pacocha-Goodwin,Abernathy-Stamm,Lindgren-Marquardt,5642326493,Ward,amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum,0489GD18P85,,2022-07-29,4682.12,User,nscroggs2d,Naoma,Scroggs,nscroggs2d@yelp.com,, +Muller LLC,Skid-Steer,VYY-4278214,Rebar & Wire Mesh Install,"Kunde, Doyle and Kozey",Okuneva Group,Schinner Group,0946092591,Paloma,,,felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis,2022-09-07,4400.17,Location,,,,,Lynch and Sons, +Kerluke Inc,Backhoe,HRP-6178185,Retaining Wall and Brick Pavers,Mosciski Inc,Berge Inc,Treutel Inc,6532044804,Nial,,,,2022-09-07,1345.45,Location,,,,,Lindgren-Marquardt, +"Harvey, Schulist and Kemmer",Trencher,QND-9017996,Painting & Vinyl Wall Covering,Abernathy-Stamm,Pollich LLC,Fritsch-Abernathy,4977996097,Paloma,est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce,2497DB20W09,habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo,2023-01-27,317.08,,,,,,, +Schimmel and Sons,Dragline,DAX-0085828,Drywall & Acoustical (MOB),Okuneva Group,Watsica LLC,"Jenkins, Goldner and Cruickshank",7739403397,Flo,,,,2022-12-28,4521.28,User,pwippers,Peirce,Wipper,pwippers@liveinternet.ru,, +Grimes-Lowe,Dragline,VED-7099927,Masonry,Rippin-Schiller,"Stokes, Daniel and Johnson",Lynch and Sons,3351670325,Karry,,,ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at,2023-02-04,2620.69,,,,,,, +Krajcik-Cruickshank,Bulldozer,ZHU-0211288,Painting & Vinyl Wall Covering,"Fritsch, Sauer and Conn",Pacocha-Goodwin,Lindgren-Marquardt,5042401893,Paloma,vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo,0511VF33D78,proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem,2022-09-27,923.99,,,,,,, +Donnelly-Maggio,Compactor,HFG-7624506,Electrical and Fire Alarm,Pollich LLC,"Fritsch, Sauer and Conn","Torp, Kautzer and Rodriguez",3157087051,Illa,libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim,5980WY24F50,tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non,2022-12-05,4670.18,Location,,,,,"Jenkins, Goldner and Cruickshank", +Pfeffer-Zieme,Trencher,AZY-1682920,Wall Protection,Abernathy-Stamm,Mosciski Inc,"Torp, Kautzer and Rodriguez",5186444169,Beverly,consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim,6620HZ14T83,aliquam convallis nunc proin at turpis a pede posuere nonummy,2022-06-27,1674.34,User,cstamper1h,Celie,Stamper,cstamper1h@usda.gov,, +"Cormier, Glover and Hickle",Compactor,EYP-3561396,Termite Control,Rippin-Schiller,"Kunde, Doyle and Kozey","Torp, Kautzer and Rodriguez",4964027369,Paloma,quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus,4352FH58S52,,2022-11-21,136.12,,,,,,, +Rau Group,Skid-Steer,DYU-7769483,Marlite Panels (FED),Krajcik LLC,Krajcik LLC,Fritsch-Abernathy,2457705388,Sumner,,2286KF82X10,,2023-05-14,1147.8,User,flewins10,Filip,Lewins,flewins10@amazonaws.com,, +"Shanahan, Bradtke and Rowe",Backhoe,JYC-0713874,Hard Tile & Stone,"Upton, Feil and Jast",Ankunding-Ledner,Waters LLC,0372144769,Rolland,porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in,7775EM65V29,phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu,2022-05-27,1691.36,,,,,,, +Bogisich LLC,Bulldozer,MIQ-6169091,Soft Flooring and Base,"Romaguera, Goldner and Crooks",Mosciski Inc,Powlowski LLC,8984702329,Ward,,,justo eu massa donec dapibus duis at velit eu est,2022-10-15,1578.57,User,ndubery1e,Neda,Dubery,ndubery1e@fotki.com,, +Ondricka-Schuster,Dump Truck,KEN-1184794,"Doors, Frames & Hardware",Walker-Towne,Watsica LLC,Pacocha-Kiehn,4745674969,Mabelle,ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat,,,2022-12-10,4298.98,,,,,,, +Mayer-Abernathy,Scraper,ZMD-6316371,Rebar & Wire Mesh Install,"Stokes, Daniel and Johnson",Abernathy-Stamm,Powlowski LLC,2526471701,Flo,,1918JS43H32,,2023-03-24,1171.9,User,gbassingdenm,Godwin,Bassingden,gbassingdenm@dedecms.com,, +Towne-Adams,Scraper,QCU-6826093,Prefabricated Aluminum Metal Canopies,Abernathy-Stamm,Rippin-Schiller,Waters LLC,4899233400,Debbi,arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst,6487ZI24B86,nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas,2023-02-22,602.41,Location,,,,,"McGlynn, Hagenes and Bruen", +Kunde Group,Compactor,ITE-9907827,Sitework & Site Utilities,Mosciski Inc,"Romaguera, Goldner and Crooks",Treutel Inc,4671828697,Kizzee,,3508XP22O98,sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est,2023-02-18,3119.76,Location,,,,,Fritsch-Abernathy, +Welch Group,Compactor,GXW-3680693,Drywall & Acoustical (MOB),Okuneva Group,Watsica LLC,"Nitzsche, Gislason and Douglas",4413339149,Karry,mi integer ac neque duis bibendum morbi non quam nec,,,2023-02-16,1570.25,User,ewhitebrook1r,Emmalyn,Whitebrook,ewhitebrook1r@webnode.com,, +"Zieme, Metz and Schamberger",Grader,GDQ-7108837,Masonry,"Heaney, Altenwerth and Emmerich","Upton, Feil and Jast","Jenkins, Goldner and Cruickshank",8560447476,Chase,,4743VU30T06,nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non,2022-08-09,1530.46,User,pwoollons2q,Pyotr,Woollons,pwoollons2q@nps.gov,, +"Kuhlman, Walker and Denesik",Dragline,QZO-1370026,Asphalt Paving,Krajcik LLC,"Botsford, Boyle and Herzog",Erdman-West,5748443707,Mabelle,,2693SH27I09,accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis,2022-08-23,2694.26,,,,,,, +Cormier Inc,Bulldozer,AQW-7617737,RF Shielding,"Heaney, Altenwerth and Emmerich",Watsica LLC,"Mills, Gleichner and Schamberger",1502376915,Rolland,,8461WN86M04,sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis,2023-03-04,1055.33,,,,,,, +Beier LLC,Dragline,NDV-6470913,Masonry,Mosciski Inc,"Kutch, Johnson and Olson",Lynch and Sons,3437983455,Ward,volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus,9621QF22B21,,2022-07-27,1251.77,User,rrappa27,Rabi,Rappa,rrappa27@cyberchimps.com,, +Skiles and Sons,Compactor,FOR-0865018,Exterior Signage,"Stokes, Daniel and Johnson","Kutch, Johnson and Olson","Torp, Kautzer and Rodriguez",3955766875,Tommy,,6385GQ84X66,quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere,2023-04-26,1109.44,Location,,,,,Frami and Sons, +"Corkery, Kunde and Schulist",Crawler,SCK-0860481,Rebar & Wire Mesh Install,"Fritsch, Sauer and Conn",Walker-Towne,Lynch and Sons,5146662358,Karry,amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu,,,2022-10-05,2029.54,Location,,,,,"Torp, Kautzer and Rodriguez", +Brakus LLC,Backhoe,DAI-7864625,Casework,Pollich LLC,"Kunde, Doyle and Kozey","Jenkins, Goldner and Cruickshank",3421666270,Mabelle,,3209SC49Y83,vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis,2022-06-01,3611.16,User,tcicutto1l,Thornie,Cicutto,tcicutto1l@irs.gov,, +"Donnelly, Daniel and Kuhn",Skid-Steer,AXA-8500000,Glass & Glazing,Pacocha-Goodwin,"Upton, Feil and Jast",Powlowski LLC,5545256559,Chase,,5792EV06A79,,2023-01-19,3029.64,,,,,,, +Wehner-Wyman,Compactor,MGX-6732943,Fire Sprinkler System,Berge Inc,Mosciski Inc,Ledner-Barrows,3758714940,Jeana,ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur,,,2023-01-09,2587.65,User,dhailwood18,Duffy,Hailwood,dhailwood18@ask.com,, +Hauck-Bosco,Bulldozer,NIS-2897343,Electrical,"Kunde, Doyle and Kozey","Botsford, Boyle and Herzog","Jenkins, Goldner and Cruickshank",8342227248,Illa,,0783AR26Y44,,2023-01-11,904.38,Location,,,,,Pacocha-Kiehn, +Shields Inc,Bulldozer,QZL-7700638,Construction Clean and Final Clean,Okuneva Group,Ankunding-Ledner,Pacocha-Kiehn,3955442162,Mabelle,et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi,2293HV69Q96,,2022-05-30,2920.56,Location,,,,,Erdman-West, +"Willms, O'Connell and Cormier",Grader,RJS-7752630,Fire Sprinkler System,Ankunding-Ledner,Ankunding-Ledner,Treutel Inc,1717119122,Bondon,,2846FL33H31,mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui,2022-11-12,3998.15,,,,,,, +Schinner-Price,Compactor,MDX-2483852,Construction Clean and Final Clean,Abernathy-Stamm,"Kutch, Johnson and Olson",Lindgren-Marquardt,9849455784,Bondon,,8475JD14T05,in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel,2023-04-21,2625.55,Location,,,,,"Wilkinson, Waters and Kerluke", +"Treutel, Berge and Batz",Grader,FZI-9158744,Marlite Panels (FED),"Kutch, Johnson and Olson","Kutch, Johnson and Olson",Russel Group,7516475364,Nial,,2620DL01N52,,2023-02-02,4272.01,Location,,,,,"Jenkins, Goldner and Cruickshank", +"McDermott, Koch and Skiles",Skid-Steer,RDJ-1659181,Marlite Panels (FED),Walker-Towne,"Romaguera, Goldner and Crooks","Jenkins, Goldner and Cruickshank",2002850756,Robby,congue eget semper rutrum nulla nunc purus phasellus in felis donec semper sapien a libero nam dui proin leo,4676YV46O78,,2022-07-24,3087.45,User,kharbage1w,Kinny,Harbage,kharbage1w@wordpress.org,, +Tremblay-Collier,Compactor,XCQ-5970453,Drilled Shafts,"Legros, Paucek and Collier",Mosciski Inc,Roberts-Anderson,7468627157,Kizzee,turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem duis,2895CV95N88,velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam,2022-10-23,2726.04,,,,,,, +"Shanahan, Bergnaum and Buckridge",Trencher,IRM-4172620,Drywall & Acoustical (MOB),"Romaguera, Goldner and Crooks","Fritsch, Sauer and Conn","Mills, Gleichner and Schamberger",8440065950,Debbi,habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante,4296XD75M64,,2023-05-18,2627.55,User,pwoollons2q,Pyotr,Woollons,pwoollons2q@nps.gov,, +"Dickens, Huel and Reilly",Compactor,DPP-2739545,Sitework & Site Utilities,"Botsford, Boyle and Herzog",Okuneva Group,Russel Group,1933518954,Debbi,sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus,4147HN29Y26,,2023-02-01,4571.33,,,,,,, +Shields Inc,Scraper,EBH-1609775,Roofing (Asphalt),Watsica LLC,Pollich LLC,"Nitzsche, Gislason and Douglas",3751836527,Debbi,,,,2023-01-16,3195.45,User,ahugonnet1f,Alvis,Hugonnet,ahugonnet1f@vinaora.com,, diff --git a/tests/Browser/LoginTest.php b/tests/Browser/LoginTest.php deleted file mode 100644 index 18f5172f15..0000000000 --- a/tests/Browser/LoginTest.php +++ /dev/null @@ -1,46 +0,0 @@ -make(); - - // We override the existing password to use a hash of one we know - $user->password = '$2y$10$8o5W8fgAKJbN3Kz4taepeeRVgKsG8pkZ1L4eJfdEKrn2mgI/JgCJy'; - - // We want a user that is a superuser - $user->permissions = '{"superuser": 1}'; - - $user->save(); - - Setting::factory()->create(); - - $this->browse(function (Browser $browser) { - $browser->visitRoute('login') - ->assertSee(trans('auth/general.login_prompt')); - }); - - $this->browse(function ($browser) use ($user) { - $browser->visitRoute('login') - ->type('username', $user->username) - ->type('password', 'password') - ->press(trans('auth/general.login')) - ->assertPathIs('/'); - $browser->screenshot('dashboard'); - }); - } -} diff --git a/tests/Browser/Pages/HomePage.php b/tests/Browser/Pages/HomePage.php deleted file mode 100644 index 26bf174f3d..0000000000 --- a/tests/Browser/Pages/HomePage.php +++ /dev/null @@ -1,41 +0,0 @@ - '#selector', - ]; - } -} diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php deleted file mode 100644 index f8d76222c0..0000000000 --- a/tests/Browser/Pages/Page.php +++ /dev/null @@ -1,20 +0,0 @@ - '#selector', - ]; - } -} diff --git a/tests/Browser/console/.gitignore b/tests/Browser/console/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/tests/Browser/console/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/tests/Browser/screenshots/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Browser/source/.gitignore b/tests/Browser/source/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/tests/Browser/source/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php deleted file mode 100644 index af46d0e3dc..0000000000 --- a/tests/DuskTestCase.php +++ /dev/null @@ -1,70 +0,0 @@ -addArguments(collect([ - '--window-size=1920,1080', - ])->unless($this->hasHeadlessDisabled(), function ($items) { - return $items->merge([ - '--disable-gpu', - '--headless', - ]); - })->all()); - - return RemoteWebDriver::create( - $_ENV['DUSK_DRIVER_URL'] ?? 'http://127.0.0.1:9515', - DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, $options - ) - ); - } - - /** - * Determine whether the Dusk command has disabled headless mode. - * - * @return bool - */ - protected function hasHeadlessDisabled() - { - return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || - isset($_ENV['DUSK_HEADLESS_DISABLED']); - } -} diff --git a/tests/Feature/Api/Assets/AssetCheckinTest.php b/tests/Feature/Api/Assets/AssetCheckinTest.php new file mode 100644 index 0000000000..f71191d80c --- /dev/null +++ b/tests/Feature/Api/Assets/AssetCheckinTest.php @@ -0,0 +1,30 @@ +superuser()->create(); + $asset = Asset::factory()->create(['last_checkin' => null]); + + $asset->checkOut(User::factory()->create(), $admin, now()); + + $this->actingAsForApi($admin) + ->postJson(route('api.asset.checkin', $asset)) + ->assertOk(); + + $this->assertNotNull( + $asset->fresh()->last_checkin, + 'last_checkin field should be set on checkin' + ); + } +} diff --git a/tests/Feature/Api/Assets/AssetStoreTest.php b/tests/Feature/Api/Assets/AssetStoreTest.php new file mode 100644 index 0000000000..92a58a5006 --- /dev/null +++ b/tests/Feature/Api/Assets/AssetStoreTest.php @@ -0,0 +1,441 @@ +actingAsForApi(User::factory()->create()) + ->postJson(route('api.assets.store')) + ->assertForbidden(); + } + + public function testAllAssetAttributesAreStored() + { + $company = Company::factory()->create(); + $location = Location::factory()->create(); + $model = AssetModel::factory()->create(); + $rtdLocation = Location::factory()->create(); + $status = Statuslabel::factory()->create(); + $supplier = Supplier::factory()->create(); + $user = User::factory()->createAssets()->create(); + $userAssigned = User::factory()->create(); + + $response = $this->actingAsForApi($user) + ->postJson(route('api.assets.store'), [ + 'asset_eol_date' => '2024-06-02', + 'asset_tag' => 'random_string', + 'assigned_user' => $userAssigned->id, + 'company_id' => $company->id, + 'last_audit_date' => '2023-09-03', + 'location_id' => $location->id, + 'model_id' => $model->id, + 'name' => 'A New Asset', + 'notes' => 'Some notes', + 'order_number' => '5678', + 'purchase_cost' => '123.45', + 'purchase_date' => '2023-09-02', + 'requestable' => true, + 'rtd_location_id' => $rtdLocation->id, + 'serial' => '1234567890', + 'status_id' => $status->id, + 'supplier_id' => $supplier->id, + 'warranty_months' => 10, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + + $this->assertTrue($asset->adminuser->is($user)); + + $this->assertEquals('2024-06-02', $asset->asset_eol_date); + $this->assertEquals('random_string', $asset->asset_tag); + $this->assertEquals($userAssigned->id, $asset->assigned_to); + $this->assertTrue($asset->company->is($company)); + // I don't see this on the GUI side either, but it's in the docs so I'm guessing that's a mistake? It wasn't in the controller. + // $this->assertEquals('2023-09-03', $asset->last_audit_date); + $this->assertTrue($asset->location->is($location)); + $this->assertTrue($asset->model->is($model)); + $this->assertEquals('A New Asset', $asset->name); + $this->assertEquals('Some notes', $asset->notes); + $this->assertEquals('5678', $asset->order_number); + $this->assertEquals('123.45', $asset->purchase_cost); + $this->assertTrue($asset->purchase_date->is('2023-09-02')); + $this->assertEquals('1', $asset->requestable); + $this->assertTrue($asset->defaultLoc->is($rtdLocation)); + $this->assertEquals('1234567890', $asset->serial); + $this->assertTrue($asset->assetstatus->is($status)); + $this->assertTrue($asset->supplier->is($supplier)); + $this->assertEquals(10, $asset->warranty_months); + } + + public function testArchivedDepreciateAndPhysicalCanBeNull() + { + $model = AssetModel::factory()->ipadModel()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'archive' => null, + 'depreciate' => null, + 'physical' => null + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertEquals(0, $asset->archived); + $this->assertEquals(1, $asset->physical); + $this->assertEquals(0, $asset->depreciate); + } + + public function testArchivedDepreciateAndPhysicalCanBeEmpty() + { + $model = AssetModel::factory()->ipadModel()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'archive' => '', + 'depreciate' => '', + 'physical' => '' + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertEquals(0, $asset->archived); + $this->assertEquals(1, $asset->physical); + $this->assertEquals(0, $asset->depreciate); + } + + public function testAssetEolDateIsCalculatedIfPurchaseDateSet() + { + $model = AssetModel::factory()->mbp13Model()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'purchase_date' => '2021-01-01', + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertEquals('2024-01-01', $asset->asset_eol_date); + } + + public function testAssetEolDateIsNotCalculatedIfPurchaseDateNotSet() + { + $model = AssetModel::factory()->mbp13Model()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertNull($asset->asset_eol_date); + } + + public function testAssetEolExplicitIsSetIfAssetEolDateIsExplicitlySet() + { + $model = AssetModel::factory()->mbp13Model()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'asset_eol_date' => '2025-01-01', + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertEquals('2025-01-01', $asset->asset_eol_date); + $this->assertTrue($asset->eol_explicit); + } + + public function testAssetGetsAssetTagWithAutoIncrement() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + $this->assertNotNull($asset->asset_tag); + } + + public function testAssetCreationFailsWithNoAssetTagOrAutoIncrement() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + + $this->settings->disableAutoIncrement(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('error'); + } + + public function testUniqueSerialNumbersIsEnforcedWhenEnabled() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $serial = '1234567890'; + + $this->settings->enableAutoIncrement(); + $this->settings->enableUniqueSerialNumbers(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'serial' => $serial, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'serial' => $serial, + ]) + ->assertOk() + ->assertStatusMessageIs('error'); + } + + public function testUniqueSerialNumbersIsNotEnforcedWhenDisabled() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $serial = '1234567890'; + + $this->settings->enableAutoIncrement(); + $this->settings->disableUniqueSerialNumbers(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'serial' => $serial, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'serial' => $serial, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + } + + public function testAssetTagsMustBeUniqueWhenUndeleted() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $asset_tag = '1234567890'; + + $this->settings->disableAutoIncrement(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => $asset_tag, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => $asset_tag, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('error'); + } + + public function testAssetTagsCanBeDuplicatedIfDeleted() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $asset_tag = '1234567890'; + + $this->settings->disableAutoIncrement(); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => $asset_tag, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + Asset::find($response['payload']['id'])->delete(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => $asset_tag, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success'); + } + + public function testAnAssetCanBeCheckedOutToUserOnStore() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $user = User::factory()->createAssets()->create(); + $userAssigned = User::factory()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi($user) + ->postJson(route('api.assets.store'), [ + 'assigned_user' => $userAssigned->id, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + + $this->assertTrue($asset->adminuser->is($user)); + $this->assertTrue($asset->checkedOutToUser()); + $this->assertTrue($asset->assignedTo->is($userAssigned)); + } + + public function testAnAssetCanBeCheckedOutToLocationOnStore() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $location = Location::factory()->create(); + $user = User::factory()->createAssets()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi($user) + ->postJson(route('api.assets.store'), [ + 'assigned_location' => $location->id, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $asset = Asset::find($response['payload']['id']); + + $this->assertTrue($asset->adminuser->is($user)); + $this->assertTrue($asset->checkedOutToLocation()); + $this->assertTrue($asset->location->is($location)); + } + + public function testAnAssetCanBeCheckedOutToAssetOnStore() + { + $model = AssetModel::factory()->create(); + $status = Statuslabel::factory()->create(); + $asset = Asset::factory()->create(); + $user = User::factory()->createAssets()->create(); + + $this->settings->enableAutoIncrement(); + + $response = $this->actingAsForApi($user) + ->postJson(route('api.assets.store'), [ + 'assigned_asset' => $asset->id, + 'model_id' => $model->id, + 'status_id' => $status->id, + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->json(); + + $apiAsset = Asset::find($response['payload']['id']); + + $this->assertTrue($apiAsset->adminuser->is($user)); + $this->assertTrue($apiAsset->checkedOutToAsset()); + // I think this makes sense, but open to a sanity check + $this->assertTrue($asset->assignedAssets()->find($response['payload']['id'])->is($apiAsset)); + } + + public function testCompanyIdNeedsToBeInteger() + { + $this->actingAsForApi(User::factory()->createAssets()->create()) + ->postJson(route('api.assets.store'), [ + 'company_id' => [1], + ]) + ->assertStatusMessageIs('error') + ->assertJson(function (AssertableJson $json) { + $json->has('messages.company_id')->etc(); + }); + } +} diff --git a/tests/Feature/Api/Departments/DepartmentIndexTest.php b/tests/Feature/Api/Departments/DepartmentIndexTest.php new file mode 100644 index 0000000000..1a3884308f --- /dev/null +++ b/tests/Feature/Api/Departments/DepartmentIndexTest.php @@ -0,0 +1,94 @@ +getJson(route('api.departments.index'))->assertRedirect(); + } + + public function testViewingDepartmentIndexRequiresPermission() + { + $this->actingAsForApi(User::factory()->create()) + ->getJson(route('api.departments.index')) + ->assertForbidden(); + } + + public function testDepartmentIndexReturnsExpectedDepartments() + { + Department::factory()->count(3)->create(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.departments.index', [ + 'sort' => 'name', + 'order' => 'asc', + 'offset' => '0', + 'limit' => '20', + ])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testDepartmentIndexAdheresToCompanyScoping() + { + [$companyA, $companyB] = Company::factory()->count(2)->create(); + + $departmentA = Department::factory()->for($companyA)->create(); + $departmentB = Department::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewDepartments()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewDepartments()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.departments.index')) + ->assertResponseContainsInRows($departmentA) + ->assertResponseContainsInRows($departmentB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.departments.index')) + ->assertResponseContainsInRows($departmentA) + ->assertResponseContainsInRows($departmentB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.departments.index')) + ->assertResponseContainsInRows($departmentA) + ->assertResponseContainsInRows($departmentB); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.departments.index')) + ->assertResponseContainsInRows($departmentA) + ->assertResponseContainsInRows($departmentB); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.departments.index')) + ->assertResponseContainsInRows($departmentA) + ->assertResponseDoesNotContainInRows($departmentB); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.departments.index')) + ->assertResponseDoesNotContainInRows($departmentA) + ->assertResponseContainsInRows($departmentB); + } +} diff --git a/tests/Feature/Api/Groups/GroupStoreTest.php b/tests/Feature/Api/Groups/GroupStoreTest.php new file mode 100644 index 0000000000..9ffba51913 --- /dev/null +++ b/tests/Feature/Api/Groups/GroupStoreTest.php @@ -0,0 +1,41 @@ +actingAsForApi(User::factory()->create()) + ->postJson(route('api.groups.store')) + ->assertForbidden(); + } + + public function testCanStoreGroup() + { + $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.groups.store'), [ + 'name' => 'My Awesome Group', + 'permissions' => [ + 'admin' => '1', + 'import' => '1', + 'reports.view' => '0', + ], + ]) + ->assertOk(); + + $group = Group::where('name', 'My Awesome Group')->first(); + + $this->assertNotNull($group); + $this->assertEquals('1', $group->decodePermissions()['admin']); + $this->assertEquals('1', $group->decodePermissions()['import']); + $this->assertEquals('0', $group->decodePermissions()['reports.view']); + } +} diff --git a/tests/Feature/Api/Users/UpdateUserApiTest.php b/tests/Feature/Api/Users/UpdateUserApiTest.php new file mode 100644 index 0000000000..81c1154648 --- /dev/null +++ b/tests/Feature/Api/Users/UpdateUserApiTest.php @@ -0,0 +1,68 @@ +superuser()->create(); + $user = User::factory()->create(['activated' => 0]); + + $this->actingAsForApi($admin) + ->patch(route('api.users.update', $user), [ + 'activated' => 1, + ]); + + $this->assertEquals(1, $user->refresh()->activated); + } + + public function testApiUsersCanBeActivatedWithBooleanTrue() + { + $admin = User::factory()->superuser()->create(); + $user = User::factory()->create(['activated' => false]); + + $this->actingAsForApi($admin) + ->patch(route('api.users.update', $user), [ + 'activated' => true, + ]); + + $this->assertEquals(1, $user->refresh()->activated); + } + + public function testApiUsersCanBeDeactivatedWithNumber() + { + $admin = User::factory()->superuser()->create(); + $user = User::factory()->create(['activated' => true]); + + $this->actingAsForApi($admin) + ->patch(route('api.users.update', $user), [ + 'activated' => 0, + ]); + + $this->assertEquals(0, $user->refresh()->activated); + } + + public function testApiUsersCanBeDeactivatedWithBooleanFalse() + { + $admin = User::factory()->superuser()->create(); + $user = User::factory()->create(['activated' => true]); + + $this->actingAsForApi($admin) + ->patch(route('api.users.update', $user), [ + 'activated' => false, + ]); + + $this->assertEquals(0, $user->refresh()->activated); + } + +} diff --git a/tests/Feature/Api/Users/UsersSearchTest.php b/tests/Feature/Api/Users/UsersSearchTest.php index f14d704b0f..723a115db1 100644 --- a/tests/Feature/Api/Users/UsersSearchTest.php +++ b/tests/Feature/Api/Users/UsersSearchTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Api\Users; +use App\Models\Company; use App\Models\User; use Laravel\Passport\Passport; use Tests\Support\InteractsWithSettings; @@ -83,4 +84,67 @@ class UsersSearchTest extends TestCase 'Expected deleted user does not appear in results' ); } + + public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled() + { + $this->settings->enableMultipleFullCompanySupport(); + + $companyA = Company::factory() + ->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User'])) + ->create(); + + Company::factory() + ->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User'])) + ->create(); + + $response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create()) + ->getJson(route('api.users.index')) + ->assertOk(); + + $results = collect($response->json('rows')); + + $this->assertTrue( + $results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')), + 'User index does not contain expected user' + ); + $this->assertFalse( + $results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')), + 'User index contains unexpected user from another company' + ); + } + + public function testUsersScopedToCompanyDuringSearchWhenMultipleFullCompanySupportEnabled() + { + $this->settings->enableMultipleFullCompanySupport(); + + $companyA = Company::factory() + ->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User'])) + ->create(); + + Company::factory() + ->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User'])) + ->create(); + + $response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create()) + ->getJson(route('api.users.index', [ + 'deleted' => 'false', + 'company_id' => null, + 'search' => 'user', + 'order' => 'asc', + 'offset' => '0', + 'limit' => '20', + ])) + ->assertOk(); + + $results = collect($response->json('rows')); + + $this->assertTrue( + $results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')), + 'User index does not contain expected user' + ); + $this->assertFalse( + $results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')), + 'User index contains unexpected user from another company' + ); + } } diff --git a/tests/Feature/Api/Users/UsersUpdateTest.php b/tests/Feature/Api/Users/UsersUpdateTest.php new file mode 100644 index 0000000000..953a671cf1 --- /dev/null +++ b/tests/Feature/Api/Users/UsersUpdateTest.php @@ -0,0 +1,87 @@ +superuser()->create(); + $manager = User::factory()->create(); + $company = Company::factory()->create(); + $department = Department::factory()->create(); + $location = Location::factory()->create(); + [$groupA, $groupB] = Group::factory()->count(2)->create(); + + $user = User::factory()->create([ + 'activated' => false, + 'remote' => false, + 'vip' => false, + ]); + + $this->actingAsForApi($admin) + ->patchJson(route('api.users.update', $user), [ + 'first_name' => 'Mabel', + 'last_name' => 'Mora', + 'username' => 'mabel', + 'password' => 'super-secret', + 'email' => 'mabel@onlymurderspod.com', + 'permissions' => '{"a.new.permission":"1"}', + 'activated' => true, + 'phone' => '619-555-5555', + 'jobtitle' => 'Host', + 'manager_id' => $manager->id, + 'employee_num' => '1111', + 'notes' => 'Pretty good artist', + 'company_id' => $company->id, + 'department_id' => $department->id, + 'location_id' => $location->id, + 'remote' => true, + 'groups' => $groupA->id, + 'vip' => true, + 'start_date' => '2021-08-01', + 'end_date' => '2025-12-31', + ]) + ->assertOk(); + + $user->refresh(); + $this->assertEquals('Mabel', $user->first_name, 'First name was not updated'); + $this->assertEquals('Mora', $user->last_name, 'Last name was not updated'); + $this->assertEquals('mabel', $user->username, 'Username was not updated'); + $this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated'); + $this->assertEquals('mabel@onlymurderspod.com', $user->email, 'Email was not updated'); + $this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated'); + $this->assertTrue((bool)$user->activated, 'User not marked as activated'); + $this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated'); + $this->assertEquals('Host', $user->jobtitle, 'Job title was not updated'); + $this->assertTrue($user->manager->is($manager), 'Manager was not updated'); + $this->assertEquals('1111', $user->employee_num, 'Employee number was not updated'); + $this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated'); + $this->assertTrue($user->company->is($company), 'Company was not updated'); + $this->assertTrue($user->department->is($department), 'Department was not updated'); + $this->assertTrue($user->location->is($location), 'Location was not updated'); + $this->assertEquals(1, $user->remote, 'Remote was not updated'); + $this->assertTrue($user->groups->contains($groupA), 'Groups were not updated'); + $this->assertEquals(1, $user->vip, 'VIP was not updated'); + $this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated'); + $this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated'); + + // `groups` can be an id or array or ids + $this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]); + + $user->refresh(); + $this->assertTrue($user->groups->contains($groupA), 'Not part of expected group'); + $this->assertTrue($user->groups->contains($groupB), 'Not part of expected group'); + } +} diff --git a/tests/Feature/Assets/AssetCheckinTest.php b/tests/Feature/Assets/AssetCheckinTest.php new file mode 100644 index 0000000000..059fb1294f --- /dev/null +++ b/tests/Feature/Assets/AssetCheckinTest.php @@ -0,0 +1,32 @@ +superuser()->create(); + $asset = Asset::factory()->create(['last_checkin' => null]); + + $asset->checkOut(User::factory()->create(), $admin, now()); + + $this->actingAs($admin) + ->post(route('hardware.checkin.store', [ + 'assetId' => $asset->id, + ])) + ->assertRedirect(); + + $this->assertNotNull( + $asset->fresh()->last_checkin, + 'last_checkin field should be set on checkin' + ); + } +} diff --git a/tests/Feature/Checkouts/LicenseCheckoutTest.php b/tests/Feature/Checkouts/LicenseCheckoutTest.php new file mode 100644 index 0000000000..978fac28f2 --- /dev/null +++ b/tests/Feature/Checkouts/LicenseCheckoutTest.php @@ -0,0 +1,62 @@ +superuser()->create(); + $asset = Asset::factory()->create(); + $licenseSeat = LicenseSeat::factory()->create(); + + $this->actingAs($admin) + ->post("/licenses/{$licenseSeat->license->id}/checkout", [ + 'checkout_to_type' => 'asset', + 'assigned_to' => null, + 'asset_id' => $asset->id, + 'notes' => 'oh hi there', + ]); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $asset->id, + 'target_type' => Asset::class, + 'item_id' => $licenseSeat->license->id, + 'item_type' => License::class, + 'note' => 'oh hi there', + ]); + } + + public function testNotesAreStoredInActionLogOnCheckoutToUser() + { + $admin = User::factory()->superuser()->create(); + $licenseSeat = LicenseSeat::factory()->create(); + + $this->actingAs($admin) + ->post("/licenses/{$licenseSeat->license->id}/checkout", [ + 'checkout_to_type' => 'user', + 'assigned_to' => $admin->id, + 'asset_id' => null, + 'notes' => 'oh hi there', + ]); + + $this->assertDatabaseHas('action_logs', [ + 'action_type' => 'checkout', + 'target_id' => $admin->id, + 'target_type' => User::class, + 'item_id' => $licenseSeat->license->id, + 'item_type' => License::class, + 'note' => 'oh hi there', + ]); + } +} diff --git a/tests/Feature/Reports/CustomReportTest.php b/tests/Feature/Reports/CustomReportTest.php index b27ebc27ef..dd3199212e 100644 --- a/tests/Feature/Reports/CustomReportTest.php +++ b/tests/Feature/Reports/CustomReportTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\Assert; use Tests\Support\InteractsWithSettings; use Tests\TestCase; + class CustomReportTest extends TestCase { use InteractsWithSettings; @@ -107,4 +108,29 @@ class CustomReportTest extends TestCase ->assertDontSeeTextInStreamedResponse('Asset A') ->assertSeeTextInStreamedResponse('Asset B'); } + + public function testCanLimitAssetsByLastCheckIn() + { + Asset::factory()->create(['name' => 'Asset A', 'last_checkin' => '2023-08-01']); + Asset::factory()->create(['name' => 'Asset B', 'last_checkin' => '2023-08-02']); + Asset::factory()->create(['name' => 'Asset C', 'last_checkin' => '2023-08-03']); + Asset::factory()->create(['name' => 'Asset D', 'last_checkin' => '2023-08-04']); + Asset::factory()->create(['name' => 'Asset E', 'last_checkin' => '2023-08-05']); + + $this->actingAs(User::factory()->canViewReports()->create()) + ->post('reports/custom', [ + 'asset_name' => '1', + 'asset_tag' => '1', + 'serial' => '1', + 'checkin_date' => '1', + 'checkin_date_start' => '2023-08-02', + 'checkin_date_end' => '2023-08-04', + ])->assertOk() + ->assertHeader('content-type', 'text/csv; charset=UTF-8') + ->assertDontSeeTextInStreamedResponse('Asset A') + ->assertSeeTextInStreamedResponse('Asset B') + ->assertSeeTextInStreamedResponse('Asset C') + ->assertSeeTextInStreamedResponse('Asset D') + ->assertDontSeeTextInStreamedResponse('Asset E'); + } } diff --git a/tests/Feature/Users/UpdateUserTest.php b/tests/Feature/Users/UpdateUserTest.php index 9ddb323625..92245059ef 100644 --- a/tests/Feature/Users/UpdateUserTest.php +++ b/tests/Feature/Users/UpdateUserTest.php @@ -10,10 +10,10 @@ class UpdateUserTest extends TestCase { use InteractsWithSettings; - public function testUsersCanBeActivated() + public function testUsersCanBeActivatedWithNumber() { $admin = User::factory()->superuser()->create(); - $user = User::factory()->create(['activated' => false]); + $user = User::factory()->create(['activated' => 0]); $this->actingAs($admin) ->put(route('users.update', $user), [ @@ -22,10 +22,25 @@ class UpdateUserTest extends TestCase 'activated' => 1, ]); - $this->assertTrue($user->refresh()->activated); + $this->assertEquals(1, $user->refresh()->activated); } - public function testUsersCanBeDeactivated() + public function testUsersCanBeActivatedWithBooleanTrue() + { + $admin = User::factory()->superuser()->create(); + $user = User::factory()->create(['activated' => false]); + + $this->actingAs($admin) + ->put(route('users.update', $user), [ + 'first_name' => $user->first_name, + 'username' => $user->username, + 'activated' => true, + ]); + + $this->assertEquals(1, $user->refresh()->activated); + } + + public function testUsersCanBeDeactivatedWithNumber() { $admin = User::factory()->superuser()->create(); $user = User::factory()->create(['activated' => true]); @@ -34,12 +49,25 @@ class UpdateUserTest extends TestCase ->put(route('users.update', $user), [ 'first_name' => $user->first_name, 'username' => $user->username, - // checkboxes that are not checked are - // not included in the request payload - // 'activated' => 0, + 'activated' => 0, ]); - $this->assertFalse($user->refresh()->activated); + $this->assertEquals(0, $user->refresh()->activated); + } + + public function testUsersCanBeDeactivatedWithBooleanFalse() + { + $admin = User::factory()->superuser()->create(); + $user = User::factory()->create(['activated' => true]); + + $this->actingAs($admin) + ->put(route('users.update', $user), [ + 'first_name' => $user->first_name, + 'username' => $user->username, + 'activated' => false, + ]); + + $this->assertEquals(0, $user->refresh()->activated); } public function testUsersUpdatingThemselvesDoNotDeactivateTheirAccount() @@ -50,12 +78,8 @@ class UpdateUserTest extends TestCase ->put(route('users.update', $admin), [ 'first_name' => $admin->first_name, 'username' => $admin->username, - // checkboxes that are disabled are not - // included in the request payload - // even if they are checked - // 'activated' => 0, ]); - $this->assertTrue($admin->refresh()->activated); + $this->assertEquals(1, $admin->refresh()->activated); } } diff --git a/tests/Support/CustomTestMacros.php b/tests/Support/CustomTestMacros.php index 4242b28653..555a20bf2d 100644 --- a/tests/Support/CustomTestMacros.php +++ b/tests/Support/CustomTestMacros.php @@ -74,5 +74,18 @@ trait CustomTestMacros return $this; } ); + + TestResponse::macro( + 'assertStatusMessageIs', + function (string $message) { + Assert::assertEquals( + $message, + $this['status'], + "Response status message was not {$message}" + ); + + return $this; + } + ); } } diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php index 17f8af23d2..d5aad59ada 100644 --- a/tests/Support/Settings.php +++ b/tests/Support/Settings.php @@ -3,6 +3,7 @@ namespace Tests\Support; use App\Models\Setting; +use Illuminate\Support\Facades\Crypt; class Settings { @@ -61,10 +62,62 @@ class Settings return $this->update([ 'auto_increment_assets' => 1, 'auto_increment_prefix' => 'ABCD', - 'next_auto_tag_base' => '123', + 'next_auto_tag_base' => 123, 'zerofill_count' => 5 ]); + } + public function disableAutoIncrement(): Settings + { + return $this->update([ + 'auto_increment_assets' => 0, + 'auto_increment_prefix' => 0, + 'next_auto_tag_base' => 0, + 'zerofill_count' => 0 + ]); + } + + public function enableUniqueSerialNumbers(): Settings + { + return $this->update(['unique_serial' => 1]); + } + + public function disableUniqueSerialNumbers(): Settings + { + return $this->update(['unique_serial' => 0]); + } + + public function enableLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', + 'ldap_uname' => 'fake_username', + 'ldap_pword' => Crypt::encrypt("fake_password"), + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); + } + + public function enableAnonymousLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', +// 'ldap_uname' => 'fake_username', + 'ldap_pword' => Crypt::encrypt("fake_password"), + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); + } + + public function enableBadPasswordLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', + 'ldap_uname' => 'fake_username', + 'ldap_pword' => "badly_encrypted_password!", + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); } /** diff --git a/tests/Unit/AssetMaintenanceTest.php b/tests/Unit/AssetMaintenanceTest.php index b5ee25cf2b..69c4c30938 100644 --- a/tests/Unit/AssetMaintenanceTest.php +++ b/tests/Unit/AssetMaintenanceTest.php @@ -46,7 +46,5 @@ class AssetMaintenanceTest extends TestCase $this->assertTrue($c->completion_date === null); $c->completion_date = '0000-00-00'; $this->assertTrue($c->completion_date === null); - $c->completion_date = '2017-05-12'; - $this->assertTrue($c->completion_date == Carbon::parse('2017-05-12')); } } diff --git a/tests/Unit/AssetModelTest.php b/tests/Unit/AssetModelTest.php index 8771187b2a..845e7fce90 100644 --- a/tests/Unit/AssetModelTest.php +++ b/tests/Unit/AssetModelTest.php @@ -22,19 +22,16 @@ class AssetModelTest extends TestCase public function testAnAssetModelContainsAssets() { - $category = Category::factory()->create( - ['category_type' => 'asset'] - ); + $category = Category::factory()->create([ + 'category_type' => 'asset' + ]); $model = AssetModel::factory()->create([ 'category_id' => $category->id, ]); - $asset = Asset::factory() - ->create( - [ + $asset = Asset::factory()->create([ 'model_id' => $model->id - ] - ); + ]); $this->assertEquals(1, $model->assets()->count()); } } diff --git a/tests/Unit/Helpers/HelperTest.php b/tests/Unit/Helpers/HelperTest.php new file mode 100644 index 0000000000..0b5fba986c --- /dev/null +++ b/tests/Unit/Helpers/HelperTest.php @@ -0,0 +1,19 @@ +assertIsString(Helper::defaultChartColors(1000)); + } + + public function testDefaultChartColorsMethodHandlesNegativeNumbers() + { + $this->assertIsString(Helper::defaultChartColors(-1)); + } +} diff --git a/tests/Unit/LdapTest.php b/tests/Unit/LdapTest.php new file mode 100644 index 0000000000..bae4f3ff4c --- /dev/null +++ b/tests/Unit/LdapTest.php @@ -0,0 +1,210 @@ +settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + + $blah = Ldap::connectToLdap(); + $this->assertEquals('hello',$blah,"LDAP_connect should return 'hello'"); + } + + // other test cases - with/without client-side certs? + // with/without LDAP version 3? + // with/without ignore cert validation? + // test (and mock) ldap_start_tls() ? + + public function testBindAdmin() + { + $this->settings->enableLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBindBad() + { + $this->settings->enableLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false); + $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + $this->expectExceptionMessage("Could not bind to LDAP:"); + + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + // other test cases - test donked password? + + public function testAnonymousBind() + { + //todo - would be nice to introspect somehow to make sure the right parameters were passed? + $this->settings->enableAnonymousLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBadAnonymousBind() + { + $this->settings->enableAnonymousLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false); + $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + $this->expectExceptionMessage("Could not bind to LDAP:"); + + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBadEncryptedPassword() + { + $this->settings->enableBadPasswordLdap(); + + $this->expectExceptionMessage("Your app key has changed"); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testFindAndBind() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_first_entry")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_get_attributes")->expects($this->once())->willReturn( + [ + "count" => 1, + 0 => [ + 'sn' => 'Surname', + 'firstName' => 'FirstName' + ] + ] + ); + + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertEqualsCanonicalizing(["count" =>1,0 =>['sn' => 'Surname','firstname' => 'FirstName']],$results); + } + + public function testFindAndBindBadPassword() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + // note - we return FALSE first, to simulate a bad-bind, then TRUE the second time to simulate a successful admin bind + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->exactly(2))->willReturn(false, true); + +// $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + + +// $this->expectExceptionMessage("exception"); + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertFalse($results); + } + + public function testFindAndBindCannotFindSelf() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(false); + + $this->expectExceptionMessage("Could not search LDAP:"); + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertFalse($results); + } + + //maybe should do an AD test as well? + + public function testFindLdapUsers() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(["stuff"]); + + $this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->once())->willReturn(["count" => 1]); + + $results = Ldap::findLdapUsers(); + + $this->assertEqualsCanonicalizing(["count" => 1], $results); + } + + public function testFindLdapUsersPaginated() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->exactly(2))->willReturn(["stuff"]); + + $this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->exactly(2))->willReturnCallback( + function ($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, &$controls) { + static $count = 0; + if($count == 0) { + $count++; + $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] = "cookie"; + return ["count" => 1]; + } else { + $controls = []; + return ["count" => 1]; + } + + } + ); + + $this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->exactly(2))->willReturn(["count" => 1]); + + $results = Ldap::findLdapUsers(); + + $this->assertEqualsCanonicalizing(["count" => 2], $results); + } + +} diff --git a/webpack.mix.js b/webpack.mix.js index babab0fad6..fdda6618ab 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -17,7 +17,6 @@ mix "./node_modules/bootstrap/dist/css/bootstrap.css", "./node_modules/@fortawesome/fontawesome-free/css/all.css", "./public/css/build/AdminLTE.css", - "./node_modules/jquery-ui-bundle/jquery-ui.css", "./node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.standalone.css", "./node_modules/bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css", "./node_modules/blueimp-file-upload/css/jquery.fileupload.css", @@ -143,6 +142,7 @@ mix [ "./node_modules/bootstrap-table/dist/bootstrap-table.css", "./node_modules/bootstrap-table/dist/extensions/sticky-header/bootstrap-table-sticky-header.css", + "./resources/assets/css/dragtable.css", ], "public/css/dist/bootstrap-table.css" ) @@ -158,7 +158,7 @@ mix.combine( "./node_modules/select2/dist/js/select2.full.min.js", "./node_modules/admin-lte/dist/js/adminlte.min.js", "./node_modules/tether/dist/js/tether.js", - "./node_modules/jquery-ui-bundle/jquery-ui.js", + "./node_modules/jquery-ui/dist/jquery-ui.js", "./node_modules/jquery-slimscroll/jquery.slimscroll.js", "./node_modules/jquery.iframe-transport/jquery.iframe-transport.js", "./node_modules/blueimp-file-upload/js/jquery.fileupload.js", @@ -170,6 +170,7 @@ mix.combine( "./resources/assets/js/signature_pad.js", "./node_modules/jquery-form-validator/form-validator/jquery.form-validator.js", //problem? "./node_modules/list.js/dist/list.js", + "./node_modules/clipboard/dist/clipboard.js", ], "public/js/build/vendor.js" // this file seems OK! ); @@ -180,10 +181,13 @@ mix.combine( mix .combine( [ + "./resources/assets/js/dragtable.js", './node_modules/bootstrap-table/dist/bootstrap-table.js', + "./resources/assets/js/bootstrap-table-reorder-columns.js", './node_modules/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile.js', './node_modules/bootstrap-table/dist/extensions/export/bootstrap-table-export.js', './node_modules/bootstrap-table/dist/extensions/cookie/bootstrap-table-cookie.js', + './node_modules/bootstrap-table/dist/extensions/sticky-header/bootstrap-table-sticky-header.js', './resources/assets/js/extensions/jquery.base64.js', './node_modules/tableexport.jquery.plugin/tableExport.min.js', './node_modules/tableexport.jquery.plugin/libs/jsPDF/jspdf.umd.min.js',