Merge branch 'develop' into eol_date_range_for_reports

This commit is contained in:
akemidx 2023-11-21 16:07:26 -05:00 committed by GitHub
commit 697595d963
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2367 changed files with 33670 additions and 13838 deletions

View file

@ -2907,6 +2907,87 @@
"contributions": [
"code"
]
},
{
"login": "vikaas-cyper",
"name": "VIKAAS-A",
"avatar_url": "https://avatars.githubusercontent.com/u/74609912?v=4",
"profile": "https://github.com/vikaas-cyper",
"contributions": [
"code"
]
},
{
"login": "ak-piracha",
"name": "Abdul Kareem",
"avatar_url": "https://avatars.githubusercontent.com/u/88882041?v=4",
"profile": "https://github.com/ak-piracha",
"contributions": [
"code"
]
},
{
"login": "NojoudAlshehri",
"name": "NojoudAlshehri",
"avatar_url": "https://avatars.githubusercontent.com/u/111287779?v=4",
"profile": "https://github.com/NojoudAlshehri",
"contributions": [
"code"
]
},
{
"login": "stefanstidlffg",
"name": "Stefan Stidl",
"avatar_url": "https://avatars.githubusercontent.com/u/54367449?v=4",
"profile": "https://github.com/stefanstidlffg",
"contributions": [
"code"
]
},
{
"login": "qay21",
"name": "Quentin Aymard",
"avatar_url": "https://avatars.githubusercontent.com/u/87803479?v=4",
"profile": "https://github.com/qay21",
"contributions": [
"code"
]
},
{
"login": "cram42",
"name": "Grant Le Roux",
"avatar_url": "https://avatars.githubusercontent.com/u/5396871?v=4",
"profile": "https://github.com/cram42",
"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"
]
}
]
}

View file

@ -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

View file

@ -85,6 +85,7 @@ COOKIE_NAME=snipeit_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
@ -148,6 +149,7 @@ AWS_DEFAULT_REGION=null
# --------------------------------------------
LOGIN_MAX_ATTEMPTS=5
LOGIN_LOCKOUT_DURATION=60
LOGIN_AUTOCOMPLETE=false
# --------------------------------------------
# OPTIONAL: FORGOTTEN PASSWORD SETTINGS

View file

@ -2,8 +2,6 @@ name: Feature Request
description: Suggest an idea for this project
title: "[Feature Request]: "
labels: ["feature request"]
assignees:
- snipe
body:
- type: textarea
attributes:

View file

@ -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

View file

@ -2,5 +2,6 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "develop"
schedule:
interval: "weekly"

View file

@ -26,7 +26,7 @@ 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

73
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,73 @@
name: Tests
on:
push:
branches:
- master
- develop
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: snipeit
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1.1"
name: PHP ${{ matrix.php-version }}
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v4
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Copy .env
run: |
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: mysql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: php artisan test --parallel

2
.gitignore vendored
View file

@ -1,8 +1,6 @@
.couscous
.DS_Store
.env
.env.dusk.*
!.env.dusk.example
.env.testing
phpstan.neon
.idea

View file

@ -38,6 +38,7 @@ gcc \
make \
autoconf \
libc-dev \
libldap-common \
pkg-config \
libmcrypt-dev \
php8.1-dev \

View file

@ -1,5 +1,5 @@
![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-320-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)
[![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-329-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 - Open Source Asset Management System
@ -144,7 +144,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -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.

View file

@ -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.
@ -83,7 +83,16 @@ class LdapSync extends Command
$summary = [];
try {
if ($this->option('base_dn') != '') {
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.'\".');
}
}
else if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
@ -106,17 +115,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 +193,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) {
@ -212,9 +221,12 @@ class LdapSync extends Command
$item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
$item['location'] = $results[$i][$ldap_result_location][0] ?? '';
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_result_location && $item['location']) {
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
}
$department = Department::firstOrCreate([
'name' => $item['department'],
]);
@ -226,22 +238,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;
$user->location_id = $location->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']])) {
@ -281,6 +315,7 @@ class LdapSync extends Command
}
}
}
// Sync activated state for Active Directory.
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....

View file

@ -38,7 +38,14 @@ class SystemBackup extends Command
public function handle()
{
if ($this->option('filename')) {
$this->call('backup:run', ['--filename' => $this->option('filename')]);
$filename = $this->option('filename');
// Make sure the filename ends in .zip
if (!ends_with($filename, '.zip')) {
$filename = $filename.'.zip';
}
$this->call('backup:run', ['--filename' => $filename]);
} else {
$this->call('backup:run');
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -75,7 +75,12 @@ class Handler extends ExceptionHandler
// Handle SCIM exceptions
if ($e instanceof SCIMException) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Invalid SCIM Request'), 400);
try {
$e->report(); // logs as 'debug', so shouldn't get too noisy
} catch(\Exception $reportException) {
//do nothing
}
return $e->render($request); // ALL SCIMExceptions have the 'render()' method
}
// Handle standard requests that fail because Carbon cannot parse the date on validation (when a submitted date value is definitely not a date)
@ -145,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.

View file

@ -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;
@ -33,6 +35,16 @@ class Helper
}
}
public static function parseEscapedMarkedownInline($str = null)
{
$Parsedown = new \Parsedown();
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->line($str);
}
}
/**
* The importer has formatted number strings since v3,
* so the value might be a string, or an integer.
@ -61,10 +73,14 @@ class Helper
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @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',
@ -337,7 +353,19 @@ class Helper
$total_colors = count($colors);
if ($index >= $total_colors) {
$index = $index - $total_colors;
\Log::error('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];
@ -543,8 +571,8 @@ class Helper
'license' => trans('general.license'),
];
if($selection != null){
return $category_types[$selection];
if ($selection != null){
return $category_types[strtolower($selection)];
}
else
return $category_types;
@ -633,6 +661,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 = [];
@ -695,6 +724,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;
}
@ -1210,10 +1261,60 @@ class Helper
return true;
\Log::debug('app locked!');
}
return false;
}
/**
* Conversion between units of measurement
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @since 5.0
* @param float $value Measurement value to convert
* @param string $srcUnit Source unit of measurement
* @param string $dstUnit Destination unit of measurement
* @param int $round Round the result to decimals (Default false - No rounding)
* @return float
*/
public static function convertUnit($value, $srcUnit, $dstUnit, $round=false) {
$srcFactor = static::getUnitConversionFactor($srcUnit);
$dstFactor = static::getUnitConversionFactor($dstUnit);
$output = $value * $srcFactor / $dstFactor;
return ($round !== false) ? round($output, $round) : $output;
}
/**
* Get conversion factor from unit of measurement to mm
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @since 5.0
* @param string $unit Unit of measurement
* @return float
*/
public static function getUnitConversionFactor($unit) {
switch (strtolower($unit)) {
case 'mm':
return 1.0;
case 'cm':
return 10.0;
case 'm':
return 1000.0;
case 'in':
return 25.4;
case 'ft':
return 12 * static::getUnitConversionFactor('in');
case 'yd':
return 3 * static::getUnitConversionFactor('ft');
case 'pt':
return (1 / 72) * static::getUnitConversionFactor('in');
default:
throw new \InvalidArgumentException('Unit: \'' . $unit . '\' is not supported');
return false;
}
}
/*
* 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,

View file

@ -126,12 +126,13 @@ class AccessoriesController extends Controller
public function getClone($accessoryId = null)
{
$this->authorize('create', Accesory::class);
$this->authorize('create', Accessory::class);
// Check if the asset exists
if (is_null($accessory_to_clone = Accessory::find($accessoryId))) {
// Redirect to the asset management page
return redirect()->route('accessory.index')->with('error', trans('admin/accessories/message.does_not_exist'));
return redirect()->route('accessories.index')
->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryId]));
}
$accessory = clone $accessory_to_clone;

View file

@ -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);
}
}
}

View file

@ -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'));
@ -121,7 +121,6 @@ class AcceptanceController extends Controller
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
$sig_filename='';
if ($request->input('asset_acceptance') == 'accepted') {
/**
@ -153,7 +152,6 @@ class AcceptanceController extends Controller
}
}
// this is horrible
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
@ -170,7 +168,7 @@ class AcceptanceController extends Controller
$pdf_view_route ='account.accept.accept-accessory-eula';
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($item->assignedTo);
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
@ -247,17 +245,51 @@ 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();
// This is the most horriblest
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
$asset_model = AssetModel::find($item->model_id);
$display_model = $asset_model->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Accessory':
$assigned_to = User::find($item->assignedTo);
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
@ -269,6 +301,8 @@ class AcceptanceController extends Controller
break;
case 'App\Models\Consumable':
$consumable = Consumable::find($item->id);
$display_model = $consumable->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
}
@ -277,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));
@ -292,4 +333,4 @@ class AcceptanceController extends Controller
return redirect()->to('account/accept')->with('success', $return_msg);
}
}
}

View file

@ -150,7 +150,7 @@ class AccessoriesController extends Controller
public function show($id)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::findOrFail($id);
$accessory = Accessory::withCount('users as users_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
@ -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()) {

View file

@ -71,7 +71,8 @@ class AssetMaintenancesController extends Controller
'asset_tag',
'asset_name',
'user_id',
'supplier'
'supplier',
'is_warranty',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';

View file

@ -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',
@ -63,7 +66,7 @@ class AssetModelsController extends Controller
'models.deleted_at',
'models.updated_at',
])
->with('category', 'depreciation', 'manufacturer', 'fieldset')
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues')
->withCount('assets as assets_count');
if ($request->input('status')=='deleted') {
@ -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;

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
@ -33,6 +34,7 @@ use TCPDF;
use Validator;
use Route;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@ -48,7 +50,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $audit = null)
{
@ -115,7 +117,7 @@ class AssetsController extends Controller
$allowed_columns[] = $field->db_column_name();
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
@ -125,6 +127,8 @@ class AssetsController extends Controller
$assets->InModelList($non_deprecable_models->toArray());
}
// These are used by the API to query against specific ID numbers.
// They are also used by the individual searches on detail pages like
// locations, etc.
@ -136,6 +140,101 @@ class AssetsController extends Controller
}
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
}
}
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
// related to fulltext searches on complex queries.
// I am sad. :(
switch ($request->input('status')) {
case 'Deleted':
$assets->onlyTrashed();
break;
case 'Pending':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 1)
->where('status_alias.archived', '=', 0);
});
break;
case 'RTD':
$assets->whereNull('assets.assigned_to')
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Undeployable':
$assets->Undeployable();
break;
case 'Archived':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 1);
});
break;
case 'Requestable':
$assets->where('assets.requestable', '=', 1)
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Deployed':
// more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->whereNotNull('assets.assigned_to');
break;
case 'byod':
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
// sidebar nav links a little less chaotic
$assets->where('assets.byod', '=', '1');
break;
default:
if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) {
// terrible workaround for complex-query Laravel bug in fulltext
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.archived', '=', 0);
});
// If there is a status ID, don't take show_archived_in_list into consideration
} else {
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id');
});
}
}
// Leave these under the TextSearch scope, else the fuzziness will override the specific ID (status ID, etc) requested
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
}
@ -197,110 +296,10 @@ class AssetsController extends Controller
$assets->where('assets.byod', '=', $request->input('byod'));
}
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
// 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'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
}
if ($request->filled('order_number')) {
$assets->where('assets.order_number', '=', strval($request->get('order_number')));
}
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
// related to fulltext searches on complex queries.
// I am sad. :(
switch ($request->input('status')) {
case 'Deleted':
$assets->onlyTrashed();
break;
case 'Pending':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 1)
->where('status_alias.archived', '=', 0);
});
break;
case 'RTD':
$assets->whereNull('assets.assigned_to')
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Undeployable':
$assets->Undeployable();
break;
case 'Archived':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 1);
});
break;
case 'Requestable':
$assets->where('assets.requestable', '=', 1)
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Deployed':
// more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->where('assets.assigned_to', '>', '0');
break;
case 'byod':
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
// sidebar nav links a little less chaotic
$assets->where('assets.byod', '=', '1');
break;
default:
if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) {
// terrible workaround for complex-query Laravel bug in fulltext
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.archived', '=', 0);
});
// If there is a status ID, don't take show_archived_in_list into consideration
} else {
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id');
});
}
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
// API passes custom field ordering as custom_fields.fieldname, and we have to strip
// that out to let the default sorter below order them correctly on the assets table.
@ -310,7 +309,8 @@ class AssetsController extends Controller
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
@ -347,6 +347,10 @@ 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() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
@ -441,7 +445,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
@ -472,12 +476,12 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
* @return \Illuminate\Http\JsonResponse
*/
public function selectlist(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select([
$assets = Asset::select([
'assets.id',
'assets.name',
'assets.asset_tag',
@ -485,7 +489,7 @@ class AssetsController extends Controller
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id',
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets');
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
@ -528,12 +532,10 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function store(ImageUploadRequest $request)
public function store(StoreAssetRequest $request)
{
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
@ -543,7 +545,8 @@ class AssetsController extends Controller
$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());
$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';
@ -573,9 +576,6 @@ class AssetsController extends Controller
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
if (!$model) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 200);
}
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
@ -639,7 +639,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@ -666,10 +666,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 +721,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
@ -749,7 +750,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v5.1.18]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function restore(Request $request, $assetId = null)
{
@ -789,7 +790,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 +806,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
@ -889,7 +890,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkin(Request $request, $asset_id)
{
@ -905,6 +906,7 @@ class AssetsController extends Controller
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@ -924,10 +926,14 @@ 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')));
}
@ -940,7 +946,7 @@ class AssetsController extends Controller
*
* @author [A. Janes] [<ajanes@adagiohealth.org>]
* @since [v6.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkinByTag(Request $request, $tag = null)
{
@ -966,7 +972,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function audit(Request $request)
@ -1027,23 +1033,54 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')->requestableAssets();
$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','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);
@ -1051,17 +1088,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();

View file

@ -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';

View file

@ -27,6 +27,9 @@ class CompaniesController extends Controller
$allowed_columns = [
'id',
'name',
'phone',
'fax',
'email',
'created_at',
'updated_at',
'users_count',
@ -47,9 +50,13 @@ class CompaniesController extends Controller
$companies->where('name', '=', $request->input('name'));
}
if ($request->filled('email')) {
$companies->where('email', '=', $request->input('email'));
}
// 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');
@ -166,6 +173,7 @@ class CompaniesController extends Controller
$companies = Company::select([
'companies.id',
'companies.name',
'companies.email',
'companies.image',
]);

View file

@ -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
{
@ -44,9 +45,8 @@ class ComponentsController extends Controller
'notes',
];
$components = Company::scopeCompanyables(Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier'));
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier');
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
@ -77,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';
@ -204,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);
}
@ -236,7 +253,7 @@ class ComponentsController extends Controller
$this->authorize('checkout', $component);
$validator = Validator::make($request->all(), [
'asset_id' => 'required|exists:assets,id',
'assigned_to' => 'required|exists:assets,id',
'assigned_qty' => "required|numeric|min:1|digits_between:1,".$component->numRemaining(),
]);
@ -246,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')])));
}

View file

@ -45,11 +45,8 @@ class ConsumablesController extends Controller
'notes',
];
$consumables = Company::scopeCompanyables(
Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer')
);
$consumables = Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
@ -89,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'];
@ -269,6 +266,14 @@ class ConsumablesController extends Controller
\Log::debug('No enough remaining');
}
// Make sure there is a valid category
if (!$consumable->category){
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
return redirect()->route('consumables.index')->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
}
// 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

View file

@ -27,16 +27,18 @@ 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',
'departments.fax',
'departments.location_id',
'departments.company_id',
'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'));
@ -59,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';

View file

@ -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';

View file

@ -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')));

View file

@ -160,7 +160,7 @@ class ImportController extends Controller
// Run a backup immediately before processing
if ($request->get('run-backup')) {
\Log::debug('Backup manually requested via importer');
Artisan::call('backup:run');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
} else {
\Log::debug('NO BACKUP requested via importer');
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\LabelsTransformer;
use App\Models\Labels\Label;
use Illuminate\Http\Request;
use Illuminate\Support\ItemNotFoundException;
use Auth;
class LabelsController extends Controller
{
/**
* Returns JSON listing of all labels.
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('view', Label::class);
$labels = Label::find();
if ($request->filled('search')) {
$search = $request->get('search');
$labels = $labels->filter(function ($label, $index) use ($search) {
return stripos($label->getName(), $search) !== false;
});
}
$total = $labels->count();
$offset = $request->get('offset', 0);
$offset = ($offset > $total) ? $total : $offset;
$maxLimit = config('app.max_results');
$limit = $request->get('limit', $maxLimit);
$limit = ($limit > $maxLimit) ? $maxLimit : $limit;
$labels = $labels->skip($offset)->take($limit);
return (new LabelsTransformer)->transformLabels($labels, $total, $request);
}
/**
* Returns JSON with information about a label for detail view.
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @param string $labelName
* @return JsonResponse
*/
public function show(string $labelName)
{
$labelName = str_replace('/', '\\', $labelName);
try {
$label = Label::find($labelName);
} catch(ItemNotFoundException $e) {
return response()
->json(
Helper::formatStandardApiResponse('error', null, trans('admin/labels/message.does_not_exist')),
404
);
}
$this->authorize('view', $label);
return (new LabelsTransformer)->transformLabel($label);
}
}

View file

@ -41,7 +41,12 @@ 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;
}
$limit = app('api_limit_value');
$seats = $seats->skip($offset)->take($limit)->get();

View file

@ -26,8 +26,8 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count'));
$licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) {
$licenses->where('company_id', '=', $request->input('company_id'));
@ -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';

View file

@ -37,6 +37,8 @@ class LocationsController extends Controller
'locations.city',
'locations.state',
'locations.zip',
'locations.phone',
'locations.fax',
'locations.country',
'locations.parent_id',
'locations.manager_id',
@ -79,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';
@ -251,8 +253,12 @@ class LocationsController extends Controller
*/
public function selectlist(Request $request)
{
$this->authorize('view.selectlists');
// If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location.
// Otherwise, we do our normal check that they can view select lists.
$request->headers->get('referer') === route('profile')
? $this->authorize('self.edit_location')
: $this->authorize('view.selectlists');
$locations = Location::select([
'locations.id',

View file

@ -62,7 +62,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';

View file

@ -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';

View file

@ -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;

View file

@ -56,7 +56,7 @@ class ReportsController extends Controller
// 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';

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset;
use App\Models\Statuslabel;
@ -51,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';
@ -291,4 +292,45 @@ class StatuslabelsController extends Controller
return '0';
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @see \App\Http\Transformers\SelectlistTransformer
*/
public function selectlist(Request $request)
{
$this->authorize('view.selectlists');
$statuslabels = Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc');
if ($request->filled('search')) {
$statuslabels = $statuslabels->where('name', 'LIKE', '%'.$request->get('search').'%');
}
if ($request->filled('deployable')) {
$statuslabels = $statuslabels->where('deployable', '=', '1');
}
if ($request->filled('pending')) {
$statuslabels = $statuslabels->where('pending', '=', '1');
}
if ($request->filled('archived')) {
$statuslabels = $statuslabels->where('archived', '=', '1');
}
$statuslabels = $statuslabels->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($statuslabels as $statuslabel) {
$statuslabels->use_text = $statuslabel->name;
}
return (new SelectlistTransformer)->transformSelectlist($statuslabels);
}
}

View file

@ -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';

View file

@ -75,15 +75,8 @@ 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('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated'));
}
@ -199,7 +192,7 @@ 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'));
$offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
$limit = app('api_limit_value');
@ -272,6 +265,14 @@ class UsersController extends Controller
break;
}
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
$users = Company::scopeCompanyables($users);
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@ -362,8 +363,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');

View file

@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\AssetModel;
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 +77,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');
@ -153,6 +155,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');
@ -171,8 +174,20 @@ class AssetModelsController extends Controller
}
}
}
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'));
}
@ -286,6 +301,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);
}

View file

@ -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);

View file

@ -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;
@ -94,18 +95,25 @@ class AssetCheckinController extends Controller
\Log::debug('Manually override the location IDs');
\Log::debug('Original Location ID: '.$asset->location_id);
$asset->location_id = '';
\Log::debug('New RTD Location ID: '.$asset->location_id);
\Log::debug('New Location ID: '.$asset->location_id);
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
\Log::debug('NEW Location ID: '.$request->get('location_id'));
$asset->location_id = e($request->get('location_id'));
$asset->location_id = $request->get('location_id');
if ($request->get('update_default_location') == 0){
$asset->rtd_location_id = $request->get('location_id');
}
}
$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');
}
@ -128,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'));

View file

@ -89,6 +89,15 @@ class AssetCheckoutController extends Controller
}
}
$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, e($request->get('note')), $request->get('name'))) {
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
}

View file

@ -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);

View file

@ -6,32 +6,27 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use Illuminate\Support\Facades\Log;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CheckoutRequest;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
use Auth;
use Illuminate\Support\Facades\Auth;
use App\View\Label;
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
@ -142,7 +137,7 @@ class AssetsController extends Controller
$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);
@ -171,9 +166,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 {
@ -209,12 +204,8 @@ 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));
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' => $asset->asset_tag]));
}
@ -296,10 +287,10 @@ class AssetsController extends Controller
/**
* Validate and process asset edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v1.0]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse|Redirect
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function update(ImageUploadRequest $request, $assetId = null)
{
@ -313,9 +304,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);
@ -324,6 +333,12 @@ class AssetsController extends Controller
$asset->rtd_location_id = $request->input('rtd_location_id', null);
$asset->byod = $request->input('byod', 0);
$status = Statuslabel::find($asset->status_id);
if($status->archived){
$asset->assigned_to = null;
}
if ($asset->assigned_to == '') {
$asset->location_id = $request->input('rtd_location_id', null);
}
@ -334,7 +349,7 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::info($e);
Log::info($e);
}
}
@ -362,9 +377,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 {
@ -412,7 +427,7 @@ class AssetsController extends Controller
try {
Storage::disk('public')->delete('assets'.'/'.$asset->image);
} catch (\Exception $e) {
\Log::debug($e);
Log::debug($e);
}
}
@ -446,11 +461,12 @@ class AssetsController extends Controller
* @since [v3.0]
* @return Redirect
*/
public function getAssetByTag(Request $request)
public function getAssetByTag(Request $request, $tag=null)
{
$tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
if (! $asset = Asset::where('asset_tag', '=', $request->get('assetTag'))->first()) {
if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('view', $asset);
@ -526,7 +542,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');
}
@ -547,9 +563,11 @@ class AssetsController extends Controller
$asset = Asset::find($assetId);
$this->authorize('view', $asset);
return view('hardware/labels')
->with('assets', Asset::find($asset))
return (new Label())
->with('assets', collect([ $asset ]))
->with('settings', Setting::getSettings())
->with('template', request()->get('template'))
->with('offset', request()->get('offset'))
->with('bulkedit', false)
->with('count', 0);
}
@ -767,7 +785,7 @@ class AssetsController extends Controller
}
/**
* Retore a deleted asset.
* Restore a deleted asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
@ -845,7 +863,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()));
@ -862,7 +880,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');
}

View file

@ -8,11 +8,13 @@ use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Setting;
use App\View\Label;
use Illuminate\Http\Request;
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
{
@ -30,7 +32,7 @@ class BulkAssetsController extends Controller
public function edit(Request $request)
{
$this->authorize('view', Asset::class);
if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
@ -40,13 +42,30 @@ class BulkAssetsController extends Controller
session(['bulk_back_url' => $bulk_back_url]);
$asset_ids = array_values(array_unique($request->input('ids')));
//custom fields logic
$asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) {
return $query->where('fieldset_id', '!=', null);
})->get();
$models = $asset_custom_field->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 view('hardware/labels')
->with('assets', Asset::find($asset_ids))
$assets_found = Asset::find($asset_ids);
if ($assets_found->isEmpty()){
return redirect()->back();
}
return (new Label)
->with('assets', $assets_found)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
@ -73,7 +92,9 @@ class BulkAssetsController extends Controller
$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);
}
}
@ -91,6 +112,8 @@ 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');
@ -98,13 +121,21 @@ class BulkAssetsController extends Controller
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
if (! $request->filled('ids') || count($request->input('ids')) <= 0) {
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if (Session::exists('ids')) {
$assets = Session::get('ids');
} elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
$assets = array_keys($request->input('ids'));
if ($request->anyFilled($custom_field_columns)) {
$custom_fields_present = true;
} else {
$custom_fields_present = false;
}
if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
@ -120,6 +151,7 @@ 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) {
@ -128,13 +160,15 @@ class BulkAssetsController extends Controller
$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);
}
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
@ -152,6 +186,7 @@ class BulkAssetsController extends Controller
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
}
if ($request->filled('company_id')) {
$this->update_array['company_id'] = $request->input('company_id');
if ($request->input('company_id') == 'clear') {
@ -160,40 +195,110 @@ class BulkAssetsController extends Controller
}
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');
}
}
$changed = [];
$asset = Asset::where('id' ,$assetId)->get();
$asset = Asset::find($assetId);
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];
}
}
if ($custom_fields_present) {
$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');
$model = $asset->model()->first();
DB::table('assets')
->where('id', $assetId)
->update($this->update_array);
} // endforeach
// Use the rules of the new model fieldsets if the model changed
if ($request->filled('model_id')) {
$this->update_array['model_id'] = $request->input('model_id');
$model = \App\Models\AssetModel::find($request->input('model_id'));
}
// Make sure this model is valid
$assetCustomFields = ($model) ? $model->fieldset : null;
if ($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted=='1')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
/*
* Check if the decrypted existing value is different from one we just submitted
* and if not, pull it out of the object since it shouldn't really be updating at all.
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
* different times will have different values, so it will *look* like it was updated
* but it wasn't.
*/
if ($decrypted_old != $this->update_array[$field->db_column]) {
$asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
} else {
/*
* 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
} // end custom field check
} // end custom fields handler
// 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'));
}

View file

@ -191,9 +191,11 @@ 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.

View file

@ -60,6 +60,9 @@ final class CompaniesController extends Controller
$company = new Company;
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company = $request->handleImages($company);
@ -111,6 +114,9 @@ final class CompaniesController extends Controller
$this->authorize('update', $company);
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company = $request->handleImages($company);
@ -119,8 +125,7 @@ final class CompaniesController extends Controller
->with('success', trans('admin/companies/message.update.success'));
}
return redirect()->route('companies.edit', ['company' => $companyId])
->with('error', trans('admin/companies/message.update.error'));
return redirect()->back()->withInput()->withErrors($company->getErrors());
}
/**

View file

@ -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'));

View file

@ -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);
}
}
}
}

View file

@ -34,6 +34,11 @@ class ConsumableCheckoutController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
// Make sure there is a valid category
if (!$consumable->category){
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
}
$this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable'));

View file

@ -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);
}
}
}

View file

@ -109,6 +109,8 @@ class CustomFieldsController extends Controller
"is_unique" => $request->get("is_unique", 0),
"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()
]);
@ -265,6 +267,8 @@ class CustomFieldsController extends Controller
$field->is_unique = $request->get("is_unique", 0);
$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'));

View file

@ -94,7 +94,7 @@ class CustomFieldsetsController extends Controller
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset([
'name' => e($request->get('name')),
'name' => $request->get('name'),
'user_id' => Auth::user()->id,
]);

View file

@ -170,6 +170,8 @@ class DepartmentsController extends Controller
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->phone = $request->input('phone');
$department->fax = $request->input('fax');
$department = $request->handleImages($department);

View file

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;
use App\Models\Setting;
class GoogleAuthController extends Controller
{
/**
* We need this constructor so that we override the socialite expected config variables,
* since we want to allow this to be changed via database fields
*/
public function __construct()
{
parent::__construct();
$setting = Setting::getSettings();
config(['services.google.redirect' => config('app.url').'/google/callback']);
config(['services.google.client_id' => $setting->google_client_id]);
config(['services.google.client_secret' => $setting->google_client_secret]);
}
public function redirectToGoogle()
{
return Socialite::driver('google')->redirect();
}
public function handleGoogleCallback()
{
try {
$socialUser = Socialite::driver('google')->user();
\Log::debug('Google user found in Google Workspace');
} catch (InvalidStateException $exception) {
\Log::debug('Google user NOT found in Google Workspace');
return redirect()->route('login')
->withErrors(
[
'username' => [
trans('auth/general.google_login_failed')
],
]
);
}
$user = User::where('username', $socialUser->getEmail())->first();
if ($user) {
\Log::debug('Google user '.$socialUser->getEmail().' found in Snipe-IT');
$user->update([
'avatar' => $socialUser->avatar,
]);
Auth::login($user, true);
return redirect()->route('home');
}
\Log::debug('Google user '.$socialUser->getEmail().' NOT found in Snipe-IT');
return redirect()->route('login')
->withErrors(
[
'username' => [
trans('auth/general.google_login_failed'),
],
]
);
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers;
use App\Models\Asset;
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;
class LabelsController extends Controller
{
/**
* Returns the Label view with test data
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @param string $labelName
* @return \Illuminate\Contracts\View\View
*/
public function show(string $labelName)
{
$labelName = str_replace('/', '\\', $labelName);
$template = Label::find($labelName);
$exampleAsset = new Asset();
$exampleAsset->id = 999999;
$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([
'name' => 'Test Company Limited',
'phone' => '1-555-555-5555',
'email' => 'company@example.com',
]);
$exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker']));
$exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']);
$exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']);
$exampleAsset->model = new AssetModel();
$exampleAsset->model->id = 999999;
$exampleAsset->model->name = 'Test Model';
$exampleAsset->model->model_number = 'MDL5678';
$exampleAsset->model->manufacturer = new Manufacturer();
$exampleAsset->model->manufacturer->id = 999999;
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
$exampleAsset->model->manufacturer->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';
$settings = Setting::getSettings();
if (request()->has('settings')) {
$overrides = request()->get('settings');
foreach ($overrides as $key => $value) {
$settings->$key = $value;
}
}
return (new LabelView())
->with('assets', collect([$exampleAsset]))
->with('settings', $settings)
->with('template', $template)
->with('bulkedit', false)
->with('count', 0);
return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist'));
}
}

View file

@ -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')

View file

@ -30,15 +30,17 @@ class LicenseCheckoutController extends Controller
// Check that the license is valid
if ($license = License::find($licenseId)) {
$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');
}
return view('licenses/checkout', compact('license'));
}
$this->authorize('checkout', $license);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
return view('licenses/checkout', compact('license'));
}
/**
@ -61,6 +63,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'));
@ -102,7 +105,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;
}
@ -119,7 +122,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;
}

View file

@ -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);
}
}

View file

@ -207,7 +207,7 @@ class LicensesController extends Controller
if ($license->assigned_seats_count == 0) {
// Delete the license and the associated license seats
DB::table('license_seats')
->where('id', $license->id)
->where('license_id', $license->id)
->update(['assigned_to' => null, 'asset_id' => null]);
$licenseSeats = $license->licenseseats();

View file

@ -79,6 +79,8 @@ class LocationsController extends Controller
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->user_id = Auth::id();
$location->phone = request('phone');
$location->fax = request('fax');
$location = $request->handleImages($location);
@ -139,6 +141,8 @@ class LocationsController extends Controller
$location->state = $request->input('state');
$location->country = $request->input('country');
$location->zip = $request->input('zip');
$location->phone = request('phone');
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');

View file

@ -23,6 +23,7 @@ use Input;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
/**
@ -246,6 +247,9 @@ 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'),
'Changed',
@ -288,6 +292,9 @@ 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,
@ -403,11 +410,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) {
@ -501,7 +509,6 @@ class ReportsController extends Controller
$header[] = trans('general.zip');
}
if ($request->filled('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto');
$header[] = trans('general.type');
@ -527,24 +534,53 @@ 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');
}
if ($request->filled('warranty')) {
$header[] = 'Warranty';
$header[] = 'Warranty Expires';
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = 'Value';
$header[] = 'Diff';
$header[] = 'Fully Depreciated';
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('checkout_date')) {
$header[] = trans('admin/hardware/table.checkout_date');
}
if ($request->filled('checkin_date')) {
$header[] = trans('admin/hardware/table.last_checkin_date');
}
if ($request->filled('expected_checkin')) {
$header[] = trans('admin/hardware/form.expected_checkin');
}
@ -590,28 +626,28 @@ class ReportsController extends Controller
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
\Log::debug('Added headers: '.$executionTime);
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
$assets = Asset::select('assets.*')->with(
'location', 'assetstatus', 'company', 'defaultLoc', 'assignedTo',
'model.category', 'model.manufacturer', 'supplier');
if ($request->filled('by_location_id')) {
$assets->where('assets.location_id', $request->input('by_location_id'));
$assets->whereIn('assets.location_id', $request->input('by_location_id'));
}
if ($request->filled('by_rtd_location_id')) {
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
$assets->whereIn('assets.rtd_location_id', $request->input('by_rtd_location_id'));
}
if ($request->filled('by_supplier_id')) {
$assets->where('assets.supplier_id', $request->input('by_supplier_id'));
$assets->whereIn('assets.supplier_id', $request->input('by_supplier_id'));
}
if ($request->filled('by_company_id')) {
$assets->where('assets.company_id', $request->input('by_company_id'));
$assets->whereIn('assets.company_id', $request->input('by_company_id'));
}
if ($request->filled('by_model_id')) {
$assets->where('assets.model_id', $request->input('by_model_id'));
$assets->whereIn('assets.model_id', $request->input('by_model_id'));
}
if ($request->filled('by_category_id')) {
@ -631,7 +667,7 @@ class ReportsController extends Controller
}
if ($request->filled('by_status_id')) {
$assets->where('assets.status_id', $request->input('by_status_id'));
$assets->whereIn('assets.status_id', $request->input('by_status_id'));
}
if (($request->filled('purchase_start')) && ($request->filled('purchase_end'))) {
@ -639,14 +675,28 @@ class ReportsController extends Controller
}
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
$assets->whereBetween('assets.created_at', [$request->input('created_start'), $request->input('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'))) {
$assets->whereBetween('assets.last_checkout', [$request->input('checkout_date_start'), $request->input('checkout_date_end')]);
$checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
$checkout_end = \Carbon::parse($request->input('checkout_date_end'))->endOfDay();
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
}
if (($request->filled('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('asset_eol_date_start')) && ($request->filled('asset_eol_date_end'))) {
@ -654,7 +704,10 @@ class ReportsController extends Controller
}
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
$assets->whereBetween('assets.last_audit_date', [$request->input('last_audit_start'), $request->input('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();
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
}
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
@ -670,6 +723,7 @@ class ReportsController extends Controller
$assets->onlyTrashed();
}
\Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
@ -808,6 +862,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.')' : '';
}
@ -829,6 +931,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 : '';
}
@ -997,7 +1105,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];
@ -1014,27 +1127,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)

View file

@ -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
@ -590,6 +591,7 @@ class SettingsController extends Controller
$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(),
]);
}
/**
@ -827,6 +830,14 @@ class SettingsController extends Controller
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->label2_enable = $request->input('label2_enable');
$setting->label2_template = $request->input('label2_template');
$setting->label2_title = $request->input('label2_title');
$setting->label2_asset_logo = $request->input('label2_asset_logo');
$setting->label2_1d_type = $request->input('label2_1d_type');
$setting->label2_2d_type = $request->input('label2_2d_type');
$setting->label2_2d_target = $request->input('label2_2d_target');
$setting->label2_fields = $request->input('label2_fields');
$setting->labels_per_page = $request->input('labels_per_page');
$setting->labels_width = $request->input('labels_width');
$setting->labels_height = $request->input('labels_height');
@ -875,7 +886,7 @@ class SettingsController extends Controller
}
if ($setting->save()) {
return redirect()->route('settings.index')
return redirect()->route('settings.labels.index')
->with('success', trans('admin/settings/message.update.success'));
}
@ -1039,6 +1050,48 @@ class SettingsController extends Controller
return $pdf_branding;
}
/**
* Show Google login settings form
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return View
*/
public function getGoogleLoginSettings()
{
$setting = Setting::getSettings();
return view('settings.google', compact('setting'));
}
/**
* ShSaveow Google login settings form
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return View
*/
public function postGoogleLoginSettings(Request $request)
{
if (!config('app.lock_passwords')) {
$setting = Setting::getSettings();
$setting->google_login = $request->input('google_login', 0);
$setting->google_client_id = $request->input('google_client_id');
$setting->google_client_secret = $request->input('google_client_secret');
if ($setting->save()) {
return redirect()->route('settings.index')
->with('success', trans('admin/settings/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($setting->getErrors());
}
return redirect()->back()->with('error', trans('general.feature_disabled'));
}
/**
* Show the listing of backups.
*
@ -1094,7 +1147,7 @@ class SettingsController extends Controller
public function postBackups()
{
if (! config('app.lock_passwords')) {
Artisan::call('backup:run');
Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H-i-s')]);
$output = Artisan::output();
// Backup completed
@ -1197,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()) {
@ -1214,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);
}

View file

@ -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');
}

View file

@ -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']);
}

View file

@ -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]));
}
}

View file

@ -82,7 +82,7 @@ class ViewAssetsController extends Controller
return view('account/requestable-assets', compact('assets', 'models'));
}
public function getRequestItem(Request $request, $itemType, $itemId = null)
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null)
{
$item = null;
$fullItemType = 'App\\Models\\'.studly_case($itemType);
@ -119,16 +119,16 @@ class ViewAssetsController extends Controller
$settings = Setting::getSettings();
if ($item_request = $item->isRequestedBy($user)) {
$item->cancelRequest();
$data['item_quantity'] = $item_request->qty;
if (($item_request = $item->isRequestedBy($user)) || $cancel_by_admin) {
$item->cancelRequest($requestingUser);
$data['item_quantity'] = ($item_request) ? $item_request->qty : 1;
$logaction->logaction('request_canceled');
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
$settings->notify(new RequestAssetCancelation($data));
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
return redirect()->back()->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} else {
$item->request();
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {

View file

@ -0,0 +1,67 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class CategoryEditForm extends Component
{
public $defaultEulaText;
public $eulaText;
public $originalSendCheckInEmailValue;
public $requireAcceptance;
public $sendCheckInEmail;
public $useDefaultEula;
public function mount()
{
$this->originalSendCheckInEmailValue = $this->sendCheckInEmail;
if ($this->eulaText || $this->useDefaultEula) {
$this->sendCheckInEmail = 1;
}
}
public function render()
{
return view('livewire.category-edit-form');
}
public function updated($property, $value)
{
if (! in_array($property, ['eulaText', 'useDefaultEula'])) {
return;
}
$this->sendCheckInEmail = $this->eulaText || $this->useDefaultEula ? 1 : $this->originalSendCheckInEmailValue;
}
public function getShouldDisplayEmailMessageProperty(): bool
{
return $this->eulaText || $this->useDefaultEula;
}
public function getEmailMessageProperty(): string
{
if ($this->useDefaultEula) {
return trans('admin/categories/general.email_will_be_sent_due_to_global_eula');
}
return trans('admin/categories/general.email_will_be_sent_due_to_category_eula');
}
public function getEulaTextDisabledProperty()
{
return (bool)$this->useDefaultEula;
}
public function getSendCheckInEmailDisabledProperty()
{
return $this->eulaText || $this->useDefaultEula;
}
}

View file

@ -38,6 +38,16 @@ class Importer extends Component
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason?
// Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays)
public $accessories_fields;
public $assets_fields;
public $users_fields;
public $licenses_fields;
public $locations_fields;
public $consumables_fields;
public $components_fields;
public $aliases_fields;
protected $rules = [
'files.*.file_path' => 'required|string',
'files.*.created_at' => 'required|string',
@ -57,182 +67,34 @@ class Importer extends Component
return json_encode(array_filter($tmp));
}
// all of these 'statics', alas, may have to change to something else to handle translations?
// I'm not sure. Maybe I use them to 'populate' the translations? TBH, I don't know yet.
static $general = [
'category' => 'Category',
'company' => 'Company',
'email' => 'Email',
'item_name' => 'Item Name',
'location' => 'Location',
'maintained' => 'Maintained',
'manufacturer' => 'Manufacturer',
'notes' => 'Notes',
'order_number' => 'Order Number',
'purchase_cost' => 'Purchase Cost',
'purchase_date' => 'Purchase Date',
'quantity' => 'Quantity',
'requestable' => 'Requestable',
'serial' => 'Serial Number',
'supplier' => 'Supplier',
'username' => 'Username',
'department' => 'Department',
];
static $accessories = [
'model_number' => 'Model Number',
];
static $assets = [
'asset_tag' => 'Asset Tag',
'asset_model' => 'Model Name',
'byod' => 'BYOD',
'checkout_class' => 'Checkout Type',
'checkout_location' => 'Checkout Location',
'image' => 'Image Filename',
'model_number' => 'Model Number',
'full_name' => 'Full Name',
'status' => 'Status',
'warranty_months' => 'Warranty Months',
];
static $consumables = [
'item_no' => "Item Number",
'model_number' => "Model Number",
'min_amt' => "Minimum Quantity",
];
static $licenses = [
'asset_tag' => 'Assigned To Asset',
'expiration_date' => 'Expiration Date',
'full_name' => 'Full Name',
'license_email' => 'Licensed To Email',
'license_name' => 'Licensed To Name',
'purchase_order' => 'Purchase Order',
'reassignable' => 'Reassignable',
'seats' => 'Seats',
];
static $users = [
'employee_num' => 'Employee Number',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'jobtitle' => 'Job Title',
'phone_number' => 'Phone Number',
'manager_first_name' => 'Manager First Name',
'manager_last_name' => 'Manager Last Name',
'activated' => 'Activated',
'address' => 'Address',
'city' => 'City',
'state' => 'State',
'country' => 'Country',
'zip' => 'Zip',
'vip' => 'VIP',
'remote' => 'Remote',
];
static $locations = [
'name' => 'Name',
'address' => 'Address',
'address2' => 'Address 2',
'city' => 'City',
'state' => 'State',
'country' => 'Country',
'zip' => 'Zip',
'currency' => 'Currency',
'ldap_ou' => 'LDAP OU',
'manager_username' => 'Manager Username',
'manager' => 'Manager',
'parent_location' => 'Parent Location',
];
//array of "real fieldnames" to a list of aliases for that field
static $aliases = [
'model_number' =>
[
'model',
'model no',
'model no.',
'model number',
'model num',
'model num.'
],
'warranty_months' =>
[
'Warranty',
'Warranty Months'
],
'qty' =>
[
'QTY',
'Quantity'
],
'zip' =>
[
'Postal Code',
'Post Code'
],
'min_amt' =>
[
'Min Amount',
'Min QTY'
],
'next_audit_date' =>
[
'Next Audit',
],
'address2' =>
[
'Address 2',
'Address2',
],
'ldap_ou' =>
[
'LDAP OU',
'OU',
],
'parent_location' =>
[
'Parent',
'Parent Location',
],
'manager' =>
[
'Managed By',
'Manager Name',
'Manager Full Name',
],
'manager_username' =>
[
'Manager Username',
],
];
private function getColumns($type)
{
switch ($type) {
case 'asset':
$results = self::$general + self::$assets;
$results = $this->assets_fields;
break;
case 'accessory':
$results = self::$general + self::$accessories;
$results = $this->accessories_fields;
break;
case 'consumable':
$results = self::$general + self::$consumables;
$results = $this->consumables_fields;
break;
case 'component':
$results = $this->components_fields;
break;
case 'license':
$results = self::$general + self::$licenses;
$results = $this->licenses_fields;
break;
case 'user':
$results = self::$general + self::$users;
$results = $this->users_fields;
break;
case 'location':
$results = self::$general + self::$locations;
$results = $this->locations_fields;
break;
default:
$results = self::$general;
$results = [];
}
asort($results, SORT_FLAG_CASE | SORT_STRING);
if ($type == "asset") {
@ -261,7 +123,7 @@ class Importer extends Component
continue;
} else {
//no, this key is *INVALID* for this import type. Better set it to null
// and we'll hope that the aliases or something else picks it up.
// and we'll hope that the $aliases_fields or something else picks it up.
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
} // TODO - strictly speaking, this isn't necessary here I don't think.
}
@ -272,8 +134,8 @@ class Importer extends Component
continue 2; //don't bother with the alias check, go to the next header
}
}
// if you got here, we didn't find a match. Try the aliases
foreach (self::$aliases as $key => $alias_values) {
// if you got here, we didn't find a match. Try the $aliases_fields
foreach ($this->aliases_fields as $key => $alias_values) {
foreach ($alias_values as $alias_value) {
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
// Make *absolutely* sure that this key actually _exists_ in this import type -
@ -312,6 +174,306 @@ class Importer extends Component
'location' => trans('general.locations'),
];
/**
* These are the item-type specific columns
*/
$this->accessories_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.accessory')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
];
$this->assets_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]),
'asset_tag' => trans('general.asset_tag'),
'asset_model' => trans('general.model_name'),
'byod' => trans('general.byod'),
'model_number' => trans('general.model_no'),
'status' => trans('general.status'),
'warranty_months' => trans('admin/hardware/form.warranty'),
'category' => trans('general.category'),
'requestable' => trans('admin/hardware/general.requestable'),
'serial' => trans('general.serial_number'),
'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'image' => trans('general.importer.image_filename'),
'asset_eol_date' => trans('admin/hardware/form.eol_date'),
/**
* Checkout fields:
* Assets can be checked out to other assets, people, or locations, but we currently
* only support checkout to people and locations in the importer
**/
'checkout_class' => trans('general.importer.checkout_type'),
'first_name' => trans('general.importer.checked_out_to_first_name'),
'last_name' => trans('general.importer.checked_out_to_last_name'),
'full_name' => trans('general.importer.checked_out_to_fullname'),
'email' => trans('general.importer.checked_out_to_email'),
'username' => trans('general.importer.checked_out_to_username'),
'checkout_location' => trans('general.importer.checkout_location'),
];
$this->consumables_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.consumable')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'min_amt' => trans('mail.min_QTY'),
'category' => trans('general.category'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'checkout_class' => trans('general.importer.checkout_type'),
'supplier' => trans('general.supplier'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'item_no' => trans('admin/consumables/general.item_no'),
];
$this->components_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.component')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'serial' => trans('general.serial_number'),
];
$this->licenses_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]),
'asset_tag' => trans('general.importer.checked_out_to_tag'),
'expiration_date' => trans('admin/licenses/form.expiration'),
'full_name' => trans('general.importer.checked_out_to_fullname'),
'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'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'maintained' => trans('admin/licenses/form.maintained'),
'checkout_class' => trans('general.importer.checkout_type'),
'serial' => trans('general.license_serial'),
'email' => trans('general.importer.checked_out_to_email'),
'username' => trans('general.importer.checked_out_to_username'),
'manufacturer' => trans('general.manufacturer'),
];
$this->users_fields = [
'id' => trans('general.id'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'department' => trans('general.department'),
'first_name' => trans('general.first_name'),
'last_name' => trans('general.last_name'),
'notes' => trans('general.notes'),
'username' => trans('admin/users/table.username'),
'jobtitle' => trans('admin/users/table.title'),
'phone_number' => trans('admin/users/table.phone'),
'manager_first_name' => trans('general.importer.manager_first_name'),
'manager_last_name' => trans('general.importer.manager_last_name'),
'activated' => trans('general.activated'),
'address' => trans('general.address'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'vip' => trans('general.importer.vip'),
'remote' => trans('admin/users/general.remote'),
'email' => trans('admin/users/table.email'),
'website' => trans('general.website'),
'avatar' => trans('general.image'),
'gravatar' => trans('general.importer.gravatar'),
'start_date' => trans('general.start_date'),
'end_date' => trans('general.end_date'),
'employee_num' => trans('general.employee_number'),
];
$this->locations_fields = [
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'currency' => trans('general.importer.currency'),
'ldap_ou' => trans('admin/locations/table.ldap_ou'),
'manager_username' => trans('general.importer.manager_username'),
'manager' => trans('general.importer.manager_full_name'),
'parent_location' => trans('admin/locations/table.parent'),
];
// "real fieldnames" to a list of aliases for that field
$this->aliases_fields = [
'item_name' =>
[
'item name',
'asset name',
'accessory name',
'user name',
'consumable name',
'component name',
'name',
],
'item_no' => [
'item number',
'item no.',
'item #',
],
'asset_model' =>
[
'model name',
'model',
],
'gravatar' =>
[
'gravatar',
],
'currency' =>
[
'$',
],
'jobtitle' =>
[
'job title for user',
'job title',
],
'username' =>
[
'user name',
'username',
trans('general.importer.checked_out_to_username'),
],
'first_name' =>
[
'first name',
trans('general.importer.checked_out_to_first_name'),
],
'last_name' =>
[
'last name',
'lastname',
trans('general.importer.checked_out_to_last_name'),
],
'email' =>
[
'email',
'e-mail',
trans('general.importer.checked_out_to_email'),
],
'phone_number' =>
[
'phone',
'phone number',
'phone num',
'telephone number',
'telephone',
'tel.',
],
'serial' =>
[
'serial number',
'serial no.',
'serial no',
'product key',
'key',
],
'model_number' =>
[
'model',
'model no',
'model no.',
'model number',
'model num',
'model num.'
],
'warranty_months' =>
[
'Warranty',
'Warranty Months'
],
'qty' =>
[
'QTY',
'Quantity'
],
'zip' =>
[
'Postal Code',
'Post Code',
'Zip Code'
],
'min_amt' =>
[
'Min Amount',
'Minimum Amount',
'Min Quantity',
'Minimum Quantity',
],
'next_audit_date' =>
[
'Next Audit',
],
'address2' =>
[
'Address 2',
'Address2',
],
'ldap_ou' =>
[
'LDAP OU',
'OU',
],
'parent_location' =>
[
'Parent',
'Parent Location',
],
'manager' =>
[
'Managed By',
'Manager Name',
'Manager Full Name',
],
'manager_username' =>
[
'Manager Username',
],
];
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
foreach($this->importTypes AS $type => $name) {
$this->columnOptions[$type] = $this->getColumns($type);
@ -323,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])) {
@ -359,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.

View file

@ -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;

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
class CustomAssetReportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'purchase_start' => 'date|date_format:Y-m-d|nullable',
'purchase_end' => 'date|date_format:Y-m-d|nullable',
'created_start' => 'date|date_format:Y-m-d|nullable',
'created_end' => 'date|date_format:Y-m-d|nullable',
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
'checkout_date_end' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_start' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_end' => 'date|date_format:Y-m-d|nullable',
'checkin_date_start' => 'date|date_format:Y-m-d|nullable',
'checkin_date_end' => 'date|date_format:Y-m-d|nullable',
'last_audit_start' => 'date|date_format:Y-m-d|nullable',
'last_audit_end' => 'date|date_format:Y-m-d|nullable',
'next_audit_start' => 'date|date_format:Y-m-d|nullable',
'next_audit_end' => 'date|date_format:Y-m-d|nullable',
];
}
public function response(array $errors)
{
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
}
}

View file

@ -32,6 +32,7 @@ class SaveUserRequest extends FormRequest
public function rules()
{
$rules = [
'department_id' => 'nullable|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
];

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreAssetRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', new Asset);
}
public function prepareForValidation(): void
{
//
}
/**
* 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;
}
}

View file

@ -1,25 +0,0 @@
<?php
namespace App\Http\Traits;
use App\Models\Setting;
trait UniqueSerialTrait
{
/**
* Prepare a unique_ids rule, adding a model identifier if required.
*
* @param array $parameters
* @param string $field
*
* @return string
*/
protected function prepareUniqueSerialRule($parameters, $field)
{
if ($settings = Setting::getSettings()) {
if ($settings->unique_serial == '1') {
return 'unique_undeleted:'.$this->table.','.$this->getKey();
}
}
}
}

View file

@ -32,7 +32,7 @@ class AccessoriesTransformer
'model_number' => ($accessory->model_number) ? e($accessory->model_number) : null,
'category' => ($accessory->category) ? ['id' => $accessory->category->id, 'name'=> e($accessory->category->name)] : null,
'location' => ($accessory->location) ? ['id' => $accessory->location->id, 'name'=> e($accessory->location->name)] : null,
'notes' => ($accessory->notes) ? e($accessory->notes) : null,
'notes' => ($accessory->notes) ? Helper::parseEscapedMarkedownInline($accessory->notes) : null,
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),

View file

@ -3,8 +3,17 @@ 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\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 +47,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 = '';
@ -110,14 +178,15 @@ class ActionlogsTransformer
'type' => e($actionlog->targetType()),
] : null,
'note' => ($actionlog->note) ? e($actionlog->note): null,
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: 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 +201,98 @@ 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();
$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('asset_eol_date', $clean_meta)) {
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
unset($clean_meta['asset_eol_date']);
}
return $clean_meta;
}

View file

@ -49,7 +49,7 @@ class AssetMaintenancesTransformer
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
'name'=> e($assetmaintenance->asset->defaultLoc->name),
] : null,
'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null,
'notes' => ($assetmaintenance->notes) ? Helper::parseEscapedMarkedownInline($assetmaintenance->notes) : null,
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
@ -59,6 +59,7 @@ class AssetMaintenancesTransformer
'user_id' => ($assetmaintenance->admin) ? ['id' => $assetmaintenance->admin->id, 'name'=> e($assetmaintenance->admin->getFullNameAttribute())] : null,
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
'is_warranty'=> $assetmaintenance->is_warranty,
];

View file

@ -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),
@ -63,7 +64,7 @@ class AssetModelsTransformer
'default_fieldset_values' => $default_field_values,
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false,
'notes' => e($assetmodel->notes),
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'),

View file

@ -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->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,
@ -58,7 +59,7 @@ class AssetsTransformer
'id' => (int) $asset->supplier->id,
'name'=> e($asset->supplier->name),
] : null,
'notes' => ($asset->notes) ? e($asset->notes) : null,
'notes' => ($asset->notes) ? Helper::parseEscapedMarkedownInline($asset->notes) : null,
'order_number' => ($asset->order_number) ? e($asset->order_number) : null,
'company' => ($asset->company) ? [
'id' => (int) $asset->company->id,
@ -92,6 +93,7 @@ class AssetsTransformer
'checkout_counter' => (int) $asset->checkout_counter,
'requests_counter' => (int) $asset->requests_counter,
'user_can_checkout' => (bool) $asset->availableForCheckout(),
'book_value' => Helper::formatCurrencyOutput($asset->getLinearDepreciatedValue()),
];
@ -101,10 +103,10 @@ class AssetsTransformer
foreach ($asset->model->fieldset->fields as $field) {
if ($field->isFieldDecryptable($asset->{$field->db_column})) {
$decrypted = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
$value = (Gate::allows('superadmin')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
if ($field->format == 'DATE'){
if (Gate::allows('superadmin')){
if (Gate::allows('assets.view.encrypted_custom_fields')){
$value = Helper::getFormattedDateObject($value, 'date', false);
} else {
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
@ -230,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,

View file

@ -26,6 +26,9 @@ class CompaniesTransformer
$array = [
'id' => (int) $company->id,
'name' => e($company->name),
'phone' => ($company->phone!='') ? e($company->phone): null,
'fax' => ($company->fax!='') ? e($company->fax): null,
'email' => ($company->email!='') ? e($company->email): null,
'image' => ($company->image) ? Storage::disk('public')->url('companies/'.e($company->image)) : null,
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'),

View file

@ -46,7 +46,7 @@ class ComponentsTransformer
'id' => (int) $component->company->id,
'name' => e($component->company->name),
] : null,
'notes' => ($component->notes) ? e($component->notes) : null,
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'),
'user_can_checkout' => ($component->numRemaining() > 0) ? 1 : 0,

View file

@ -39,7 +39,7 @@ class ConsumablesTransformer
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
'qty' => (int) $consumable->qty,
'notes' => ($consumable->notes) ? e($consumable->notes) : null,
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'),
];

View file

@ -49,6 +49,7 @@ class CustomFieldsTransformer
'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false,
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false,
'show_in_listview' => ($field->show_in_listview == '1') ? true : false,
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
];

View file

@ -26,6 +26,8 @@ class DepartmentsTransformer
$array = [
'id' => (int) $department->id,
'name' => e($department->name),
'phone' => ($department->phone!='') ? e($department->phone): null,
'fax' => ($department->fax!='') ? e($department->fax): null,
'image' => ($department->image) ? Storage::disk('public')->url(app('departments_upload_url').e($department->image)) : null,
'company' => ($department->company) ? [
'id' => (int) $department->company->id,

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Labels\Label;
use App\Models\Labels\Sheet;
use App\Models\Labels\RectangleSheet;
use Illuminate\Support\Collection;
class LabelsTransformer
{
public function transformLabels(Collection $labels, $total)
{
$array = [];
foreach ($labels as $label) {
$array[] = self::transformLabel($label);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformLabel(Label $label)
{
$array = [
'name' => $label->getName(),
'unit' => $label->getUnit(),
'width' => number_format($label->getWidth(), 2),
'height' => number_format($label->getHeight(), 2),
'margin_top' => $label->getMarginTop(),
'margin_bottom' => $label->getMarginBottom(),
'margin_left' => $label->getMarginLeft(),
'margin_right' => $label->getMarginRight(),
'support_asset_tag' => $label->getSupportAssetTag(),
'support_1d_barcode' => $label->getSupport1DBarcode(),
'support_2d_barcode' => $label->getSupport2DBarcode(),
'support_fields' => $label->getSupportFields(),
'support_logo' => $label->getSupportLogo(),
'support_title' => $label->getSupportTitle(),
];
if ($label instanceof Sheet) {
$array['sheet_info'] = [
'label_width' => $label->getLabelWidth(),
'label_height' => $label->getLabelHeight(),
'label_margin_top' => $label->getLabelMarginTop(),
'label_margin_bottom' => $label->getLabelMarginBottom(),
'label_margin_left' => $label->getLabelMarginLeft(),
'label_margin_right' => $label->getLabelMarginRight(),
'labels_per_page' => $label->getLabelsPerPage(),
'label_border' => $label->getLabelBorder(),
];
}
if ($label instanceof RectangleSheet) {
$array['rectanglesheet_info'] = [
'columns' => $label->getColumns(),
'rows' => $label->getRows(),
'column_spacing' => $label->getLabelColumnSpacing(),
'row_spacing' => $label->getLabelRowSpacing(),
];
}
return $array;
}
}

View file

@ -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 == '')),
];

View file

@ -34,7 +34,7 @@ class LicensesTransformer
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
'purchase_cost_numeric' => $license->purchase_cost,
'notes' => e($license->notes),
'notes' => Helper::parseEscapedMarkedownInline($license->notes),
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'seats' => (int) $license->seats,
'free_seats_count' => (int) $license->free_seats_count,

View file

@ -43,6 +43,8 @@ class LocationsTransformer
'state' => ($location->state) ? e($location->state) : null,
'country' => ($location->country) ? e($location->country) : null,
'zip' => ($location->zip) ? e($location->zip) : null,
'phone' => ($location->phone!='') ? e($location->phone): null,
'fax' => ($location->fax!='') ? e($location->fax): null,
'assigned_assets_count' => (int) $location->assigned_assets_count,
'assets_count' => (int) $location->assets_count,
'rtd_assets_count' => (int) $location->rtd_assets_count,

View file

@ -43,7 +43,7 @@ class SuppliersTransformer
'licenses_count' => (int) $supplier->licenses_count,
'consumables_count' => (int) $supplier->consumables_count,
'components_count' => (int) $supplier->components_count,
'notes' => ($supplier->notes) ? e($supplier->notes) : null,
'notes' => ($supplier->notes) ? Helper::parseEscapedMarkedownInline($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'),

View file

@ -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),
@ -53,7 +53,7 @@ class UsersTransformer
'id' => (int) $user->userloc->id,
'name'=> e($user->userloc->name),
] : null,
'notes'=> e($user->notes),
'notes'=> Helper::parseEscapedMarkedownInline($user->notes),
'permissions' => $user->decodePermissions(),
'activated' => ($user->activated == '1') ? true : false,
'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false,

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