Merge branch 'develop' into laravel_v9

This commit is contained in:
Brady Wetherington 2023-04-18 11:01:00 -07:00
commit 9793130f6c
1414 changed files with 17558 additions and 11590 deletions

View file

@ -2837,6 +2837,58 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "AndrewSav",
"name": "Andrew Savinykh",
"avatar_url": "https://avatars.githubusercontent.com/u/658865?v=4",
"profile": "https://github.com/AndrewSav",
"contributions": [
"code"
]
},
{
"login": "kenchan0130",
"name": "Tadayuki Onishi",
"avatar_url": "https://avatars.githubusercontent.com/u/1155067?v=4",
"profile": "https://kenchan0130.github.io",
"contributions": [
"code"
]
},
{
"login": "floschoepfer",
"name": "Florian",
"avatar_url": "https://avatars.githubusercontent.com/u/112496896?v=4",
"profile": "https://github.com/floschoepfer",
"contributions": [
"code"
]
},
{
"login": "spencerrlongg",
"name": "Spencer Long",
"avatar_url": "https://avatars.githubusercontent.com/u/7305753?v=4",
"profile": "http://spencerlong.com",
"contributions": [
"code"
]
},
{
"login": "marcusmoore",
"name": "Marcus Moore",
"avatar_url": "https://avatars.githubusercontent.com/u/1141514?v=4",
"profile": "https://github.com/marcusmoore",
"contributions": [
"code"
]
},
{
"login": "Mezzle",
"name": "Martin Meredith",
"avatar_url": "https://avatars.githubusercontent.com/u/570639?v=4",
"profile": "https://github.com/Mezzle",
"contributions": []
} }
] ]
} }

View file

@ -1,75 +0,0 @@
# --------------------------------------------
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
APP_ENV=testing
APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='US/Pacific'
APP_LOCALE=en
FILESYSTEM_DISK=local
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=sqlite_testing
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=testing.sqlite
DB_USERNAME=null
DB_PASSWORD=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
MAIL_DRIVER=log
MAIL_HOST=email-smtp.us-west-2.amazonaws.com
MAIL_PORT=587
MAIL_USERNAME=YOURUSERNAME
MAIL_PASSWORD=YOURPASSWORD
MAIL_ENCRYPTION=null
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
# This should be gd or imagick
# --------------------------------------------
IMAGE_LIB=gd
# --------------------------------------------
# OPTIONAL: AWS SETTINGS
# --------------------------------------------
AWS_SECRET_ACCESS_KEY=null
AWS_ACCESS_KEY_ID=null
AWS_DEFAULT_REGION=null
AWS_BUCKET=null
AWS_BUCKET_ROOT=null
AWS_URL=null
# --------------------------------------------
# OPTIONAL: CACHE SETTINGS
# --------------------------------------------
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------
SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false
ENCRYPT=false
COOKIE_NAME=snipeittest_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
# --------------------------------------------
# OPTIONAL: APP LOG FORMAT
# --------------------------------------------
LOG_CHANNEL=single
LOG_LEVEL=debug

19
.env.testing.example Normal file
View file

@ -0,0 +1,19 @@
# --------------------------------------------
# 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=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=null
DB_USERNAME=null
DB_PASSWORD=null

View file

@ -1,18 +1,22 @@
frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*", "*livewire*"] frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*", "resources/views/livewire/*"]
skins: ["*.js", "*.css", "*.scss", "*.less"] skins: ["*.js", "*.css", "*.scss", "*.less"]
css: ["*.css","*.scss", "*.less"] css: ["*.css","*.scss", "*.less"]
backend: ["/app/*", "*.php"] javascript: ["*.js", "package.json", "package.lock"]
backend: ["/app/*", "composer.json", "composer.lock"]
translations: ["/resources/lang"]
livewire: ["/app/Http/Livewire/*", "resources/views/livewire/*"]
backups: ["*backup*"] backups: ["*backup*"]
restore: ["*restore*"] restore: ["*restore*"]
saml: ["*saml*"] saml: ["*saml*"]
scim: ["*scim*"] scim: ["*scim*"]
custom fields: ["*fields*", "*fieldsets*"] custom fields: ["*fields*", "*fieldsets*"]
dependencies: ["composer.json"] dependencies: ["composer.json", "composer.lock", "package.json", "package.lock"]
consumables: ["*consumables*"] consumables: ["*consumables*"]
api: ["/app/Http/Controllers/api/*"] api: ["/app/Http/Controllers/Api/*"]
notifications: ["/app/Notifications/*"] notifications: ["/app/Notifications/*"]
importer: ["/app/Importer/*"] importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views/livewire/importer.php"]
cli / artisan: ["/app/Console/*"] cli / artisan: ["/app/Console/*"]
LDAP: ["*LDAP*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"] LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"] docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
tests: ["/tests/*", "/stubs"]
config: .github config: .github

View file

@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI - name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.2.0 uses: codacy/codacy-analysis-cli-action@v4.3.0
with: with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations # You can also omit the token and run the tools that support default configurations

21
.github/workflows/crowdin-upload.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Crowdin Action
on:
push:
branches: [ develop ]
jobs:
upload-sources-to-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Crowdin push
uses: crowdin/github-action@v1
with:
upload_sources: true
upload_translations: false
download_translations: false
project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View file

@ -72,7 +72,7 @@ jobs:
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image - name: Build and push 'snipe-it' image
id: docker_build id: docker_build
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
file: ./Dockerfile.alpine file: ./Dockerfile.alpine

View file

@ -72,7 +72,7 @@ jobs:
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image - name: Build and push 'snipe-it' image
id: docker_build id: docker_build
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.env .env
.env.dusk.* .env.dusk.*
!.env.dusk.example !.env.dusk.example
.env.testing
phpstan.neon phpstan.neon
.idea .idea
/bin/ /bin/

View file

@ -1,4 +1,4 @@
FROM ubuntu:20.04 FROM ubuntu:22.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>" LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference: # No need to add `apt-get clean` here, reference:
@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \ apt-utils \
apache2 \ apache2 \
apache2-bin \ apache2-bin \
libapache2-mod-php7.4 \ libapache2-mod-php8.1 \
php7.4-curl \ php8.1-curl \
php7.4-ldap \ php8.1-ldap \
php7.4-mysql \ php8.1-mysql \
php7.4-gd \ php8.1-gd \
php7.4-xml \ php8.1-xml \
php7.4-mbstring \ php8.1-mbstring \
php7.4-zip \ php8.1-zip \
php7.4-bcmath \ php8.1-bcmath \
php7.4-redis \ php8.1-redis \
php-memcached \ php-memcached \
patch \ patch \
curl \ curl \
@ -40,7 +40,7 @@ autoconf \
libc-dev \ libc-dev \
pkg-config \ pkg-config \
libmcrypt-dev \ libmcrypt-dev \
php7.4-dev \ php8.1-dev \
ca-certificates \ ca-certificates \
unzip \ unzip \
dnsutils \ dnsutils \
@ -50,16 +50,16 @@ dnsutils \
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
RUN php go-pear.phar RUN php go-pear.phar
RUN pecl install mcrypt-1.0.3 RUN pecl install mcrypt
RUN bash -c "echo extension=/usr/lib/php/20190902/mcrypt.so > /etc/php/7.4/mods-available/mcrypt.ini" RUN bash -c "echo extension=/usr/lib/php/20210902/mcrypt.so > /etc/php/8.1/mods-available/mcrypt.ini"
RUN phpenmod mcrypt RUN phpenmod mcrypt
RUN phpenmod gd RUN phpenmod gd
RUN phpenmod bcmath RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.4/apache2/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.4/cli/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/cli/php.ini
RUN useradd -m --uid 1000 --gid 50 docker RUN useradd -m --uid 1000 --gid 50 docker

View file

@ -1,34 +1,34 @@
FROM alpine:3.14.2 FROM alpine:3.17.3
# Apache + PHP # Apache + PHP
RUN apk add --no-cache \ RUN apk add --no-cache \
apache2 \ apache2 \
php7 \ php81 \
php7-common \ php81-common \
php7-apache2 \ php81-apache2 \
php7-curl \ php81-curl \
php7-ldap \ php81-ldap \
php7-mysqli \ php81-mysqli \
php7-gd \ php81-gd \
php7-xml \ php81-xml \
php7-mbstring \ php81-mbstring \
php7-zip \ php81-zip \
php7-ctype \ php81-ctype \
php7-tokenizer \ php81-tokenizer \
php7-pdo_mysql \ php81-pdo_mysql \
php7-openssl \ php81-openssl \
php7-bcmath \ php81-bcmath \
php7-phar \ php81-phar \
php7-json \ php81-json \
php7-iconv \ php81-iconv \
php7-fileinfo \ php81-fileinfo \
php7-simplexml \ php81-simplexml \
php7-session \ php81-session \
php7-dom \ php81-dom \
php7-xmlwriter \ php81-xmlwriter \
php7-xmlreader \ php81-xmlreader \
php7-sodium \ php81-sodium \
php7-redis \ php81-redis \
php7-pecl-memcached \ php81-pecl-memcached \
curl \ curl \
wget \ wget \
vim \ vim \
@ -41,7 +41,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives # Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2 RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php7/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite # Enable mod_rewrite

View file

@ -1,8 +1,8 @@
ARG ENVIRONMENT=production ARG ENVIRONMENT=production
ARG SNIPEIT_RELEASE=5.1.3 ARG SNIPEIT_RELEASE=6.1.0
ARG PHP_VERSION=7.4.16 ARG PHP_VERSION=8.2
ARG PHP_ALPINE_VERSION=3.13 ARG PHP_ALPINE_VERSION=3.17
ARG COMPOSER_VERSION=2.0.11 ARG COMPOSER_VERSION=2
# Cannot use arguments with 'COPY --from' workaround # Cannot use arguments with 'COPY --from' workaround
# https://github.com/moby/moby/issues/34482#issuecomment-454716952 # https://github.com/moby/moby/issues/34482#issuecomment-454716952
@ -52,7 +52,7 @@ RUN { \
# Install php extensions inside docker containers easily # Install php extensions inside docker containers easily
# https://github.com/mlocati/docker-php-extension-installer # https://github.com/mlocati/docker-php-extension-installer
COPY --from=mlocati/php-extension-installer:1.2.19 /usr/bin/install-php-extensions /usr/local/bin/ COPY --from=mlocati/php-extension-installer:2.1.15 /usr/bin/install-php-extensions /usr/local/bin/
RUN set -eux; \ RUN set -eux; \
install-php-extensions \ install-php-extensions \
bcmath \ bcmath \

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&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade) ![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&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-312-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) [![All Contributors](https://img.shields.io/badge/all_contributors-318-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 ## Snipe-IT - Open Source Asset Management System
@ -66,8 +66,11 @@ Since the release of the JSON REST API, several third-party developers have been
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. - [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit). - [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it. - [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@RodneyLeeBrands](https://github.com/RodneyLeeBrands) - Python script to synchronize information between Mosyle and Snipe-IT - [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API - [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :) As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
@ -140,7 +143,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | | [<img src="https://avatars.githubusercontent.com/u/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/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/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 /> |
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -1,45 +1,23 @@
# Using the Test Suite # Running the Test Suite
This document is targeted at developers looking to make modifications to This document is targeted at developers looking to make modifications to this application's code base and want to run the existing test suite.
this application's code base and want to run the existing test suite.
Before starting, follow the [instructions](README.md#installation) for installing the application locally and ensure you can load it in a browser properly.
## Setup ## Unit and Feature Tests
Follow the instructions for installing the application locally, Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
making sure to have also run the [database migrations](link to db migrations).
`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
## Unit Tests Now you are ready to run the entire test suite from your terminal:
The application will use values in the `.env.testing` file located `php artisan test`
in the root directory to override the
default settings and/or other values that exist in your `.env` files.
Make sure to modify the section in `.env.testing` that has the To run individual test files, you can pass the path to the test that you want to run:
database settings. In the example below, it is connecting to the
[MariaDB](link-to-maria-db) server that is used if you install the
application using [Docker](https://docker.com).
```dotenv `php artisan test tests/Unit/AccessoryTest.php`
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=snipeit
DB_USERNAME=root
DB_PASSWORD=changeme1234
```
To run the entire unit test suite, use the following command from your terminal:
`php artisan test --env=testing`
To run individual test files, you can pass the path to the test that
you want to run.
`php artisan test --env=testing tests/Unit/AccessoryTest.php`
## Browser Tests ## Browser Tests
@ -52,11 +30,9 @@ Before attempting to run Dusk tests copy the example environment file for Dusk a
**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. **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.
### Test Setup ### Running Browser Tests
Your application needs to be configured and up and running in order for the browser 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:
tests to actually run. When running the tests locally, you can start the application
using the following command:
`php artisan serve` `php artisan serve`

View file

@ -56,7 +56,7 @@ class CheckoutLicenseToAllUsers extends Command
return false; return false;
} }
$users = User::whereNull('deleted_at')->with('licenses')->get(); $users = User::whereNull('deleted_at')->where('autoassign_licenses', '==', 1)->with('licenses')->get();
if ($users->count() > $license->getAvailSeatsCountAttribute()) { if ($users->count() > $license->getAvailSeatsCountAttribute()) {
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. '); $this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');

View file

@ -0,0 +1,52 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
class NormalizeUserNames extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:normalize-names';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Normalizes weirdly formatted names as first-letter upercased';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$users = User::get();
$this->info($users->count() . ' users');
foreach ($users as $user) {
$user->first_name = ucwords(strtolower($user->first_name));
$user->last_name = ucwords(strtolower($user->last_name));
$user->email = strtolower($user->email);
$user->save();
}
}
}

24
app/Events/UserMerged.php Normal file
View file

@ -0,0 +1,24 @@
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class UserMerged
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $from_user, User $to_user, User $admin)
{
$this->merged_from = $from_user;
$this->merged_to = $to_user;
$this->admin = $admin;
}
}

View file

@ -6,10 +6,11 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Helpers\Helper; use App\Helpers\Helper;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
use Log; use Log;
use Throwable; use Throwable;
use JsonException; use JsonException;
use Carbon\Exceptions\InvalidFormatException;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -28,6 +29,8 @@ class Handler extends ExceptionHandler
\Intervention\Image\Exception\NotSupportedException::class, \Intervention\Image\Exception\NotSupportedException::class,
\League\OAuth2\Server\Exception\OAuthServerException::class, \League\OAuth2\Server\Exception\OAuthServerException::class,
JsonException::class, JsonException::class,
SCIMException::class, //these generally don't need to be reported
InvalidFormatException::class,
]; ];
/** /**
@ -53,7 +56,7 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Exception $e * @param \Exception $e
* @return \Illuminate\Http\Response * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/ */
public function render($request, Throwable $e) public function render($request, Throwable $e)
{ {
@ -67,18 +70,34 @@ class Handler extends ExceptionHandler
// Invalid JSON exception // Invalid JSON exception
// TODO: don't understand why we have to do this when we have the invalidJson() method, below, but, well, whatever // TODO: don't understand why we have to do this when we have the invalidJson() method, below, but, well, whatever
if ($e instanceof JsonException) { if ($e instanceof JsonException) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'invalid JSON'), 422); return response()->json(Helper::formatStandardApiResponse('error', null, 'Invalid JSON'), 422);
} }
// Handle SCIM exceptions
if ($e instanceof SCIMException) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Invalid SCIM Request'), 400);
}
// Handle Ajax requests that fail because the model doesn't exist // Handle standard requests that fail because Carbon cannot parse the date on validation (when a submitted date value is definitely not a date)
if ($e instanceof InvalidFormatException) {
return redirect()->back()->withInput()->with('error', trans('validation.date', ['attribute' => 'date']));
}
// Handle API requests that fail
if ($request->ajax() || $request->wantsJson()) { if ($request->ajax() || $request->wantsJson()) {
// Handle API requests that fail because Carbon cannot parse the date on validation (when a submitted date value is definitely not a date)
if ($e instanceof InvalidFormatException) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.date', ['attribute' => 'date'])), 200);
}
// Handle API requests that fail because the model doesn't exist
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
$className = last(explode('\\', $e->getModel())); $className = last(explode('\\', $e->getModel()));
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200); return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
} }
// Handle API requests that fail because of an HTTP status code and return a useful error message
if ($this->isHttpException($e)) { if ($this->isHttpException($e)) {
$statusCode = $e->getStatusCode(); $statusCode = $e->getStatusCode();
@ -98,6 +117,8 @@ class Handler extends ExceptionHandler
} }
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) { if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {
return response()->view('layouts/basic', [ return response()->view('layouts/basic', [
'content' => view('errors/404') 'content' => view('errors/404')
@ -113,8 +134,8 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception * @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\Response * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/ */
protected function unauthenticated($request, AuthenticationException $exception) protected function unauthenticated($request, AuthenticationException $exception)
{ {
if ($request->expectsJson()) { if ($request->expectsJson()) {

View file

@ -334,7 +334,11 @@ class Helper
'#92896B', '#92896B',
]; ];
$total_colors = count($colors);
if ($index >= $total_colors) {
$index = $index - $total_colors;
}
return $colors[$index]; return $colors[$index];
} }
@ -528,20 +532,23 @@ class Helper
* @since [v2.5] * @since [v2.5]
* @return array * @return array
*/ */
public static function categoryTypeList() public static function categoryTypeList($selection=null)
{ {
$category_types = [ $category_types = [
'' => '', '' => '',
'accessory' => 'Accessory', 'accessory' => trans('general.accessory'),
'asset' => 'Asset', 'asset' => trans('general.asset'),
'consumable' => 'Consumable', 'consumable' => trans('general.consumable'),
'component' => 'Component', 'component' => trans('general.component'),
'license' => 'License', 'license' => trans('general.license'),
]; ];
if($selection != null){
return $category_types[$selection];
}
else
return $category_types; return $category_types;
} }
/** /**
* Get the list of custom fields in an array to make a dropdown menu * Get the list of custom fields in an array to make a dropdown menu
* *
@ -1092,6 +1099,15 @@ class Helper
return $file_name; return $file_name;
} }
/**
* Universal helper to show file size in human-readable formats
*
* @author A. Gianotto <snipe@snipe.net>
* @since 5.0
*
* @return string[]
*/
public static function formatFilesizeUnits($bytes) public static function formatFilesizeUnits($bytes)
{ {
if ($bytes >= 1073741824) if ($bytes >= 1073741824)
@ -1121,30 +1137,91 @@ class Helper
return $bytes; return $bytes;
} }
/**
* This is weird but used by the side nav to determine which URL to point the user to
*
* @author A. Gianotto <snipe@snipe.net>
* @since 5.0
*
* @return string[]
*/
public static function SettingUrls(){ public static function SettingUrls(){
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index']; $settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
return $settings; return $settings;
} }
public static function AgeFormat($date) {
$year = Carbon::parse($date)
->diff(now())->y;
$month = Carbon::parse($date)
->diff(now())->m;
$days = Carbon::parse($date)
->diff(now())->d;
$age='';
if ($year) {
$age .= $year.'y ';
}
if ($month) {
$age .= $month.'m ';
}
if ($days) {
$age .= $days.'d';
}
return $age;
/**
* Generic helper (largely used by livewire right now) that returns the font-awesome icon
* for the object type.
*
* @author A. Gianotto <snipe@snipe.net>
* @since 6.1.0
*
* @return string
*/
public static function iconTypeByItem($item) {
switch ($item) {
case 'asset':
return 'fas fa-barcode';
break;
case 'accessory':
return 'fas fa-keyboard';
break;
case 'component':
return 'fas fa-hdd';
break;
case 'consumable':
return 'fas fa-tint';
break;
case 'license':
return 'far fa-save';
break;
case 'location':
return 'fas fa-map-marker-alt';
break;
case 'user':
return 'fas fa-user';
break;
}
} }
/*
* This is a shorter way to see if the app is in demo mode.
*
* This makes it cleanly available in blades and in controllers, e.g.
*
* Blade:
* {{ Helper::isDemoMode() ? ' disabled' : ''}} for form blades where we need to disable a form
*
* Controller:
* if (Helper::isDemoMode()) {
* // don't allow the thing
* }
* @todo - use this everywhere else in the app where we have very long if/else config('app.lock_passwords') stuff
*/
public static function isDemoMode() {
if (config('app.lock_passwords') === true) {
return true;
\Log::debug('app locked!');
}
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,
* it seemed pretty safe to do here. Don't you judge me.
*/
public static function showDemoModeFieldWarning() {
if (Helper::isDemoMode()) {
return "<p class=\"text-warning\"><i class=\"fas fa-lock\"></i>" . trans('general.feature_disabled') . "</p>";
}
}
} }

View file

@ -115,6 +115,33 @@ class AccessoriesController extends Controller
} }
/**
* Returns a view that presents a form to clone an accessory.
*
* @author [J. Vinsmoke]
* @param int $accessoryId
* @since [v6.0]
* @return View
*/
public function getClone($accessoryId = null)
{
$this->authorize('create', Accesory::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'));
}
$accessory = clone $accessory_to_clone;
$accessory->id = null;
$accessory->location_id = null;
return view('accessories/edit')
->with('item', $accessory);
}
/** /**
* Save edited Accessory from form post * Save edited Accessory from form post

View file

@ -25,11 +25,16 @@ class AccessoryCheckoutController extends Controller
public function create($accessoryId) public function create($accessoryId)
{ {
// Check if the accessory exists // Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) { if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
// Redirect to the accessory management page with error // Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
} }
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
if ($accessory->category) { if ($accessory->category) {
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
@ -55,17 +60,23 @@ class AccessoryCheckoutController extends Controller
public function store(Request $request, $accessoryId) public function store(Request $request, $accessoryId)
{ {
// Check if the accessory exists // Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) { if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
// Redirect to the accessory management page with error // Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
} }
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
if (! $user = User::find($request->input('assigned_to'))) { if (!$user = User::find($request->input('assigned_to'))) {
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist')); return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
} }
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Update the accessory data // Update the accessory data
$accessory->assigned_to = e($request->input('assigned_to')); $accessory->assigned_to = e($request->input('assigned_to'));

View file

@ -26,7 +26,10 @@ class AccessoriesController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('view', Accessory::class); if ($request->user()->cannot('reports.view')) {
$this->authorize('view', Accessory::class);
}
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
// Relations will be handled in query scopes a little further down. // Relations will be handled in query scopes a little further down.
@ -77,12 +80,9 @@ class AccessoriesController extends Controller
$accessories->where('notes','=',$request->input('notes')); $accessories->where('notes','=',$request->input('notes'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $accessories->count()) ? $accessories->count() : abs($request->input('offset'));
$offset = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort'); $sort_override = $request->input('sort');

View file

@ -55,12 +55,9 @@ class AssetMaintenancesController extends Controller
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $maintenances->count()) ? $maintenances->count() : abs($request->input('offset'));
$offset = (($maintenances) && ($request->get('offset') > $maintenances->count())) ? $maintenances->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = [ $allowed_columns = [
'id', 'id',

View file

@ -78,12 +78,9 @@ class AssetModelsController extends Controller
$assetmodels->TextSearch($request->input('search')); $assetmodels->TextSearch($request->input('search'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $assetmodels->count()) ? $assetmodels->count() : abs($request->input('offset'));
$offset = (($assetmodels) && ($request->get('offset') > $assetmodels->count())) ? $assetmodels->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';

View file

@ -101,6 +101,7 @@ class AssetsController extends Controller
'checkin_counter', 'checkin_counter',
'requests_counter', 'requests_counter',
'byod', 'byod',
'asset_eol_date',
]; ];
$filter = []; $filter = [];
@ -128,7 +129,6 @@ class AssetsController extends Controller
// They are also used by the individual searches on detail pages like // They are also used by the individual searches on detail pages like
// locations, etc. // locations, etc.
// Search custom fields by column name // Search custom fields by column name
foreach ($all_custom_fields as $field) { foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name())) { if ($request->filled($field->db_column_name())) {
@ -136,7 +136,6 @@ class AssetsController extends Controller
} }
} }
if ($request->filled('status_id')) { if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id')); $assets->where('assets.status_id', '=', $request->input('status_id'));
} }
@ -173,6 +172,10 @@ class AssetsController extends Controller
$assets->where('assets.supplier_id', '=', $request->input('supplier_id')); $assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
} }
if ($request->filled('asset_eol_date')) {
$assets->where('assets.asset_eol_date', '=', $request->input('asset_eol_date'));
}
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) { if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
$assets->where('assets.assigned_to', '=', $request->input('assigned_to')) $assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
->where('assets.assigned_type', '=', $request->input('assigned_type')); ->where('assets.assigned_type', '=', $request->input('assigned_type'));
@ -196,13 +199,9 @@ class AssetsController extends Controller
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : ''; $request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : abs($request->input('offset'));
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@ -460,7 +459,7 @@ class AssetsController extends Controller
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
$asset = Asset::where('id', $id)->withTrashed()->first(); $asset = Asset::where('id', $id)->withTrashed()->firstorfail();
$licenses = $asset->licenses()->get(); $licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count()); return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
@ -829,7 +828,6 @@ class AssetsController extends Controller
} elseif (request('checkout_to_type') == 'asset') { } elseif (request('checkout_to_type') == 'asset') {
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset')); $target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
$asset->location_id = $target->rtd_location_id;
// Override with the asset's location_id if it has one // Override with the asset's location_id if it has one
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : ''; $asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset'); $error_payload['target_id'] = $request->input('assigned_asset');
@ -938,18 +936,21 @@ class AssetsController extends Controller
* @since [v6.0] * @since [v6.0]
* @return JsonResponse * @return JsonResponse
*/ */
public function checkinByTag(Request $request) public function checkinByTag(Request $request, $tag = null)
{ {
$this->authorize('checkin', Asset::class); $this->authorize('checkin', Asset::class);
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first(); if(null == $tag && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag');
}
$asset = Asset::where('asset_tag', $tag)->first();
if ($asset) { if ($asset) {
return $this->checkin($request, $asset->id); return $this->checkin($request, $asset->id);
} }
return response()->json(Helper::formatStandardApiResponse('error', [ return response()->json(Helper::formatStandardApiResponse('error', [
'asset'=> e($request->input('asset_tag')) 'asset'=> e($tag)
], 'Asset with tag '.e($request->input('asset_tag')).' not found')); ], 'Asset with tag '.e($tag).' not found'));
} }

View file

@ -24,10 +24,48 @@ class CategoriesController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
$allowed_columns = ['id', 'name', 'category_type', 'category_type', 'use_default_eula', 'eula_text', 'require_acceptance', 'checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count', 'licenses_count', 'image']; $allowed_columns = [
'id',
'name',
'category_type',
'category_type',
'use_default_eula',
'eula_text',
'require_acceptance',
'checkin_email',
'assets_count',
'accessories_count',
'consumables_count',
'components_count',
'licenses_count',
'image',
];
$categories = Category::select(['id', 'created_at', 'updated_at', 'name', 'category_type', 'use_default_eula', 'eula_text', 'require_acceptance', 'checkin_email', 'image']) $categories = Category::select([
->withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count'); 'id',
'created_at',
'updated_at',
'name', 'category_type',
'use_default_eula',
'eula_text',
'require_acceptance',
'checkin_email',
'image'
])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
/*
* This checks to see if we should override the Admin Setting to show archived assets in list.
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
* may actually need to fetch assets that are archived.
*
* @see \App\Models\Category::showableAssets()
*/
if ($request->input('archived')=='true') {
$categories = $categories->withCount('assets as assets_count');
} else {
$categories = $categories->withCount('showableAssets as assets_count');
}
if ($request->filled('search')) { if ($request->filled('search')) {
$categories = $categories->TextSearch($request->input('search')); $categories = $categories->TextSearch($request->input('search'));
@ -53,14 +91,9 @@ class CategoriesController extends Controller
$categories->where('checkin_email', '=', $request->input('checkin_email')); $categories->where('checkin_email', '=', $request->input('checkin_email'));
} }
// 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'));
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which $limit = app('api_limit_value');
// case we override with the actual count, so we should return 0 items.
$offset = (($categories) && ($request->get('offset') > $categories->count())) ? $categories->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';

View file

@ -48,12 +48,10 @@ class CompaniesController extends Controller
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : abs($request->input('offset'));
$offset = (($companies) && ($request->get('offset') > $companies->count())) ? $companies->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View file

@ -12,6 +12,7 @@ use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn; use App\Events\ComponentCheckedIn;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
class ComponentsController extends Controller class ComponentsController extends Controller
{ {
@ -45,7 +46,7 @@ class ComponentsController extends Controller
$components = Company::scopeCompanyables(Component::select('components.*') $components = Company::scopeCompanyables(Component::select('components.*')
->with('company', 'location', 'category', 'assets')); ->with('company', 'location', 'category', 'assets', 'supplier'));
if ($request->filled('search')) { if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search')); $components = $components->TextSearch($request->input('search'));
@ -63,6 +64,10 @@ class ComponentsController extends Controller
$components->where('category_id', '=', $request->input('category_id')); $components->where('category_id', '=', $request->input('category_id'));
} }
if ($request->filled('supplier_id')) {
$components->where('supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('location_id')) { if ($request->filled('location_id')) {
$components->where('location_id', '=', $request->input('location_id')); $components->where('location_id', '=', $request->input('location_id'));
} }
@ -71,13 +76,9 @@ class ComponentsController extends Controller
$components->where('notes','=',$request->input('notes')); $components->where('notes','=',$request->input('notes'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $components->count()) ? $components->count() : abs($request->input('offset'));
$offset = (($components) && ($request->get('offset') > $components->count())) ? $components->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort'); $sort_override = $request->input('sort');
@ -93,6 +94,9 @@ class ComponentsController extends Controller
case 'company': case 'company':
$components = $components->OrderCompany($order); $components = $components->OrderCompany($order);
break; break;
case 'supplier':
$components = $components->OrderSupplier($order);
break;
default: default:
$components = $components->orderBy($column_sort, $order); $components = $components->orderBy($column_sort, $order);
break; break;
@ -225,20 +229,30 @@ class ComponentsController extends Controller
public function checkout(Request $request, $componentId) public function checkout(Request $request, $componentId)
{ {
// Check if the component exists // Check if the component exists
if (is_null($component = Component::find($componentId))) { if (!$component = Component::find($componentId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist')));
} }
$this->authorize('checkout', $component); $this->authorize('checkout', $component);
$validator = Validator::make($request->all(), [
'asset_id' => 'required|exists:assets,id',
'assigned_qty' => "required|numeric|min:1|digits_between:1,".$component->numRemaining(),
]);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', $validator->errors()));
}
// Make sure there is at least one available to checkout
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')])));
}
if ($component->numRemaining() >= $request->get('assigned_qty')) { if ($component->numRemaining() >= $request->get('assigned_qty')) {
if (!$asset = Asset::find($request->input('assigned_to'))) { $asset = Asset::find($request->input('assigned_to'));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')));
}
// Update the accessory data
$component->assigned_to = $request->input('assigned_to'); $component->assigned_to = $request->input('assigned_to');
$component->assets()->attach($component->id, [ $component->assets()->attach($component->id, [
@ -255,7 +269,7 @@ class ComponentsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Not enough components remaining: '.$component->numRemaining().' remaining, '.$request->get('assigned_qty').' requested.')); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
} }
/** /**

View file

@ -75,6 +75,10 @@ class ConsumablesController extends Controller
$consumables->where('manufacturer_id', '=', $request->input('manufacturer_id')); $consumables->where('manufacturer_id', '=', $request->input('manufacturer_id'));
} }
if ($request->filled('supplier_id')) {
$consumables->where('supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('location_id')) { if ($request->filled('location_id')) {
$consumables->where('location_id','=',$request->input('location_id')); $consumables->where('location_id','=',$request->input('location_id'));
} }
@ -84,12 +88,9 @@ class ConsumablesController extends Controller
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : abs($request->input('offset'));
$offset = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image']; $allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@ -111,6 +112,9 @@ class ConsumablesController extends Controller
case 'company': case 'company':
$consumables = $consumables->OrderCompany($order); $consumables = $consumables->OrderCompany($order);
break; break;
case 'supplier':
$components = $consumables->OrderSupplier($order);
break;
default: default:
$consumables = $consumables->orderBy($column_sort, $order); $consumables = $consumables->orderBy($column_sort, $order);
break; break;
@ -154,7 +158,7 @@ class ConsumablesController extends Controller
public function show($id) public function show($id)
{ {
$this->authorize('view', Consumable::class); $this->authorize('view', Consumable::class);
$consumable = Consumable::findOrFail($id); $consumable = Consumable::with('users')->findOrFail($id);
return (new ConsumablesTransformer)->transformConsumable($consumable); return (new ConsumablesTransformer)->transformConsumable($consumable);
} }
@ -253,33 +257,39 @@ class ConsumablesController extends Controller
public function checkout(Request $request, $id) public function checkout(Request $request, $id)
{ {
// Check if the consumable exists // Check if the consumable exists
if (is_null($consumable = Consumable::find($id))) { if (!$consumable = Consumable::with('users')->find($id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.does_not_exist'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.does_not_exist')));
} }
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
if ($consumable->qty > 0) { // Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
\Log::debug('No enough remaining');
}
// Check if the user exists // Check if the user exists - @TODO: this should probably be handled via validation, not here??
$assigned_to = $request->input('assigned_to'); if (!$user = User::find($request->input('assigned_to'))) {
if (is_null($user = User::find($assigned_to))) { // Return error message
// Return error message return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found')); \Log::debug('No valid user');
} }
// Update the consumable data // Update the consumable data
$consumable->assigned_to = e($assigned_to); $consumable->assigned_to = $request->input('assigned_to');
$consumable->users()->attach($consumable->id, [ $consumable->users()->attach($consumable->id,
'consumable_id' => $consumable->id, [
'user_id' => $user->id, 'consumable_id' => $consumable->id,
'assigned_to' => $assigned_to, 'user_id' => $user->id,
'note' => $request->input('note'), 'assigned_to' => $request->input('assigned_to'),
]); 'note' => $request->input('note'),
]
);
// Log checkout event // Log checkout event
$logaction = $consumable->logCheckout(e($request->input('note')), $user); $logaction = $consumable->logCheckout($request->input('note'), $user);
$data['log_id'] = $logaction->id; $data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula(); $data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name; $data['first_name'] = $user->first_name;
@ -289,9 +299,7 @@ class ConsumablesController extends Controller
$data['require_acceptance'] = $consumable->requireAcceptance(); $data['require_acceptance'] = $consumable->requireAcceptance();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No consumables remaining'));
} }
/** /**

View file

@ -58,12 +58,9 @@ class DepartmentsController extends Controller
$departments->where('location_id', '=', $request->input('location_id')); $departments->where('location_id', '=', $request->input('location_id'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $departments->count()) ? $departments->count() : abs($request->input('offset'));
$offset = (($departments) && ($request->get('offset') > $departments->count())) ? $departments->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View file

@ -28,12 +28,9 @@ class DepreciationsController extends Controller
$depreciations = $depreciations->TextSearch($request->input('search')); $depreciations = $depreciations->TextSearch($request->input('search'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : abs($request->input('offset'));
$offset = (($depreciations) && ($request->get('offset') > $depreciations->count())) ? $depreciations->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View file

@ -8,6 +8,7 @@ use App\Http\Transformers\GroupsTransformer;
use App\Models\Group; use App\Models\Group;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class GroupsController extends Controller class GroupsController extends Controller
{ {
/** /**
@ -19,6 +20,8 @@ class GroupsController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('superadmin');
$this->authorize('view', Group::class); $this->authorize('view', Group::class);
$allowed_columns = ['id', 'name', 'created_at', 'users_count']; $allowed_columns = ['id', 'name', 'created_at', 'users_count'];
@ -32,12 +35,9 @@ class GroupsController extends Controller
$groups->where('name', '=', $request->input('name')); $groups->where('name', '=', $request->input('name'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : abs($request->input('offset'));
$offset = (($groups) && ($request->get('offset') > $groups->count())) ? $groups->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@ -59,9 +59,11 @@ class GroupsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', Group::class); $this->authorize('superadmin');
$group = new Group; $group = new Group;
$group->fill($request->all());
$group->name = $request->input('name');
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
if ($group->save()) { if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));
@ -80,7 +82,7 @@ class GroupsController extends Controller
*/ */
public function show($id) public function show($id)
{ {
$this->authorize('view', Group::class); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
return (new GroupsTransformer)->transformGroup($group); return (new GroupsTransformer)->transformGroup($group);
@ -97,9 +99,11 @@ class GroupsController extends Controller
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
$this->authorize('update', Group::class); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
$group->fill($request->all());
$group->name = $request->input('name');
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
if ($group->save()) { if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.update.success')));
@ -118,9 +122,8 @@ class GroupsController extends Controller
*/ */
public function destroy($id) public function destroy($id)
{ {
$this->authorize('delete', Group::class); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
$this->authorize('delete', $group);
$group->delete(); $group->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/groups/message.delete.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/groups/message.delete.success')));

View file

@ -126,7 +126,14 @@ class ImportController extends Controller
} }
$file_name = date('Y-m-d-his').'-'.$fixed_filename; $file_name = date('Y-m-d-his').'-'.$fixed_filename;
$import->file_path = $file_name; $import->file_path = $file_name;
$import->filesize = null;
if (!file_exists($path.'/'.$file_name)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 500);
}
$import->filesize = filesize($path.'/'.$file_name); $import->filesize = filesize($path.'/'.$file_name);
$import->save(); $import->save();
$results[] = $import; $results[] = $import;
} }

View file

@ -39,8 +39,10 @@ class LicenseSeatsController extends Controller
} }
$total = $seats->count(); $total = $seats->count();
$offset = (($seats) && (request('offset') >= $total)) ? 0 : request('offset', 0);
$limit = request('limit', 50); // 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'));
$limit = app('api_limit_value');
$seats = $seats->skip($offset)->take($limit)->get(); $seats = $seats->skip($offset)->take($limit)->get();

View file

@ -94,12 +94,9 @@ class LicensesController extends Controller
$licenses->onlyTrashed(); $licenses->onlyTrashed();
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : abs($request->input('offset'));
$offset = (($licenses) && ($request->get('offset') > $licenses->count())) ? $licenses->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View file

@ -78,14 +78,15 @@ class LocationsController extends Controller
$locations->where('locations.country', '=', $request->input('country')); $locations->where('locations.country', '=', $request->input('country'));
} }
$offset = (($locations) && (request('offset') > $locations->count())) ? $locations->count() : request('offset', 0); // 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'));
// Check to make sure the limit is not higher than the max allowed $limit = app('api_limit_value');
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($request->input('sort')) { switch ($request->input('sort')) {
case 'parent': case 'parent':
$locations->OrderParent($order); $locations->OrderParent($order);

View file

@ -57,12 +57,9 @@ class ManufacturersController extends Controller
$manufacturers->where('support_email', '=', $request->input('support_email')); $manufacturers->where('support_email', '=', $request->input('support_email'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : abs($request->input('offset'));
$offset = (($manufacturers) && ($request->get('offset') > $manufacturers->count())) ? $manufacturers->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View file

@ -29,8 +29,10 @@ class PredefinedKitsController extends Controller
$kits = $kits->TextSearch($request->input('search')); $kits = $kits->TextSearch($request->input('search'));
} }
$offset = $request->input('offset', 0); // Make sure the offset and limit are actually integers and do not exceed system limits
$limit = $request->input('limit', 50); $offset = ($request->input('offset') > $kits->count()) ? $kits->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc'; $order = $request->input('order') === 'desc' ? 'desc' : 'asc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name';
$kits->orderBy($sort, $order); $kits->orderBy($sort, $order);

View file

@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository; use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Gate; use Illuminate\Support\Facades\Gate;
use DB; use DB;
class ProfileController extends Controller class ProfileController extends Controller

View file

@ -54,11 +54,15 @@ class ReportsController extends Controller
'note', 'note',
]; ];
// 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'));
$limit = app('api_limit_value');
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc'; $order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
$offset = request('offset', 0);
$limit = request('limit', 50);
$total = $actionlogs->count(); $total = $actionlogs->count();
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get(); $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE); return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);

View file

@ -271,7 +271,7 @@ class SettingsController extends Controller
$headers = ['ContentType' => 'application/zip']; $headers = ['ContentType' => 'application/zip'];
return Storage::download($path.'/'.$file, $file, $headers); return Storage::download($path.'/'.$file, $file, $headers);
} else { } else {
return response()->json(Helper::formatStandardApiResponse('error', null, 'File not found')); return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')));
} }
} }

View file

@ -50,12 +50,9 @@ class StatuslabelsController extends Controller
} }
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : abs($request->input('offset'));
$offset = (($statuslabels) && ($request->get('offset') > $statuslabels->count())) ? $statuslabels->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@ -196,6 +193,7 @@ class StatuslabelsController extends Controller
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::withCount('assets')->get(); $statuslabels = Statuslabel::withCount('assets')->get();
$total = Array();
foreach ($statuslabels as $statuslabel) { foreach ($statuslabels as $statuslabel) {

View file

@ -23,11 +23,30 @@ class SuppliersController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$allowed_columns = ['id', 'name', 'address', 'phone', 'contact', 'fax', 'email', 'image', 'assets_count', 'licenses_count', 'accessories_count', 'url']; $allowed_columns = ['
id',
'name',
'address',
'phone',
'contact',
'fax',
'email',
'image',
'assets_count',
'licenses_count',
'accessories_count',
'components_count',
'consumables_count',
'url',
];
$suppliers = Supplier::select( $suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes'] ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes'])
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count'); ->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
->withCount('components as components_count')
->withCount('consumables as consumables_count');
if ($request->filled('search')) { if ($request->filled('search')) {
@ -74,12 +93,9 @@ class SuppliersController extends Controller
$suppliers->where('notes', '=', $request->input('notes')); $suppliers->where('notes', '=', $request->input('notes'));
} }
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : abs($request->input('offset'));
$offset = (($suppliers) && ($request->get('offset') > $suppliers->count())) ? $suppliers->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View file

@ -20,6 +20,7 @@ use Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class UsersController extends Controller class UsersController extends Controller
{ {
@ -69,6 +70,7 @@ class UsersController extends Controller
'users.ldap_import', 'users.ldap_import',
'users.start_date', 'users.start_date',
'users.end_date', 'users.end_date',
'users.vip',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',) ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
@ -149,6 +151,10 @@ class UsersController extends Controller
$users = $users->where('remote', '=', $request->input('remote')); $users = $users->where('remote', '=', $request->input('remote'));
} }
if ($request->filled('vip')) {
$users = $users->where('vip', '=', $request->input('vip'));
}
if ($request->filled('two_factor_enrolled')) { if ($request->filled('two_factor_enrolled')) {
$users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled')); $users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled'));
} }
@ -186,14 +192,10 @@ class UsersController extends Controller
} }
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Make sure the offset and limit are actually integers and do not exceed system limits
// case we override with the actual count, so we should return 0 items. $offset = ($request->input('offset') > $users->count()) ? $users->count() : abs($request->input('offset'));
$offset = (($users) && ($request->get('offset') > $users->count())) ? $users->count() : $request->get('offset', 0); $limit = app('api_limit_value');
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
switch ($request->input('sort')) { switch ($request->input('sort')) {
@ -212,6 +214,14 @@ class UsersController extends Controller
case 'company': case 'company':
$users = $users->OrderCompany($order); $users = $users->OrderCompany($order);
break; break;
case 'first_name':
$users->orderBy('first_name', $order);
$users->orderBy('last_name', $order);
break;
case 'last_name':
$users->orderBy('last_name', $order);
$users->orderBy('first_name', $order);
break;
default: default:
$allowed_columns = $allowed_columns =
[ [
@ -246,6 +256,7 @@ class UsersController extends Controller
'two_factor_optin', 'two_factor_optin',
'two_factor_enrolled', 'two_factor_enrolled',
'remote', 'remote',
'vip',
'start_date', 'start_date',
'end_date', 'end_date',
]; ];
@ -286,9 +297,11 @@ class UsersController extends Controller
$users = Company::scopeCompanyables($users); $users = Company::scopeCompanyables($users);
if ($request->filled('search')) { if ($request->filled('search')) {
$users = $users->SimpleNameSearch($request->get('search')) $users = $users->where(function ($query) use ($request) {
->orWhere('username', 'LIKE', '%'.$request->get('search').'%') $query->SimpleNameSearch($request->get('search'))
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%'); ->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
});
} }
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc'); $users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
@ -449,6 +462,13 @@ class UsersController extends Controller
// Check if the request has groups passed and has a value // Check if the request has groups passed and has a value
if ($request->filled('groups')) { if ($request->filled('groups')) {
$validator = Validator::make($request->all(), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()){
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
$user->groups()->sync($request->input('groups')); $user->groups()->sync($request->input('groups'));
// The groups field has been passed but it is null, so we should blank it out // The groups field has been passed but it is null, so we should blank it out
} elseif ($request->has('groups')) { } elseif ($request->has('groups')) {

View file

@ -82,8 +82,8 @@ class AssetModelsController extends Controller
$model->user_id = Auth::id(); $model->user_id = Auth::id();
$model->requestable = Request::has('requestable'); $model->requestable = Request::has('requestable');
if ($request->input('custom_fieldset') != '') { if ($request->input('fieldset_id') != '') {
$model->fieldset_id = e($request->input('custom_fieldset')); $model->fieldset_id = e($request->input('fieldset_id'));
} }
$model = $request->handleImages($model); $model = $request->handleImages($model);
@ -160,10 +160,10 @@ class AssetModelsController extends Controller
$this->removeCustomFieldsDefaultValues($model); $this->removeCustomFieldsDefaultValues($model);
if ($request->input('custom_fieldset') == '') { if ($request->input('fieldset_id') == '') {
$model->fieldset_id = null; $model->fieldset_id = null;
} else { } else {
$model->fieldset_id = $request->input('custom_fieldset'); $model->fieldset_id = $request->input('fieldset_id');
if ($this->shouldAddDefaultValues($request->input())) { if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){ if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
@ -444,7 +444,7 @@ class AssetModelsController extends Controller
{ {
return ! empty($input['add_default_values']) return ! empty($input['add_default_values'])
&& ! empty($input['default_values']) && ! empty($input['default_values'])
&& ! empty($input['custom_fieldset']); && ! empty($input['fieldset_id']);
} }
/** /**

View file

@ -27,7 +27,7 @@ class AssetCheckoutController extends Controller
public function create($assetId) public function create($assetId)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find(e($assetId)))) { if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
} }

View file

@ -16,7 +16,7 @@ use App\Models\User;
use Auth; use Auth;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use DB;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -142,6 +142,7 @@ class AssetsController extends Controller
$asset->warranty_months = request('warranty_months', null); $asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost')); $asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null); $asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null); $asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null); $asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0); $asset->requestable = request('requestable', 0);
@ -312,6 +313,8 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null); $asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null); $asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null)); $asset->purchase_cost = Helper::ParseCurrency($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);
$asset->supplier_id = $request->input('supplier_id', null); $asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null); $asset->expected_checkin = $request->input('expected_checkin', null);
@ -624,7 +627,11 @@ class AssetsController extends Controller
$csv->setHeaderOffset(0); $csv->setHeaderOffset(0);
$header = $csv->getHeader(); $header = $csv->getHeader();
$isCheckinHeaderExplicit = in_array('checkin date', (array_map('strtolower', $header))); $isCheckinHeaderExplicit = in_array('checkin date', (array_map('strtolower', $header)));
$results = $csv->getRecords(); try {
$results = $csv->getRecords();
} catch (\Exception $e) {
return back()->with('error', trans('general.error_in_import_file', ['error' => $e->getMessage()]));
}
$item = []; $item = [];
$status = []; $status = [];
$status['error'] = []; $status['error'] = [];

View file

@ -49,6 +49,7 @@ class BulkAssetsController extends Controller
->with('settings', Setting::getSettings()) ->with('settings', Setting::getSettings())
->with('bulkedit', true) ->with('bulkedit', true)
->with('count', 0); ->with('count', 0);
case 'delete': case 'delete':
$assets = Asset::with('assignedTo', 'location')->find($asset_ids); $assets = Asset::with('assignedTo', 'location')->find($asset_ids);
$assets->each(function ($asset) { $assets->each(function ($asset) {
@ -56,6 +57,15 @@ class BulkAssetsController extends Controller
}); });
return view('hardware/bulk-delete')->with('assets', $assets); return view('hardware/bulk-delete')->with('assets', $assets);
case 'restore':
$assets = Asset::withTrashed()->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit': case 'edit':
return view('hardware/bulk') return view('hardware/bulk')
->with('assets', $asset_ids) ->with('assets', $asset_ids)
@ -102,8 +112,11 @@ class BulkAssetsController extends Controller
|| ($request->filled('company_id')) || ($request->filled('company_id'))
|| ($request->filled('status_id')) || ($request->filled('status_id'))
|| ($request->filled('model_id')) || ($request->filled('model_id'))
|| ($request->filled('next_audit_date'))
|| ($request->filled('null_purchase_date')) || ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date')) || ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
) { ) {
foreach ($assets as $assetId) { foreach ($assets as $assetId) {
@ -116,7 +129,8 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('requestable') ->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id') ->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id') ->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months'); ->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date');
if ($request->input('null_purchase_date')=='1') { if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null; $this->update_array['purchase_date'] = null;
@ -126,6 +140,10 @@ class BulkAssetsController extends Controller
$this->update_array['expected_checkin'] = null; $this->update_array['expected_checkin'] = null;
} }
if ($request->input('null_next_audit_date')=='1') {
$this->update_array['next_audit_date'] = null;
}
if ($request->filled('purchase_cost')) { if ($request->filled('purchase_cost')) {
$this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost')); $this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
} }
@ -288,7 +306,8 @@ class BulkAssetsController extends Controller
foreach ($asset_ids as $asset_id) { foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset); $this->authorize('checkout', $asset);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), null);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
if ($target->location_id != '') { if ($target->location_id != '') {
$asset->location_id = $target->location_id; $asset->location_id = $target->location_id;
@ -311,5 +330,18 @@ class BulkAssetsController extends Controller
} catch (ModelNotFoundException $e) { } catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors()); return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
} }
}
public function restore(Request $request) {
$assetIds = $request->get('ids');
if (empty($assetIds)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
} else {
foreach ($assetIds as $key => $assetId) {
$asset = Asset::withTrashed()->find($assetId);
$asset->restore();
}
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
}
} }
} }

View file

@ -92,7 +92,7 @@ class BulkAssetModelsController extends Controller
AssetModel::whereIn('id', $models_raw_array)->update($update_array); AssetModel::whereIn('id', $models_raw_array)->update($update_array);
return redirect()->route('models.index') return redirect()->route('models.index')
->with('success', trans('admin/models/message.bulkedit.success')); ->with('success', trans_choice('admin/models/message.bulkedit.success', count($models_raw_array), ['model_count' => count($models_raw_array)]));
} }
return redirect()->route('models.index') return redirect()->route('models.index')

View file

@ -33,6 +33,11 @@ class ComponentCheckoutController extends Controller
} }
$this->authorize('checkout', $component); $this->authorize('checkout', $component);
// Make sure there is at least one available to checkout
if ($component->numRemaining() <= 0){
return redirect()->route('components.index')->with('error', trans('admin/components/message.checkout.unavailable'));
}
return view('components/checkout', compact('component')); return view('components/checkout', compact('component'));
} }
@ -50,7 +55,7 @@ class ComponentCheckoutController extends Controller
public function store(Request $request, $componentId) public function store(Request $request, $componentId)
{ {
// Check if the component exists // Check if the component exists
if (is_null($component = Component::find($componentId))) { if (!$component = Component::find($componentId)) {
// Redirect to the component management page with error // Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found')); return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
} }
@ -58,9 +63,15 @@ class ComponentCheckoutController extends Controller
$this->authorize('checkout', $component); $this->authorize('checkout', $component);
$max_to_checkout = $component->numRemaining(); $max_to_checkout = $component->numRemaining();
// Make sure there are at least the requested number of components available to checkout
if ($max_to_checkout < $request->get('assigned_qty')) {
return redirect()->back()->withInput()->with('error', trans('admin/components/message.checkout.unavailable', ['remaining' => $max_to_checkout, 'requested' => $request->get('assigned_qty')]));
}
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'asset_id' => 'required', 'asset_id' => 'required|exists:assets,id',
'assigned_qty' => "required|numeric|between:1,$max_to_checkout", 'assigned_qty' => "required|numeric|min:1|digits_between:1,$max_to_checkout",
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@ -69,24 +80,17 @@ class ComponentCheckoutController extends Controller
->withInput(); ->withInput();
} }
$admin_user = Auth::user();
$asset_id = e($request->input('asset_id'));
// Check if the user exists // Check if the user exists
if (is_null($asset = Asset::find($asset_id))) { $asset = Asset::find($request->input('asset_id'));
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.asset_does_not_exist'));
}
// Update the component data // Update the component data
$component->asset_id = $asset_id; $component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [ $component->assets()->attach($component->id, [
'component_id' => $component->id, 'component_id' => $component->id,
'user_id' => $admin_user->id, 'user_id' => Auth::user(),
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'), 'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $asset_id, 'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'), 'note' => $request->input('note'),
]); ]);

View file

@ -71,6 +71,7 @@ class ComponentsController extends Controller
$component = new Component(); $component = new Component();
$component->name = $request->input('name'); $component->name = $request->input('name');
$component->category_id = $request->input('category_id'); $component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->location_id = $request->input('location_id'); $component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null); $component->order_number = $request->input('order_number', null);
@ -145,6 +146,7 @@ class ComponentsController extends Controller
// Update the component data // Update the component data
$component->name = $request->input('name'); $component->name = $request->input('name');
$component->category_id = $request->input('category_id'); $component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->location_id = $request->input('location_id'); $component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number'); $component->order_number = $request->input('order_number');

View file

@ -24,9 +24,16 @@ class ConsumableCheckoutController extends Controller
*/ */
public function create($consumableId) public function create($consumableId)
{ {
if (is_null($consumable = Consumable::find($consumableId))) {
if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist')); return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
} }
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable')); return view('consumables/checkout', compact('consumable'));
@ -44,12 +51,18 @@ class ConsumableCheckoutController extends Controller
*/ */
public function store(Request $request, $consumableId) public function store(Request $request, $consumableId)
{ {
if (is_null($consumable = Consumable::find($consumableId))) { if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found')); return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
} }
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
$admin_user = Auth::user(); $admin_user = Auth::user();
$assigned_to = e($request->input('assigned_to')); $assigned_to = e($request->input('assigned_to'));

View file

@ -68,6 +68,7 @@ class ConsumablesController extends Controller
$consumable = new Consumable(); $consumable = new Consumable();
$consumable->name = $request->input('name'); $consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id'); $consumable->category_id = $request->input('category_id');
$consumable->supplier_id = $request->input('supplier_id');
$consumable->location_id = $request->input('location_id'); $consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id')); $consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number'); $consumable->order_number = $request->input('order_number');
@ -144,6 +145,7 @@ class ConsumablesController extends Controller
$consumable->name = $request->input('name'); $consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id'); $consumable->category_id = $request->input('category_id');
$consumable->supplier_id = $request->input('supplier_id');
$consumable->location_id = $request->input('location_id'); $consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id')); $consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number'); $consumable->order_number = $request->input('order_number');

View file

@ -9,11 +9,11 @@
* *
* **THIS DOCUMENTATION DOES NOT COVER INSTALLATION.** If you're here and you're not a * **THIS DOCUMENTATION DOES NOT COVER INSTALLATION.** If you're here and you're not a
* developer, you're probably in the wrong place. Please see the * developer, you're probably in the wrong place. Please see the
* [Installation documentation](http://docs.snipeitapp.com) for * [Installation documentation](https://snipe-it.readme.io) for
* information on how to install Snipe-IT. * information on how to install Snipe-IT.
* *
* To learn how to set up a development environment and get started developing for Snipe-IT, * To learn how to set up a development environment and get started developing for Snipe-IT,
* please see the [contributing documentation](http://docs.snipeitapp.com/contributing.html). * please see the [contributing documentation](https://snipe-it.readme.io/docs/contributing-overview).
* *
* Only the Snipe-IT specific controllers, models, helpers, service providers, * Only the Snipe-IT specific controllers, models, helpers, service providers,
* etc have been included in this documentation (excluding vendors, Laravel core, etc) * etc have been included in this documentation (excluding vendors, Laravel core, etc)

View file

@ -92,7 +92,7 @@ class GroupsController extends Controller
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions')); return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
} }
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found')); return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
} }
/** /**
@ -107,7 +107,7 @@ class GroupsController extends Controller
public function update(Request $request, $id = null) public function update(Request $request, $id = null)
{ {
if (! $group = Group::find($id)) { if (! $group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id'))); return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
} }
$group->name = $request->input('name'); $group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission')); $group->permissions = json_encode($request->input('permission'));
@ -133,14 +133,13 @@ class GroupsController extends Controller
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Exception * @throws \Exception
*/ */
public function destroy($id = null) public function destroy($id)
{ {
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
if (! $group = Group::find($id)) { if (! $group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id'))); return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
} }
$group->delete(); $group->delete();
// Redirect to the group management page
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.delete')); return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.delete'));
} }
@ -164,6 +163,6 @@ class GroupsController extends Controller
return view('groups/view', compact('group')); return view('groups/view', compact('group'));
} }
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id'))); return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
} }
} }

View file

@ -1,22 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Transformers\ImportsTransformer;
use App\Models\Asset;
use App\Models\Import;
class ImportsController extends Controller
{
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('import');
$imports = (new ImportsTransformer)->transformImports(Import::latest()->get());
return view('importer/import')->with('imports', $imports);
}
}

View file

@ -59,6 +59,12 @@ class LicenseCheckinController extends Controller
} }
$license = License::find($licenseSeat->license_id); $license = License::find($licenseSeat->license_id);
// LicenseSeat is not assigned, it can't be checked in
if (is_null($licenseSeat->assigned_to) && is_null($licenseSeat->asset_id)) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkin.error'));
}
$this->authorize('checkout', $license); $this->authorize('checkout', $license);
if (! $license->reassignable) { if (! $license->reassignable) {

View file

@ -91,29 +91,30 @@ class LicenseFilesController extends Controller
*/ */
public function destroy($licenseId = null, $fileId = null) public function destroy($licenseId = null, $fileId = null)
{ {
$license = License::find($licenseId); if ($license = License::find($licenseId)) {
// the asset is valid
if (isset($license->id)) {
$this->authorize('update', $license); $this->authorize('update', $license);
$log = Actionlog::find($fileId);
// Remove the file if one exists if ($log = Actionlog::find($fileId)) {
if (Storage::exists('licenses/'.$log->filename)) {
try { // Remove the file if one exists
Storage::delete('licenses/'.$log->filename); if (Storage::exists('licenses/'.$log->filename)) {
} catch (\Exception $e) { try {
\Log::debug($e); Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
}
} }
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
} }
$log->delete(); return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist'));
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
} }
// Redirect to the licence management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
} }
@ -129,7 +130,6 @@ class LicenseFilesController extends Controller
*/ */
public function show($licenseId = null, $fileId = null, $download = true) public function show($licenseId = null, $fileId = null, $download = true)
{ {
\Log::info('Private filesystem is: '.config('filesystems.default'));
$license = License::find($licenseId); $license = License::find($licenseId);
// the license is valid // the license is valid

View file

@ -227,6 +227,36 @@ class LocationsController extends Controller
} }
/**
* Returns a view that presents a form to clone a location.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v6.0.14]
* @return View
*/
public function getClone($locationId = null)
{
$this->authorize('create', Location::class);
// Check if the asset exists
if (is_null($location_to_clone = Location::find($locationId))) {
// Redirect to the asset management page
return redirect()->route('licenses.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
$location = clone $location_to_clone;
// unset these values
$location->id = null;
$location->image = null;
return view('locations/edit')
->with('item', $location);
}
public function print_all_assigned($id) public function print_all_assigned($id)
{ {
if ($location = Location::where('id', $id)->first()) { if ($location = Location::where('id', $id)->first()) {

View file

@ -8,7 +8,7 @@ use App\Models\Setting;
use App\Models\User; use App\Models\User;
use App\Notifications\CurrentInventory; use App\Notifications\CurrentInventory;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View file

@ -51,9 +51,8 @@ class ReportsController extends Controller
public function getAccessoryReport() public function getAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->with('company')->get();
return view('reports/accessories', compact('accessories')); return view('reports/accessories');
} }
/** /**
@ -285,7 +284,7 @@ class ReportsController extends Controller
$row = [ $row = [
$actionlog->created_at, $actionlog->created_at,
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '', ($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '',
$actionlog->present()->actionType(), $actionlog->present()->actionType(),
e($actionlog->itemType()), e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
@ -1025,7 +1024,11 @@ class ReportsController extends Controller
if (is_null($acceptance->created_at)){ if (is_null($acceptance->created_at)){
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data')); return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} else { } else {
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0]; $logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
if ($logItem_res->isEmpty()){
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$logItem = $logItem_res[0];
} }
if(!$assetItem->assignedTo->locale){ if(!$assetItem->assignedTo->locale){

View file

@ -65,18 +65,27 @@ class SettingsController extends Controller
$start_settings['db_error'] = $e->getMessage(); $start_settings['db_error'] = $e->getMessage();
} }
$protocol = array_key_exists('HTTPS', $_SERVER) && ('on' == $_SERVER['HTTPS']) ? 'https://' : 'http://'; if (array_key_exists("HTTP_X_FORWARDED_PROTO", $_SERVER)) {
$protocol = $_SERVER["HTTP_X_FORWARDED_PROTO"] . "://";
} elseif (array_key_exists('HTTPS', $_SERVER) && ('on' == $_SERVER['HTTPS'])) {
$protocol = "https://";
} else {
$protocol = "http://";
}
$host = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : null; if (array_key_exists("HTTP_X_FORWARDED_HOST", $_SERVER)) {
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null; $host = $_SERVER["HTTP_X_FORWARDED_HOST"];
if (('http://' === $protocol && '80' != $port) || ('https://' === $protocol && '443' != $port)) { } else {
$host .= ':'.$port; $host = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : null;
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null;
if (('http://' === $protocol && '80' != $port) || ('https://' === $protocol && '443' != $port)) {
$host .= ':'.$port;
}
} }
$pageURL = $protocol.$host.$_SERVER['REQUEST_URI']; $pageURL = $protocol.$host.$_SERVER['REQUEST_URI'];
$start_settings['url_valid'] = (url('/').'/setup' === $pageURL); $start_settings['url_config'] = config('app.url').'/setup';
$start_settings['url_valid'] = ($start_settings['url_config'] === $pageURL);
$start_settings['url_config'] = url('/');
$start_settings['real_url'] = $pageURL; $start_settings['real_url'] = $pageURL;
$start_settings['php_version_min'] = true; $start_settings['php_version_min'] = true;
@ -111,17 +120,17 @@ class SettingsController extends Controller
$start_settings['prod'] = true; $start_settings['prod'] = true;
} }
$start_settings['owner'] = '';
if (function_exists('posix_getpwuid')) { // Probably Linux if (function_exists('posix_getpwuid')) { // Probably Linux
$owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME'])); $owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME']));
$start_settings['owner'] = $owner['name']; // This *should* be an array, but we've seen this return a bool in some chrooted environments
} else { // Windows if (is_array($owner)) {
// TODO: Is there a way of knowing if a windows user has elevated permissions $start_settings['owner'] = $owner['name'];
// This just gets the user name, which likely isn't 'root' }
// $start_settings['owner'] = getenv('USERNAME');
$start_settings['owner'] = '';
} }
if (('root' === $start_settings['owner']) || ('0' === $start_settings['owner'])) { if (($start_settings['owner'] === 'root') || ($start_settings['owner'] === '0')) {
$start_settings['owner_is_admin'] = true; $start_settings['owner_is_admin'] = true;
} else { } else {
$start_settings['owner_is_admin'] = false; $start_settings['owner_is_admin'] = false;

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Users; namespace App\Http\Controllers\Users;
use App\Events\UserMerged;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Accessory; use App\Models\Accessory;
@ -13,6 +14,7 @@ use App\Models\LicenseSeat;
use App\Models\ConsumableAssignment; use App\Models\ConsumableAssignment;
use App\Models\Consumable; use App\Models\Consumable;
use App\Models\User; use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -21,7 +23,7 @@ use Illuminate\Support\Facades\Password;
class BulkUsersController extends Controller class BulkUsersController extends Controller
{ {
/** /**
* Returns a view that confirms the user's a bulk delete will be applied to. * Returns a view that confirms the user's a bulk action will be applied to.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7] * @since [v1.7]
@ -35,16 +37,32 @@ class BulkUsersController extends Controller
// Make sure there were users selected // Make sure there were users selected
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) { if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
// Get the list of affected users // Get the list of affected users
$user_raw_array = request('ids'); $user_raw_array = request('ids');
$users = User::whereIn('id', $user_raw_array) $users = User::whereIn('id', $user_raw_array)
->with('groups', 'assets', 'licenses', 'accessories')->get(); ->with('groups', 'assets', 'licenses', 'accessories')->get();
// bulk edit, display the bulk edit form
if ($request->input('bulk_actions') == 'edit') { if ($request->input('bulk_actions') == 'edit') {
return view('users/bulk-edit', compact('users')) return view('users/bulk-edit', compact('users'))
->with('groups', Group::pluck('name', 'id')); ->with('groups', Group::pluck('name', 'id'));
// bulk delete, display the bulk delete confirmation form
} elseif ($request->input('bulk_actions') == 'delete') { } elseif ($request->input('bulk_actions') == 'delete') {
return view('users/confirm-bulk-delete')->with('users', $users)->with('statuslabel_list', Helper::statusLabelList()); return view('users/confirm-bulk-delete')->with('users', $users)->with('statuslabel_list', Helper::statusLabelList());
// merge, confirm they have at least 2 users selected and display the merge screen
} elseif ($request->input('bulk_actions') == 'merge') {
if (($request->filled('ids')) && (count($request->input('ids')) > 1)) {
return view('users/confirm-merge')->with('users', $users);
// Not enough users selected, send them back
} else {
return redirect()->back()->with('error', trans('general.not_enough_users_selected', ['count' => 2]));
}
// bulk password reset, just do the thing
} elseif ($request->input('bulk_actions') == 'bulkpasswordreset') { } elseif ($request->input('bulk_actions') == 'bulkpasswordreset') {
foreach ($users as $user) { foreach ($users as $user) {
if (($user->activated == '1') && ($user->email != '')) { if (($user->activated == '1') && ($user->email != '')) {
@ -59,7 +77,7 @@ class BulkUsersController extends Controller
} }
} }
return redirect()->back()->with('error', 'No users selected'); return redirect()->back()->with('error', trans('general.no_users_selected'));
} }
/** /**
@ -76,7 +94,7 @@ class BulkUsersController extends Controller
$this->authorize('update', User::class); $this->authorize('update', User::class);
if ((! $request->filled('ids')) || $request->input('ids') <= 0) { if ((! $request->filled('ids')) || $request->input('ids') <= 0) {
return redirect()->back()->with('error', 'No users selected'); return redirect()->back()->with('error', trans('general.no_users_selected'));
} }
$user_raw_array = $request->input('ids'); $user_raw_array = $request->input('ids');
@ -105,6 +123,11 @@ class BulkUsersController extends Controller
'warning' => trans('admin/users/message.bulk_manager_warn'), 'warning' => trans('admin/users/message.bulk_manager_warn'),
]; ];
} }
if ($request->input('null_location_id')=='1') {
$this->update_array['location_id'] = null;
}
if (! $manager_conflict) { if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id'); $this->conditionallyAddItem('manager_id');
} }
@ -163,11 +186,11 @@ class BulkUsersController extends Controller
$this->authorize('update', User::class); $this->authorize('update', User::class);
if ((! $request->filled('ids')) || (count($request->input('ids')) == 0)) { if ((! $request->filled('ids')) || (count($request->input('ids')) == 0)) {
return redirect()->back()->with('error', 'No users selected'); return redirect()->back()->with('error', trans('general.no_users_selected'));
} }
if (config('app.lock_passwords')) { if (config('app.lock_passwords')) {
return redirect()->route('users.index')->with('error', 'Bulk delete is not enabled in this installation'); return redirect()->route('users.index')->with('error', trans('general.feature_disabled'));
} }
$user_raw_array = request('ids'); $user_raw_array = request('ids');
@ -249,4 +272,80 @@ class BulkUsersController extends Controller
$logAction->logaction('checkin from'); $logAction->logaction('checkin from');
} }
} }
/**
* Save bulk-edited users
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function merge(Request $request)
{
$this->authorize('update', User::class);
if (config('app.lock_passwords')) {
return redirect()->route('users.index')->with('error', trans('general.feature_disabled'));
}
$user_ids_to_merge = $request->input('ids_to_merge');
$user_ids_to_merge = array_diff($user_ids_to_merge, array($request->input('merge_into_id')));
if ((!$request->filled('merge_into_id')) || (count($user_ids_to_merge) < 1)) {
return redirect()->back()->with('error', trans('general.no_users_selected'));
}
// Get the users
$merge_into_user = User::find($request->input('merge_into_id'));
$users_to_merge = User::whereIn('id', $user_ids_to_merge)->with('assets', 'licenses', 'consumables','accessories')->get();
$admin = User::find(Auth::user()->id);
// Walk users
foreach ($users_to_merge as $user_to_merge) {
foreach ($user_to_merge->assets as $asset) {
\Log::debug('Updating asset: '.$asset->asset_tag . ' to '.$merge_into_user->id);
$asset->assigned_to = $request->input('merge_into_id');
$asset->save();
}
foreach ($user_to_merge->licenses as $license) {
\Log::debug('Updating license pivot: '.$license->id . ' to '.$merge_into_user->id);
$user_to_merge->licenses()->updateExistingPivot($license->id, ['assigned_to' => $merge_into_user->id]);
}
foreach ($user_to_merge->consumables as $consumable) {
\Log::debug('Updating consumable pivot: '.$consumable->id . ' to '.$merge_into_user->id);
$user_to_merge->consumables()->updateExistingPivot($consumable->id, ['assigned_to' => $merge_into_user->id]);
}
foreach ($user_to_merge->accessories as $accessory) {
$user_to_merge->accessories()->updateExistingPivot($accessory->id, ['assigned_to' => $merge_into_user->id]);
}
foreach ($user_to_merge->userlog as $log) {
$log->target_id = $user_to_merge->id;
$log->save();
}
User::where('manager_id', '=', $user_to_merge->id)->update(['manager_id' => $merge_into_user->id]);
foreach ($user_to_merge->managedLocations as $managedLocation) {
$managedLocation->manager_id = $merge_into_user->id;
$managedLocation->save();
}
$user_to_merge->delete();
//$user_to_merge->save();
event(new UserMerged($user_to_merge, $merge_into_user, $admin));
}
return redirect()->route('users.index')->with('success', trans('general.merge_success', ['count' => $users_to_merge->count(), 'into_username' => $merge_into_user->username]));
}
} }

View file

@ -74,7 +74,6 @@ class UsersController extends Controller
$permissions = $this->filterDisplayable($permissions); $permissions = $this->filterDisplayable($permissions);
$user = new User; $user = new User;
$user->activated = 1;
return view('users/edit', compact('groups', 'userGroups', 'permissions', 'userPermissions')) return view('users/edit', compact('groups', 'userGroups', 'permissions', 'userPermissions'))
->with('user', $user); ->with('user', $user);
@ -121,6 +120,7 @@ class UsersController extends Controller
$user->created_by = Auth::user()->id; $user->created_by = Auth::user()->id;
$user->start_date = $request->input('start_date', null); $user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null); $user->end_date = $request->input('end_date', null);
$user->autoassign_licenses= $request->input('autoassign_licenses', 1);
// Strip out the superuser permission if the user isn't a superadmin // Strip out the superuser permission if the user isn't a superadmin
$permissions_array = $request->input('permission'); $permissions_array = $request->input('permission');
@ -271,9 +271,11 @@ class UsersController extends Controller
$user->activated = $request->input('activated', 0); $user->activated = $request->input('activated', 0);
$user->zip = $request->input('zip', null); $user->zip = $request->input('zip', null);
$user->remote = $request->input('remote', 0); $user->remote = $request->input('remote', 0);
$user->vip = $request->input('vip', 0);
$user->website = $request->input('website', null); $user->website = $request->input('website', null);
$user->start_date = $request->input('start_date', null); $user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null); $user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 1);
// Update the location of any assets checked out to this user // Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class) Asset::where('assigned_type', User::class)

View file

@ -0,0 +1,322 @@
<?php
namespace App\Http\Livewire;
use App\Models\CustomField;
use Livewire\Component;
use App\Models\Import;
use Storage;
use Log;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Importer extends Component
{
use AuthorizesRequests;
public $files;
public $progress; //upload progress - '-1' means don't show
public $progress_message;
public $progress_bar_class;
public $message; //status/error message?
public $message_type; //success/error?
//originally from ImporterFile
public $import_errors; //
public ?Import $activeFile = null;
public $importTypes;
public $columnOptions;
public $statusType;
public $statusText;
public $update;
public $send_welcome;
public $run_backup;
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?
protected $rules = [
'files.*.file_path' => 'required|string',
'files.*.created_at' => 'required|string',
'files.*.filesize' => 'required|integer',
'activeFile' => 'Import',
'activeFile.import_type' => 'string',
'activeFile.field_map' => 'array',
'activeFile.header_row' => 'array',
'field_map' => 'array'
];
public function generate_field_map()
{
\Log::debug("header row is: ".print_r($this->activeFile->header_row,true));
\Log::debug("Field map is: ".print_r($this->field_map,true));
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
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',
'jobtitle' => 'Job Title',
'last_name' => 'Last Name',
'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',
'vip' => 'VIP'
];
//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'
],
'min_amt' =>
[
'Min Amount',
'Min QTY'
],
'next_audit_date' =>
[
'Next Audit',
],
];
private function getColumns($type)
{
switch ($type) {
case 'asset':
$results = self::$general + self::$assets;
break;
case 'accessory':
$results = self::$general + self::$accessories;
break;
case 'consumable':
$results = self::$general + self::$consumables;
break;
case 'license':
$results = self::$general + self::$licenses;
break;
case 'user':
$results = self::$general + self::$users;
break;
default:
$results = self::$general;
}
asort($results, SORT_FLAG_CASE | SORT_STRING);
if ($type == "asset") {
// add Custom Fields after a horizontal line
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
foreach (CustomField::orderBy('name')->get() as $field) {
$results[$field->db_column_name()] = $field->name;
}
}
return $results;
}
public function updating($name, $new_import_type)
{
if ($name == "activeFile.import_type") {
\Log::debug("WE ARE CHANGING THE import_type!!!!! TO: " . $new_import_type);
\Log::debug("so, what's \$this->>field_map at?: " . print_r($this->field_map, true));
// go through each header, find a matching field to try and map it to.
foreach ($this->activeFile->header_row as $i => $header) {
// do we have something mapped already?
if (array_key_exists($i, $this->field_map)) {
// yes, we do. Is it valid for this type of import?
// (e.g. the import type might have been changed...?)
if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) {
//yes, this key *is* valid. Continue on to the next field.
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.
$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.
}
// first, check for exact matches
foreach ($this->columnOptions[$new_import_type] as $value => $text) {
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
$this->field_map[$i] = $value;
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) {
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 -
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
// in "Accessories"!)
if (array_key_exists($key, $this->columnOptions[$new_import_type])) {
$this->field_map[$i] = $key;
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
}
}
}
}
// and if you got here, we got nothing. Let's recommend 'null'
$this->field_map[$i] = null; // Booooo :(
}
}
}
public function boot() { // FIXME - delete or undelete.
///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever.
}
public function mount()
{
$this->authorize('import');
$this->progress = -1; // '-1' means 'don't show the progressbar'
$this->progress_bar_class = 'progress-bar-warning';
\Log::debug("Hey, we are calling MOUNT (in the importer-file) !!!!!!!!"); //fcuk
$this->importTypes = [
'asset' => trans('general.assets'),
'accessory' => trans('general.accessories'),
'consumable' => trans('general.consumables'),
'component' => trans('general.components'),
'license' => trans('general.licenses'),
'user' => trans('general.users'),
];
$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);
}
if ($this->activeFile) {
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
}
}
public function selectFile($id)
{
\Log::debug("TOGGLE EVENT FIRED!");
\Log::debug("The ID we are trying to find is AS FOLLOWS: ".$id);
$this->activeFile = Import::find($id);
$this->field_map = null;
foreach($this->activeFile->header_row as $element) {
if(isset($this->activeFile->field_map[$element])) {
$this->field_map[] = $this->activeFile->field_map[$element];
} else {
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
}
}
//$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : []; // this is wrong
$this->file_id = $id;
$this->import_errors = null;
$this->statusText = null;
\Log::debug("The import type we are about to try and load up is gonna be this: ".$this->activeFile->import_type);
}
public function destroy($id)
{
// TODO: why don't we just do File::find($id)? This seems dumb.
foreach($this->files as $file) {
\Log::debug("File id is: ".$file->id);
if($id == $file->id) {
if(Storage::delete('private_uploads/imports/'.$file->file_path)) {
$file->delete();
$this->message = trans('admin/hardware/message.import.file_delete_success');
$this->message_type = 'success';
return;
} else {
$this->message = trans('admin/hardware/message.import.file_delete_error');
$this->message_type = 'danger';
}
}
}
}
public function render()
{
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
return view('livewire.importer')
->extends('layouts.default')
->section('content');
}
}

View file

@ -5,50 +5,106 @@ namespace App\Http\Livewire;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Livewire\Component; use Livewire\Component;
use App\Models\Setting; use App\Models\Setting;
use App\Helpers\Helper;
class SlackSettingsForm extends Component class SlackSettingsForm extends Component
{ {
public $slack_endpoint; public $webhook_endpoint;
public $slack_channel; public $webhook_channel;
public $slack_botname; public $webhook_botname;
public $isDisabled ='disabled' ; public $isDisabled ='' ;
public $webhook_name;
public $webhook_link;
public $webhook_placeholder;
public $webhook_icon;
public $webhook_selected;
public array $webhook_text;
public Setting $setting; public Setting $setting;
protected $rules = [ protected $rules = [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable', 'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable', 'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
'slack_botname' => 'string|nullable', 'webhook_botname' => 'string|nullable',
]; ];
public function mount(){ public function mount() {
$this->webhook_text= [
"slack" => array(
"name" => trans('admin/settings/general.slack') ,
"icon" => 'fab fa-slack',
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
"link" => 'https://api.slack.com/messaging/webhooks',
),
"general"=> array(
"name" => trans('admin/settings/general.general_webhook'),
"icon" => "fab fa-hashtag",
"placeholder" => "",
"link" => "",
),
];
$this->setting = Setting::getSettings(); $this->setting = Setting::getSettings();
$this->slack_endpoint = $this->setting->slack_endpoint; $this->save_button = trans('general.save');
$this->slack_channel = $this->setting->slack_channel; $this->webhook_selected = $this->setting->webhook_selected;
$this->slack_botname = $this->setting->slack_botname; $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"];
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
$this->webhook_endpoint = $this->setting->webhook_endpoint;
$this->webhook_channel = $this->setting->webhook_channel;
$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= '';
}
} }
public function updated($field){ public function updated($field) {
if($this->webhook_selected != 'general') {
$this->validateOnly($field, $this->rules);
}
}
public function updatedWebhookSelected() {
$this->webhook_name = $this->webhook_text[$this->webhook_selected]['name'];
$this->webhook_icon = $this->webhook_text[$this->webhook_selected]["icon"]; ;
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
if($this->webhook_selected != 'slack'){
$this->isDisabled= '';
$this->save_button = trans('general.save');
}
}
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');
}
if (empty($this->webhook_channel)) {
$this->isDisabled = 'disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
}
}
$this->validateOnly($field ,$this->rules);
} }
public function render() public function render()
{ {
if(empty($this->slack_channel || $this->slack_endpoint)){ $this->isButtonDisabled();
$this->isDisabled= 'disabled';
}
if(empty($this->slack_endpoint && $this->slack_channel)){
$this->isDisabled= '';
}
return view('livewire.slack-settings-form'); return view('livewire.slack-settings-form');
} }
public function testSlack(){ public function testWebhook(){
$slack = new Client([ $webhook = new Client([
'base_url' => e($this->slack_endpoint), 'base_url' => e($this->webhook_endpoint),
'defaults' => [ 'defaults' => [
'exceptions' => false, 'exceptions' => false,
], ],
@ -56,40 +112,66 @@ class SlackSettingsForm extends Component
$payload = json_encode( $payload = json_encode(
[ [
'channel' => e($this->slack_channel), 'channel' => e($this->webhook_channel),
'text' => trans('general.slack_test_msg'), 'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
'username' => e($this->slack_botname), 'username' => e($this->webhook_botname),
'icon_emoji' => ':heart:', 'icon_emoji' => ':heart:',
]); ]);
try { try {
$slack->post($this->slack_endpoint, ['body' => $payload]);
$webhook->post($this->webhook_endpoint, ['body' => $payload]);
$this->isDisabled=''; $this->isDisabled='';
return session()->flash('success' , 'Your Slack Integration works!'); $this->save_button = trans('general.save');
return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->isDisabled= 'disabled'; $this->isDisabled= 'disabled';
return session()->flash('error' , trans('admin/settings/message.slack.error', ['error_message' => $e->getMessage()])); return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
} }
//} return session()->flash('error' , trans('admin/settings/message.webhook.error_misc'));
return session()->flash('message' , trans('admin/settings/message.slack.error_misc'));
} }
public function clearSettings(){
if (Helper::isDemoMode()) {
session()->flash('error',trans('general.feature_disabled'));
} else {
$this->webhook_endpoint = '';
$this->webhook_channel = '';
$this->webhook_botname = '';
$this->setting->webhook_endpoint = '';
$this->setting->webhook_channel = '';
$this->setting->webhook_botname = '';
$this->setting->save();
session()->flash('success', trans('admin/settings/message.update.success'));
}
}
public function submit() public function submit()
{ {
$this->validate($this->rules); if (Helper::isDemoMode()) {
session()->flash('error',trans('general.feature_disabled'));
} else {
if ($this->webhook_selected != 'general') {
$this->validate($this->rules);
}
$this->setting->slack_endpoint = $this->slack_endpoint; $this->setting->webhook_selected = $this->webhook_selected;
$this->setting->slack_channel = $this->slack_channel; $this->setting->webhook_endpoint = $this->webhook_endpoint;
$this->setting->slack_botname = $this->slack_botname; $this->setting->webhook_channel = $this->webhook_channel;
$this->setting->webhook_botname = $this->webhook_botname;
$this->setting->save(); $this->setting->save();
session()->flash('save',trans('admin/settings/message.update.success')); session()->flash('success',trans('admin/settings/message.update.success'));
}
} }
} }

View file

@ -20,13 +20,13 @@ class CheckForSetup
if (Setting::setupCompleted()) { if (Setting::setupCompleted()) {
if ($request->is('setup*')) { if ($request->is('setup*')) {
return redirect(url('/')); return redirect(config('app.url'));
} else { } else {
return $next($request); return $next($request);
} }
} else { } else {
if (! ($request->is('setup*')) && ! ($request->is('.env')) && ! ($request->is('health'))) { if (! ($request->is('setup*')) && ! ($request->is('.env')) && ! ($request->is('health'))) {
return redirect(url('/').'/setup'); return redirect(config('app.url').'/setup');
} }
return $next($request); return $next($request);

View file

@ -3,7 +3,7 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Gate; use Illuminate\Support\Facades\Gate;
class CheckPermissions class CheckPermissions
{ {

View file

@ -24,7 +24,7 @@ class AssetFileRequest extends Request
$max_file_size = \App\Helpers\Helper::file_upload_max_size(); $max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [ return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,webp|max:'.$max_file_size, 'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp|max:'.$max_file_size,
]; ];
} }
} }

View file

@ -7,7 +7,9 @@ use enshrined\svgSanitize\Sanitizer;
use Intervention\Image\Facades\Image; use Intervention\Image\Facades\Image;
use App\Http\Traits\ConvertsBase64ToFiles; use App\Http\Traits\ConvertsBase64ToFiles;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exception\NotReadableException;
class ImageUploadRequest extends Request class ImageUploadRequest extends Request
{ {
@ -106,10 +108,18 @@ class ImageUploadRequest extends Request
\Log::debug('Not an SVG or webp - resize'); \Log::debug('Not an SVG or webp - resize');
\Log::debug('Trying to upload to: '.$path.'/'.$file_name); \Log::debug('Trying to upload to: '.$path.'/'.$file_name);
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) { try {
$constraint->aspectRatio(); $upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->upsize(); $constraint->aspectRatio();
}); $constraint->upsize();
});
} catch(NotReadableException $e) {
\Log::debug($e);
$validator = \Validator::make([], []);
$validator->errors()->add($form_fieldname, trans('general.unaccepted_image_type', ['mimetype' => $image->getClientMimeType()]));
throw new \Illuminate\Validation\ValidationException($validator);
}
// This requires a string instead of an object, so we use ($string) // This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode()); Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Accessory; use App\Models\Accessory;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -51,6 +51,8 @@ class AccessoriesTransformer
'checkin' => false, 'checkin' => false,
'update' => Gate::allows('update', Accessory::class), 'update' => Gate::allows('update', Accessory::class),
'delete' => Gate::allows('delete', Accessory::class), 'delete' => Gate::allows('delete', Accessory::class),
'clone' => Gate::allows('create', Accessory::class),
]; ];
$permissions_array['user_can_checkout'] = false; $permissions_array['user_can_checkout'] = false;

View file

@ -5,7 +5,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetMaintenance; use App\Models\AssetMaintenance;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class AssetMaintenancesTransformer class AssetMaintenancesTransformer
@ -45,6 +45,10 @@ class AssetMaintenancesTransformer
'name'=> e($assetmaintenance->asset->location->name), 'name'=> e($assetmaintenance->asset->location->name),
] : null, ] : null,
'rtd_location' => ($assetmaintenance->asset->defaultLoc) ? [
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
'name'=> e($assetmaintenance->asset->defaultLoc->name),
] : null,
'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null, 'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null,
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null, 'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost), 'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),

View file

@ -5,7 +5,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Setting; use App\Models\Setting;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -38,7 +38,8 @@ class AssetsTransformer
'byod' => ($asset->byod ? true : false), 'byod' => ($asset->byod ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => ($asset->purchase_date != '') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null, 'eol' => ($asset->model->eol != '') ? $asset->model->eol : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
'status_label' => ($asset->assetstatus) ? [ 'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id, 'id' => (int) $asset->assetstatus->id,
'name'=> e($asset->assetstatus->name), 'name'=> e($asset->assetstatus->name),
@ -83,7 +84,7 @@ class AssetsTransformer
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'), 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'), 'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
'age' => $asset->purchase_date ? Helper::AgeFormat($asset->purchase_date) : '', 'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'), 'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'), 'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Category; use App\Models\Category;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -47,7 +47,7 @@ class CategoriesTransformer
'id' => (int) $category->id, 'id' => (int) $category->id,
'name' => e($category->name), 'name' => e($category->name),
'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null, 'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null,
'category_type' => ucwords(e($category->category_type)), 'category_type' => Helper::categoryTypeList($category->category_type),
'has_eula' => ($category->getEula() ? true : false), 'has_eula' => ($category->getEula() ? true : false),
'use_default_eula' => ($category->use_default_eula=='1' ? true : false), 'use_default_eula' => ($category->use_default_eula=='1' ? true : false),
'eula' => ($category->getEula()), 'eula' => ($category->getEula()),

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Company; use App\Models\Company;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View file

@ -3,7 +3,7 @@
namespace App\Http\Transformers; namespace App\Http\Transformers;
use App\Models\Asset; use App\Models\Asset;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class ComponentsAssetsTransformer class ComponentsAssetsTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Component; use App\Models\Component;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -37,6 +37,7 @@ class ComponentsTransformer
'id' => (int) $component->category->id, 'id' => (int) $component->category->id,
'name' => e($component->category->name), 'name' => e($component->category->name),
] : null, ] : null,
'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null,
'order_number' => e($component->order_number), 'order_number' => e($component->order_number),
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'), 'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Consumable; use App\Models\Consumable;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -31,6 +31,7 @@ class ConsumablesTransformer
'item_no' => e($consumable->item_no), 'item_no' => e($consumable->item_no),
'location' => ($consumable->location) ? ['id' => (int) $consumable->location->id, 'name' => e($consumable->location->name)] : null, 'location' => ($consumable->location) ? ['id' => (int) $consumable->location->id, 'name' => e($consumable->location->name)] : null,
'manufacturer' => ($consumable->manufacturer) ? ['id' => (int) $consumable->manufacturer->id, 'name' => e($consumable->manufacturer->name)] : null, 'manufacturer' => ($consumable->manufacturer) ? ['id' => (int) $consumable->manufacturer->id, 'name' => e($consumable->manufacturer->name)] : null,
'supplier' => ($consumable->supplier) ? ['id' => $consumable->supplier->id, 'name'=> e($consumable->supplier->name)] : null,
'min_amt' => (int) $consumable->min_amt, 'min_amt' => (int) $consumable->min_amt,
'model_number' => ($consumable->model_number != '') ? e($consumable->model_number) : null, 'model_number' => ($consumable->model_number != '') ? e($consumable->model_number) : null,
'remaining' => $consumable->numRemaining(), 'remaining' => $consumable->numRemaining(),

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Department; use App\Models\Department;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View file

@ -5,7 +5,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Depreciable; use App\Models\Depreciable;
use App\Models\Depreciation; use App\Models\Depreciation;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class DepreciationsTransformer class DepreciationsTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Group; use App\Models\Group;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class GroupsTransformer class GroupsTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Models\License; use App\Models\License;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class LicenseSeatsTransformer class LicenseSeatsTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\License; use App\Models\License;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class LicensesTransformer class LicensesTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Location; use App\Models\Location;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -63,6 +63,7 @@ class LocationsTransformer
$permissions_array['available_actions'] = [ $permissions_array['available_actions'] = [
'update' => Gate::allows('update', Location::class) ? true : false, 'update' => Gate::allows('update', Location::class) ? true : false,
'delete' => $location->isDeletable(), 'delete' => $location->isDeletable(),
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
]; ];
$array += $permissions_array; $array += $permissions_array;

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Manufacturer; use App\Models\Manufacturer;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Models\PredefinedKit; use App\Models\PredefinedKit;
use App\Models\SnipeModel; use App\Models\SnipeModel;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
/** /**

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class StatuslabelsTransformer class StatuslabelsTransformer

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Supplier; use App\Models\Supplier;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -41,6 +41,8 @@ class SuppliersTransformer
'assets_count' => (int) $supplier->assets_count, 'assets_count' => (int) $supplier->assets_count,
'accessories_count' => (int) $supplier->accessories_count, 'accessories_count' => (int) $supplier->accessories_count,
'licenses_count' => (int) $supplier->licenses_count, '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) ? e($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'),

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\User; use App\Models\User;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class UsersTransformer class UsersTransformer
@ -36,6 +36,7 @@ class UsersTransformer
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name), 'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
] : null, ] : null,
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null, 'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
'vip' => ($user->vip == '1') ? true : false,
'phone' => ($user->phone) ? e($user->phone) : null, 'phone' => ($user->phone) ? e($user->phone) : null,
'website' => ($user->website) ? e($user->website) : null, 'website' => ($user->website) ? e($user->website) : null,
'address' => ($user->address) ? e($user->address) : null, 'address' => ($user->address) ? e($user->address) : null,

View file

@ -60,7 +60,7 @@ class AssetImporter extends ItemImporter
$asset_tag = Asset::autoincrement_asset(); $asset_tag = Asset::autoincrement_asset();
} }
$asset = Asset::where(['asset_tag'=> $asset_tag])->first(); $asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($asset) { if ($asset) {
if (! $this->updating) { if (! $this->updating) {
$this->log('A matching Asset '.$asset_tag.' already exists'); $this->log('A matching Asset '.$asset_tag.' already exists');
@ -76,10 +76,12 @@ class AssetImporter extends ItemImporter
} }
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes'); $this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
$this->item['image'] = $this->findCsvMatch($row, 'image'); $this->item['image'] = $this->findCsvMatch($row, 'image');
$this->item['requestable'] = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')); $this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
$asset->requestable = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')); $asset->requestable = $this->item['requestable'];
$this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months')); $this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
$this->item['model_id'] = $this->createOrFetchAssetModel($row); $this->item['model_id'] = $this->createOrFetchAssetModel($row);
$this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
// If no status ID is found // If no status ID is found
if (! array_key_exists('status_id', $this->item) && ! $editingAsset) { if (! array_key_exists('status_id', $this->item) && ! $editingAsset) {
@ -135,7 +137,7 @@ class AssetImporter extends ItemImporter
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by //-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
//-- the class that needs to use it (command importer or GUI importer inside the project). //-- the class that needs to use it (command importer or GUI importer inside the project).
if (isset($target)) { if (isset($target)) {
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s')); $asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
} }
return; return;

View file

@ -74,7 +74,7 @@ class ItemImporter extends Importer
$this->item['purchase_date'] = null; $this->item['purchase_date'] = null;
if ($this->findCsvMatch($row, 'purchase_date') != '') { if ($this->findCsvMatch($row, 'purchase_date') != '') {
$this->item['purchase_date'] = date('Y-m-d 00:00:01', strtotime($this->findCsvMatch($row, 'purchase_date'))); $this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date')));
} }
$this->item['last_audit_date'] = null; $this->item['last_audit_date'] = null;
@ -90,7 +90,7 @@ class ItemImporter extends Importer
$this->item['qty'] = $this->findCsvMatch($row, 'quantity'); $this->item['qty'] = $this->findCsvMatch($row, 'quantity');
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); $this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
$this->item['user_id'] = $this->user_id; $this->item['user_id'] = $this->user_id;
$this->item['serial'] = $this->findCsvMatch($row, 'serial number'); $this->item['serial'] = $this->findCsvMatch($row, 'serial');
// NO need to call this method if we're running the user import. // NO need to call this method if we're running the user import.
// TODO: Merge these methods. // TODO: Merge these methods.
$this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class'); $this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class');

View file

@ -58,6 +58,8 @@ class UserImporter extends ItemImporter
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department')); $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name')); $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0; $this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
$this->item['vip'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0;
$user_department = $this->findCsvMatch($row, 'department'); $user_department = $this->findCsvMatch($row, 'department');
if ($this->shouldUpdateField($user_department)) { if ($this->shouldUpdateField($user_department)) {

View file

@ -36,8 +36,10 @@
| name | | | | name | | |
| email | | | | email | | |
| username | | | | username | | |
| address | address | User | | address | address | User |
| city | city | User | | city | city | User |
| state | state | User | | state | state | User |
| country | country | User | | country | country | User |
| vip | vip | User |
| byod | byod | Asset |

View file

@ -18,7 +18,9 @@ use App\Events\ItemDeclined;
use App\Events\LicenseCheckedIn; use App\Events\LicenseCheckedIn;
use App\Events\LicenseCheckedOut; use App\Events\LicenseCheckedOut;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\User;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
use App\Events\UserMerged;
class LogListener class LogListener
{ {
@ -87,6 +89,43 @@ class LogListener
$logaction->save(); $logaction->save();
} }
public function onUserMerged(UserMerged $event)
{
$to_from_array = [
'to_id' => $event->merged_to->id,
'to_username' => $event->merged_to->username,
'from_id' => $event->merged_from->id,
'from_username' => $event->merged_from->username,
];
// Add a record to the users being merged FROM
\Log::debug('Users merged: '.$event->merged_from->id .' ('.$event->merged_from->username.') merged into '. $event->merged_to->id. ' ('.$event->merged_to->username.')');
$logaction = new Actionlog();
$logaction->item_id = $event->merged_from->id;
$logaction->item_type = User::class;
$logaction->target_id = $event->merged_to->id;
$logaction->target_type = User::class;
$logaction->action_type = 'merged';
$logaction->note = trans('general.merged_log_this_user_from', $to_from_array);
$logaction->user_id = $event->admin->id;
$logaction->save();
// Add a record to the users being merged TO
$logaction = new Actionlog();
$logaction->target_id = $event->merged_from->id;
$logaction->target_type = User::class;
$logaction->item_id = $event->merged_to->id;
$logaction->item_type = User::class;
$logaction->action_type = 'merged';
$logaction->note = trans('general.merged_log_this_user_into', $to_from_array);
$logaction->user_id = $event->admin->id;
$logaction->save();
}
/** /**
* Register the listeners for the subscriber. * Register the listeners for the subscriber.
* *
@ -99,6 +138,7 @@ class LogListener
'CheckoutableCheckedOut', 'CheckoutableCheckedOut',
'CheckoutAccepted', 'CheckoutAccepted',
'CheckoutDeclined', 'CheckoutDeclined',
'UserMerged',
]; ];
foreach ($list as $event) { foreach ($list as $event) {
@ -108,4 +148,6 @@ class LogListener
); );
} }
} }
} }

View file

@ -330,7 +330,11 @@ class Accessory extends SnipeModel
/** /**
* Check how many items of an accessory remain * Check how many items of an accessory remain.
*
* In order to use this model method, you MUST call withCount('users as users_count')
* on the eloquent query in the controller, otherwise $this->>users_count will be null and
* bad things happen.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]

View file

@ -70,19 +70,6 @@ class Asset extends Depreciable
*/ */
protected $injectUniqueIdentifier = true; protected $injectUniqueIdentifier = true;
// We set these as protected dates so that they will be easily accessible via Carbon
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'purchase_date',
'last_checkout',
'expected_checkin',
'last_audit_date',
'next_audit_date'
];
protected $casts = [ protected $casts = [
'purchase_date' => 'date', 'purchase_date' => 'date',
'last_checkout' => 'datetime', 'last_checkout' => 'datetime',
@ -96,6 +83,9 @@ class Asset extends Depreciable
'rtd_company_id' => 'integer', 'rtd_company_id' => 'integer',
'supplier_id' => 'integer', 'supplier_id' => 'integer',
'byod' => 'boolean', 'byod' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
]; ];
protected $rules = [ protected $rules = [
@ -114,6 +104,7 @@ class Asset extends Depreciable
'serial' => 'unique_serial|nullable', 'serial' => 'unique_serial|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
'supplier_id' => 'exists:suppliers,id|nullable', 'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|max:10|min:10|nullable',
]; ];
/** /**
@ -143,9 +134,9 @@ class Asset extends Depreciable
'last_checkout', 'last_checkout',
'expected_checkin', 'expected_checkin',
'byod', 'byod',
'asset_eol_date',
'last_audit_date', 'last_audit_date',
'next_audit_date', 'next_audit_date',
]; ];
use Searchable; use Searchable;
@ -168,6 +159,7 @@ class Asset extends Depreciable
'expected_checkin', 'expected_checkin',
'next_audit_date', 'next_audit_date',
'last_audit_date', 'last_audit_date',
'asset_eol_date',
]; ];
/** /**
@ -181,11 +173,19 @@ class Asset extends Depreciable
'company' => ['name'], 'company' => ['name'],
'defaultLoc' => ['name'], 'defaultLoc' => ['name'],
'location' => ['name'], 'location' => ['name'],
'model' => ['name', 'model_number'], 'model' => ['name', 'model_number', 'eol'],
'model.category' => ['name'], 'model.category' => ['name'],
'model.manufacturer' => ['name'], 'model.manufacturer' => ['name'],
]; ];
// To properly set the expected checkin as Y-m-d
public function setExpectedCheckinAttribute($value)
{
if ($value == '') {
$value = null;
}
$this->attributes['expected_checkin'] = $value;
}
/** /**
* This handles the custom field validation for assets * This handles the custom field validation for assets
@ -537,6 +537,28 @@ class Asset extends Depreciable
return strtolower(class_basename($this->assigned_type)); return strtolower(class_basename($this->assigned_type));
} }
/**
* This is annoying, but because we don't say "assets" in our route names, we have to make an exception here
* @todo - normalize the route names - API endpoint URLS can stay the same
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.0]
* @return string
*/
public function targetShowRoute()
{
$route = str_plural($this->assignedType());
if ($route=='assets') {
return 'hardware';
}
return $route;
}
/** /**
* Get the asset's location based on default RTD location * Get the asset's location based on default RTD location
* *
@ -1633,9 +1655,9 @@ class Asset extends Depreciable
*/ */
public function scopeOrderManufacturer($query, $order) public function scopeOrderManufacturer($query, $order)
{ {
return $query->join('models', 'assets.model_id', '=', 'models.id') return $query->join('models as order_asset_model', 'assets.model_id', '=', 'order_asset_model.id')
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id') ->join('manufacturers as manufacturer_order', 'order_asset_model.manufacturer_id', '=', 'manufacturer_order.id')
->orderBy('manufacturers.name', $order); ->orderBy('manufacturer_order.name', $order);
} }
/** /**

View file

@ -194,7 +194,25 @@ class Category extends SnipeModel
*/ */
public function assets() public function assets()
{ {
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id'); return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id');
}
/**
* Establishes the category -> assets relationship but also takes into consideration
* the setting to show archived in lists.
*
* We could have complicated the assets() method above, but keeping this separate
* should give us more flexibility if we need to return actually archived assets
* by their category.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.0]
* @see \App\Models\Asset::scopeAssetsForShow()
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function showableAssets()
{
return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id')->AssetsForShow();
} }
/** /**

View file

@ -25,7 +25,10 @@ class CheckoutAcceptance extends Model
{ {
// At this point the endpoint is the same for everything. // At this point the endpoint is the same for everything.
// In the future this may want to be adapted for individual notifications. // In the future this may want to be adapted for individual notifications.
return Setting::getSettings()->alert_email; $recipients_string = explode(',', Setting::getSettings()->alert_email);
$recipients = array_map('trim', $recipients_string);
return array_filter($recipients);
} }
/** /**

View file

@ -33,7 +33,8 @@ class Component extends SnipeModel
'name' => 'required|min:3|max:255', 'name' => 'required|min:3|max:255',
'qty' => 'required|integer|min:1', 'qty' => 'required|integer|min:1',
'category_id' => 'required|integer|exists:categories,id', 'category_id' => 'required|integer|exists:categories,id',
'company_id' => 'integer|nullable', 'supplier_id' => 'nullable|integer|exists:suppliers,id',
'company_id' => 'integer|nullable|exists:companies,id',
'min_amt' => 'integer|min:0|nullable', 'min_amt' => 'integer|min:0|nullable',
'purchase_date' => 'date_format:Y-m-d|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
@ -57,6 +58,7 @@ class Component extends SnipeModel
protected $fillable = [ protected $fillable = [
'category_id', 'category_id',
'company_id', 'company_id',
'supplier_id',
'location_id', 'location_id',
'name', 'name',
'purchase_cost', 'purchase_cost',
@ -86,6 +88,7 @@ class Component extends SnipeModel
'category' => ['name'], 'category' => ['name'],
'company' => ['name'], 'company' => ['name'],
'location' => ['name'], 'location' => ['name'],
'supplier' => ['name'],
]; ];
@ -168,6 +171,18 @@ class Component extends SnipeModel
return $this->belongsTo(\App\Models\Category::class, 'category_id'); return $this->belongsTo(\App\Models\Category::class, 'category_id');
} }
/**
* Establishes the item -> supplier relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function supplier()
{
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
}
/** /**
* Establishes the component -> action logs relationship * Establishes the component -> action logs relationship
* *
@ -247,4 +262,17 @@ class Component extends SnipeModel
{ {
return $query->leftJoin('companies', 'components.company_id', '=', 'companies.id')->orderBy('companies.name', $order); return $query->leftJoin('companies', 'components.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
} }
/**
* Query builder scope to order on supplier
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderSupplier($query, $order)
{
return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
}
} }

View file

@ -27,6 +27,7 @@ class Consumable extends SnipeModel
'requestable' => 'boolean', 'requestable' => 'boolean',
'category_id' => 'integer', 'category_id' => 'integer',
'company_id' => 'integer', 'company_id' => 'integer',
'supplier_id',
'qty' => 'integer', 'qty' => 'integer',
'min_amt' => 'integer', 'min_amt' => 'integer',
]; ];
@ -95,6 +96,7 @@ class Consumable extends SnipeModel
'company' => ['name'], 'company' => ['name'],
'location' => ['name'], 'location' => ['name'],
'manufacturer' => ['name'], 'manufacturer' => ['name'],
'supplier' => ['name'],
]; ];
@ -249,6 +251,18 @@ class Consumable extends SnipeModel
return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps();
} }
/**
* Establishes the item -> supplier relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function supplier()
{
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
}
/** /**
* Determine whether to send a checkin/checkout email based on * Determine whether to send a checkin/checkout email based on
@ -376,4 +390,17 @@ class Consumable extends SnipeModel
{ {
return $query->leftJoin('companies', 'consumables.company_id', '=', 'companies.id')->orderBy('companies.name', $order); return $query->leftJoin('companies', 'consumables.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
} }
/**
* Query builder scope to order on supplier
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderSupplier($query, $order)
{
return $query->leftJoin('suppliers', 'consumables.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
}
} }

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