Merge branch 'develop' into develop

This commit is contained in:
Grant Le Roux 2023-07-31 07:57:29 +08:00 committed by GitHub
commit b1464e028c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2447 changed files with 39814 additions and 29351 deletions

View file

@ -2837,6 +2837,121 @@
"contributions": [
"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": []
},
{
"login": "dboth",
"name": "dboth",
"avatar_url": "https://avatars.githubusercontent.com/u/5731963?v=4",
"profile": "http://dboth.de",
"contributions": [
"code"
]
},
{
"login": "zacharyfleck",
"name": "Zachary Fleck",
"avatar_url": "https://avatars.githubusercontent.com/u/87536651?v=4",
"profile": "https://github.com/zacharyfleck",
"contributions": [
"code"
]
},
{
"login": "vikaas-cyper",
"name": "VIKAAS-A",
"avatar_url": "https://avatars.githubusercontent.com/u/74609912?v=4",
"profile": "https://github.com/vikaas-cyper",
"contributions": [
"code"
]
},
{
"login": "ak-piracha",
"name": "Abdul Kareem",
"avatar_url": "https://avatars.githubusercontent.com/u/88882041?v=4",
"profile": "https://github.com/ak-piracha",
"contributions": [
"code"
]
},
{
"login": "NojoudAlshehri",
"name": "NojoudAlshehri",
"avatar_url": "https://avatars.githubusercontent.com/u/111287779?v=4",
"profile": "https://github.com/NojoudAlshehri",
"contributions": [
"code"
]
},
{
"login": "stefanstidlffg",
"name": "Stefan Stidl",
"avatar_url": "https://avatars.githubusercontent.com/u/54367449?v=4",
"profile": "https://github.com/stefanstidlffg",
"contributions": [
"code"
]
},
{
"login": "qay21",
"name": "Quentin Aymard",
"avatar_url": "https://avatars.githubusercontent.com/u/87803479?v=4",
"profile": "https://github.com/qay21",
"contributions": [
"code"
]
}
]
}

63
.chipperci.yml Normal file
View file

@ -0,0 +1,63 @@
version: 1
environment:
php: 8.0
node: 12
services:
- mysql: 5.7
- dusk:
on:
push:
branches:
- master
- develop
pull_request:
branches: .*
pipeline:
- name: Setup
cmd: |
cp -v .env.example .env
# This is simply to allow passing the guard in TestCase@setUp()
# https://chipperci.com/docs/builds/env
touch .env.testing
composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Generate Key
cmd: |
php artisan key:generate --force
- name: Passport Keys
cmd: |
php artisan passport:keys
- name: Run Migrations
cmd: |
# php artisan migrate --force
- name: PHPUnit Unit Tests
cmd: |
# php artisan test --testsuite Unit
- name: PHPUnit Feature Tests
cmd: |
# php artisan test --testsuite Feature
# - name: Browser Tests
# cmd: |
# cp -v .env.dusk.example .env.dusk.ci
# sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci
# sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
# #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci
# sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci
# sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci
# sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci
# sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci
#
# php -S [::0]:8000 -t public 2>server.log &
# sleep 2
# php artisan dusk:chrome-driver $CHROME_DRIVER
# php artisan dusk --env=ci

View file

@ -20,13 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=localhost
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=snipeit-local
DB_USERNAME=snipeit-local
DB_PASSWORD=snipeit-local
DB_DATABASE=null
DB_USERNAME=null
DB_PASSWORD=null
DB_PREFIX=null
DB_DUMP_PATH='/Applications/MAMP/Library/bin'
#DB_DUMP_PATH=
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS

View file

@ -85,6 +85,7 @@ COOKIE_NAME=snipeit_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
@ -175,6 +176,15 @@ REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
# --------------------------------------------
# OPTIONAL: HASHING
# --------------------------------------------
HASHING_DRIVER='bcrypt'
BCRYPT_ROUNDS=10
ARGON_MEMORY=1024
ARGON_THREADS=2
ARGON_TIME=2
# --------------------------------------------
# OPTIONAL: SCIM
# --------------------------------------------

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

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

View file

@ -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"]
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*"]
restore: ["*restore*"]
saml: ["*saml*"]
scim: ["*scim*"]
custom fields: ["*fields*", "*fieldsets*"]
dependencies: ["composer.json"]
dependencies: ["composer.json", "composer.lock", "package.json", "package.lock"]
consumables: ["*consumables*"]
api: ["/app/Http/Controllers/api/*"]
api: ["/app/Http/Controllers/Api/*"]
notifications: ["/app/Notifications/*"]
importer: ["/app/Importer/*"]
importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views/livewire/importer.php"]
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"]
tests: ["/tests/*", "/stubs"]
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
- name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.2.0
uses: codacy/codacy-analysis-cli-action@v4.3.0
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations

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,11 +72,11 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}

View file

@ -72,11 +72,11 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}

4
.gitignore vendored
View file

@ -1,6 +1,10 @@
.couscous
.DS_Store
.env
.env.dusk.*
!.env.dusk.example
.env.testing
phpstan.neon
.idea
/bin/
/bootstrap/compiled.php

View file

@ -1,4 +1,4 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference:
@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \
apache2 \
apache2-bin \
libapache2-mod-php7.4 \
php7.4-curl \
php7.4-ldap \
php7.4-mysql \
php7.4-gd \
php7.4-xml \
php7.4-mbstring \
php7.4-zip \
php7.4-bcmath \
php7.4-redis \
libapache2-mod-php8.1 \
php8.1-curl \
php8.1-ldap \
php8.1-mysql \
php8.1-gd \
php8.1-xml \
php8.1-mbstring \
php8.1-zip \
php8.1-bcmath \
php8.1-redis \
php-memcached \
patch \
curl \
@ -38,9 +38,10 @@ gcc \
make \
autoconf \
libc-dev \
libldap-common \
pkg-config \
libmcrypt-dev \
php7.4-dev \
php8.1-dev \
ca-certificates \
unzip \
dnsutils \
@ -50,16 +51,16 @@ dnsutils \
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/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 gd
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/7.4/cli/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/8.1/cli/php.ini
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
RUN apk add --no-cache \
apache2 \
php7 \
php7-common \
php7-apache2 \
php7-curl \
php7-ldap \
php7-mysqli \
php7-gd \
php7-xml \
php7-mbstring \
php7-zip \
php7-ctype \
php7-tokenizer \
php7-pdo_mysql \
php7-openssl \
php7-bcmath \
php7-phar \
php7-json \
php7-iconv \
php7-fileinfo \
php7-simplexml \
php7-session \
php7-dom \
php7-xmlwriter \
php7-xmlreader \
php7-sodium \
php7-redis \
php7-pecl-memcached \
php81 \
php81-common \
php81-apache2 \
php81-curl \
php81-ldap \
php81-mysqli \
php81-gd \
php81-xml \
php81-mbstring \
php81-zip \
php81-ctype \
php81-tokenizer \
php81-pdo_mysql \
php81-openssl \
php81-bcmath \
php81-phar \
php81-json \
php81-iconv \
php81-fileinfo \
php81-simplexml \
php81-session \
php81-dom \
php81-xmlwriter \
php81-xmlreader \
php81-sodium \
php81-redis \
php81-pecl-memcached \
curl \
wget \
vim \
@ -41,7 +41,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/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
# Enable mod_rewrite

View file

@ -1,8 +1,8 @@
ARG ENVIRONMENT=production
ARG SNIPEIT_RELEASE=5.1.3
ARG PHP_VERSION=7.4.16
ARG PHP_ALPINE_VERSION=3.13
ARG COMPOSER_VERSION=2.0.11
ARG SNIPEIT_RELEASE=6.1.0
ARG PHP_VERSION=8.2
ARG PHP_ALPINE_VERSION=3.17
ARG COMPOSER_VERSION=2
# Cannot use arguments with 'COPY --from' workaround
# https://github.com/moby/moby/issues/34482#issuecomment-454716952
@ -52,7 +52,7 @@ RUN { \
# Install php extensions inside docker containers easily
# 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; \
install-php-extensions \
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)
[![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-325-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
@ -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.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@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
- [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. :)
@ -140,7 +143,9 @@ 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/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -1,65 +1,51 @@
# Using the Test Suite
# Running the Test Suite
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 document is targeted at developers looking to make modifications to 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,
making sure to have also run the [database migrations](link to db migrations).
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
`cp .env.testing.example .env.testing`
> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
## 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
in the root directory to override the
default settings and/or other values that exist in your `.env` files.
`php artisan test`
Make sure to modify the section in `.env.testing` that has the
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).
To run individual test files, you can pass the path to the test that you want to run:
```dotenv
# --------------------------------------------
# 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`
`php artisan test tests/Unit/AccessoryTest.php`
## Browser Tests
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them.
When troubleshooting any problems, make sure that your `.env` file is configured
correctly to run the existing application.
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
### Test Setup
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
Your application needs to be configued 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:
`cp .env.dusk.example .env.dusk.local`
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
### Running Browser Tests
Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
`php artisan serve`
To run the test suite use the following command from another terminal tab or window:
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
`php artisan dusk`
To run individual test files, you can pass the path to the test that you want to run.
To run individual test files, you can pass the path to the test that you want to run:
`php artisan dusk tests/Browser/LoginTest.php`
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
`php artisan dusk:chrome-driver --detect`
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.

View file

@ -56,7 +56,7 @@ class CheckoutLicenseToAllUsers extends Command
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()) {
$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

@ -3,15 +3,31 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use \App\Models\User;
class CreateAdmin extends Command
{
/** @mixin User **/
/**
* The name and signature of the console command.
*
* @var string
* App\Console\CreateAdmin
* @property mixed $first_name
* @property string $last_name
* @property string $username
* @property string $email
* @property string $permissions
* @property string $password
* @property boolean $activated
* @property boolean $show_in_list
* @property boolean $autoassign_licenses
* @property \Illuminate\Support\Carbon|null $created_at
* @property mixed $created_by
*/
protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?}';
protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?} {autoassign_licenses?}';
/**
* The console command description.
@ -30,11 +46,7 @@ class CreateAdmin extends Command
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$first_name = $this->option('first_name');
@ -43,11 +55,14 @@ class CreateAdmin extends Command
$email = $this->option('email');
$password = $this->option('password');
$show_in_list = $this->argument('show_in_list');
$autoassign_licenses = $this->argument('autoassign_licenses');
if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) {
$this->info('ERROR: All fields are required.');
} else {
$user = new \App\Models\User;
$user = new User;
$user->first_name = $first_name;
$user->last_name = $last_name;
$user->username = $username;
@ -59,6 +74,11 @@ class CreateAdmin extends Command
if ($show_in_list == 'false') {
$user->show_in_list = 0;
}
if ($autoassign_licenses == 'false') {
$user->autoassign_licenses = 0;
}
if ($user->save()) {
$this->info('New user created');
$user->groups()->attach(1);

View file

@ -44,18 +44,25 @@ class LdapSync extends Command
*/
public function handle()
{
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
if (Setting::getSettings()->ldap_enabled!='1') {
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
exit();
}
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
$ldap_result_username = Setting::getSettings()->ldap_username_field;
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
$ldap_result_email = Setting::getSettings()->ldap_email;
$ldap_result_phone = Setting::getSettings()->ldap_phone_field;
$ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle;
$ldap_result_country = Setting::getSettings()->ldap_country;
$ldap_result_location = Setting::getSettings()->ldap_location;
$ldap_result_dept = Setting::getSettings()->ldap_dept;
$ldap_result_manager = Setting::getSettings()->ldap_manager;
$ldap_default_group = Setting::getSettings()->ldap_default_group;
@ -68,7 +75,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary));
}
LOG::info($e);
Log::info($e);
return [];
}
@ -78,7 +85,7 @@ class LdapSync extends Command
try {
if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
$search_base = null;
}
@ -92,7 +99,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary));
}
LOG::info($e);
Log::info($e);
return [];
}
@ -102,16 +109,16 @@ class LdapSync extends Command
if ($this->option('location') != '') {
$location = Location::where('name', '=', $this->option('location'))->first();
LOG::debug('Location name '.$this->option('location').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
Log::debug('Location name '.$this->option('location').' passed');
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
} elseif ($this->option('location_id') != '') {
$location = Location::where('id', '=', $this->option('location_id'))->first();
LOG::debug('Location ID '.$this->option('location_id').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
Log::debug('Location ID '.$this->option('location_id').' passed');
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
}
if (! isset($location)) {
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
/* Process locations with explicitly defined OUs, if doing a full import. */
@ -127,7 +134,7 @@ class LdapSync extends Command
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
if (count($ldap_ou_locations) > 0) {
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
Log::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
}
// Inject location information fields
@ -145,7 +152,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => trans('admin/users/message.error.ldap_could_not_search').' Location: '.$ldap_loc['name'].' (ID: '.$ldap_loc['id'].') cannot connect to "'.$ldap_loc['ldap_ou'].'" - '.$e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary));
}
LOG::info($e);
Log::info($e);
return [];
}
@ -191,20 +198,26 @@ class LdapSync extends Command
for ($i = 0; $i < $results['count']; $i++) {
$item = [];
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : '';
$item['employee_number'] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : '';
$item['lastname'] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : '';
$item['firstname'] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : '';
$item['email'] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : '';
$item['ldap_location_override'] = isset($results[$i]['ldap_location_override']) ? $results[$i]['ldap_location_override'] : '';
$item['location_id'] = isset($results[$i]['location_id']) ? $results[$i]['location_id'] : '';
$item['telephone'] = isset($results[$i][$ldap_result_phone][0]) ? $results[$i][$ldap_result_phone][0] : '';
$item['jobtitle'] = isset($results[$i][$ldap_result_jobtitle][0]) ? $results[$i][$ldap_result_jobtitle][0] : '';
$item['country'] = isset($results[$i][$ldap_result_country][0]) ? $results[$i][$ldap_result_country][0] : '';
$item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : '';
$item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : '';
$item['username'] = $results[$i][$ldap_result_username][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? '';
$item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? '';
$item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? '';
$item['email'] = $results[$i][$ldap_result_email][0] ?? '';
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_result_phone][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? '';
$item['country'] = $results[$i][$ldap_result_country][0] ?? '';
$item['department'] = $results[$i][$ldap_result_dept][0] ?? '';
$item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
$item['location'] = $results[$i][$ldap_result_location][0] ?? '';
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_result_location && $item['location']) {
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
}
$department = Department::firstOrCreate([
'name' => $item['department'],
]);
@ -230,6 +243,7 @@ class LdapSync extends Command
$user->jobtitle = $item['jobtitle'];
$user->country = $item['country'];
$user->department_id = $department->id;
$user->location_id = $location->id;
if($item['manager'] != null) {
// Check Cache first
@ -303,17 +317,18 @@ class LdapSync extends Command
$user->activated = 0;
} */
$enabled_accounts = [
'512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
];
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;

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

View file

@ -149,7 +149,7 @@ class RestoreFromBackup extends Command
$boring_files[] = $raw_path;
continue;
}
if (@pathinfo($raw_path)['extension'] == 'sql') {
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
\Log::debug("Found a sql file!");
$sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i;

View file

@ -11,7 +11,7 @@ class SystemBackup extends Command
*
* @var string
*/
protected $name = 'snipeit:backup';
protected $signature = 'snipeit:backup {--filename=}';
/**
* The console command description.
@ -37,7 +37,18 @@ class SystemBackup extends Command
*/
public function handle()
{
//
$this->call('backup:run');
if ($this->option('filename')) {
$filename = $this->option('filename');
// Make sure the filename ends in .zip
if (!ends_with($filename, '.zip')) {
$filename = $filename.'.zip';
}
$this->call('backup:run', ['--filename' => $filename]);
} else {
$this->call('backup:run');
}
}
}

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 Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
use Log;
use Throwable;
use JsonException;
use Carbon\Exceptions\InvalidFormatException;
class Handler extends ExceptionHandler
{
@ -28,6 +29,8 @@ class Handler extends ExceptionHandler
\Intervention\Image\Exception\NotSupportedException::class,
\League\OAuth2\Server\Exception\OAuthServerException::class,
JsonException::class,
SCIMException::class, //these generally don't need to be reported
InvalidFormatException::class,
];
/**
@ -41,7 +44,9 @@ class Handler extends ExceptionHandler
public function report(Throwable $exception)
{
if ($this->shouldReport($exception)) {
\Log::error($exception);
if (class_exists(\Log::class)) {
\Log::error($exception);
}
return parent::report($exception);
}
}
@ -51,7 +56,7 @@ class Handler extends ExceptionHandler
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/
public function render($request, Throwable $e)
{
@ -65,18 +70,39 @@ class Handler extends ExceptionHandler
// Invalid JSON exception
// TODO: don't understand why we have to do this when we have the invalidJson() method, below, but, well, whatever
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) {
try {
$e->report(); // logs as 'debug', so shouldn't get too noisy
} catch(\Exception $reportException) {
//do nothing
}
return $e->render($request); // ALL SCIMExceptions have the 'render()' method
}
// Handle 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()) {
// 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) {
$className = last(explode('\\', $e->getModel()));
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)) {
$statusCode = $e->getStatusCode();
@ -96,6 +122,8 @@ class Handler extends ExceptionHandler
}
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {
return response()->view('layouts/basic', [
'content' => view('errors/404')
@ -111,8 +139,8 @@ class Handler extends ExceptionHandler
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\Response
*/
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {

View file

@ -33,6 +33,16 @@ class Helper
}
}
public static function parseEscapedMarkedownInline($str = null)
{
$Parsedown = new \Parsedown();
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->line($str);
}
}
/**
* The importer has formatted number strings since v3,
* so the value might be a string, or an integer.
@ -334,7 +344,11 @@ class Helper
'#92896B',
];
$total_colors = count($colors);
if ($index >= $total_colors) {
$index = $index - $total_colors;
}
return $colors[$index];
}
@ -528,20 +542,23 @@ class Helper
* @since [v2.5]
* @return array
*/
public static function categoryTypeList()
public static function categoryTypeList($selection=null)
{
$category_types = [
'' => '',
'accessory' => 'Accessory',
'asset' => 'Asset',
'consumable' => 'Consumable',
'component' => 'Component',
'license' => 'License',
'accessory' => trans('general.accessory'),
'asset' => trans('general.asset'),
'consumable' => trans('general.consumable'),
'component' => trans('general.component'),
'license' => trans('general.license'),
];
if ($selection != null){
return $category_types[strtolower($selection)];
}
else
return $category_types;
}
/**
* Get the list of custom fields in an array to make a dropdown menu
*
@ -1092,6 +1109,15 @@ class Helper
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)
{
if ($bytes >= 1073741824)
@ -1121,32 +1147,84 @@ class Helper
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(){
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
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 ';
/**
* 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;
}
if ($month) {
$age .= $month.'m ';
}
if ($days) {
$age .= $days.'d';
}
return $age;
}
/*
* 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;
}
/**
* Conversion between units of measurement
*
@ -1164,7 +1242,7 @@ class Helper
$output = $value * $srcFactor / $dstFactor;
return ($round !== false) ? round($output, $round) : $output;
}
/**
* Get conversion factor from unit of measurement to mm
*
@ -1183,6 +1261,18 @@ class Helper
case 'yd': return 3 * static::getUnitConversionFactor('ft');
case 'pt': return (1/72) * static::getUnitConversionFactor('in');
default: throw new \InvalidArgumentException('Unit: \''.$unit.'\' is not supported');
return false;
}
/*
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
* 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

@ -63,6 +63,7 @@ class AccessoriesController extends Controller
public function store(ImageUploadRequest $request)
{
$this->authorize(Accessory::class);
// create a new model instance
$accessory = new Accessory();
@ -76,13 +77,12 @@ class AccessoriesController extends Controller
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory created?
@ -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
@ -127,45 +154,47 @@ class AccessoriesController extends Controller
*/
public function update(ImageUploadRequest $request, $accessoryId = null)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->users_count"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$min = $accessory->numCheckedOut();
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$min"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
}
@ -217,7 +246,7 @@ class AccessoriesController extends Controller
*/
public function show($accessoryID = null)
{
$accessory = Accessory::find($accessoryID);
$accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));

View file

@ -25,11 +25,16 @@ class AccessoryCheckoutController extends Controller
public function create($accessoryId)
{
// 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
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) {
$this->authorize('checkout', $accessory);
@ -55,17 +60,23 @@ class AccessoryCheckoutController extends Controller
public function store(Request $request, $accessoryId)
{
// 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
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
}
$this->authorize('checkout', $accessory);
if (! $user = User::find($request->input('assigned_to'))) {
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'));
}
// 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
$accessory->assigned_to = e($request->input('assigned_to'));

View file

@ -121,7 +121,6 @@ class AcceptanceController extends Controller
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
$sig_filename='';
if ($request->input('asset_acceptance') == 'accepted') {
/**
@ -153,12 +152,14 @@ class AcceptanceController extends Controller
}
}
// this is horrible
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
$pdf_view_route ='account.accept.accept-asset-eula';
$asset_model = AssetModel::find($item->model_id);
if (!$asset_model) {
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
$display_model = $asset_model->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
@ -167,7 +168,7 @@ class AcceptanceController extends Controller
$pdf_view_route ='account.accept.accept-accessory-eula';
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($item->assignedTo);
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
@ -222,8 +223,8 @@ class AcceptanceController extends Controller
'item_model' => $display_model,
'item_serial' => $item->serial,
'eula' => $item->getEula(),
'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
@ -250,11 +251,15 @@ class AcceptanceController extends Controller
// This is the most horriblest
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
$asset_model = AssetModel::find($item->model_id);
$display_model = $asset_model->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Accessory':
$assigned_to = User::find($item->assignedTo);
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
@ -266,6 +271,8 @@ class AcceptanceController extends Controller
break;
case 'App\Models\Consumable':
$consumable = Consumable::find($item->id);
$display_model = $consumable->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
}
@ -273,7 +280,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format,
@ -289,4 +296,4 @@ class AcceptanceController extends Controller
return redirect()->to('account/accept')->with('success', $return_msg);
}
}
}

View file

@ -26,7 +26,10 @@ class AccessoriesController extends Controller
*/
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
// Relations will be handled in query scopes a little further down.
@ -41,10 +44,13 @@ class AccessoriesController extends Controller
'min_amt',
'company_id',
'notes',
'users_count',
'qty',
];
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier');
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
->withCount('users as users_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
@ -74,12 +80,9 @@ class AccessoriesController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $accessories->count()) ? $accessories->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
@ -147,7 +150,7 @@ class AccessoriesController extends Controller
public function show($id)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::findOrFail($id);
$accessory = Accessory::withCount('users as users_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory);
}

View file

@ -36,7 +36,7 @@ class AssetMaintenancesController extends Controller
{
$this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'supplier', 'asset.company', 'admin');
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@ -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
// case we override with the actual count, so we should return 0 items.
$offset = (($maintenances) && ($request->get('offset') > $maintenances->count())) ? $maintenances->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $maintenances->count()) ? $maintenances->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$allowed_columns = [
'id',
@ -74,7 +71,8 @@ class AssetMaintenancesController extends Controller
'asset_tag',
'asset_name',
'user_id',
'supplier'
'supplier',
'is_warranty',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@ -121,7 +119,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(e($request->input('asset_id')));
@ -178,7 +176,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->supplier_id = e($request->input('supplier_id'));
$assetMaintenance->is_warranty = e($request->input('is_warranty'));
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(request('asset_id'));

View file

@ -63,7 +63,7 @@ class AssetModelsController extends Controller
'models.deleted_at',
'models.updated_at',
])
->with('category', 'depreciation', 'manufacturer', 'fieldset')
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues')
->withCount('assets as assets_count');
if ($request->input('status')=='deleted') {
@ -78,12 +78,9 @@ class AssetModelsController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($assetmodels) && ($request->get('offset') > $assetmodels->count())) ? $assetmodels->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assetmodels->count()) ? $assetmodels->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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',
'requests_counter',
'byod',
'asset_eol_date',
];
$filter = [];
@ -114,7 +115,7 @@ class AssetsController extends Controller
$allowed_columns[] = $field->db_column_name();
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
@ -124,11 +125,12 @@ class AssetsController extends Controller
$assets->InModelList($non_deprecable_models->toArray());
}
// These are used by the API to query against specific ID numbers.
// They are also used by the individual searches on detail pages like
// locations, etc.
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name())) {
@ -136,76 +138,12 @@ class AssetsController extends Controller
}
}
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
if ($request->filled('asset_tag')) {
$assets->where('assets.asset_tag', '=', $request->input('asset_tag'));
}
if ($request->filled('serial')) {
$assets->where('assets.serial', '=', $request->input('serial'));
}
if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->filled('model_id')) {
$assets->InModelList([$request->input('model_id')]);
}
if ($request->filled('category_id')) {
$assets->InCategory($request->input('category_id'));
}
if ($request->filled('location_id')) {
$assets->where('assets.location_id', '=', $request->input('location_id'));
}
if ($request->filled('rtd_location_id')) {
$assets->where('assets.rtd_location_id', '=', $request->input('rtd_location_id'));
}
if ($request->filled('supplier_id')) {
$assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
}
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
->where('assets.assigned_type', '=', $request->input('assigned_type'));
}
if ($request->filled('company_id')) {
$assets->where('assets.company_id', '=', $request->input('company_id'));
}
if ($request->filled('manufacturer_id')) {
$assets->ByManufacturer($request->input('manufacturer_id'));
}
if ($request->filled('depreciation_id')) {
$assets->ByDepreciationId($request->input('depreciation_id'));
}
if ($request->filled('byod')) {
$assets->where('assets.byod', '=', $request->input('byod'));
}
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->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';
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
@ -219,7 +157,6 @@ class AssetsController extends Controller
}
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
@ -269,7 +206,7 @@ class AssetsController extends Controller
break;
case 'Deployed':
// more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->where('assets.assigned_to', '>', '0');
$assets->whereNotNull('assets.assigned_to');
break;
case 'byod':
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
@ -295,12 +232,71 @@ class AssetsController extends Controller
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
// Leave these under the TextSearch scope, else the fuzziness will override the specific ID (status ID, etc) requested
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
}
if ($request->filled('asset_tag')) {
$assets->where('assets.asset_tag', '=', $request->input('asset_tag'));
}
if ($request->filled('serial')) {
$assets->where('assets.serial', '=', $request->input('serial'));
}
if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->filled('model_id')) {
$assets->InModelList([$request->input('model_id')]);
}
if ($request->filled('category_id')) {
$assets->InCategory($request->input('category_id'));
}
if ($request->filled('location_id')) {
$assets->where('assets.location_id', '=', $request->input('location_id'));
}
if ($request->filled('rtd_location_id')) {
$assets->where('assets.rtd_location_id', '=', $request->input('rtd_location_id'));
}
if ($request->filled('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'))) {
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
->where('assets.assigned_type', '=', $request->input('assigned_type'));
}
if ($request->filled('company_id')) {
$assets->where('assets.company_id', '=', $request->input('company_id'));
}
if ($request->filled('manufacturer_id')) {
$assets->ByManufacturer($request->input('manufacturer_id'));
}
if ($request->filled('depreciation_id')) {
$assets->ByDepreciationId($request->input('depreciation_id'));
}
if ($request->filled('byod')) {
$assets->where('assets.byod', '=', $request->input('byod'));
}
if ($request->filled('order_number')) {
$assets->where('assets.order_number', '=', $request->get('order_number'));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
// API passes custom field ordering as custom_fields.fieldname, and we have to strip
@ -311,7 +307,8 @@ class AssetsController extends Controller
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
@ -348,6 +345,10 @@ class AssetsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
@ -460,7 +461,7 @@ class AssetsController extends Controller
{
$this->authorize('view', Asset::class);
$this->authorize('view', License::class);
$asset = Asset::where('id', $id)->withTrashed()->first();
$asset = Asset::where('id', $id)->withTrashed()->firstorfail();
$licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
@ -478,7 +479,7 @@ class AssetsController extends Controller
public function selectlist(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select([
$assets = Asset::select([
'assets.id',
'assets.name',
'assets.asset_tag',
@ -486,7 +487,7 @@ class AssetsController extends Controller
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id',
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets');
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
@ -544,14 +545,16 @@ class AssetsController extends Controller
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset()); //yup, problem :/
// NO IT IS NOT!!! This is never firing; we SHOW the asset_tag you're going to get, so it *will* be filled in!
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = $request->get('status_id', 0);
$asset->warranty_months = $request->get('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost')); // this is the API's store method, so I don't know that I want to do this? Confusing. FIXME (or not?!)
$asset->purchase_cost = $request->get('purchase_cost');
$asset->asset_eol_date = $request->get('asset_eol_date', $asset->present()->eol_date());
$asset->purchase_date = $request->get('purchase_date', null);
$asset->assigned_to = $request->get('assigned_to', null);
$asset->supplier_id = $request->get('supplier_id');
@ -559,6 +562,7 @@ class AssetsController extends Controller
$asset->rtd_location_id = $request->get('rtd_location_id', null);
$asset->location_id = $request->get('rtd_location_id', null);
/**
* this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads.
@ -572,6 +576,7 @@ class AssetsController extends Controller
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
@ -829,7 +834,6 @@ class AssetsController extends Controller
} elseif (request('checkout_to_type') == '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
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset');
@ -857,7 +861,8 @@ class AssetsController extends Controller
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null);
$note = request('note', null);
$asset_name = request('name', null);
// Using `->has` preserves the asset name if the name parameter was not included in request.
$asset_name = request()->has('name') ? request('name') : $asset->name;
// Set the location ID to the RTD location id if there is one
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
@ -937,18 +942,21 @@ class AssetsController extends Controller
* @since [v6.0]
* @return JsonResponse
*/
public function checkinByTag(Request $request)
public function checkinByTag(Request $request, $tag = null)
{
$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) {
return $this->checkin($request, $asset->id);
}
return response()->json(Helper::formatStandardApiResponse('error', [
'asset'=> e($request->input('asset_tag'))
], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
'asset'=> e($tag)
], 'Asset with tag '.e($tag).' not found'));
}
@ -1025,9 +1033,10 @@ class AssetsController extends Controller
{
$this->authorize('viewRequestable', Asset::class);
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')->requestableAssets();
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')
->requestableAssets();
$offset = request('offset', 0);
$limit = $request->input('limit', 50);

View file

@ -24,10 +24,48 @@ class CategoriesController extends Controller
public function index(Request $request)
{
$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'])
->withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
$categories = Category::select([
'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')) {
$categories = $categories->TextSearch($request->input('search'));
@ -53,14 +91,9 @@ class CategoriesController extends Controller
$categories->where('checkin_email', '=', $request->input('checkin_email'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// 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');
// 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'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';

View file

@ -27,6 +27,8 @@ class CompaniesController extends Controller
$allowed_columns = [
'id',
'name',
'phone',
'fax',
'created_at',
'updated_at',
'users_count',
@ -48,12 +50,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
// case we override with the actual count, so we should return 0 items.
$offset = (($companies) && ($request->get('offset') > $companies->count())) ? $companies->count() : $request->get('offset', 0);
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : abs($request->input('offset'));
$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';
$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\ComponentCheckedIn;
use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
class ComponentsController extends Controller
{
@ -43,9 +44,8 @@ class ComponentsController extends Controller
'notes',
];
$components = Company::scopeCompanyables(Component::select('components.*')
->with('company', 'location', 'category', 'assets'));
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier');
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
@ -63,6 +63,10 @@ class ComponentsController extends Controller
$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')) {
$components->where('location_id', '=', $request->input('location_id'));
}
@ -71,14 +75,10 @@ class ComponentsController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($components) && ($request->get('offset') > $components->count())) ? $components->count() : $request->get('offset', 0);
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $components->count()) ? $components->count() : abs($request->input('offset'));
$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';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
@ -93,6 +93,9 @@ class ComponentsController extends Controller
case 'company':
$components = $components->OrderCompany($order);
break;
case 'supplier':
$components = $components->OrderSupplier($order);
break;
default:
$components = $components->orderBy($column_sort, $order);
break;
@ -225,20 +228,30 @@ class ComponentsController extends Controller
public function checkout(Request $request, $componentId)
{
// 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')));
}
$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 (!$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
$asset = Asset::find($request->input('assigned_to'));
$component->assigned_to = $request->input('assigned_to');
$component->assets()->attach($component->id, [
@ -255,7 +268,7 @@ class ComponentsController extends Controller
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

@ -45,11 +45,8 @@ class ConsumablesController extends Controller
'notes',
];
$consumables = Company::scopeCompanyables(
Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer')
);
$consumables = Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
@ -75,6 +72,10 @@ class ConsumablesController extends Controller
$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')) {
$consumables->where('location_id','=',$request->input('location_id'));
}
@ -84,12 +85,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
// case we override with the actual count, so we should return 0 items.
$offset = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@ -111,6 +109,9 @@ class ConsumablesController extends Controller
case 'company':
$consumables = $consumables->OrderCompany($order);
break;
case 'supplier':
$components = $consumables->OrderSupplier($order);
break;
default:
$consumables = $consumables->orderBy($column_sort, $order);
break;
@ -154,7 +155,7 @@ class ConsumablesController extends Controller
public function show($id)
{
$this->authorize('view', Consumable::class);
$consumable = Consumable::findOrFail($id);
$consumable = Consumable::with('users')->findOrFail($id);
return (new ConsumablesTransformer)->transformConsumable($consumable);
}
@ -253,33 +254,39 @@ class ConsumablesController extends Controller
public function checkout(Request $request, $id)
{
// 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')));
}
$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
$assigned_to = $request->input('assigned_to');
if (is_null($user = User::find($assigned_to))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
}
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
if (!$user = User::find($request->input('assigned_to'))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
\Log::debug('No valid user');
}
// Update the consumable data
$consumable->assigned_to = e($assigned_to);
// Update the consumable data
$consumable->assigned_to = $request->input('assigned_to');
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $user->id,
'assigned_to' => $assigned_to,
'note' => $request->input('note'),
]);
$consumable->users()->attach($consumable->id,
[
'consumable_id' => $consumable->id,
'user_id' => $user->id,
'assigned_to' => $request->input('assigned_to'),
'note' => $request->input('note'),
]
);
// Log checkout event
$logaction = $consumable->logCheckout(e($request->input('note')), $user);
$logaction = $consumable->logCheckout($request->input('note'), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
@ -289,9 +296,7 @@ class ConsumablesController extends Controller
$data['require_acceptance'] = $consumable->requireAcceptance();
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

@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\CustomFieldsetsTransformer;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Illuminate\Http\Request;
use Redirect;
use View;
@ -33,7 +34,7 @@ class CustomFieldsetsController extends Controller
*/
public function index()
{
$this->authorize('index', CustomFieldset::class);
$this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
@ -49,7 +50,7 @@ class CustomFieldsetsController extends Controller
*/
public function show($id)
{
$this->authorize('view', CustomFieldset::class);
$this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
}
@ -68,7 +69,7 @@ class CustomFieldsetsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('update', CustomFieldset::class);
$this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
$fieldset->fill($request->all());
@ -89,11 +90,23 @@ class CustomFieldsetsController extends Controller
*/
public function store(Request $request)
{
$this->authorize('create', CustomFieldset::class);
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset;
$fieldset->fill($request->all());
if ($fieldset->save()) {
// Sync fieldset with auto_add_to_fieldsets
$fields = CustomField::select('id')->where('auto_add_to_fieldsets', '=', '1')->get();
if ($fields->count() > 0) {
foreach ($fields as $field) {
$field_ids[] = $field->id;
}
$fieldset->fields()->sync($field_ids);
}
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.create.success')));
}
@ -109,7 +122,7 @@ class CustomFieldsetsController extends Controller
*/
public function destroy($id)
{
$this->authorize('delete', CustomFieldset::class);
$this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
$modelsCount = $fieldset->models->count();
@ -136,7 +149,7 @@ class CustomFieldsetsController extends Controller
*/
public function fields($id)
{
$this->authorize('view', CustomFieldset::class);
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id);
$fields = $set->fields;
@ -153,7 +166,7 @@ class CustomFieldsetsController extends Controller
*/
public function fieldsWithDefaultValues($fieldsetId, $modelId)
{
$this->authorize('view', CustomFieldset::class);
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($fieldsetId);

View file

@ -30,6 +30,8 @@ class DepartmentsController extends Controller
$departments = Company::scopeCompanyables(Department::select(
'departments.id',
'departments.name',
'departments.phone',
'departments.fax',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
@ -58,12 +60,9 @@ class DepartmentsController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($departments) && ($request->get('offset') > $departments->count())) ? $departments->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($depreciations) && ($request->get('offset') > $depreciations->count())) ? $depreciations->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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 Illuminate\Http\Request;
class GroupsController extends Controller
{
/**
@ -19,6 +20,8 @@ class GroupsController extends Controller
*/
public function index(Request $request)
{
$this->authorize('superadmin');
$this->authorize('view', Group::class);
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
@ -32,12 +35,9 @@ class GroupsController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($groups) && ($request->get('offset') > $groups->count())) ? $groups->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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)
{
$this->authorize('create', Group::class);
$this->authorize('superadmin');
$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()) {
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)
{
$this->authorize('view', Group::class);
$this->authorize('superadmin');
$group = Group::findOrFail($id);
return (new GroupsTransformer)->transformGroup($group);
@ -97,9 +99,11 @@ class GroupsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('update', Group::class);
$this->authorize('superadmin');
$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()) {
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)
{
$this->authorize('delete', Group::class);
$this->authorize('superadmin');
$group = Group::findOrFail($id);
$this->authorize('delete', $group);
$group->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/groups/message.delete.success')));

View file

@ -10,6 +10,7 @@ use App\Models\Asset;
use App\Models\Company;
use App\Models\Import;
use Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
@ -35,7 +36,7 @@ class ImportController extends Controller
* Process and store a CSV upload file.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*/
public function store()
{
@ -56,7 +57,7 @@ class ImportController extends Controller
'text/tsv', ])) {
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
}
//TODO: is there a lighter way to do this?
@ -64,7 +65,19 @@ class ImportController extends Controller
ini_set('auto_detect_line_endings', '1');
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$import->header_row = $reader->fetchOne(0);
try {
$import->header_row = $reader->fetchOne(0);
} catch (JsonEncodingException $e) {
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.header_row_has_malformed_characters')
),
422
);
}
//duplicate headers check
$duplicate_headers = [];
@ -82,11 +95,22 @@ class ImportController extends Controller
}
}
if (count($duplicate_headers) > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)), 500); //should this be '4xx'?
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)),422);
}
// Grab the first row to display via ajax as the user picks fields
$import->first_row = $reader->fetchOne(1);
try {
// Grab the first row to display via ajax as the user picks fields
$import->first_row = $reader->fetchOne(1);
} catch (JsonEncodingException $e) {
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.content_row_has_malformed_characters')
),
422
);
}
$date = date('Y-m-d-his');
$fixed_filename = str_slug($file->getClientOriginalName());
@ -102,18 +126,25 @@ class ImportController extends Controller
}
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
$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->save();
$results[] = $import;
}
$results = (new ImportsTransformer)->transformImports($results);
return [
return response()->json([
'files' => $results,
];
]);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 500);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 422);
}
/**
@ -129,7 +160,7 @@ class ImportController extends Controller
// Run a backup immediately before processing
if ($request->get('run-backup')) {
\Log::debug('Backup manually requested via importer');
Artisan::call('backup:run');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
} else {
\Log::debug('NO BACKUP requested via importer');
}
@ -162,6 +193,9 @@ class ImportController extends Controller
case 'user':
$redirectTo = 'users.index';
break;
case 'location':
$redirectTo = 'locations.index';
break;
}
if ($errors) { //Failure

View file

@ -39,8 +39,15 @@ class LicenseSeatsController extends Controller
}
$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'));
if ($offset >= $total ){
$offset = 0;
}
$limit = app('api_limit_value');
$seats = $seats->skip($offset)->take($limit)->get();

View file

@ -26,8 +26,8 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count'));
$licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) {
$licenses->where('company_id', '=', $request->input('company_id'));
@ -94,12 +94,9 @@ class LicensesController extends Controller
$licenses->onlyTrashed();
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($licenses) && ($request->get('offset') > $licenses->count())) ? $licenses->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View file

@ -37,6 +37,8 @@ class LocationsController extends Controller
'locations.city',
'locations.state',
'locations.zip',
'locations.phone',
'locations.fax',
'locations.country',
'locations.parent_id',
'locations.manager_id',
@ -78,14 +80,15 @@ class LocationsController extends Controller
$locations->where('locations.country', '=', $request->input('country'));
}
$offset = (($locations) && (request('offset') > $locations->count())) ? $locations->count() : request('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');
// 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'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($request->input('sort')) {
case 'parent':
$locations->OrderParent($order);
@ -250,8 +253,12 @@ class LocationsController extends Controller
*/
public function selectlist(Request $request)
{
$this->authorize('view.selectlists');
// If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location.
// Otherwise, we do our normal check that they can view select lists.
$request->headers->get('referer') === route('profile')
? $this->authorize('self.edit_location')
: $this->authorize('view.selectlists');
$locations = Location::select([
'locations.id',

View file

@ -23,10 +23,10 @@ class ManufacturersController extends Controller
public function index(Request $request)
{
$this->authorize('view', Manufacturer::class);
$allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
$allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
$manufacturers = Manufacturer::select(
['id', 'name', 'url', 'support_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at']
['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at']
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count');
if ($request->input('deleted') == 'true') {
@ -49,6 +49,10 @@ class ManufacturersController extends Controller
$manufacturers->where('support_url', '=', $request->input('support_url'));
}
if ($request->filled('warranty_lookup_url')) {
$manufacturers->where('warranty_lookup_url', '=', $request->input('warranty_lookup_url'));
}
if ($request->filled('support_phone')) {
$manufacturers->where('support_phone', '=', $request->input('support_phone'));
}
@ -57,12 +61,9 @@ class ManufacturersController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($manufacturers) && ($request->get('offset') > $manufacturers->count())) ? $manufacturers->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $kits->count()) ? $kits->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name';
$kits->orderBy($sort, $order);

View file

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

View file

@ -54,11 +54,15 @@ class ReportsController extends Controller
'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';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
$offset = request('offset', 0);
$limit = request('limit', 50);
$total = $actionlogs->count();
$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);

View file

@ -143,47 +143,6 @@ class SettingsController extends Controller
}
public function slacktest(SlackSettingsRequest $request)
{
$validator = Validator::make($request->all(), [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
]);
if ($validator->fails()) {
return response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422);
}
// If validation passes, continue to the curl request
$slack = new Client([
'base_url' => e($request->input('slack_endpoint')),
'defaults' => [
'exceptions' => false,
],
]);
$payload = json_encode(
[
'channel' => e($request->input('slack_channel')),
'text' => trans('general.slack_test_msg'),
'username' => e($request->input('slack_botname')),
'icon_emoji' => ':heart:',
]);
try {
$slack->post($request->input('slack_endpoint'), ['body' => $payload]);
return response()->json(['message' => 'Success'], 200);
} catch (\Exception $e) {
return response()->json(['message' => 'Please check the channel name and webhook endpoint URL ('.e($request->input('slack_endpoint')).'). Slack responded with: '.$e->getMessage()], 400);
}
//}
return response()->json(['message' => 'Something went wrong :( '], 400);
}
/**
* Test the email configuration
*
@ -312,7 +271,7 @@ class SettingsController extends Controller
$headers = ['ContentType' => 'application/zip'];
return Storage::download($path.'/'.$file, $file, $headers);
} 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

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset;
use App\Models\Statuslabel;
@ -50,12 +51,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
// case we override with the actual count, so we should return 0 items.
$offset = (($statuslabels) && ($request->get('offset') > $statuslabels->count())) ? $statuslabels->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@ -196,6 +194,7 @@ class StatuslabelsController extends Controller
{
$this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::withCount('assets')->get();
$total = Array();
foreach ($statuslabels as $statuslabel) {
@ -293,4 +292,45 @@ class StatuslabelsController extends Controller
return '0';
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @see \App\Http\Transformers\SelectlistTransformer
*/
public function selectlist(Request $request)
{
$this->authorize('view.selectlists');
$statuslabels = Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc');
if ($request->filled('search')) {
$statuslabels = $statuslabels->where('name', 'LIKE', '%'.$request->get('search').'%');
}
if ($request->filled('deployable')) {
$statuslabels = $statuslabels->where('deployable', '=', '1');
}
if ($request->filled('pending')) {
$statuslabels = $statuslabels->where('pending', '=', '1');
}
if ($request->filled('archived')) {
$statuslabels = $statuslabels->where('archived', '=', '1');
}
$statuslabels = $statuslabels->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($statuslabels as $statuslabel) {
$statuslabels->use_text = $statuslabel->name;
}
return (new SelectlistTransformer)->transformSelectlist($statuslabels);
}
}

View file

@ -23,11 +23,30 @@ class SuppliersController extends Controller
public function index(Request $request)
{
$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(
['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');
['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('components as components_count')
->withCount('consumables as consumables_count');
if ($request->filled('search')) {
@ -74,12 +93,9 @@ class SuppliersController extends Controller
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($suppliers) && ($request->get('offset') > $suppliers->count())) ? $suppliers->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$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 App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class UsersController extends Controller
{
@ -69,18 +70,14 @@ class UsersController extends Controller
'users.ldap_import',
'users.start_date',
'users.end_date',
'users.vip',
'users.autoassign_licenses',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
$users = Company::scopeCompanyables($users);
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated'));
}
@ -149,6 +146,10 @@ class UsersController extends Controller
$users = $users->where('remote', '=', $request->input('remote'));
}
if ($request->filled('vip')) {
$users = $users->where('vip', '=', $request->input('vip'));
}
if ($request->filled('two_factor_enrolled')) {
$users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled'));
}
@ -181,19 +182,19 @@ class UsersController extends Controller
$users->has('accessories', '=', $request->input('accessories_count'));
}
if ($request->filled('autoassign_licenses')) {
$users->where('autoassign_licenses', '=', $request->input('autoassign_licenses'));
}
if ($request->filled('search')) {
$users = $users->TextSearch($request->input('search'));
}
$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
// case we override with the actual count, so we should return 0 items.
$offset = (($users) && ($request->get('offset') > $users->count())) ? $users->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');
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $users->count()) ? $users->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
switch ($request->input('sort')) {
@ -212,6 +213,14 @@ class UsersController extends Controller
case 'company':
$users = $users->OrderCompany($order);
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:
$allowed_columns =
[
@ -246,8 +255,10 @@ class UsersController extends Controller
'two_factor_optin',
'two_factor_enrolled',
'remote',
'vip',
'start_date',
'end_date',
'autoassign_licenses',
];
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
@ -255,6 +266,12 @@ class UsersController extends Controller
break;
}
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@ -286,9 +303,11 @@ class UsersController extends Controller
$users = Company::scopeCompanyables($users);
if ($request->filled('search')) {
$users = $users->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($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');
@ -343,7 +362,7 @@ class UsersController extends Controller
$user->permissions = $permissions_array;
}
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40);
$user->password = bcrypt($request->get('password', $tmp_pass));
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
@ -449,6 +468,13 @@ class UsersController extends Controller
// Check if the request has groups passed and has a value
if ($request->filled('groups')) {
$validator = Validator::make($request->all(), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()){
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
$user->groups()->sync($request->input('groups'));
// The groups field has been passed but it is null, so we should blank it out
} elseif ($request->has('groups')) {
@ -542,9 +568,10 @@ class UsersController extends Controller
if (empty($user->email)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
}
$user->notify((new CurrentInventory($user)));
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success'));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
}
/**

View file

@ -101,7 +101,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$asset = Asset::find($request->input('asset_id'));
@ -211,7 +211,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id'));

View file

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

View file

@ -94,14 +94,18 @@ class AssetCheckinController extends Controller
\Log::debug('Manually override the location IDs');
\Log::debug('Original Location ID: '.$asset->location_id);
$asset->location_id = '';
\Log::debug('New RTD Location ID: '.$asset->location_id);
\Log::debug('New Location ID: '.$asset->location_id);
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
\Log::debug('NEW Location ID: '.$request->get('location_id'));
$asset->location_id = e($request->get('location_id'));
$asset->location_id = $request->get('location_id');
if ($request->get('update_default_location') == 0){
$asset->rtd_location_id = $request->get('location_id');
}
}
$checkin_at = date('Y-m-d H:i:s');

View file

@ -27,7 +27,7 @@ class AssetCheckoutController extends Controller
public function create($assetId)
{
// 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'));
}

View file

@ -12,12 +12,13 @@ use App\Models\CheckoutRequest;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
use App\View\Label;
use Auth;
use Carbon\Carbon;
use DB;
use Gate;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
@ -141,8 +142,9 @@ class AssetsController extends Controller
$asset->depreciate = '0';
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost'));
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date());
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
@ -312,7 +314,9 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
$asset->purchase_cost = $request->input('purchase_cost', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->purchase_date = $request->input('purchase_date', null);
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
@ -322,6 +326,12 @@ class AssetsController extends Controller
$asset->rtd_location_id = $request->input('rtd_location_id', null);
$asset->byod = $request->input('byod', 0);
$status = Statuslabel::find($asset->status_id);
if($status->archived){
$asset->assigned_to = null;
}
if ($asset->assigned_to == '') {
$asset->location_id = $request->input('rtd_location_id', null);
}
@ -628,7 +638,11 @@ class AssetsController extends Controller
$csv->setHeaderOffset(0);
$header = $csv->getHeader();
$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 = [];
$status = [];
$status['error'] = [];
@ -764,7 +778,7 @@ class AssetsController extends Controller
}
/**
* Retore a deleted asset.
* Restore a deleted asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId

View file

@ -30,7 +30,7 @@ class BulkAssetsController extends Controller
*/
public function edit(Request $request)
{
$this->authorize('update', Asset::class);
$this->authorize('view', Asset::class);
if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
@ -45,19 +45,33 @@ class BulkAssetsController extends Controller
if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
return (new Label)
->with('assets', Asset::find($asset_ids))
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
case 'delete':
$this->authorize('delete', Asset::class);
$assets = Asset::with('assignedTo', 'location')->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-delete')->with('assets', $assets);
case 'restore':
$this->authorize('update', Asset::class);
$assets = Asset::withTrashed()->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit':
$this->authorize('update', Asset::class);
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList());
@ -103,8 +117,11 @@ class BulkAssetsController extends Controller
|| ($request->filled('company_id'))
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('next_audit_date'))
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
) {
foreach ($assets as $assetId) {
@ -117,7 +134,8 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months');
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date');
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
@ -127,8 +145,12 @@ class BulkAssetsController extends Controller
$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')) {
$this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
}
if ($request->filled('company_id')) {
@ -289,7 +311,8 @@ class BulkAssetsController extends Controller
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
$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 != '') {
$asset->location_id = $target->location_id;
@ -312,5 +335,19 @@ class BulkAssetsController extends Controller
} catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
}
}
public function restore(Request $request) {
$this->authorize('update', Asset::class);
$assetIds = $request->get('ids');
if (empty($assetIds)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
} 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);
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')

View file

@ -60,6 +60,8 @@ final class CompaniesController extends Controller
$company = new Company;
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company = $request->handleImages($company);
@ -111,6 +113,8 @@ final class CompaniesController extends Controller
$this->authorize('update', $company);
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company = $request->handleImages($company);

View file

@ -33,6 +33,11 @@ class ComponentCheckoutController extends Controller
}
$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'));
}
@ -50,7 +55,7 @@ class ComponentCheckoutController extends Controller
public function store(Request $request, $componentId)
{
// 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
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);
$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(), [
'asset_id' => 'required',
'assigned_qty' => "required|numeric|between:1,$max_to_checkout",
'asset_id' => 'required|exists:assets,id',
'assigned_qty' => "required|numeric|min:1|digits_between:1,$max_to_checkout",
]);
if ($validator->fails()) {
@ -69,24 +80,17 @@ class ComponentCheckoutController extends Controller
->withInput();
}
$admin_user = Auth::user();
$asset_id = e($request->input('asset_id'));
// Check if the user exists
if (is_null($asset = Asset::find($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'));
}
$asset = Asset::find($request->input('asset_id'));
// Update the component data
$component->asset_id = $asset_id;
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'user_id' => $admin_user->id,
'user_id' => Auth::user(),
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $asset_id,
'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'),
]);

View file

@ -71,13 +71,14 @@ class ComponentsController extends Controller
$component = new Component();
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
$component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
$component->notes = $request->input('notes');
@ -145,13 +146,14 @@ class ComponentsController extends Controller
// Update the component data
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number');
$component->min_amt = $request->input('min_amt');
$component->serial = $request->input('serial');
$component->purchase_date = $request->input('purchase_date');
$component->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$component->purchase_cost = request('purchase_cost');
$component->qty = $request->input('qty');
$component->notes = $request->input('notes');

View file

@ -24,9 +24,16 @@ class ConsumableCheckoutController extends Controller
*/
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'));
}
// 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);
return view('consumables/checkout', compact('consumable'));
@ -44,12 +51,18 @@ class ConsumableCheckoutController extends Controller
*/
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'));
}
$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();
$assigned_to = e($request->input('assigned_to'));

View file

@ -68,6 +68,7 @@ class ConsumablesController extends Controller
$consumable = new Consumable();
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->supplier_id = $request->input('supplier_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
@ -76,7 +77,7 @@ class ConsumablesController extends Controller
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = $request->input('qty');
$consumable->user_id = Auth::id();
$consumable->notes = $request->input('notes');
@ -144,6 +145,7 @@ class ConsumablesController extends Controller
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->supplier_id = $request->input('supplier_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
@ -152,7 +154,7 @@ class ConsumablesController extends Controller
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = Helper::ParseFloat($request->input('qty'));
$consumable->notes = $request->input('notes');

View file

@ -9,11 +9,11 @@
*
* **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
* [Installation documentation](http://docs.snipeitapp.com) for
* [Installation documentation](https://snipe-it.readme.io) for
* information on how to install 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,
* etc have been included in this documentation (excluding vendors, Laravel core, etc)

View file

@ -7,6 +7,7 @@ use App\Http\Requests\CustomFieldRequest;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Redirect;
/**
@ -45,7 +46,7 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField()
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.1.5]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show()
@ -63,14 +64,17 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
public function create(Request $request)
{
$this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get();
return view('custom_fields.fields.edit', [
'predefinedFormats' => Helper::predefined_formats(),
'customFormat' => '',
])->with('field', new CustomField());
'customFormat' => '',
'fieldsets' => $fieldsets,
'field' => new CustomField(),
]);
}
/**
@ -79,7 +83,7 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::createField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(CustomFieldRequest $request)
@ -104,21 +108,33 @@ class CustomFieldsController extends Controller
"show_in_email" => $show_in_email,
"is_unique" => $request->get("is_unique", 0),
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"user_id" => Auth::id()
]);
if ($request->filled('custom_format')) {
$field->format = e($request->get('custom_format'));
$field->format = $request->get('custom_format');
} else {
$field->format = e($request->get('format'));
$field->format = $request->get('format');
}
if ($field->save()) {
// Sync fields with fieldsets
$fieldset_array = $request->input('associate_fieldsets');
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
$field->fieldset()->sync(array_keys($fieldset_array));
} else {
$field->fieldset()->sync([]);
}
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.create.success'));
}
return redirect()->back()->withInput()
return redirect()->back()->with('selected_fieldsets', $request->input('associate_fieldsets'))->withInput()
->with('error', trans('admin/custom_fields/message.field.create.error'));
}
@ -128,7 +144,7 @@ class CustomFieldsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function deleteFieldFromFieldset($field_id, $fieldset_id)
@ -147,8 +163,7 @@ class CustomFieldsController extends Controller
->with('success', trans('admin/custom_fields/message.field.delete.success'));
} else {
return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]);
}
}
}
return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]);
@ -161,7 +176,7 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($field_id)
@ -190,12 +205,12 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
public function edit(Request $request, $id)
{
if ($field = CustomField::find($id)) {
$this->authorize('update', $field);
$fieldsets = CustomFieldset::get();
$customFormat = '';
if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) {
$customFormat = $field->format;
@ -204,6 +219,7 @@ class CustomFieldsController extends Controller
return view('custom_fields.fields.edit', [
'field' => $field,
'customFormat' => $customFormat,
'fieldsets' => $fieldsets,
'predefinedFormats' => Helper::predefined_formats(),
]);
}
@ -222,7 +238,7 @@ class CustomFieldsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(CustomFieldRequest $request, $id)
@ -249,6 +265,8 @@ class CustomFieldsController extends Controller
$field->show_in_email = $show_in_email;
$field->is_unique = $request->get("is_unique", 0);
$field->display_in_user_view = $display_in_user_view;
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));
@ -256,11 +274,21 @@ class CustomFieldsController extends Controller
$field->format = e($request->get('format'));
}
if($field->element == 'checkbox' || $field->element == 'radio'){
if ($field->element == 'checkbox' || $field->element == 'radio'){
$field->format = 'ANY';
}
if ($field->save()) {
// Sync fields with fieldsets
$fieldset_array = $request->input('associate_fieldsets');
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
$field->fieldset()->sync(array_keys($fieldset_array));
} else {
$field->fieldset()->sync([]);
}
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success'));
}

View file

@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller
*/
public function create()
{
$this->authorize('create', CustomFieldset::class);
$this->authorize('create', CustomField::class);
return view('custom_fields.fieldsets.edit');
return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
}
/**
@ -91,18 +91,29 @@ class CustomFieldsetsController extends Controller
*/
public function store(Request $request)
{
$this->authorize('create', CustomFieldset::class);
$this->authorize('create', CustomField::class);
$cfset = new CustomFieldset([
$fieldset = new CustomFieldset([
'name' => e($request->get('name')),
'user_id' => Auth::user()->id,
]);
$validator = Validator::make($request->all(), $cfset->rules);
if ($validator->passes()) {
$cfset->save();
$validator = Validator::make($request->all(), $fieldset->rules);
return redirect()->route('fieldsets.show', [$cfset->id])
if ($validator->passes()) {
$fieldset->save();
// Sync fieldset with auto_add_to_fieldsets
$fields = CustomField::select('id')->where('auto_add_to_fieldsets', '=', '1')->get();
if ($fields->count() > 0) {
foreach ($fields as $field) {
$field_ids[] = $field->id;
}
$fieldset->fields()->sync($field_ids);
}
return redirect()->route('fieldsets.show', [$fieldset->id])
->with('success', trans('admin/custom_fields/message.fieldset.create.success'));
}
@ -110,31 +121,52 @@ class CustomFieldsetsController extends Controller
}
/**
* What the actual fuck, Brady?
* Presents edit form for fieldset
*
* @todo Uhh, build this?
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v1.8]
* @return Fuckall
* @since [v6.0.14]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
{
//
$this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
}
/**
* GET IN THE SEA BRADY.
* Saves updated fieldset data
*
* @todo Uhh, build this too?
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v1.8]
* @return Fuckall
* @since [v6.0.14]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update($id)
public function update(Request $request, $id)
{
//
$this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
$fieldset->name = $request->input('name');
if ($fieldset->save()) {
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
}
/**
@ -202,7 +234,7 @@ class CustomFieldsetsController extends Controller
*/
public function makeFieldRequired($fieldset_id, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 1];
@ -220,7 +252,7 @@ class CustomFieldsetsController extends Controller
*/
public function makeFieldOptional($fieldset_id, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 0];

View file

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

View file

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

View file

@ -92,7 +92,7 @@ class GroupsController extends Controller
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)
{
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->permissions = json_encode($request->input('permission'));
@ -133,14 +133,13 @@ class GroupsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Exception
*/
public function destroy($id = null)
public function destroy($id)
{
if (! config('app.lock_passwords')) {
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();
// Redirect to the group management page
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 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);
// 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);
if (! $license->reassignable) {
@ -106,4 +112,54 @@ class LicenseCheckinController extends Controller
// Redirect to the license page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkin.error'));
}
/**
* Bulk checkin all license seats
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicenseCheckinController::create() method that provides the form view
* @since [v6.1.1]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function bulkCheckin(Request $request, $licenseId) {
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
->whereNotNull('assigned_to')
->with('user')
->get();
foreach ($licenseSeatsByUser as $user_seat) {
$user_seat->assigned_to = null;
if ($user_seat->save()) {
\Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
}
}
$licenseSeatsByAsset = LicenseSeat::where('license_id', '=', $licenseId)
->whereNotNull('asset_id')
->with('asset')
->get();
$count = 0;
foreach ($licenseSeatsByAsset as $asset_seat) {
$asset_seat->asset_id = null;
if ($asset_seat->save()) {
\Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
$count++;
}
}
return redirect()->back()->with('success', trans_choice('admin/licenses/general.bulk.checkin_all.success', 2, ['count' => $count] ));
}
}

View file

@ -30,15 +30,17 @@ class LicenseCheckoutController extends Controller
// Check that the license is valid
if ($license = License::find($licenseId)) {
$this->authorize('checkout', $license);
// If the license is valid, check that there is an available seat
if ($license->avail_seats_count < 1) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
return view('licenses/checkout', compact('license'));
}
$this->authorize('checkout', $license);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
return view('licenses/checkout', compact('license'));
}
/**
@ -126,4 +128,70 @@ class LicenseCheckoutController extends Controller
return false;
}
/**
* Bulk checkin all license seats
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicenseCheckinController::create() method that provides the form view
* @since [v6.1.1]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function bulkCheckout($licenseId) {
\Log::debug('Checking out '.$licenseId.' via bulk');
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
$avail_count = $license->getAvailSeatsCountAttribute();
$users = User::whereNull('deleted_at')->where('autoassign_licenses', '=', 1)->with('licenses')->get();
\Log::debug($avail_count.' will be assigned');
if ($users->count() > $avail_count) {
\Log::debug('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
}
// If the license is valid, check that there is an available seat
if ($license->availCount()->count() < 1) {
return redirect()->back()->with('error', trans('admin/licenses/general.bulk.checkout_all.error_no_seats'));
}
$assigned_count = 0;
foreach ($users as $user) {
// Check to make sure this user doesn't already have this license checked out to them
if ($user->licenses->where('id', '=', $licenseId)->count()) {
\Log::debug($user->username.' already has this license checked out to them. Skipping... ');
continue;
}
$licenseSeat = $license->freeSeat();
// Update the seat with checkout info
$licenseSeat->assigned_to = $user->id;
if ($licenseSeat->save()) {
$avail_count--;
$assigned_count++;
$licenseSeat->logCheckout(trans('admin/licenses/general.bulk.checkout_all.log_msg'), $user);
\Log::debug('License '.$license->name.' seat '.$licenseSeat->id.' checked out to '.$user->username);
}
if ($avail_count == 0) {
return redirect()->back()->with('warning', trans('admin/licenses/general.bulk.checkout_all.warn_not_enough_seats', ['count' => $assigned_count]));
}
}
if ($assigned_count == 0) {
return redirect()->back()->with('warning', trans('admin/licenses/general.bulk.checkout_all.warn_no_avail_users', ['count' => $assigned_count]));
}
return redirect()->back()->with('success', trans_choice('admin/licenses/general.bulk.checkout_all.success', 2, ['count' => $assigned_count] ));
}
}

View file

@ -91,29 +91,30 @@ class LicenseFilesController extends Controller
*/
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);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('licenses/'.$log->filename)) {
try {
Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
if ($log = Actionlog::find($fileId)) {
// Remove the file if one exists
if (Storage::exists('licenses/'.$log->filename)) {
try {
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()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist'));
}
// Redirect to the licence management page
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)
{
\Log::info('Private filesystem is: '.config('filesystems.default'));
$license = License::find($licenseId);
// the license is valid

View file

@ -6,6 +6,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@ -86,7 +88,7 @@ class LicensesController extends Controller
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->purchase_order = $request->input('purchase_order');
@ -164,7 +166,7 @@ class LicensesController extends Controller
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
@ -205,7 +207,7 @@ class LicensesController extends Controller
if ($license->assigned_seats_count == 0) {
// Delete the license and the associated license seats
DB::table('license_seats')
->where('id', $license->id)
->where('license_id', $license->id)
->update(['assigned_to' => null, 'asset_id' => null]);
$licenseSeats = $license->licenseseats();
@ -233,16 +235,40 @@ class LicensesController extends Controller
{
$license = License::with('assignedusers')->find($licenseId);
if ($license) {
$this->authorize('view', $license);
return view('licenses/view', compact('license'));
if (!$license) {
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'));
$users_count = User::where('autoassign_licenses', '1')->count();
$total_seats_count = $license->totalSeatsByLicenseID();
$available_seats_count = $license->availCount()->count();
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
\Log::debug('Total: '.$total_seats_count);
\Log::debug('Users: '.$users_count);
\Log::debug('Available: '.$available_seats_count);
\Log::debug('Checkedout: '.$checkedout_seats_count);
$this->authorize('view', $license);
return view('licenses.view', compact('license'))
->with('users_count', $users_count)
->with('total_seats_count', $total_seats_count)
->with('available_seats_count', $available_seats_count)
->with('checkedout_seats_count', $checkedout_seats_count);
}
/**
* Returns a view with prepopulated data for clone
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getClone($licenseId = null)
{
if (is_null($license_to_clone = License::find($licenseId))) {

View file

@ -79,6 +79,8 @@ class LocationsController extends Controller
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->user_id = Auth::id();
$location->phone = request('phone');
$location->fax = request('fax');
$location = $request->handleImages($location);
@ -139,6 +141,8 @@ class LocationsController extends Controller
$location->state = $request->input('state');
$location->country = $request->input('country');
$location->zip = $request->input('zip');
$location->phone = request('phone');
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
@ -227,6 +231,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)
{
if ($location = Location::where('id', $id)->first()) {

View file

@ -68,6 +68,7 @@ class ManufacturersController extends Controller
$manufacturer->user_id = Auth::id();
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
$manufacturer = $request->handleImages($manufacturer);
@ -123,10 +124,11 @@ class ManufacturersController extends Controller
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
// Save the data
// Save the data
$manufacturer->name = $request->input('name');
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');

View file

@ -17,7 +17,7 @@ class ModalController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net]
* @return View
*/
function show ($type, $itemId = null) {
public function show ($type, $itemId = null) {
// These values should correspond to a file in resources/views/modals/
$allowed_types = [

View file

@ -8,7 +8,7 @@ use App\Models\Setting;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Illuminate\Support\Facades\Auth;
use Gate;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
@ -67,37 +67,9 @@ class ProfileController extends Controller
$user->location_id = $request->input('location_id');
}
// Handle the avatar upload and/or delete if necessary
app('\App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($request->input('avatar_delete') == 1) {
$user->avatar = null;
}
if ($request->hasFile('avatar')) {
$path = 'avatars';
if (! Storage::disk('public')->exists($path)) {
Storage::disk('public')->makeDirectory($path, 775);
}
$upload = $image = $request->file('avatar');
$ext = $image->getClientOriginalExtension();
$file_name = 'avatar-'.str_random(18).'.'.$ext;
if ($image->getClientOriginalExtension() != 'svg') {
$upload = Image::make($image->getRealPath())->resize(84, 84);
}
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
// Remove Current image if exists
if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) {
Storage::disk('public')->delete($path.'/'.$user->avatar);
}
$user->avatar = $file_name;
}
if ($user->save()) {
return redirect()->route('profile')->with('success', 'Account successfully updated');

View file

@ -51,9 +51,8 @@ class ReportsController extends Controller
public function getAccessoryReport()
{
$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 = [
$actionlog->created_at,
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '',
($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '',
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
@ -502,7 +501,6 @@ class ReportsController extends Controller
$header[] = trans('general.zip');
}
if ($request->filled('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto');
$header[] = trans('general.type');
@ -533,13 +531,14 @@ class ReportsController extends Controller
}
if ($request->filled('warranty')) {
$header[] = 'Warranty';
$header[] = 'Warranty Expires';
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = 'Value';
$header[] = 'Diff';
$header[] = 'Fully Depreciated';
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('checkout_date')) {
@ -596,23 +595,23 @@ class ReportsController extends Controller
'model.category', 'model.manufacturer', 'supplier');
if ($request->filled('by_location_id')) {
$assets->where('assets.location_id', $request->input('by_location_id'));
$assets->whereIn('assets.location_id', $request->input('by_location_id'));
}
if ($request->filled('by_rtd_location_id')) {
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
$assets->whereIn('assets.rtd_location_id', $request->input('by_rtd_location_id'));
}
if ($request->filled('by_supplier_id')) {
$assets->where('assets.supplier_id', $request->input('by_supplier_id'));
$assets->whereIn('assets.supplier_id', $request->input('by_supplier_id'));
}
if ($request->filled('by_company_id')) {
$assets->where('assets.company_id', $request->input('by_company_id'));
$assets->whereIn('assets.company_id', $request->input('by_company_id'));
}
if ($request->filled('by_model_id')) {
$assets->where('assets.model_id', $request->input('by_model_id'));
$assets->whereIn('assets.model_id', $request->input('by_model_id'));
}
if ($request->filled('by_category_id')) {
@ -632,7 +631,7 @@ class ReportsController extends Controller
}
if ($request->filled('by_status_id')) {
$assets->where('assets.status_id', $request->input('by_status_id'));
$assets->whereIn('assets.status_id', $request->input('by_status_id'));
}
if (($request->filled('purchase_start')) && ($request->filled('purchase_end'))) {
@ -642,6 +641,9 @@ class ReportsController extends Controller
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
$assets->whereBetween('assets.created_at', [$request->input('created_start'), $request->input('created_end')]);
}
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
$assets->whereBetween('assets.last_checkout', [$request->input('checkout_date_start'), $request->input('checkout_date_end')]);
}
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
@ -664,6 +666,7 @@ class ReportsController extends Controller
$assets->onlyTrashed();
}
\Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
@ -899,12 +902,8 @@ class ReportsController extends Controller
public function getAssetMaintenancesReport()
{
$this->authorize('reports.view');
// Grab all the improvements
$assetMaintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company')
->orderBy('created_at', 'DESC')
->get();
return view('reports/asset_maintenances', compact('assetMaintenances'));
return view('reports.asset_maintenances');
}
/**
@ -1025,7 +1024,11 @@ class ReportsController extends Controller
if (is_null($acceptance->created_at)){
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} 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){
@ -1120,8 +1123,6 @@ class ReportsController extends Controller
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
$rows[] = implode(',', $row);
} else {
// Log the error maybe?
}
}

View file

@ -65,18 +65,27 @@ class SettingsController extends Controller
$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;
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null;
if (('http://' === $protocol && '80' != $port) || ('https://' === $protocol && '443' != $port)) {
$host .= ':'.$port;
if (array_key_exists("HTTP_X_FORWARDED_HOST", $_SERVER)) {
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
} else {
$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'];
$start_settings['url_valid'] = (url('/').'/setup' === $pageURL);
$start_settings['url_config'] = url('/');
$start_settings['url_config'] = config('app.url').'/setup';
$start_settings['url_valid'] = ($start_settings['url_config'] === $pageURL);
$start_settings['real_url'] = $pageURL;
$start_settings['php_version_min'] = true;
@ -111,17 +120,17 @@ class SettingsController extends Controller
$start_settings['prod'] = true;
}
$start_settings['owner'] = '';
if (function_exists('posix_getpwuid')) { // Probably Linux
$owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME']));
$start_settings['owner'] = $owner['name'];
} else { // Windows
// TODO: Is there a way of knowing if a windows user has elevated permissions
// This just gets the user name, which likely isn't 'root'
// $start_settings['owner'] = getenv('USERNAME');
$start_settings['owner'] = '';
// This *should* be an array, but we've seen this return a bool in some chrooted environments
if (is_array($owner)) {
$start_settings['owner'] = $owner['name'];
}
}
if (('root' === $start_settings['owner']) || ('0' === $start_settings['owner'])) {
if (($start_settings['owner'] === 'root') || ($start_settings['owner'] === '0')) {
$start_settings['owner_is_admin'] = true;
} else {
$start_settings['owner_is_admin'] = false;
@ -679,33 +688,6 @@ class SettingsController extends Controller
return view('settings.slack', compact('setting'));
}
/**
* Return a form to allow a super admin to update settings.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.0]
*
* @return View
*/
public function postSlack(SlackSettingsRequest $request)
{
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->slack_endpoint = $request->input('slack_endpoint');
$setting->slack_channel = $request->input('slack_channel');
$setting->slack_botname = $request->input('slack_botname');
if ($setting->save()) {
return redirect()->route('settings.index')
->with('success', trans('admin/settings/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($setting->getErrors());
}
/**
* Return a form to allow a super admin to update settings.
*
@ -807,7 +789,7 @@ class SettingsController extends Controller
*/
public function getPhpInfo()
{
if (true === config('app.debug')) {
if (config('app.debug') === true) {
return view('settings.phpinfo');
}
@ -987,6 +969,7 @@ class SettingsController extends Controller
$setting->ldap_phone_field = $request->input('ldap_phone');
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
$setting->ldap_country = $request->input('ldap_country');
$setting->ldap_location = $request->input('ldap_location');
$setting->ldap_dept = $request->input('ldap_dept');
$setting->ldap_client_tls_cert = $request->input('ldap_client_tls_cert');
$setting->ldap_client_tls_key = $request->input('ldap_client_tls_key');
@ -1064,6 +1047,48 @@ class SettingsController extends Controller
return $pdf_branding;
}
/**
* Show Google login settings form
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return View
*/
public function getGoogleLoginSettings()
{
$setting = Setting::getSettings();
return view('settings.google', compact('setting'));
}
/**
* ShSaveow Google login settings form
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1]
* @return View
*/
public function postGoogleLoginSettings(Request $request)
{
if (!config('app.lock_passwords')) {
$setting = Setting::getSettings();
$setting->google_login = $request->input('google_login', 0);
$setting->google_client_id = $request->input('google_client_id');
$setting->google_client_secret = $request->input('google_client_secret');
if ($setting->save()) {
return redirect()->route('settings.index')
->with('success', trans('admin/settings/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($setting->getErrors());
}
return redirect()->back()->with('error', trans('general.feature_disabled'));
}
/**
* Show the listing of backups.
*
@ -1119,7 +1144,7 @@ class SettingsController extends Controller
public function postBackups()
{
if (! config('app.lock_passwords')) {
Artisan::call('backup:run');
Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H-i-s')]);
$output = Artisan::output();
// Backup completed

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Users;
use App\Events\UserMerged;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
@ -13,6 +14,7 @@ use App\Models\LicenseSeat;
use App\Models\ConsumableAssignment;
use App\Models\Consumable;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@ -21,7 +23,7 @@ use Illuminate\Support\Facades\Password;
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>]
* @since [v1.7]
@ -35,16 +37,32 @@ class BulkUsersController extends Controller
// Make sure there were users selected
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
// Get the list of affected users
$user_raw_array = request('ids');
$users = User::whereIn('id', $user_raw_array)
->with('groups', 'assets', 'licenses', 'accessories')->get();
// bulk edit, display the bulk edit form
if ($request->input('bulk_actions') == 'edit') {
return view('users/bulk-edit', compact('users'))
->with('groups', Group::pluck('name', 'id'));
// bulk delete, display the bulk delete confirmation form
} elseif ($request->input('bulk_actions') == 'delete') {
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') {
foreach ($users as $user) {
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);
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');
@ -95,7 +113,8 @@ class BulkUsersController extends Controller
->conditionallyAddItem('locale')
->conditionallyAddItem('remote')
->conditionallyAddItem('ldap_import')
->conditionallyAddItem('activated');
->conditionallyAddItem('activated')
->conditionallyAddItem('autoassign_licenses');
// If the manager_id is one of the users being updated, generate a warning.
@ -105,6 +124,11 @@ class BulkUsersController extends Controller
'warning' => trans('admin/users/message.bulk_manager_warn'),
];
}
if ($request->input('null_location_id')=='1') {
$this->update_array['location_id'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
@ -163,11 +187,11 @@ class BulkUsersController extends Controller
$this->authorize('update', User::class);
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')) {
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');
@ -249,4 +273,80 @@ class BulkUsersController extends Controller
$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);
$user = new User;
$user->activated = 1;
return view('users/edit', compact('groups', 'userGroups', 'permissions', 'userPermissions'))
->with('user', $user);
@ -121,6 +120,7 @@ class UsersController extends Controller
$user->created_by = Auth::user()->id;
$user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
// Strip out the superuser permission if the user isn't a superadmin
$permissions_array = $request->input('permission');
@ -131,7 +131,7 @@ class UsersController extends Controller
$user->permissions = json_encode($permissions_array);
// we have to invoke the
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'image', 'avatars', 'avatar');
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($user->save()) {
if ($request->filled('groups')) {
@ -210,7 +210,6 @@ class UsersController extends Controller
*/
public function update(SaveUserRequest $request, $id = null)
{
// We need to reverse the UI specific logic for our
// permissions here before we update the user.
$permissions = $request->input('permissions', []);
@ -268,12 +267,15 @@ class UsersController extends Controller
$user->city = $request->input('city', null);
$user->state = $request->input('state', null);
$user->country = $request->input('country', null);
$user->activated = $request->input('activated', 0);
// if a user is editing themselves we should always keep activated true
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
$user->zip = $request->input('zip', null);
$user->remote = $request->input('remote', 0);
$user->vip = $request->input('vip', 0);
$user->website = $request->input('website', null);
$user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
@ -296,7 +298,7 @@ class UsersController extends Controller
$user->permissions = json_encode($permissions_array);
// Handle uploaded avatar
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
//\Log::debug(print_r($user, true));
@ -668,4 +670,4 @@ class UsersController extends Controller
return redirect()->back()->with('error', 'User is not activated, is LDAP synced, or does not have an email address ');
}
}
}

View file

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

View file

@ -0,0 +1,529 @@
<?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?
// Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays)
public $accessories_fields;
public $assets_fields;
public $users_fields;
public $licenses_fields;
public $locations_fields;
public $consumables_fields;
public $components_fields;
public $aliases_fields;
protected $rules = [
'files.*.file_path' => 'required|string',
'files.*.created_at' => 'required|string',
'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));
}
private function getColumns($type)
{
switch ($type) {
case 'asset':
$results = $this->assets_fields;
break;
case 'accessory':
$results = $this->accessories_fields;
break;
case 'consumable':
$results = $this->consumables_fields;
break;
case 'component':
$results = $this->components_fields;
break;
case 'license':
$results = $this->licenses_fields;
break;
case 'user':
$results = $this->users_fields;
break;
case 'location':
$results = $this->locations_fields;
break;
default:
$results = [];
}
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_fields or something else picks it up.
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
} // TODO - strictly speaking, this isn't necessary here I don't think.
}
// 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_fields
foreach ($this->aliases_fields as $key => $alias_values) {
foreach ($alias_values as $alias_value) {
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
// Make *absolutely* sure that this key actually _exists_ in this import type -
// 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';
$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'),
'location' => trans('general.locations'),
];
/**
* These are the item-type specific columns
*/
$this->accessories_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.accessory')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
];
$this->assets_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]),
'asset_tag' => trans('general.asset_tag'),
'asset_model' => trans('general.model_name'),
'byod' => trans('general.byod'),
'model_number' => trans('general.model_no'),
'status' => trans('general.status'),
'warranty_months' => trans('admin/hardware/form.warranty'),
'category' => trans('general.category'),
'requestable' => trans('admin/hardware/general.requestable'),
'serial' => trans('general.serial_number'),
'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'image' => trans('general.importer.image_filename'),
/**
* Checkout fields:
* Assets can be checked out to other assets, people, or locations, but we currently
* only support checkout to people and locations in the importer
**/
'checkout_class' => trans('general.importer.checkout_type'),
'first_name' => trans('general.importer.checked_out_to_first_name'),
'last_name' => trans('general.importer.checked_out_to_last_name'),
'full_name' => trans('general.importer.checked_out_to_fullname'),
'email' => trans('general.importer.checked_out_to_email'),
'username' => trans('general.importer.checked_out_to_username'),
'checkout_location' => trans('general.importer.checkout_location'),
];
$this->consumables_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.consumable')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'min_amt' => trans('mail.min_QTY'),
'category' => trans('general.category'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'checkout_class' => trans('general.importer.checkout_type'),
'supplier' => trans('general.supplier'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'item_no' => trans('admin/consumables/general.item_no'),
];
$this->components_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.component')]),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'serial' => trans('general.serial_number'),
];
$this->licenses_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]),
'asset_tag' => trans('general.importer.checked_out_to_tag'),
'expiration_date' => trans('admin/licenses/form.expiration'),
'full_name' => trans('general.importer.checked_out_to_fullname'),
'license_email' => trans('admin/licenses/form.to_email'),
'license_name' => trans('admin/licenses/form.to_name'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'reassignable' => trans('admin/licenses/form.reassignable'),
'seats' => trans('admin/licenses/form.seats'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'maintained' => trans('admin/licenses/form.maintained'),
'checkout_class' => trans('general.importer.checkout_type'),
'serial' => trans('general.license_serial'),
'email' => trans('general.importer.checked_out_to_email'),
'username' => trans('general.importer.checked_out_to_username'),
'manufacturer' => trans('general.manufacturer'),
];
$this->users_fields = [
'id' => trans('general.id'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'department' => trans('general.department'),
'first_name' => trans('general.first_name'),
'last_name' => trans('general.last_name'),
'notes' => trans('general.notes'),
'username' => trans('admin/users/table.username'),
'jobtitle' => trans('admin/users/table.title'),
'phone_number' => trans('admin/users/table.phone'),
'manager_first_name' => trans('general.importer.manager_first_name'),
'manager_last_name' => trans('general.importer.manager_last_name'),
'activated' => trans('general.activated'),
'address' => trans('general.address'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'vip' => trans('general.importer.vip'),
'remote' => trans('admin/users/general.remote'),
'email' => trans('admin/users/table.email'),
'website' => trans('general.website'),
'avatar' => trans('general.image'),
'gravatar' => trans('general.importer.gravatar'),
'start_date' => trans('general.start_date'),
'end_date' => trans('general.end_date'),
'employee_num' => trans('general.employee_number'),
];
$this->locations_fields = [
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'currency' => trans('general.importer.currency'),
'ldap_ou' => trans('admin/locations/table.ldap_ou'),
'manager_username' => trans('general.importer.manager_username'),
'manager' => trans('general.importer.manager_full_name'),
'parent_location' => trans('admin/locations/table.parent'),
];
// "real fieldnames" to a list of aliases for that field
$this->aliases_fields = [
'item_name' =>
[
'item name',
'asset name',
'accessory name',
'user name',
'consumable name',
'component name',
'name',
],
'item_no' => [
'item number',
'item no.',
'item #',
],
'asset_model' =>
[
'model name',
'model',
],
'gravatar' =>
[
'gravatar',
],
'currency' =>
[
'$',
],
'jobtitle' =>
[
'job title for user',
'job title',
],
'username' =>
[
'user name',
'username',
trans('general.importer.checked_out_to_username'),
],
'first_name' =>
[
'first name',
trans('general.importer.checked_out_to_first_name'),
],
'last_name' =>
[
'last name',
'lastname',
trans('general.importer.checked_out_to_last_name'),
],
'email' =>
[
'email',
'e-mail',
trans('general.importer.checked_out_to_email'),
],
'phone_number' =>
[
'phone',
'phone number',
'phone num',
'telephone number',
'telephone',
'tel.',
],
'serial' =>
[
'serial number',
'serial no.',
'serial no',
'product key',
'key',
],
'model_number' =>
[
'model',
'model no',
'model no.',
'model number',
'model num',
'model num.'
],
'warranty_months' =>
[
'Warranty',
'Warranty Months'
],
'qty' =>
[
'QTY',
'Quantity'
],
'zip' =>
[
'Postal Code',
'Post Code',
'Zip Code'
],
'min_amt' =>
[
'Min Amount',
'Minimum Amount',
'Min Quantity',
'Minimum Quantity',
],
'next_audit_date' =>
[
'Next Audit',
],
'address2' =>
[
'Address 2',
'Address2',
],
'ldap_ou' =>
[
'LDAP OU',
'OU',
],
'parent_location' =>
[
'Parent',
'Parent Location',
],
'manager' =>
[
'Managed By',
'Manager Name',
'Manager Full Name',
],
'manager_username' =>
[
'Manager Username',
],
];
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
foreach($this->importTypes AS $type => $name) {
$this->columnOptions[$type] = $this->getColumns($type);
}
if ($this->activeFile) {
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
}
}
public function selectFile($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->file_id = $id;
$this->import_errors = null;
$this->statusText = null;
}
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

@ -0,0 +1,177 @@
<?php
namespace App\Http\Livewire;
use GuzzleHttp\Client;
use Livewire\Component;
use App\Models\Setting;
use App\Helpers\Helper;
class SlackSettingsForm extends Component
{
public $webhook_endpoint;
public $webhook_channel;
public $webhook_botname;
public $isDisabled ='' ;
public $webhook_name;
public $webhook_link;
public $webhook_placeholder;
public $webhook_icon;
public $webhook_selected;
public array $webhook_text;
public Setting $setting;
protected $rules = [
'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
'webhook_botname' => 'string|nullable',
];
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->save_button = trans('general.save');
$this->webhook_selected = $this->setting->webhook_selected;
$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) {
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');
}
}
}
public function render()
{
$this->isButtonDisabled();
return view('livewire.slack-settings-form');
}
public function testWebhook(){
$webhook = new Client([
'base_url' => e($this->webhook_endpoint),
'defaults' => [
'exceptions' => false,
],
]);
$payload = json_encode(
[
'channel' => e($this->webhook_channel),
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
'username' => e($this->webhook_botname),
'icon_emoji' => ':heart:',
]);
try {
$webhook->post($this->webhook_endpoint, ['body' => $payload]);
$this->isDisabled='';
$this->save_button = trans('general.save');
return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
} catch (\Exception $e) {
$this->isDisabled= 'disabled';
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'));
}
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()
{
if (Helper::isDemoMode()) {
session()->flash('error',trans('general.feature_disabled'));
} else {
if ($this->webhook_selected != 'general') {
$this->validate($this->rules);
}
$this->setting->webhook_selected = $this->webhook_selected;
$this->setting->webhook_endpoint = $this->webhook_endpoint;
$this->setting->webhook_channel = $this->webhook_channel;
$this->setting->webhook_botname = $this->webhook_botname;
$this->setting->save();
session()->flash('success',trans('admin/settings/message.update.success'));
}
}
}

View file

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

View file

@ -11,7 +11,7 @@ class CheckForTwoFactor
/**
* Routes to ignore for Two Factor Auth
*/
const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout'];
public const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout'];
/**
* Handle an incoming request.

View file

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

View file

@ -24,7 +24,7 @@ class AssetFileRequest extends Request
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
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

@ -26,6 +26,8 @@ class CustomFieldRequest extends FormRequest
{
$rules = [];
$rules['associate_fieldsets.*'] = 'nullable|integer|exists:custom_fieldsets,id';
switch ($this->method()) {
// Brand new
@ -54,4 +56,11 @@ class CustomFieldRequest extends FormRequest
return $rules;
}
public function messages()
{
return [
'associate_fieldsets.*.exists' => trans('admin/custom_fields/message/does_not_exist'),
];
}
}

View file

@ -7,7 +7,9 @@ use enshrined\svgSanitize\Sanitizer;
use Intervention\Image\Facades\Image;
use App\Http\Traits\ConvertsBase64ToFiles;
use Illuminate\Http\UploadedFile;
use Storage;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exception\NotReadableException;
class ImageUploadRequest extends Request
{
@ -63,11 +65,13 @@ class ImageUploadRequest extends Request
* @param string $path location for uploaded images, defaults to uploads/plural of item type.
* @return SnipeModel Target asset is being checked out to.
*/
public function handleImages($item, $w = 600, $form_fieldname = null, $path = null, $db_fieldname = 'image')
public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
{
$type = strtolower(class_basename(get_class($item)));
if (is_null($path)) {
$path = str_plural($type);
if ($type == 'assetmodel') {
@ -79,49 +83,47 @@ class ImageUploadRequest extends Request
}
}
if (is_null($form_fieldname)) {
$form_fieldname = 'image';
}
// This is dumb, but we need it for overriding field names for exceptions like avatars and logo uploads
if (is_null($db_fieldname)) {
$use_db_field = $form_fieldname;
} else {
$use_db_field = $db_fieldname;
}
// ConvertBase64ToFiles just changes object type,
// as it cannot currently insert files to $this->files
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
$image=$this->offsetGet($form_fieldname);
$image = $this->offsetGet($form_fieldname);
\Log::debug('Image is an instance of UploadedFile');
} elseif ($this->hasFile($form_fieldname)) {
$image = $this->file($form_fieldname);
\Log::debug('Just use regular upload for '.$form_fieldname);
} else {
if ($this->hasFile($form_fieldname)) {
$image = $this->file($form_fieldname);
}
\Log::debug('No image found for form fieldname: '.$form_fieldname);
}
if (isset($image)) {
\Log::debug($image);
if (!config('app.lock_passwords')) {
$ext = $image->getClientOriginalExtension();
$file_name = $type.'-'.$form_fieldname.'-'.str_random(10).'.'.$ext;
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
\Log::info('File name will be: '.$file_name);
\Log::debug('File extension is: '.$ext);
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
\Log::debug('Not an SVG or webp - resize');
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
try {
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$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)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
} else {
// If the file is a webp, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available
@ -146,30 +148,30 @@ class ImageUploadRequest extends Request
}
// Remove Current image if exists
if (($item->{$use_db_field}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field}))) {
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
} catch (\Exception $e) {
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
}
}
$item->{$use_db_field} = $file_name;
$item->{$db_fieldname} = $file_name;
}
// If the user isn't uploading anything new but wants to delete their old image, do so
} else {
if ($this->input('image_delete') == '1') {
\Log::debug('Deleting image');
try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
$item->{$use_db_field} = null;
} catch (\Exception $e) {
\Log::debug($e);
}
} elseif ($this->input('image_delete') == '1') {
\Log::debug('Deleting image');
try {
Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
$item->{$db_fieldname} = null;
} catch (\Exception $e) {
\Log::debug($e);
}
}
return $item;

View file

@ -39,14 +39,12 @@ class SaveUserRequest extends FormRequest
// Brand new user
case 'POST':
{
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
if ($this->request->get('ldap_import') == false) {
$rules['password'] = Setting::passwordComplexityRulesSaving('store').'|confirmed';
}
break;
}
// Save all fields
case 'PUT':
@ -57,12 +55,11 @@ class SaveUserRequest extends FormRequest
// Save only what's passed
case 'PATCH':
{
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
break;
}
default:break;
default:
break;
}
return $rules;

View file

@ -1,33 +0,0 @@
<?php
namespace App\Http\Requests;
class SlackSettingsRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:"https://hooks.slack.com"|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
'slack_botname' => 'string|nullable',
];
}
}

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Accessory;
use Gate;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
@ -32,13 +32,14 @@ class AccessoriesTransformer
'model_number' => ($accessory->model_number) ? e($accessory->model_number) : null,
'category' => ($accessory->category) ? ['id' => $accessory->category->id, 'name'=> e($accessory->category->name)] : null,
'location' => ($accessory->location) ? ['id' => $accessory->location->id, 'name'=> e($accessory->location->name)] : null,
'notes' => ($accessory->notes) ? e($accessory->notes) : null,
'notes' => ($accessory->notes) ? Helper::parseEscapedMarkedownInline($accessory->notes) : null,
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => $accessory->numRemaining(),
'remaining_qty' => (int) $accessory->numRemaining(),
'users_count' => $accessory->users_count,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
@ -50,6 +51,8 @@ class AccessoriesTransformer
'checkin' => false,
'update' => Gate::allows('update', Accessory::class),
'delete' => Gate::allows('delete', Accessory::class),
'clone' => Gate::allows('create', Accessory::class),
];
$permissions_array['user_can_checkout'] = false;

View file

@ -60,12 +60,14 @@ class ActionlogsTransformer
if ($actionlog->action_type == 'accepted') {
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
} else {
if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') {
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
if ($actionlog->item) {
if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') {
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
}
}
}
}
@ -108,7 +110,7 @@ class ActionlogsTransformer
'type' => e($actionlog->targetType()),
] : null,
'note' => ($actionlog->note) ? e($actionlog->note): null,
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),

View file

@ -5,7 +5,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use Gate;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
class AssetMaintenancesTransformer
@ -45,7 +45,11 @@ class AssetMaintenancesTransformer
'name'=> e($assetmaintenance->asset->location->name),
] : null,
'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null,
'rtd_location' => ($assetmaintenance->asset->defaultLoc) ? [
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
'name'=> e($assetmaintenance->asset->defaultLoc->name),
] : null,
'notes' => ($assetmaintenance->notes) ? Helper::parseEscapedMarkedownInline($assetmaintenance->notes) : null,
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
@ -55,6 +59,7 @@ class AssetMaintenancesTransformer
'user_id' => ($assetmaintenance->admin) ? ['id' => $assetmaintenance->admin->id, 'name'=> e($assetmaintenance->admin->getFullNameAttribute())] : null,
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
'is_warranty'=> $assetmaintenance->is_warranty,
];

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use Gate;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
@ -63,7 +63,7 @@ class AssetModelsTransformer
'default_fieldset_values' => $default_field_values,
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false,
'notes' => e($assetmodel->notes),
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'),

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