diff --git a/.all-contributorsrc b/.all-contributorsrc index 146f6178ce..58469f5931 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3018,6 +3018,96 @@ "contributions": [ "code" ] + }, + { + "login": "koiakoia", + "name": "koiakoia", + "avatar_url": "https://avatars.githubusercontent.com/u/60405354?v=4", + "profile": "https://github.com/koiakoia", + "contributions": [ + "code" + ] + }, + { + "login": "mustafa-online", + "name": "Mustafa Online", + "avatar_url": "https://avatars.githubusercontent.com/u/5323832?v=4", + "profile": "https://github.com/mustafa-online", + "contributions": [ + "code" + ] + }, + { + "login": "franceslui", + "name": "franceslui", + "avatar_url": "https://avatars.githubusercontent.com/u/104601439?v=4", + "profile": "https://github.com/franceslui", + "contributions": [ + "code" + ] + }, + { + "login": "Q4kK", + "name": "Q4kK", + "avatar_url": "https://avatars.githubusercontent.com/u/125313163?v=4", + "profile": "https://github.com/Q4kK", + "contributions": [ + "code" + ] + }, + { + "login": "squintfox", + "name": "squintfox", + "avatar_url": "https://avatars.githubusercontent.com/u/55590532?v=4", + "profile": "https://github.com/squintfox", + "contributions": [ + "code" + ] + }, + { + "login": "jeffclay", + "name": "Jeff Clay", + "avatar_url": "https://avatars.githubusercontent.com/u/1380084?v=4", + "profile": "https://github.com/jeffclay", + "contributions": [ + "code" + ] + }, + { + "login": "PP-JN-RL", + "name": "Phil J R", + "avatar_url": "https://avatars.githubusercontent.com/u/52716446?v=4", + "profile": "https://github.com/PP-JN-RL", + "contributions": [ + "code" + ] + }, + { + "login": "chandanchowdhury", + "name": "i_virus", + "avatar_url": "https://avatars.githubusercontent.com/u/1496725?v=4", + "profile": "https://www.corelight.com/", + "contributions": [ + "code" + ] + }, + { + "login": "gitgrimbo", + "name": "Paul Grime", + "avatar_url": "https://avatars.githubusercontent.com/u/1020541?v=4", + "profile": "https://github.com/gitgrimbo", + "contributions": [ + "code" + ] + }, + { + "login": "LeePorte", + "name": "Lee Porte", + "avatar_url": "https://avatars.githubusercontent.com/u/922815?v=4", + "profile": "https://leeporte.co.uk", + "contributions": [ + "code" + ] } ] } diff --git a/.env.example b/.env.example index 8f3e5a2d69..47e3b96609 100644 --- a/.env.example +++ b/.env.example @@ -96,6 +96,7 @@ APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1 ALLOW_IFRAMING=false REFERRER_POLICY=same-origin ENABLE_CSP=false +ADDITIONAL_CSP_URLS=null CORS_ALLOWED_ORIGINS=null ENABLE_HSTS=false diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml index d7438a7ae9..f8368dff83 100644 --- a/.github/workflows/codacy-analysis.yml +++ b/.github/workflows/codacy-analysis.yml @@ -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.4.0 + uses: codacy/codacy-analysis-cli-action@v4.4.1 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 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index afaad9a7a6..251e150bcf 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,444 +1,56 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
snipe
snipe

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

💻 📖 🚇 👀
Daniel Meltzer
Daniel Meltzer

💻 ⚠️ 📖
Michael T
Michael T

💻
madd15
madd15

📖 💬
Vincent Sposato
Vincent Sposato

💻
Andrea Bergamasco
Andrea Bergamasco

💻
Karol
Karol

🌍 💻
morph027
morph027

💻
fvleminckx
fvleminckx

🚇
itsupportcmsukorg
itsupportcmsukorg

💻 🐛
Frank
Frank

💻
Deleted user
Deleted user

🌍 💻
tiagom62
tiagom62

💻 🚇
Ryan Stafford
Ryan Stafford

💻
Eammon Hanlon
Eammon Hanlon

💻
zjean
zjean

💻
Matthias Frei
Matthias Frei

💻
opsydev
opsydev

💻
Daniel Dreier
Daniel Dreier

💻
Nikolai Prokoschenko
Nikolai Prokoschenko

💻
Drew
Drew

💻
Walter
Walter

💻
Petr Baloun
Petr Baloun

💻
reidblomquist
reidblomquist

📖
Mathieu Kooiman
Mathieu Kooiman

💻
csayre
csayre

📖
Adam Dunson
Adam Dunson

💻
Hereward
Hereward

💻
swoopdk
swoopdk

💻
Abdullah Alansari
Abdullah Alansari

💻
Micael Rodrigues
Micael Rodrigues

💻
Patrick Gallagher
Patrick Gallagher

📖
Miliamber
Miliamber

💻
hawk554
hawk554

💻
Justin Kerr
Justin Kerr

💻
Ira W. Snyder
Ira W. Snyder

📖
Aladin Alaily
Aladin Alaily

💻
Chase Hansen
Chase Hansen

💻 💬 🐛
IDM Helpdesk
IDM Helpdesk

💻
Kai
Kai

💻
Michael Daniels
Michael Daniels

💻
Tom Castleman
Tom Castleman

💻
Daniel Nemanic
Daniel Nemanic

💻
SouthWolf
SouthWolf

💻
Ivar Nesje
Ivar Nesje

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

📖
Chris Leathley
Chris Leathley

🚇
splaer
splaer

🐛 💻
Joe Ferguson
Joe Ferguson

💻
diwanicki
diwanicki

💻 📖
Lee Thoong Ching
Lee Thoong Ching

📖 💻
Marek Šuppa
Marek Šuppa

💻
Juan J. Martinez
Juan J. Martinez

🌍
R Ryan Dial
R Ryan Dial

🌍
Andrej Manduch
Andrej Manduch

📖
Jay Richards
Jay Richards

💻
Alexander Innes
Alexander Innes

💻
Danny Garcia
Danny Garcia

💻
archpoint
archpoint

💻
Jake McGraw
Jake McGraw

💻
FleischKarussel
FleischKarussel

📖
Dylan Yi
Dylan Yi

💻
Gil Rutkowski
Gil Rutkowski

💻
Desmond Morris
Desmond Morris

💻
Nick Peelman
Nick Peelman

💻
Abraham Vegh
Abraham Vegh

💻
Mohamed Rashid
Mohamed Rashid

📖
Kasey
Kasey

💻
Brett
Brett

⚠️
Jason Spriggs
Jason Spriggs

💻
Nate Felton
Nate Felton

💻
Manasses Ferreira
Manasses Ferreira

💻
Steve
Steve

⚠️
matc
matc

⚠️
Cole R. Davis
Cole R. Davis

⚠️
gibsonjoshua55
gibsonjoshua55

💻
Robin Temme
Robin Temme

💻
Iman
Iman

💻
Richard Hofman
Richard Hofman

💻
gizzmojr
gizzmojr

💻
Jenny Li
Jenny Li

📖
Geoff Young
Geoff Young

💻
Elliot Blackburn
Elliot Blackburn

📖
Tõnis Ormisson
Tõnis Ormisson

💻
Nicolai Essig
Nicolai Essig

💻
Danielle
Danielle

📖
Lawrence
Lawrence

⚠️ 🐛
uknzaeinozpas
uknzaeinozpas

⚠️ 💻
Ryan
Ryan

📖
vcordes79
vcordes79

💻
fordster78
fordster78

💻
CronKz
CronKz

💻 🌍
Tim Bishop
Tim Bishop

💻
Sean McIlvenna
Sean McIlvenna

💻
cepacs
cepacs

🐛 📖
lea-mink
lea-mink

💻
Hannah Tinkler
Hannah Tinkler

💻
Doeke Zanstra
Doeke Zanstra

💻
Djamon Staal
Djamon Staal

💻
Earl Ramirez
Earl Ramirez

💻
Richard Ray Thomas
Richard Ray Thomas

💻
Ryan Kuba
Ryan Kuba

💻
Brian Monroe
Brian Monroe

💻
plexorama
plexorama

💻
Till Deeke
Till Deeke

💻
5quirrel
5quirrel

💻
Jason
Jason

💻
Antti
Antti

💻
DeusMaximus
DeusMaximus

💻
a-royal
a-royal

🌍
Alberto Aldrigo
Alberto Aldrigo

🌍
Alex Stanev
Alex Stanev

🌍
Andreas Rehm
Andreas Rehm

🌍
Andreas Erhard
Andreas Erhard

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

🌍
Antonio Schiavon
Antonio Schiavon

🌍
benunter
benunter

🌍
Borys Żmuda
Borys Żmuda

🌍
chibacityblues
chibacityblues

🌍
Chien Wei Lin
Chien Wei Lin

🌍
Christian Schuster
Christian Schuster

🌍
Christian Stefanus
Christian Stefanus

🌍
wxcafé
wxcafé

🌍
dpyroc
dpyroc

🌍
Daniel Friedlmaier
Daniel Friedlmaier

🌍
Daniel Heene
Daniel Heene

🌍
danielcb
danielcb

🌍
Dominik Senti
Dominik Senti

🌍
Eric Gautheron
Eric Gautheron

🌍
Erlend Pilø
Erlend Pilø

🌍
Fabio Rapposelli
Fabio Rapposelli

🌍
Felipe Barros
Felipe Barros

🌍
Fernando Possebon
Fernando Possebon

🌍
gdraque
gdraque

🌍
Georg Wallisch
Georg Wallisch

🌍
Gerardo Robles
Gerardo Robles

🌍
Gluek
Gluek

🌍
AdnanAbuShahad
AdnanAbuShahad

🌍
Hafidzi My
Hafidzi My

🌍
Harim Park
Harim Park

🌍
Henrik Kentsson
Henrik Kentsson

🌍
Husnul Yaqien
Husnul Yaqien

🌍
Ibrahim
Ibrahim

🌍
igolman
igolman

🌍
itangiang
itangiang

🌍
jarby1211
jarby1211

🌍
Jhonn Willker
Jhonn Willker

🌍
Jose
Jose

🌍
laopangzi
laopangzi

🌍
Lars Strojny
Lars Strojny

🌍
MarcosBL
MarcosBL

🌍
marie joy cajes
marie joy cajes

🌍
Mark S. Johansen
Mark S. Johansen

🌍
Martin Stub
Martin Stub

🌍
Meyer Flavio
Meyer Flavio

🌍
Micael Rodrigues
Micael Rodrigues

🌍
Mikael Rasmussen
Mikael Rasmussen

🌍
IxFail
IxFail

🌍
Mohammed Fota
Mohammed Fota

🌍
Moayad Alserihi
Moayad Alserihi

🌍
saymd
saymd

🌍
Patrik Larsson
Patrik Larsson

🌍
drcryo
drcryo

🌍
pawel1615
pawel1615

🌍
bodrovics
bodrovics

🌍
priatna
priatna

🌍
Fan Jiang
Fan Jiang

🌍
ragnarcx
ragnarcx

🌍
Rein van Haaren
Rein van Haaren

🌍
Teguh Dwicaksana
Teguh Dwicaksana

🌍
fraccie
fraccie

🌍
vinzruzell
vinzruzell

🌍
Kevin Austin
Kevin Austin

🌍
Wira Sandy
Wira Sandy

🌍
Илья
Илья

🌍
GodUseVPN
GodUseVPN

🌍
周周
周周

🌍
Sam
Sam

💻
Azerothian
Azerothian

💻
Wes Hulette
Wes Hulette

💻
patrict
patrict

💻
Dmitriy Minaev
Dmitriy Minaev

💻
liquidhorse
liquidhorse

💻
Jordi Boggiano
Jordi Boggiano

💻
Ivan Nieto
Ivan Nieto

💻
Ben RUBSON
Ben RUBSON

💻
NMathar
NMathar

💻
Steffen
Steffen

💻
Sxderp
Sxderp

💻
fanta8897
fanta8897

💻
Andrey Bolonin
Andrey Bolonin

💻
shinayoshi
shinayoshi

💻
Hubert
Hubert

💻
KeenRivals
KeenRivals

💻
omyno
omyno

💻
Evgeny
Evgeny

💻
Colin Campbell
Colin Campbell

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

💻
Martin Meredith
Martin Meredith

💻
Tim Farmer
Tim Farmer

💻
Marián Skrip
Marián Skrip

💻
Godfrey Martinez
Godfrey Martinez

💻
bigtreeEdo
bigtreeEdo

💻
Colin  McNeil
Colin McNeil

💻
JoKneeMo
JoKneeMo

💻
Joshi
Joshi

💻
Anthony Burns
Anthony Burns

💻
johnson-yi
johnson-yi

💻
Sanjay Govind
Sanjay Govind

💻
Peter Upfold
Peter Upfold

💻
Jared Biel
Jared Biel

💻
Dampfklon
Dampfklon

💻
Charles Hamilton
Charles Hamilton

💻
Giuseppe Iannello
Giuseppe Iannello

💻
Peter Dave Hello
Peter Dave Hello

💻
sigmoidal
sigmoidal

💻
Vincent Lainé
Vincent Lainé

💻
Lucas Pleß
Lucas Pleß

💻
Ian Littman
Ian Littman

💻
João Paulo
João Paulo

💻
ThoBur
ThoBur

💻
Alexander Chibrikin
Alexander Chibrikin

💻
Anthony Winstanley
Anthony Winstanley

💻
Folke
Folke

💻
Bennett Blodinger
Bennett Blodinger

💻
NMC
NMC

💻
andres-baller
andres-baller

💻
sean-borg
sean-borg

💻
EDVLeer
EDVLeer

💻
Kurokat
Kurokat

💻
Kevin Köllmann
Kevin Köllmann

💻
sw-mreyes
sw-mreyes

💻
Joel Pittet
Joel Pittet

💻
Eli Young
Eli Young

💻
Raell Dottin
Raell Dottin

💻
Tom Misilo
Tom Misilo

💻
David Davenne
David Davenne

💻
Mark Stenglein
Mark Stenglein

💻
ajsy
ajsy

💻
Jan Kiesewetter
Jan Kiesewetter

💻
Tetrachloromethane250
Tetrachloromethane250

💻
Lars Kajes
Lars Kajes

💻
Joly0
Joly0

💻
theburger
theburger

💻
David Valin Alonso
David Valin Alonso

💻
andreaci
andreaci

💻
Jelle Sebreghts
Jelle Sebreghts

💻
Michael Pietsch
Michael Pietsch

Masudul Haque Shihab
Masudul Haque Shihab

💻
Supapong Areeprasertkul
Supapong Areeprasertkul

💻
Peter Sarossy
Peter Sarossy

💻
Renee Margaret McConahy
Renee Margaret McConahy

💻
JohnnyPicnic
JohnnyPicnic

💻
markbrule
markbrule

💻
Mike Campbell
Mike Campbell

💻
tbrconnect
tbrconnect

💻
kcoyo
kcoyo

💻
Travis Miller
Travis Miller

💻
Evan Taylor
Evan Taylor

💻
Petri Asikainen
Petri Asikainen

💻
derdeagle
derdeagle

💻
Mike Frysinger
Mike Frysinger

💻
ALPHA
ALPHA

💻
FliegenKLATSCH
FliegenKLATSCH

💻
Jeremy Price
Jeremy Price

💻
Toreg87
Toreg87

💻
Matthew Nickson
Matthew Nickson

💻
Jethro Nederhof
Jethro Nederhof

💻
Oskar Stenberg
Oskar Stenberg

💻
Robert-Azelis
Robert-Azelis

💻
Alexander William Smith
Alexander William Smith

💻
LEITWERK AG
LEITWERK AG

💻
Adam
Adam

💻
Ian
Ian

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

💻
Haxatron
Haxatron

💻
PlaneNuts
PlaneNuts

💻
Bradley Coudriet
Bradley Coudriet

💻
Dalton Durst
Dalton Durst

💻
Alex Janes
Alex Janes

💻
Nuraeil
Nuraeil

💻
TenOfTens
TenOfTens

💻
waffle
waffle

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Achmad Fienan Rahardianto
Achmad Fienan Rahardianto

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Christian Weirich
Christian Weirich

💻
denzfarid
denzfarid

ntbutler-nbcs
ntbutler-nbcs

💻
Naveen
Naveen

💻
Mike Roquemore
Mike Roquemore

💻
Daniel Reeder
Daniel Reeder

🌍 🌍 💻
vickyjaura183
vickyjaura183

💻
Peace
Peace

💻
Kyle Gordon
Kyle Gordon

💻
Katharina Drexel
Katharina Drexel

💻
David Sferruzza
David Sferruzza

💻
Rick Nelson
Rick Nelson

💻
BasO12
BasO12

💻
Vautia
Vautia

💻
Chris Hartjes
Chris Hartjes

💻
geo-chen
geo-chen

💻
Phan Nguyen
Phan Nguyen

💻
Iisakki Jaakkola
Iisakki Jaakkola

💻
Ikko Ashimine
Ikko Ashimine

💻
Lukas Fehling
Lukas Fehling

💻
Fernando Almeida
Fernando Almeida

💻
akemidx
akemidx

💻
Oguz Bilgic
Oguz Bilgic

💻
Scooter Crawford
Scooter Crawford

💻
subdriven
subdriven

💻
Andrew Savinykh
Andrew Savinykh

💻
Tadayuki Onishi
Tadayuki Onishi

💻
Florian
Florian

💻
Spencer Long
Spencer Long

💻
Marcus Moore
Marcus Moore

💻
Martin Meredith
Martin Meredith

dboth
dboth

💻
Zachary Fleck
Zachary Fleck

💻
VIKAAS-A
VIKAAS-A

💻
Abdul Kareem
Abdul Kareem

💻
NojoudAlshehri
NojoudAlshehri

💻
Stefan Stidl
Stefan Stidl

💻
Quentin Aymard
Quentin Aymard

💻
Grant Le Roux
Grant Le Roux

💻
Bogdan
Bogdan

💻
mmanjos
mmanjos

💻
Abdelaziz Faki
Abdelaziz Faki

💻
bilias
bilias

💻
coach1988
coach1988

💻
MrM
MrM

💻
- - - - +| [
snipe](http://www.snipe.net)
[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") | +| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") | +| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") | +| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") | +| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") | +| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") | +| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") | +| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") | +| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") | +| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") | +| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") | +| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") | +| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") | +| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") | +| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") | +| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") | +| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") | +| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") | +| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") | +| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") | +| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") | +| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") | +| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") | +| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") | +| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") | +| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") | +| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") | +| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | +| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") | +| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") | +| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") | +| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") | +| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") | +| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") | +| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") | +| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") | +| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") | +| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") | +| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") | +| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | +| [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | +| [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | +| [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | +| [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | +| [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | +| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | +| [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | +| [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/Dockerfile b/Dockerfile index 88de52858b..bd363ccd18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,7 +105,7 @@ RUN \ && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.cert" "/var/www/html/storage/ldap_client_tls.cert" \ && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.key" "/var/www/html/storage/ldap_client_tls.key" \ && chown docker "/var/lib/snipeit/keys/" \ - && chown -h docker "/var/www/html/storage/" \ + && chown -Rh docker "/var/www/html/storage/" \ && chmod +x /var/www/html/artisan \ && echo "Finished setting up application in /var/www/html" diff --git a/README.md b/README.md index b271d88c26..37fa83549c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) +[![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://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) [![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) ## Snipe-IT - Open Source Asset Management System @@ -12,7 +12,7 @@ It is built on [Laravel 8](http://laravel.com). Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) > [!TIP] -> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. +> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. ----- diff --git a/app/Console/Commands/SendExpectedCheckinAlerts.php b/app/Console/Commands/SendExpectedCheckinAlerts.php index 34e9609ce5..e042e8b088 100644 --- a/app/Console/Commands/SendExpectedCheckinAlerts.php +++ b/app/Console/Commands/SendExpectedCheckinAlerts.php @@ -9,7 +9,6 @@ use App\Notifications\ExpectedCheckinAdminNotification; use App\Notifications\ExpectedCheckinNotification; use Carbon\Carbon; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; class SendExpectedCheckinAlerts extends Command { @@ -43,25 +42,31 @@ class SendExpectedCheckinAlerts extends Command public function handle() { $settings = Setting::getSettings(); - $whenNotify = Carbon::now(); - $assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get(); + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval); + + $assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get(); + + $this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline'); - $this->info($whenNotify.' is deadline'); - $this->info($assets->count().' assets'); foreach ($assets as $asset) { - if ($asset->assigned && $asset->checkedOutToUser()) { - Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email); - $asset->assigned->notify((new ExpectedCheckinNotification($asset))); + if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) { + $this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email); + $asset->assignedTo->notify((new ExpectedCheckinNotification($asset))); } } if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { + $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { return new AlertRecipient($item); }); + + $this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email); \Notification::send($recipients, new ExpectedCheckinAdminNotification($assets)); + } } } diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php index e9cba106c6..40ebb64c50 100644 --- a/app/Console/Commands/SendUpcomingAuditReport.php +++ b/app/Console/Commands/SendUpcomingAuditReport.php @@ -3,10 +3,8 @@ namespace App\Console\Commands; use App\Models\Asset; -use App\Models\License; -use App\Models\Recipients; +use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\ExpiringAssetsNotification; use App\Notifications\SendUpcomingAuditNotification; use Carbon\Carbon; use DB; @@ -46,39 +44,24 @@ class SendUpcomingAuditReport extends Command public function handle() { $settings = Setting::getSettings(); + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval); - if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) { + $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); + $this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline'); + + if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { - return new \App\Models\Recipients\AlertRecipient($item); + $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { + return new AlertRecipient($item); }); - // Assets due for auditing + $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email); + \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); - $assets = Asset::whereNotNull('next_audit_date') - ->DueOrOverdueForAudit($settings) - ->orderBy('last_audit_date', 'asc')->get(); - - if ($assets->count() > 0) { - $this->info(trans_choice('mail.upcoming-audits', $assets->count(), - ['count' => $assets->count(), 'threshold' => $settings->audit_warning_days])); - \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); - $this->info('Audit report sent to '.$settings->alert_email); - } else { - $this->info('No assets to be audited. No report sent.'); - } - } elseif ($settings->alert_email == '') { - $this->error('Could not send email. No alert email configured in settings'); - } elseif (! $settings->audit_warning_days) { - $this->error('No audit warning days set in Admin Notifications. No mail will be sent.'); - } elseif ($settings->alerts_enabled != 1) { - $this->info('Alerts are disabled in the settings. No mail will be sent'); - } else { - $this->error('Something went wrong. :( '); - $this->error('Admin Notifications Email Setting: '.$settings->alert_email); - $this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days); - $this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled); } + } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 030e069bd2..b33d83cdd0 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -223,6 +223,7 @@ class AcceptanceController extends Controller 'item_model' => $display_model, 'item_serial' => $item->serial, 'eula' => $item->getEula(), + 'note' => $request->input('note'), '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, @@ -238,7 +239,7 @@ class AcceptanceController extends Controller Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); } - $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename); + $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); event(new CheckoutAccepted($acceptance)); @@ -306,10 +307,12 @@ class AcceptanceController extends Controller $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; } + $data = [ 'item_tag' => $item->asset_tag, 'item_model' => $display_model, 'item_serial' => $item->serial, + 'note' => $request->input('note'), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'), 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'assigned_to' => $assigned_to, @@ -323,7 +326,7 @@ class AcceptanceController extends Controller Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); } - $acceptance->decline($sig_filename); + $acceptance->decline($sig_filename, $request->input('note')); $acceptance->notify(new AcceptanceAssetDeclinedNotification($data)); event(new CheckoutDeclined($acceptance)); $return_msg = trans('admin/users/message.declined'); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index be95b3fd2c..eb6a09900b 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -60,7 +60,7 @@ class AssetsController extends Controller * @since [v4.0] * @return \Illuminate\Http\JsonResponse */ - public function index(Request $request, $audit = null) + public function index(Request $request, $action = null, $upcoming_status = null) { $filter_non_deprecable_assets = false; @@ -156,17 +156,44 @@ class AssetsController extends Controller $assets->TextSearch($request->input('search')); } - // This is used by the audit reporting routes - if (Gate::allows('audit', Asset::class)) { - switch ($audit) { - case 'due': - $assets->DueOrOverdueForAudit($settings); - break; - case 'overdue': - $assets->overdueForAudit($settings); - break; + + /** + * Handle due and overdue audits and checkin dates + */ + switch ($action) { + case 'audits': + + switch ($upcoming_status) { + case 'due': + $assets->DueForAudit($settings); + break; + case 'overdue': + $assets->OverdueForAudit(); + break; + case 'due-or-overdue': + $assets->DueOrOverdueForAudit($settings); + break; + } + break; + + case 'checkins': + switch ($upcoming_status) { + case 'due': + $assets->DueForCheckin($settings); + break; + case 'overdue': + $assets->OverdueForCheckin(); + break; + case 'due-or-overdue': + $assets->DueOrOverdueForCheckin($settings); + break; + } + break; } - } + + /** + * End handling due and overdue audits and checkin dates + */ // This is used by the sidenav, mostly @@ -999,25 +1026,39 @@ class AssetsController extends Controller { $this->authorize('audit', Asset::class); - $rules = [ - 'asset_tag' => 'required', - 'location_id' => 'exists:locations,id|nullable|numeric', - 'next_audit_date' => 'date|nullable', - ]; - - $validator = Validator::make($request->all(), $rules); - if ($validator->fails()) { - return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all())); - } $settings = Setting::getSettings(); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); + // No tag passed - return an error + if (!$request->filled('asset_tag')) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> '', + 'error'=> trans('admin/hardware/message.no_tag'), + ], trans('admin/hardware/message.no_tag')), 200); + } + + $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); if ($asset) { - // We don't want to log this as a normal update, so let's bypass that + + /** + * Even though we do a save() further down, we don't want to log this as a "normal" asset update, + * which would trigger the Asset Observer and would log an asset *update* log entry (because the + * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to + * the audit log entry we're creating through this controller. + * + * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass + * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() + * will bypass normal model-level validation that's usually handled at the observer ) + * + * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, + * which manually invokes Watson Validating to make sure the asset's model is valid. + * + * @see \App\Observers\AssetObserver::updating() + */ $asset->unsetEventDispatcher(); $asset->next_audit_date = $dt; @@ -1033,8 +1074,12 @@ class AssetsController extends Controller $asset->last_audit_date = date('Y-m-d H:i:s'); - if ($asset->save()) { - $log = $asset->logAudit(request('note'), request('location_id')); + /** + * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. + * We have to invoke this manually because of the unsetEventDispatcher() above.) + */ + if ($asset->isValid() && $asset->save()) { + $asset->logAudit(request('note'), request('location_id')); return response()->json(Helper::formatStandardApiResponse('success', [ 'asset_tag'=> e($asset->asset_tag), @@ -1042,9 +1087,23 @@ class AssetsController extends Controller 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), ], trans('admin/hardware/message.audit.success'))); } + + // Asset failed validation or was not able to be saved + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> e($asset->asset_tag), + 'error'=> $asset->getErrors()->first(), + ], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200); + } - return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found')); + + // No matching asset for the asset tag that was passed. + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> e($request->input('asset_tag')), + 'error'=> trans('admin/hardware/message.audit.error'), + ], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200); + + } diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index ba1a5366f6..24def9414e 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -75,8 +75,8 @@ class UsersController extends Controller 'users.autoassign_licenses', 'users.website', - ])->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'); + ])->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', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count'); if ($request->filled('activated')) { @@ -187,6 +187,14 @@ class UsersController extends Controller $users->has('accessories', '=', $request->input('accessories_count')); } + if ($request->filled('manages_users_count')) { + $users->has('manages_users_count', '=', $request->input('manages_users_count')); + } + + if ($request->filled('manages_locations_count')) { + $users->has('manages_locations_count', '=', $request->input('manages_locations_count')); + } + if ($request->filled('autoassign_licenses')) { $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); } @@ -244,6 +252,8 @@ class UsersController extends Controller 'licenses_count', 'consumables_count', 'accessories_count', + 'manages_user_count', + 'manages_locations_count', 'phone', 'address', 'city', @@ -405,11 +415,15 @@ class UsersController extends Controller { $this->authorize('view', User::class); - $user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id); - $user = Company::scopeCompanyables($user)->find($id); - $this->authorize('update', $user); + $user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count'); + + if ($user = Company::scopeCompanyables($user)->find($id)) { + $this->authorize('view', $user); + return (new UsersTransformer)->transformUser($user); + } + + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id')))); - return (new UsersTransformer)->transformUser($user); } @@ -461,7 +475,7 @@ class UsersController extends Controller if ($request->has('permissions')) { $permissions_array = $request->input('permissions'); - // Strip out the superuser permission if the API user isn't a superadmin + // Strip out the individual superuser permission if the API user isn't a superadmin if (! Auth::user()->isSuperUser()) { unset($permissions_array['superuser']); } @@ -470,7 +484,6 @@ class UsersController extends Controller } - // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); @@ -480,38 +493,23 @@ class UsersController extends Controller if ($user->save()) { - // Sync group memberships: - // This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5 - // which changes the behavior of has vs filled. - // The $request->has method will now return true even if the input value is an empty string or null. - // A new $request->filled method has was added that provides the previous behavior of the has method. + // Check if the request has groups passed and has a value, AND that the user us a superuser + if (($request->has('groups')) && (Auth::user()->isSuperUser())) { - // Check if the request has groups passed and has a value - if ($request->filled('groups')) { - - $validator = Validator::make($request->all(), [ + $validator = Validator::make($request->only('groups'), [ 'groups.*' => 'integer|exists:permission_groups,id', ]); - - if ($validator->fails()){ - return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors())); + + if ($validator->fails()) { + return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors())); } - // Only save groups if the user is a superuser - if (Auth::user()->isSuperUser()) { - $user->groups()->sync($request->input('groups')); - } + // Sync the groups since the user is a superuser and the groups pass validation + $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')) { - - // Only save groups if the user is a superuser - if (Auth::user()->isSuperUser()) { - $user->groups()->sync($request->input('groups')); - } } - return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); } diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php index a4472c3504..2a0a23a9f2 100644 --- a/app/Http/Controllers/AssetModelsFilesController.php +++ b/app/Http/Controllers/AssetModelsFilesController.php @@ -70,8 +70,6 @@ class AssetModelsFilesController extends Controller } $file = 'private_uploads/assetmodels/'.$log->filename; - \Log::debug('Checking for '.$file); - if (! Storage::exists($file)) { return response('File '.$file.' not found on server', 404) diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index a096f16678..aa282b443d 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -62,7 +62,7 @@ class AssetCheckoutController extends Controller $this->authorize('checkout', $asset); $admin = Auth::user(); - $target = $this->determineCheckoutTarget($asset); + $target = $this->determineCheckoutTarget(); $asset = $this->updateAssetLocation($asset, $target); diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index 7debfb479c..ca7a72e3ae 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -70,7 +70,6 @@ class AssetFilesController extends Controller } $file = 'private_uploads/assets/'.$log->filename; - \Log::debug('Checking for '.$file); if ($log->action_type == 'audit') { $file = 'private_uploads/audits/'.$log->filename; diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 6054718e6b..cd138f7102 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -6,7 +6,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; use App\Models\Actionlog; -use App\Models\Manufacturer; +use App\Http\Requests\UploadFileRequest; use Illuminate\Support\Facades\Log; use App\Models\Asset; use App\Models\AssetModel; @@ -293,7 +293,7 @@ class AssetsController extends Controller * * @param int $assetId * @return \Illuminate\Http\RedirectResponse|Redirect - *@since [v1.0] + * @since [v1.0] * @author [A. Gianotto] [] */ public function update(ImageUploadRequest $request, $assetId = null) @@ -308,7 +308,8 @@ class AssetsController extends Controller $asset->status_id = $request->input('status_id', null); $asset->warranty_months = $request->input('warranty_months', null); $asset->purchase_cost = $request->input('purchase_cost', null); - $asset->purchase_date = $request->input('purchase_date', null); + $asset->purchase_date = $request->input('purchase_date', null); + $asset->next_audit_date = $request->input('next_audit_date', null); if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) { $asset->purchase_date = $request->input('purchase_date', null); $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); @@ -854,15 +855,15 @@ class AssetsController extends Controller return view('hardware/audit-due'); } - public function overdueForAudit() + public function dueForCheckin() { - $this->authorize('audit', Asset::class); + $this->authorize('checkin', Asset::class); - return view('hardware/audit-overdue'); + return view('hardware/checkin-due'); } - public function auditStore(Request $request, $id) + public function auditStore(UploadFileRequest $request, $id) { $this->authorize('audit', Asset::class); @@ -879,7 +880,21 @@ class AssetsController extends Controller $asset = Asset::findOrFail($id); - // We don't want to log this as a normal update, so let's bypass that + /** + * Even though we do a save() further down, we don't want to log this as a "normal" asset update, + * which would trigger the Asset Observer and would log an asset *update* log entry (because the + * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to + * the audit log entry we're creating through this controller. + * + * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass + * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() + * will bypass normal model-level validation that's usually handled at the observer ) + * + * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, + * which manually invokes Watson Validating to make sure the asset's model is valid. + * + * @see \App\Observers\AssetObserver::updating() + */ $asset->unsetEventDispatcher(); $asset->next_audit_date = $request->input('next_audit_date'); @@ -888,29 +903,27 @@ class AssetsController extends Controller // Check to see if they checked the box to update the physical location, // not just note it in the audit notes if ($request->input('update_location') == '1') { - Log::debug('update location in audit'); $asset->location_id = $request->input('location_id'); } - if ($asset->save()) { - $file_name = ''; - // Upload an image, if attached - if ($request->hasFile('image')) { - $path = 'private_uploads/audits'; - if (! Storage::exists($path)) { - Storage::makeDirectory($path, 775); - } - $upload = $image = $request->file('image'); - $ext = $image->getClientOriginalExtension(); - $file_name = 'audit-'.str_random(18).'.'.$ext; - Storage::putFileAs($path, $upload, $file_name); - } + /** + * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. + * We have to invoke this manually because of the unsetEventDispatcher() above.) + */ + if ($asset->isValid() && $asset->save()) { + $file_name = null; + // Create the image (if one was chosen.) + if ($request->hasFile('image')) { + $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image')); + } $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success')); } + + return redirect()->back()->withInput()->withErrors($asset->getErrors()); } public function getRequestedIndex($user_id = null) diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index 561e13b200..ffbf81944b 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -13,7 +13,9 @@ use App\Models\Setting; use App\View\Label; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; use App\Http\Requests\AssetCheckoutRequest; @@ -189,7 +191,6 @@ class BulkAssetsController extends Controller * Save bulk edits * * @author [A. Gianotto] [] - * @return Redirect * @internal param array $assets * @since [v2.0] */ @@ -214,7 +215,7 @@ class BulkAssetsController extends Controller } - $assets = Asset::whereIn('id', array_keys($request->input('ids')))->get(); + $assets = Asset::whereIn('id', $request->input('ids'))->get(); @@ -379,28 +380,30 @@ class BulkAssetsController extends Controller foreach ($asset->model->fieldset->fields as $field) { if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) { - $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); + if (Gate::allows('admin')) { + $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); - /* - * Check if the decrypted existing value is different from one we just submitted - * and if not, pull it out of the object since it shouldn't really be updating at all. - * If we don't do this, it will try to re-encrypt it, and the same value encrypted two - * different times will have different values, so it will *look* like it was updated - * but it wasn't. - */ - if ($decrypted_old != $this->update_array[$field->db_column]) { - $asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]); - } else { /* - * Remove the encrypted custom field from the update_array, since nothing changed + * Check if the decrypted existing value is different from one we just submitted + * and if not, pull it out of the object since it shouldn't really be updating at all. + * If we don't do this, it will try to re-encrypt it, and the same value encrypted two + * different times will have different values, so it will *look* like it was updated + * but it wasn't. */ - unset($this->update_array[$field->db_column]); - unset($asset->{$field->db_column}); - } + if ($decrypted_old != $this->update_array[$field->db_column]) { + $asset->{$field->db_column} = Crypt::encrypt($this->update_array[$field->db_column]); + } else { + /* + * Remove the encrypted custom field from the update_array, since nothing changed + */ + unset($this->update_array[$field->db_column]); + unset($asset->{$field->db_column}); + } - /* - * These custom fields aren't encrypted, just carry on as usual - */ + /* + * These custom fields aren't encrypted, just carry on as usual + */ + } } else { if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) { diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index c55181c518..01de4b4d46 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -11,6 +11,7 @@ use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * This controller handles all actions related to Licenses for @@ -289,4 +290,106 @@ class LicensesController extends Controller ->with('item', $license) ->with('maintained_list', $maintained_list); } + + /** + * Exports Licenses to CSV + * + * @author [G. Martinez] + * @since [v6.3] + * @return StreamedResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function getExportLicensesCsv() + { + $this->authorize('view', License::class); + \Debugbar::disable(); + + $response = new StreamedResponse(function () { + // Open output stream + $handle = fopen('php://output', 'w'); + $licenses= License::with('company', + 'manufacturer', + 'category', + 'supplier', + 'adminuser', + 'assignedusers') + ->orderBy('created_at', 'DESC'); + Company::scopeCompanyables($licenses) + ->chunk(500, function ($licenses) use ($handle) { + $headers = [ + // strtolower to prevent Excel from trying to open it as a SYLK file + strtolower(trans('general.id')), + trans('general.company'), + trans('general.name'), + trans('general.serial_number'), + trans('general.purchase_date'), + trans('general.purchase_cost'), + trans('general.order_number'), + trans('general.licenses_available'), + trans('admin/licenses/table.seats'), + trans('general.created_by'), + trans('general.depreciation'), + trans('general.updated_at'), + trans('admin/licenses/table.deleted_at'), + trans('general.email'), + trans('admin/hardware/form.fully_depreciated'), + trans('general.supplier'), + trans('admin/licenses/form.expiration'), + trans('admin/licenses/form.purchase_order'), + trans('admin/licenses/form.termination_date'), + trans('admin/licenses/form.maintained'), + trans('general.manufacturer'), + trans('general.category'), + trans('general.min_amt'), + trans('admin/licenses/form.reassignable'), + trans('general.notes'), + trans('general.created_at'), + ]; + + fputcsv($handle, $headers); + + foreach ($licenses as $license) { + // Add a new row with data + $values = [ + $license->id, + $license->company ? $license->company->name: '', + $license->name, + $license->serial, + $license->purchase_date, + $license->purchase_cost, + $license->order_number, + $license->free_seat_count, + $license->seats, + ($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')), + $license->depreciation ? $license->depreciation->name: '', + $license->updated_at, + $license->deleted_at, + $license->email, + ( $license->depreciate == '1') ? trans('general.yes') : trans('general.no'), + ($license->supplier) ? $license->supplier->name: '', + $license->expiration_date, + $license->purchase_order, + $license->termination_date, + ( $license->maintained == '1') ? trans('general.yes') : trans('general.no'), + $license->manufacturer ? $license->manufacturer->name: '', + $license->category ? $license->category->name: '', + $license->min_amt, + ( $license->reassignable == '1') ? trans('general.yes') : trans('general.no'), + $license->notes, + $license->created_at, + ]; + + fputcsv($handle, $values); + } + }); + + // Close the output stream + fclose($handle); + }, 200, [ + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => 'attachment; filename="licenses-'.date('Y-m-d-his').'.csv"', + ]); + + return $response; + } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 2e81facc76..c505018e64 100755 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -72,7 +72,7 @@ class ProfileController extends Controller if ($user->save()) { - return redirect()->route('profile')->with('success', 'Account successfully updated'); + return redirect()->route('profile')->with('success', trans('account.general.profile_updated')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php index 87213f2498..ded44f35f6 100644 --- a/app/Http/Controllers/Users/UserFilesController.php +++ b/app/Http/Controllers/Users/UserFilesController.php @@ -78,24 +78,28 @@ class UserFilesController extends Controller */ public function destroy($userId = null, $fileId = null) { - $user = User::find($userId); - $destinationPath = config('app.private_uploads').'/users'; + if ($user = User::find($userId)) { + + $this->authorize('delete', $user); + $rel_path = 'private_uploads/users'; + + + if ($log = Actionlog::find($fileId)) { + $filename = $log->filename; + $log->delete(); + + if (Storage::exists($rel_path.'/'.$filename)) { + Storage::delete($rel_path.'/'.$filename); + return redirect()->back()->with('success', trans('admin/users/message.deletefile.success')); + } - if (isset($user->id)) { - $this->authorize('update', $user); - $log = Actionlog::find($fileId); - $full_filename = $destinationPath.'/'.$log->filename; - if (file_exists($full_filename)) { - unlink($destinationPath.'/'.$log->filename); } - $log->delete(); + // The log record doesn't exist somehow return redirect()->back()->with('success', trans('admin/users/message.deletefile.success')); } - // Prepare the error message - $error = trans('admin/users/message.user_not_found', ['id' => $userId]); - // Redirect to the licence management page - return redirect()->route('users.index')->with('error', $error); + + return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId])); } diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 9316c2dec4..b0874cb569 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -293,8 +293,15 @@ class UsersController extends Controller $user->password = bcrypt($request->input('password')); } + + // Update the location of any assets checked out to this user + Asset::where('assigned_type', User::class) + ->where('assigned_to', $user->id) + ->update(['location_id' => $user->location_id]); + $permissions_array = $request->input('permission'); + // Strip out the superuser permission if the user isn't a superadmin if (! Auth::user()->isSuperUser()) { unset($permissions_array['superuser']); diff --git a/app/Http/Middleware/AssetCountForSidebar.php b/app/Http/Middleware/AssetCountForSidebar.php index 5d9656f5c1..fa439f7753 100644 --- a/app/Http/Middleware/AssetCountForSidebar.php +++ b/app/Http/Middleware/AssetCountForSidebar.php @@ -5,6 +5,7 @@ namespace App\Http\Middleware; use App\Models\Asset; use Auth; use Closure; +use App\Models\Setting; class AssetCountForSidebar { @@ -17,6 +18,32 @@ class AssetCountForSidebar */ public function handle($request, Closure $next) { + /** + * This needs to be set for the /setup process, since the tables might not exist yet + */ + $total_assets = 0; + $total_due_for_checkin = 0; + $total_overdue_for_checkin = 0; + $total_due_for_audit = 0; + $total_overdue_for_audit = 0; + + try { + $settings = Setting::getSettings(); + view()->share('settings', $settings); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_assets = Asset::count(); + if ($settings->show_archived_in_list != '1') { + $total_assets -= Asset::Archived()->count(); + } + view()->share('total_assets', $total_assets); + } catch (\Exception $e) { + \Log::debug($e); + } + try { $total_rtd_sidebar = Asset::RTD()->count(); view()->share('total_rtd_sidebar', $total_rtd_sidebar); @@ -59,6 +86,37 @@ class AssetCountForSidebar \Log::debug($e); } + try { + $total_due_for_audit = Asset::DueForAudit($settings)->count(); + view()->share('total_due_for_audit', $total_due_for_audit); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_overdue_for_audit = Asset::OverdueForAudit()->count(); + view()->share('total_overdue_for_audit', $total_overdue_for_audit); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_due_for_checkin = Asset::DueForCheckin($settings)->count(); + view()->share('total_due_for_checkin', $total_due_for_checkin); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_overdue_for_checkin = Asset::OverdueForCheckin()->count(); + view()->share('total_overdue_for_checkin', $total_overdue_for_checkin); + } catch (\Exception $e) { + \Log::debug($e); + } + + view()->share('total_due_and_overdue_for_checkin', ($total_due_for_checkin + $total_overdue_for_checkin)); + view()->share('total_due_and_overdue_for_audit', ($total_due_for_audit + $total_overdue_for_audit)); + return $next($request); } } diff --git a/app/Http/Middleware/SecurityHeaders.php b/app/Http/Middleware/SecurityHeaders.php index 25f0461fcf..8e6c17b4e7 100644 --- a/app/Http/Middleware/SecurityHeaders.php +++ b/app/Http/Middleware/SecurityHeaders.php @@ -88,13 +88,13 @@ class SecurityHeaders $csp_policy[] = "connect-src 'self'"; $csp_policy[] = "object-src 'none'"; $csp_policy[] = "font-src 'self' data:"; - $csp_policy[] = "img-src 'self' data: ".config('app.url').' '.env('PUBLIC_AWS_URL').' https://secure.gravatar.com http://gravatar.com maps.google.com maps.gstatic.com *.googleapis.com'; + $csp_policy[] = "img-src 'self' data: ".config('app.url').' '.config('app.additional_csp_urls').' '.env('PUBLIC_AWS_URL').' https://secure.gravatar.com http://gravatar.com maps.google.com maps.gstatic.com *.googleapis.com'; if (config('filesystems.disks.public.driver') == 's3') { $csp_policy[] = "img-src 'self' data: ".config('filesystems.disks.public.url'); } $csp_policy = join(';', $csp_policy); - + $response->headers->set('Content-Security-Policy', $csp_policy); } diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index 9677111059..59af9259f4 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -86,12 +86,8 @@ class ImageUploadRequest extends Request if ($this->offsetGet($form_fieldname) instanceof UploadedFile) { $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 { - \Log::debug('No image found for form fieldname: '.$form_fieldname); } if (isset($image)) { diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 8e7559673e..007dd26843 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests; use App\Models\Asset; use App\Models\Company; +use App\Models\Setting; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; use Illuminate\Support\Facades\Gate; @@ -45,12 +46,21 @@ class StoreAssetRequest extends ImageUploadRequest */ public function rules(): array { - $rules = array_merge( - (new Asset)->getRules(), + $modelRules = (new Asset)->getRules(); + + if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) { + // If purchase_cost was submitted as a string with a comma separator + // then we need to ignore the normal numeric rules. + // Since the original rules still live on the model they will be run + // right before saving (and after purchase_cost has been + // converted to a float via setPurchaseCostAttribute). + $modelRules = $this->removeNumericRulesFromPurchaseCost($modelRules); + } + + return array_merge( + $modelRules, parent::rules(), ); - - return $rules; } private function parseLastAuditDate(): void @@ -69,4 +79,20 @@ class StoreAssetRequest extends ImageUploadRequest } } } + + private function removeNumericRulesFromPurchaseCost(array $rules): array + { + $purchaseCost = $rules['purchase_cost']; + + // If rule is in "|" format then turn it into an array + if (is_string($purchaseCost)) { + $purchaseCost = explode('|', $purchaseCost); + } + + $rules['purchase_cost'] = array_filter($purchaseCost, function ($rule) { + return $rule !== 'numeric' && $rule !== 'gte:0'; + }); + + return $rules; + } } diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index d99e570810..ca0f6296ee 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -85,20 +85,23 @@ class ActionlogsTransformer $enc_old = ''; $enc_new = ''; - try { - $enc_old = \Crypt::decryptString($this->clean_field($fieldata->old)); - } catch (\Exception $e) { - \Log::debug('Could not decrypt field - maybe the key changed?'); + if ($this->clean_field($fieldata->old!='')) { + try { + $enc_old = \Crypt::decryptString($this->clean_field($fieldata->old)); + } catch (\Exception $e) { + \Log::debug('Could not decrypt old field value - maybe the key changed?'); + } } - try { - $enc_new = \Crypt::decryptString($this->clean_field($fieldata->new)); - } catch (\Exception $e) { - \Log::debug('Could not decrypt field - maybe the key changed?'); + if ($this->clean_field($fieldata->new!='')) { + try { + $enc_new = \Crypt::decryptString($this->clean_field($fieldata->new)); + } catch (\Exception $e) { + \Log::debug('Could not decrypt new field value - maybe the key changed?'); + } } if ($enc_old != $enc_new) { - \Log::debug('custom fields do not match'); $clean_meta[$fieldname]['old'] = "************"; $clean_meta[$fieldname]['new'] = "************"; diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 0ebaca2692..64752d0445 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -21,6 +21,7 @@ class UsersTransformer public function transformUser(User $user) { + $array = [ 'id' => (int) $user->id, 'avatar' => e($user->present()->gravatar), @@ -64,6 +65,8 @@ class UsersTransformer 'licenses_count' => (int) $user->licenses_count, 'accessories_count' => (int) $user->accessories_count, 'consumables_count' => (int) $user->consumables_count, + 'manages_users_count' => (int) $user->manages_users_count, + 'manages_locations_count' => (int) $user->manages_locations_count, 'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null, 'created_by' => ($user->createdBy) ? [ 'id' => (int) $user->createdBy->id, diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php index 4b584c668b..0345ac1341 100644 --- a/app/Listeners/LogListener.php +++ b/app/Listeners/LogListener.php @@ -62,6 +62,7 @@ class LogListener $logaction->target()->associate($event->acceptance->assignedTo); $logaction->accept_signature = $event->acceptance->signature_filename; $logaction->filename = $event->acceptance->stored_eula_file; + $logaction->note = $event->acceptance->note; $logaction->action_type = 'accepted'; // TODO: log the actual license seat that was checked out @@ -78,6 +79,7 @@ class LogListener $logaction->item()->associate($event->acceptance->checkoutable); $logaction->target()->associate($event->acceptance->assignedTo); $logaction->accept_signature = $event->acceptance->signature_filename; + $logaction->note = $event->acceptance->note; $logaction->action_type = 'declined'; // TODO: log the actual license seat that was checked out diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 4df74659a2..05b4fe64a2 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -74,9 +74,9 @@ class Asset extends Depreciable 'eol_explicit' => 'boolean', 'last_checkout' => 'datetime', 'last_checkin' => 'datetime', - 'expected_checkin' => 'date', + 'expected_checkin' => 'datetime:m-d-Y', 'last_audit_date' => 'datetime', - 'next_audit_date' => 'date', + 'next_audit_date' => 'datetime:m-d-Y', 'model_id' => 'integer', 'status_id' => 'integer', 'company_id' => 'integer', @@ -99,7 +99,8 @@ class Asset extends Depreciable 'last_checkin' => ['nullable', 'date_format:Y-m-d H:i:s'], 'expected_checkin' => ['nullable', 'date'], 'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'], - 'next_audit_date' => ['nullable', 'date', 'after:last_audit_date'], + 'next_audit_date' => ['nullable', 'date'], + //'after:last_audit_date'], 'location_id' => ['nullable', 'exists:locations,id'], 'rtd_location_id' => ['nullable', 'exists:locations,id'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], @@ -907,6 +908,23 @@ class Asset extends Depreciable } + + /** + * Determine whether this asset's next audit date is before the last audit date + * + * @return bool + * @since [v6.4.1] + * @author [A. Gianotto] [] + * */ + public function checkInvalidNextAuditDate() + { + if (($this->last_audit_date) && ($this->next_audit_date) && ($this->last_audit_date > $this->next_audit_date)) { + return true; + } + return false; + } + + /** * Checks for a category-specific EULA, and if that doesn't exist, * checks for a settings level EULA @@ -944,6 +962,25 @@ class Asset extends Depreciable * ----------------------------------------------- **/ + /** + * Make sure the next_audit_date is formatted as Y-m-d. + * + * This is kind of dumb and confusing, since we already cast it that way AND it's a date field + * in the database, but here we are. + * + * @param $value + * @return void + */ + public function getNextAuditDateAttribute($value) + { + return $this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null; + } + + public function setNextAuditDateAttribute($value) + { + $this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null; + } + /** * This sets the requestable to a boolean 0 or 1. This accounts for forms or API calls that * explicitly pass the requestable field but it has a null or empty value. @@ -1163,10 +1200,11 @@ class Asset extends Depreciable public function scopeDueForAudit($query, $settings) { $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); return $query->whereNotNull('assets.next_audit_date') - ->where('assets.next_audit_date', '>=', Carbon::now()) - ->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'") + ->whereBetween('assets.next_audit_date', [$today->format('Y-m-d'), $interval_date]) ->where('assets.archived', '=', 0) ->NotArchived(); } @@ -1188,7 +1226,7 @@ class Asset extends Depreciable public function scopeOverdueForAudit($query) { return $query->whereNotNull('assets.next_audit_date') - ->where('assets.next_audit_date', '<', Carbon::now()) + ->where('assets.next_audit_date', '<', Carbon::now()->format('Y-m-d')) ->where('assets.archived', '=', 0) ->NotArchived(); } @@ -1209,14 +1247,69 @@ class Asset extends Depreciable public function scopeDueOrOverdueForAudit($query, $settings) { - $interval = $settings->audit_warning_days ?? 0; - return $query->whereNotNull('assets.next_audit_date') - ->whereRaw('DATE_SUB('.DB::getTablePrefix()."assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'") + return $query->where(function ($query) { + $query->OverdueForAudit(); + })->orWhere(function ($query) use ($settings) { + $query->DueForAudit($settings); + }); + } + + + /** + * Query builder scope for Assets that are DUE for checkin, based on the assets.expected_checkin + * and settings.audit_warning_days. It checks to see if assets.expected_checkin is now + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + + public function scopeDueForCheckin($query, $settings) + { + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); + + return $query->whereNotNull('assets.expected_checkin') + ->whereBetween('assets.expected_checkin', [$today->format('Y-m-d'), $interval_date]) ->where('assets.archived', '=', 0) + ->whereNotNull('assets.assigned_to') ->NotArchived(); } + /** + * Query builder scope for Assets that are overdue for checkin OR overdue + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOverdueForCheckin($query) + { + return $query->whereNotNull('assets.expected_checkin') + ->where('assets.expected_checkin', '<', Carbon::now()->format('Y-m-d')) + ->where('assets.archived', '=', 0) + ->whereNotNull('assets.assigned_to') + ->NotArchived(); + } + + /** + * Query builder scope for Assets that are due for checkin OR overdue + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeDueOrOverdueForCheckin($query, $settings) + { + return $query->where(function ($query) { + $query->OverdueForCheckin(); + })->orWhere(function ($query) use ($settings) { + $query->DueForCheckin($settings); + }); + } + /** * Query builder scope for Archived assets counting diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php index 4a4360c40a..9c501aaf63 100644 --- a/app/Models/CheckoutAcceptance.php +++ b/app/Models/CheckoutAcceptance.php @@ -80,12 +80,13 @@ class CheckoutAcceptance extends Model * * @param string $signature_filename */ - public function accept($signature_filename, $eula = null, $filename = null) + public function accept($signature_filename, $eula = null, $filename = null, $note = null) { $this->accepted_at = now(); $this->signature_filename = $signature_filename; $this->stored_eula = $eula; $this->stored_eula_file = $filename; + $this->note = $note; $this->save(); /** @@ -99,9 +100,10 @@ class CheckoutAcceptance extends Model * * @param string $signature_filename */ - public function decline($signature_filename) + public function decline($signature_filename, $note = null) { $this->declined_at = now(); + $this->note = $note; $this->signature_filename = $signature_filename; $this->save(); diff --git a/app/Models/Company.php b/app/Models/Company.php index 4b718b4200..aac002bdcd 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -262,7 +262,6 @@ final class Company extends SnipeModel if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) { return $query; } else { - \Log::debug('Fire scopeCompanyablesDirectly.'); return static::scopeCompanyablesDirectly($query, $column, $table_name); } } @@ -275,7 +274,6 @@ final class Company extends SnipeModel { // Get the company ID of the logged in user, or set it to null if there is no company assicoated with the user if (Auth::user()) { - \Log::debug('Admin company is: '.Auth::user()->company_id); $company_id = Auth::user()->company_id; } else { $company_id = null; @@ -283,9 +281,6 @@ final class Company extends SnipeModel // Dynamically get the table name if it's not passed in, based on the model we're querying against $table = ($table_name) ? $table_name."." : $query->getModel()->getTable()."."; - \Log::debug('Model is: '.$query->getModel()); - - \Log::debug('Table is: '.$table); // If the column exists in the table, use it to scope the query if (\Schema::hasColumn($query->getModel()->getTable(), $column)) { @@ -307,7 +302,6 @@ final class Company extends SnipeModel */ public static function scopeCompanyableChildren(array $companyable_names, $query) { - \Log::debug('Company Names in scopeCompanyableChildren: '.print_r($companyable_names, true)); if (count($companyable_names) == 0) { throw new Exception('No Companyable Children to scope'); diff --git a/app/Models/Labels/FieldOption.php b/app/Models/Labels/FieldOption.php index 38a90e31dc..94394eda23 100644 --- a/app/Models/Labels/FieldOption.php +++ b/app/Models/Labels/FieldOption.php @@ -27,6 +27,11 @@ class FieldOption { return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null; } + + // Handle Laravel's stupid Carbon datetime casting + if ($dataPath[0] === 'purchase_date') { + return $asset->purchase_date ? $asset->purchase_date->format('Y-m-d') : null; + } return $dataPath->reduce(function ($myValue, $path) { try { return $myValue ? $myValue->{$path} : ${$myValue}; } diff --git a/app/Models/Labels/Tapes/Brother/TZe_18mm.php b/app/Models/Labels/Tapes/Brother/TZe_18mm.php new file mode 100644 index 0000000000..38c14c7aa4 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_18mm.php @@ -0,0 +1,19 @@ +getUnit()); } + public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } + public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} + public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php new file mode 100644 index 0000000000..32156f5ee6 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php @@ -0,0 +1,56 @@ +getPrintableArea(); + + if ($record->has('barcode1d')) { + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $pa->x1, $pa->y1, $pa->w, self::BARCODE_SIZE + ); + } + + $currentY = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN; + $usableHeight = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN; + $fontSize = $usableHeight + self::TEXT_SIZE_MOD; + + $tagWidth = $pa->w / 3; + $fieldWidth = $pa->w / 3 * 2; + + static::writeText( + $pdf, $record->get('tag'), + $pa->x1, $currentY, + 'freemono', 'b', $fontSize, 'L', + $tagWidth, $usableHeight, true, 0, 0 + ); + + if ($record->get('fields')->count() >= 1) { + static::writeText( + $pdf, $record->get('fields')->values()->get(0)['value'], + $pa->x1 + ($tagWidth), $currentY, + 'freemono', 'b', $fontSize, 'R', + $fieldWidth, $usableHeight, true, 0, 0 + ); + } + + } +} \ No newline at end of file diff --git a/app/Models/License.php b/app/Models/License.php index 7fb4f9e4cb..deb1221e73 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -81,6 +81,7 @@ class License extends Depreciable 'serial', 'supplier_id', 'termination_date', + 'free_seat_count', 'user_id', 'min_amt', ]; @@ -114,6 +115,7 @@ class License extends Depreciable 'category' => ['name'], 'depreciation' => ['name'], ]; + protected $appends = ['free_seat_count']; /** * Update seat counts when the license is updated @@ -280,6 +282,16 @@ class License extends Depreciable } $this->attributes['termination_date'] = $value; } + /** + * Sets free_seat_count attribute + * + * @author G. Martinez + * @since [v6.3] + * @return mixed + */ + public function getFreeSeatCountAttribute(){ + return $this->attributes['free_seat_count'] = $this->remaincount(); + } /** * Establishes the license -> company relationship @@ -502,7 +514,13 @@ class License extends Depreciable ->whereNull('deleted_at') ->count(); } - + /** + * Returns the available seats remaining + * + * @author A. Gianotto + * @since [v2.0] + * @return int + */ /** * Returns the number of total available seats for this license @@ -579,7 +597,7 @@ class License extends Depreciable $taken = $this->assigned_seats_count; $diff = ($total - $taken); - return $diff; + return (int) $diff; } /** diff --git a/app/Models/User.php b/app/Models/User.php index e535fa0fde..e30136703e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -214,10 +214,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo public function isDeletable() { return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->licenses()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->accessories()->count() === 0) + && ($this->assets->count() === 0) + && ($this->licenses->count() === 0) + && ($this->consumables->count() === 0) + && ($this->accessories->count() === 0) + && ($this->managedLocations->count() === 0) + && ($this->managesUsers->count() === 0) && ($this->deleted_at == ''); } @@ -410,6 +412,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->belongsTo(self::class, 'manager_id')->withTrashed(); } + /** + * Establishes the user -> managed users relationship + * + * @author A. Gianotto + * @since [v6.4.1] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function managesUsers() + { + return $this->hasMany(\App\Models\User::class, 'manager_id'); + } + + /** * Establishes the user -> managed locations relationship * diff --git a/app/Notifications/AcceptanceAssetAcceptedNotification.php b/app/Notifications/AcceptanceAssetAcceptedNotification.php index ca016acd34..db1555b574 100644 --- a/app/Notifications/AcceptanceAssetAcceptedNotification.php +++ b/app/Notifications/AcceptanceAssetAcceptedNotification.php @@ -26,6 +26,7 @@ class AcceptanceAssetAcceptedNotification extends Notification $this->item_serial = $params['item_serial']; $this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false); $this->assigned_to = $params['assigned_to']; + $this->note = $params['note']; $this->company_name = $params['company_name']; $this->settings = Setting::getSettings(); @@ -64,6 +65,7 @@ class AcceptanceAssetAcceptedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'note' => $this->note, 'accepted_date' => $this->accepted_date, 'assigned_to' => $this->assigned_to, 'company_name' => $this->company_name, diff --git a/app/Notifications/AcceptanceAssetDeclinedNotification.php b/app/Notifications/AcceptanceAssetDeclinedNotification.php index 11b022e095..abdfbbf0c0 100644 --- a/app/Notifications/AcceptanceAssetDeclinedNotification.php +++ b/app/Notifications/AcceptanceAssetDeclinedNotification.php @@ -25,6 +25,7 @@ class AcceptanceAssetDeclinedNotification extends Notification $this->item_model = $params['item_model']; $this->item_serial = $params['item_serial']; $this->declined_date = Helper::getFormattedDateObject($params['declined_date'], 'date', false); + $this->note = $params['note']; $this->assigned_to = $params['assigned_to']; $this->company_name = $params['company_name']; $this->settings = Setting::getSettings(); @@ -62,6 +63,7 @@ class AcceptanceAssetDeclinedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'note' => $this->note, 'declined_date' => $this->declined_date, 'assigned_to' => $this->assigned_to, 'company_name' => $this->company_name, diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index 4726205c72..a5b99adb14 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -221,7 +221,7 @@ class UserPresenter extends Presenter 'switchable' => true, 'escape' => true, 'class' => 'css-barcode', - 'title' => 'Assets', + 'title' => trans('general.assets'), 'visible' => true, ], [ @@ -230,7 +230,7 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-license', - 'title' => 'License', + 'title' => trans('general.licenses'), 'visible' => true, ], [ @@ -239,7 +239,7 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-consumable', - 'title' => 'Consumables', + 'title' => trans('general.consumables'), 'visible' => true, ], [ @@ -248,7 +248,25 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-accessory', - 'title' => 'Accessories', + 'title' => trans('general.accessories'), + 'visible' => true, + ], + [ + 'field' => 'manages_users_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'class' => 'css-users', + 'title' => trans('admin/users/table.managed_users'), + 'visible' => true, + ], + [ + 'field' => 'manages_locations_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'class' => 'css-location', + 'title' => trans('admin/users/table.managed_locations'), 'visible' => true, ], [ diff --git a/composer.lock b/composer.lock index 127254236b..4f5a0df23b 100644 --- a/composer.lock +++ b/composer.lock @@ -10983,20 +10983,20 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.6.2", + "version": "6.7.5", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "e3cffc9bcbc76e89e167e9eb0bbda0cab7518459" + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/e3cffc9bcbc76e89e167e9eb0bbda0cab7518459", - "reference": "e3cffc9bcbc76e89e167e9eb0bbda0cab7518459", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.5.0" }, "type": "library", "autoload": { @@ -11021,7 +11021,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-only" + "LGPL-3.0-or-later" ], "authors": [ { @@ -11043,7 +11043,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.6.2" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5" }, "funding": [ { @@ -11051,7 +11051,7 @@ "type": "custom" } ], - "time": "2022-12-17T10:28:59+00:00" + "time": "2024-04-20T17:25:10+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", diff --git a/config/app.php b/config/app.php index eb288f5feb..2c25cd645b 100755 --- a/config/app.php +++ b/config/app.php @@ -201,6 +201,9 @@ return [ 'enable_csp' => env('ENABLE_CSP', true), + 'additional_csp_urls' => env('ADDITIONAL_CSP_URLS', ''), + + /* |-------------------------------------------------------------------------- diff --git a/config/version.php b/config/version.php index bb750c1364..e35bee694d 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v6.3.4', - 'full_app_version' => 'v6.3.4 - build 13226-g5229dd65c', - 'build_version' => '13226', + 'app_version' => 'v6.4.2', + 'full_app_version' => 'v6.4.2 - build 13487-gb489c71fa2', + 'build_version' => '13487', 'prerelease_version' => '', - 'hash_version' => 'g5229dd65c', - 'full_hash' => 'v6.3.4-85-g5229dd65c', + 'hash_version' => 'gb489c71fa2', + 'full_hash' => 'v6.4.2-97-gb489c71fa2', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 970c196c95..a787ffb417 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -375,6 +375,15 @@ class AssetFactory extends Factory }); } + public function hasMultipleCustomFields(array $fields = null): self + { + return $this->state(function () use ($fields) { + return [ + 'model_id' => AssetModel::factory()->hasMultipleCustomFields($fields), + ]; + }); + } + /** * This allows bypassing model level validation if you want to purposefully * create an asset in an invalid state. Validation is turned back on diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index ed3d478261..6790897567 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -439,4 +439,13 @@ class AssetModelFactory extends Factory ]; }); } + + public function hasMultipleCustomFields(array $fields = null) + { + return $this->state(function () use ($fields) { + return [ + 'fieldset_id' => CustomFieldset::factory()->hasMultipleCustomFields($fields), + ]; + }); + } } diff --git a/database/factories/CustomFieldsetFactory.php b/database/factories/CustomFieldsetFactory.php index 9a410ba25f..a9e8b9ae12 100644 --- a/database/factories/CustomFieldsetFactory.php +++ b/database/factories/CustomFieldsetFactory.php @@ -53,4 +53,23 @@ class CustomFieldsetFactory extends Factory $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); }); } + + public function hasMultipleCustomFields(array $fields = null): self + { + return $this->afterCreating(function (CustomFieldset $fieldset) use ($fields) { + if (empty($fields)) { + $mac_address = CustomField::factory()->macAddress()->create(); + $ram = CustomField::factory()->ram()->create(); + $cpu = CustomField::factory()->cpu()->create(); + + $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); + $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); + $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); + } else { + foreach ($fields as $field) { + $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); + } + } + }); + } } diff --git a/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php b/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php new file mode 100644 index 0000000000..75d251b60d --- /dev/null +++ b/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php @@ -0,0 +1,32 @@ +text('note')->after('signature_filename')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('checkout_acceptances', function (Blueprint $table) { + $table->dropColumn('note'); + }); + } +} diff --git a/package-lock.json b/package-lock.json index 4dfe83582d..e444bbb02f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1329,9 +1329,9 @@ "dev": true }, "@fortawesome/fontawesome-free": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", - "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", + "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==" }, "@jridgewell/gen-mapping": { "version": "0.1.1", @@ -1460,9 +1460,9 @@ }, "dependencies": { "tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, @@ -1752,9 +1752,9 @@ "dev": true }, "@types/raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", "optional": true }, "@types/range-parser": { @@ -2379,9 +2379,9 @@ } }, "alpinejs": { - "version": "3.13.5", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz", - "integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==", + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.10.tgz", + "integrity": "sha512-86RB307VWICex0vG15Eq0x058cNNsvS57ohrjN6n/TJAVSFV+zXOK/E34nNHDHc6Poq+yTNCLqEzPqEkRBTMRQ==", "requires": { "@vue/reactivity": "~3.1.1" } @@ -2424,12 +2424,48 @@ } }, "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } } }, "array-filter": { @@ -3781,9 +3817,9 @@ }, "dependencies": { "core-js": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.0.tgz", - "integrity": "sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz", + "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==", "optional": true }, "regenerator-runtime": { @@ -4373,9 +4409,9 @@ } }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css-declaration-sorter": { "version": "6.3.0", @@ -4607,14 +4643,14 @@ } }, "deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "requires": { "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", @@ -4624,36 +4660,58 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" }, "dependencies": { "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } }, "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "has-symbols": { @@ -4693,26 +4751,26 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, "which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" } } } @@ -4731,6 +4789,16 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -4902,9 +4970,9 @@ } }, "dompurify": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz", - "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.2.tgz", + "integrity": "sha512-5vSyvxRAb45EoWwAktUT3AYqAwXK4FL7si22Cgj46U6ICsj/YJczCN+Bk7WNABIQmpWRymGfslMhrRUZkQNnqA==", "optional": true }, "domutils": { @@ -5071,6 +5139,43 @@ "string.prototype.trimstart": "^1.0.3" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -5087,15 +5192,21 @@ "stop-iteration-iterator": "^1.0.0" }, "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "has-symbols": { @@ -15154,15 +15265,21 @@ "get-intrinsic": "^1.1.3" }, "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "has-symbols": { @@ -15236,17 +15353,17 @@ "dev": true }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" } }, "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" }, "has-symbols": { "version": "1.0.1", @@ -15254,11 +15371,11 @@ "integrity": "sha1-n1IUdYpEGWxAbZvXbOv4HsLdMeg=" }, "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "dependencies": { "has-symbols": { @@ -15318,6 +15435,21 @@ "minimalistic-assert": "^1.0.1" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -15813,31 +15945,13 @@ } }, "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" - }, - "dependencies": { - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - } } }, "interpret": { @@ -15880,55 +15994,35 @@ } }, "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "requires": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" }, "dependencies": { - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "requires": { - "which-typed-array": "^1.1.11" - } - }, - "which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } } } }, @@ -16018,9 +16112,9 @@ } }, "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==" }, "is-negative-zero": { "version": "2.0.1", @@ -16065,16 +16159,52 @@ } }, "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==" }, "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } } }, "is-stream": { @@ -16112,17 +16242,53 @@ } }, "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==" }, "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } } }, "is-what": { @@ -16366,9 +16532,9 @@ }, "dependencies": { "core-js": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.0.tgz", - "integrity": "sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz", + "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==", "optional": true } } @@ -17500,12 +17666,58 @@ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" }, "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } } }, "object-keys": { @@ -17853,6 +18065,11 @@ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" + }, "postcss": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", @@ -18695,23 +18912,59 @@ } }, "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "dependencies": { - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" } } }, @@ -19077,6 +19330,54 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + } + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -19333,9 +19634,9 @@ "dev": true }, "stackblur-canvas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", - "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", "optional": true }, "statuses": { @@ -19628,9 +19929,9 @@ } }, "tableexport.jquery.plugin": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/tableexport.jquery.plugin/-/tableexport.jquery.plugin-1.28.0.tgz", - "integrity": "sha512-ydDjOhw8A+LOu+801zPXDeMF8MoU1q2HtS2msphCuny0tdXgbXG9GJfA4ll1hBs0ABiAnOaVVZaRuxBmW/qHtw==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/tableexport.jquery.plugin/-/tableexport.jquery.plugin-1.30.0.tgz", + "integrity": "sha512-kMztiFUsGbxsknFVCDph+5j4e9xmqBBV6Na7T9vRYCUGDxtlndrdxMs9qLJDSOXjlgkIqCUv+S/e5iTUNAFF/g==", "requires": { "file-saver": ">=2.0.4", "html2canvas": ">=1.0.0", @@ -20607,14 +20908,14 @@ } }, "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" } }, "which-module": { diff --git a/package.json b/package.json index ebadfb06be..13bfc8a25f 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ "vue-template-compiler": "2.4.4" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.5.0", + "@fortawesome/fontawesome-free": "^6.5.2", "acorn": "^8.11.2", "acorn-import-assertions": "^1.9.0", "admin-lte": "^2.4.18", "ajv": "^6.12.6", - "alpinejs": "3.13.5", + "alpinejs": "^3.13.10", "blueimp-file-upload": "^9.34.0", "bootstrap": "^3.4.1", "bootstrap-colorpicker": "^2.5.3", @@ -57,7 +57,7 @@ "select2": "4.0.13", "sheetjs": "^2.0.0", "signature_pad": "^4.2.0", - "tableexport.jquery.plugin": "1.28.0", + "tableexport.jquery.plugin": "1.30.0", "tether": "^1.4.0", "vue-resource": "^1.5.2", "webpack": "^5.90.2" diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt new file mode 100644 index 0000000000..5b8d0c144a --- /dev/null +++ b/public/.well-known/security.txt @@ -0,0 +1,7 @@ +Contact: mailto:security@snipeitapp.com +Expires: 2025-05-16T11:30:00.000Z +Acknowledgments: https://snipeitapp.com/thanks +Preferred-Languages: en-US, pt-PT, de-DE +Canonical: https://github.com/snipe/snipe-it/blob/master/public/.well-known/security.txt +Policy: https://snipeitapp.com/security +Hiring: https://snipeitapp.com/company/careers \ No newline at end of file diff --git a/public/css/build/app.css b/public/css/build/app.css index 6b9729d7e2..e48198e10f 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -881,6 +881,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -894,6 +896,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -908,6 +912,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -935,6 +940,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -1154,4 +1170,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index e879341cf1..402329d6f1 100644 --- a/public/css/build/overrides.css +++ b/public/css/build/overrides.css @@ -514,6 +514,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -527,6 +529,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -541,6 +545,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -568,6 +573,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -787,4 +803,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 834ade82e4..a8bd7025d6 100644 --- a/public/css/dist/all.css +++ b/public/css/dist/all.css @@ -6833,9 +6833,9 @@ button.close { } /*# sourceMappingURL=bootstrap.css.map */ /*! - * Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com + * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2023 Fonticons, Inc. + * Copyright 2024 Fonticons, Inc. */ .fa { font-family: var(--fa-style-family, "Font Awesome 6 Free"); @@ -7297,8 +7297,8 @@ button.close { transform: scale(-1, -1); } .fa-rotate-by { - -webkit-transform: rotate(var(--fa-rotate-angle, none)); - transform: rotate(var(--fa-rotate-angle, none)); } + -webkit-transform: rotate(var(--fa-rotate-angle, 0)); + transform: rotate(var(--fa-rotate-angle, 0)); } .fa-stack { display: inline-block; @@ -9602,6 +9602,9 @@ readers do not read off random characters that represent icons */ .fa-italic::before { content: "\f033"; } +.fa-table-cells-column-lock::before { + content: "\e678"; } + .fa-church::before { content: "\f51d"; } @@ -11774,6 +11777,9 @@ readers do not read off random characters that represent icons */ .fa-font::before { content: "\f031"; } +.fa-table-cells-row-lock::before { + content: "\e67a"; } + .fa-rupiah-sign::before { content: "\e23d"; } @@ -13315,6 +13321,9 @@ readers do not read off random characters that represent icons */ .fa-drupal:before { content: "\f1a9"; } +.fa-jxl:before { + content: "\e67b"; } + .fa-hire-a-helper:before { content: "\f3b0"; } @@ -13618,6 +13627,9 @@ readers do not read off random characters that represent icons */ .fa-kickstarter:before { content: "\f3bb"; } +.fa-square-kickstarter:before { + content: "\f3bb"; } + .fa-grav:before { content: "\f2d6"; } @@ -14050,6 +14062,9 @@ readers do not read off random characters that represent icons */ .fa-google-plus-square:before { content: "\f0d4"; } +.fa-web-awesome:before { + content: "\e682"; } + .fa-mandalorian:before { content: "\f50f"; } @@ -14212,6 +14227,9 @@ readers do not read off random characters that represent icons */ .fa-xbox:before { content: "\f412"; } +.fa-square-web-awesome-stroke:before { + content: "\e684"; } + .fa-searchengin:before { content: "\f3eb"; } @@ -14320,6 +14338,9 @@ readers do not read off random characters that represent icons */ .fa-whatsapp:before { content: "\f232"; } +.fa-square-upwork:before { + content: "\e67c"; } + .fa-slideshare:before { content: "\f1e7"; } @@ -14398,6 +14419,9 @@ readers do not read off random characters that represent icons */ .fa-sellsy:before { content: "\f213"; } +.fa-square-web-awesome:before { + content: "\e683"; } + .fa-sass:before { content: "\f41e"; } @@ -14434,6 +14458,9 @@ readers do not read off random characters that represent icons */ .fa-waze:before { content: "\f83f"; } +.fa-bluesky:before { + content: "\e671"; } + .fa-cc-jcb:before { content: "\f24b"; } @@ -22381,6 +22408,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -22394,6 +22423,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -22408,6 +22439,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -22435,6 +22467,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -22654,6 +22697,11 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} .select2-container { @@ -23654,6 +23702,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -23667,6 +23717,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -23681,6 +23733,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -23708,6 +23761,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -23927,4 +23991,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/css/webfonts/fa-brands-400.ttf b/public/css/webfonts/fa-brands-400.ttf index 5efb1d4f96..1fbb1f7c32 100644 Binary files a/public/css/webfonts/fa-brands-400.ttf and b/public/css/webfonts/fa-brands-400.ttf differ diff --git a/public/css/webfonts/fa-brands-400.woff2 b/public/css/webfonts/fa-brands-400.woff2 index 36fbda7d33..5d28021697 100644 Binary files a/public/css/webfonts/fa-brands-400.woff2 and b/public/css/webfonts/fa-brands-400.woff2 differ diff --git a/public/css/webfonts/fa-regular-400.ttf b/public/css/webfonts/fa-regular-400.ttf index 838b4e2cfe..549d68dc02 100644 Binary files a/public/css/webfonts/fa-regular-400.ttf and b/public/css/webfonts/fa-regular-400.ttf differ diff --git a/public/css/webfonts/fa-regular-400.woff2 b/public/css/webfonts/fa-regular-400.woff2 index b6cabbacb6..18400d7fad 100644 Binary files a/public/css/webfonts/fa-regular-400.woff2 and b/public/css/webfonts/fa-regular-400.woff2 differ diff --git a/public/css/webfonts/fa-solid-900.ttf b/public/css/webfonts/fa-solid-900.ttf index ec24749db9..bb2a869565 100644 Binary files a/public/css/webfonts/fa-solid-900.ttf and b/public/css/webfonts/fa-solid-900.ttf differ diff --git a/public/css/webfonts/fa-solid-900.woff2 b/public/css/webfonts/fa-solid-900.woff2 index 824d518eb4..758dd4f607 100644 Binary files a/public/css/webfonts/fa-solid-900.woff2 and b/public/css/webfonts/fa-solid-900.woff2 differ diff --git a/public/css/webfonts/fa-v4compatibility.ttf b/public/css/webfonts/fa-v4compatibility.ttf index b175aa8ece..8c5864c42f 100644 Binary files a/public/css/webfonts/fa-v4compatibility.ttf and b/public/css/webfonts/fa-v4compatibility.ttf differ diff --git a/public/css/webfonts/fa-v4compatibility.woff2 b/public/css/webfonts/fa-v4compatibility.woff2 index e09b5a5500..f94bec2275 100644 Binary files a/public/css/webfonts/fa-v4compatibility.woff2 and b/public/css/webfonts/fa-v4compatibility.woff2 differ diff --git a/public/js/dist/all-defer.js b/public/js/dist/all-defer.js index 50470c93f9..7480ea7c6d 100644 --- a/public/js/dist/all-defer.js +++ b/public/js/dist/all-defer.js @@ -104,120 +104,6 @@ return () => release(effectReference); } - // packages/alpinejs/src/utils/dispatch.js - function dispatch(el, name, detail = {}) { - el.dispatchEvent( - new CustomEvent(name, { - detail, - bubbles: true, - // Allows events to pass the shadow DOM barrier. - composed: true, - cancelable: true - }) - ); - } - - // packages/alpinejs/src/utils/walk.js - function walk(el, callback) { - if (typeof ShadowRoot === "function" && el instanceof ShadowRoot) { - Array.from(el.children).forEach((el2) => walk(el2, callback)); - return; - } - let skip = false; - callback(el, () => skip = true); - if (skip) - return; - let node = el.firstElementChild; - while (node) { - walk(node, callback, false); - node = node.nextElementSibling; - } - } - - // packages/alpinejs/src/utils/warn.js - function warn(message, ...args) { - console.warn(`Alpine Warning: ${message}`, ...args); - } - - // packages/alpinejs/src/lifecycle.js - var started = false; - function start() { - if (started) - warn("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."); - started = true; - if (!document.body) - warn("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` @stop \ No newline at end of file diff --git a/resources/views/groups/view.blade.php b/resources/views/groups/view.blade.php index 9b6c098638..a408266783 100644 --- a/resources/views/groups/view.blade.php +++ b/resources/views/groups/view.blade.php @@ -52,7 +52,7 @@ @if (is_array($group->decodePermissions()))
    @foreach ($group->decodePermissions() as $permission_name => $permission) -
  • {!! ($permission == '1') ? 'GRANTED: ' : 'DENIED: ' !!} {{ e(str_replace('.', ': ', ucwords($permission_name))) }}
  • +
  • {!! ($permission == '1') ? ''.trans('general.yes').': ' : ''.trans('general.no').': ' !!} {{ e(str_replace('.', ': ', ucwords($permission_name))) }}
  • @endforeach
diff --git a/resources/views/hardware/audit-due.blade.php b/resources/views/hardware/audit-due.blade.php index 0232facd77..0ee17ec23d 100644 --- a/resources/views/hardware/audit-due.blade.php +++ b/resources/views/hardware/audit-due.blade.php @@ -1,40 +1,59 @@ @extends('layouts/default') -@section('title0') - - @if ((Request::get('company_id')) && ($company)) - {{ $company->name }} - @endif - - {{ trans('general.audit_due') }} - -@stop - {{-- Page title --}} @section('title') - @yield('title0') @parent + {{ trans_choice('general.audit_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }} @stop + {{-- Page content --}} @section('content') - + {{-- Page content --}}
-
-
- @include('partials.asset-bulk-actions') + +
- {{ Form::close() }} -
-
- - + @stop @section('moar_scripts') @include('partials.bootstrap-table') - @stop diff --git a/resources/views/hardware/audit-overdue.blade.php b/resources/views/hardware/audit-overdue.blade.php deleted file mode 100644 index ddd477fb23..0000000000 --- a/resources/views/hardware/audit-overdue.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -@extends('layouts/default') - -@section('title0') - - @if ((Request::get('company_id')) && ($company)) - {{ $company->name }} - @endif - - {{ trans('general.audit_overdue') }} - -@stop - -{{-- Page title --}} -@section('title') - @yield('title0') @parent -@stop - - -{{-- Page content --}} -@section('content') - -
-
-
-
- @include('partials.asset-bulk-actions') -
-
- - -
- -
-
- {{ Form::close() }} -
-
-
-
-@stop - -@section('moar_scripts') - @include('partials.bootstrap-table') - -@stop diff --git a/resources/views/hardware/audit.blade.php b/resources/views/hardware/audit.blade.php index 8b859a40a3..9713b6fc1e 100644 --- a/resources/views/hardware/audit.blade.php +++ b/resources/views/hardware/audit.blade.php @@ -34,7 +34,7 @@ {{csrf_field()}} @if ($asset->model->name) -
+
{{ Form::label('name', trans('admin/hardware/form.model'), array('class' => 'col-md-3 control-label')) }}

{{ $asset->model->name }}

@@ -43,7 +43,7 @@ @endif -
+
{{ Form::label('name', trans('admin/hardware/form.name'), array('class' => 'col-md-3 control-label')) }}

{{ $asset->name }}

@@ -66,21 +66,40 @@
+ +
+ +
+ +

+ @if ($asset->last_audit_date) + {{ Helper::getFormattedDateObject($asset->last_audit_date, 'datetime', false) }} + @else + {{ trans('admin/settings/general.none') }} + @endif +

+
+
+ + -
+
{{ Form::label('name', trans('general.next_audit_date'), array('class' => 'col-md-3 control-label')) }} -
+
{!! $errors->first('next_audit_date', '') !!} +

{!! trans('general.next_audit_date_help') !!}

-
+
{{ Form::label('note', trans('admin/hardware/form.notes'), array('class' => 'col-md-3 control-label')) }}
@@ -88,13 +107,8 @@
- - - @include ('partials.forms.edit.image-upload') - - - - + + @include ('partials.forms.edit.image-upload', ['help_text' => trans('general.audit_images_help')])
diff --git a/resources/views/hardware/bulk.blade.php b/resources/views/hardware/bulk.blade.php index fa4680e2e8..371f0c2549 100755 --- a/resources/views/hardware/bulk.blade.php +++ b/resources/views/hardware/bulk.blade.php @@ -164,6 +164,8 @@ {!! $errors->first('next_audit_date', '') !!} + +
+
+

{!! trans('general.next_audit_date_help') !!}

+
@@ -196,8 +201,8 @@ @include("models/custom_fields_form_bulk_edit",["models" => $models]) - @foreach ($assets as $key => $value) - + @foreach($assets as $asset) + @endforeach
diff --git a/resources/views/hardware/checkin-due.blade.php b/resources/views/hardware/checkin-due.blade.php new file mode 100644 index 0000000000..6ed7cf42c5 --- /dev/null +++ b/resources/views/hardware/checkin-due.blade.php @@ -0,0 +1,131 @@ +@extends('layouts/default') + +{{-- Page title --}} +@section('title') + {{ trans_choice('general.checkin_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }} +@stop + +{{-- Page content --}} +@section('content') + {{-- Page content --}} +
+
+ + + + +
+
+ +@stop + +@section('moar_scripts') + @include('partials.bootstrap-table') +@stop diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index 4c1c1457b6..b585db2135 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -109,6 +109,29 @@ @include ('partials.forms.edit.name', ['translated_name' => trans('admin/hardware/form.name')]) @include ('partials.forms.edit.warranty') + +
+ + + +
+
+ + +
+
+
+ {!! $errors->first('next_audit_date', '') !!} +

{!! trans('general.next_audit_date_help') !!}

+
+ +
+ + + +
diff --git a/resources/views/hardware/quickscan.blade.php b/resources/views/hardware/quickscan.blade.php index 1745d74f38..4ba4321c63 100644 --- a/resources/views/hardware/quickscan.blade.php +++ b/resources/views/hardware/quickscan.blade.php @@ -23,9 +23,7 @@
-
-

{{ trans('general.bulkaudit') }}

-
+
{{csrf_field()}} @@ -185,7 +183,7 @@ } else { var messages = ''; } - $('#audited tbody').prepend("" + asset_tag + "" + messages + ""); + $('#audited tbody').prepend("" + data.payload.asset_tag + "" + messages + ""); } function incrementOnSuccess() { diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index a83b006c56..589803d96c 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -19,6 +19,23 @@
@endif + @if ($asset->checkInvalidNextAuditDate()) +
+
+

{{ trans('general.warning', + [ + 'warning' => trans('admin/hardware/message.warning_audit_date_mismatch', + [ + 'last_audit_date' => Helper::getFormattedDateObject($asset->last_audit_date, 'date', false), + 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date', false) + ] + ) + ] + ) }}

+
+
+ @endif + @if ($asset->deleted_at!='')
@@ -51,7 +68,7 @@ @@ -62,7 +79,7 @@ @@ -73,7 +90,7 @@ @@ -96,7 +113,7 @@ @@ -107,7 +124,7 @@ @@ -119,7 +136,7 @@ @@ -237,7 +254,8 @@
- {{ \App\Helpers\Helper::getFormattedDateObject($audit_log->created_at, 'date', false) }} + {!! $asset->checkInvalidNextAuditDate() ? '' : '' !!} + {{ Helper::getFormattedDateObject($audit_log->created_at, 'date', false) }} @if ($audit_log->user) (by {{ link_to_route('users.show', $audit_log->user->present()->fullname(), [$audit_log->user->id]) }}) @endif @@ -254,6 +272,7 @@
+ {!! $asset->checkInvalidNextAuditDate() ? '' : '' !!} {{ Helper::getFormattedDateObject($asset->next_audit_date, 'date', false) }}
diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index a1a4bc30cc..baf9228314 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -1,5 +1,6 @@ - + @@ -437,6 +438,9 @@ {{ trans('general.list_all') }} + + {{ (isset($total_assets)) ? $total_assets : '' }} + @@ -447,7 +451,8 @@ - {{ $status_nav->name }} ({{ $status_nav->asset_count }}) + {{ $status_nav->name }} + {{ $status_nav->asset_count }} @endforeach @endif @@ -455,49 +460,43 @@ - {{ trans('general.all') }} {{ trans('general.deployed') }} - ({{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }}) + {{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.ready_to_deploy') }} - ({{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }}) + {{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.pending') }} - ({{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }}) + {{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.undeployable') }} - ({{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }}) + {{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.byod') }} - ({{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }}) + {{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }} - {{ trans('general.all') }} {{ trans('admin/hardware/general.archived') }} - ({{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }}) + {{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }} {{ trans('general.audit_due') }} + {{ (isset($total_due_and_overdue_for_audit)) ? $total_due_and_overdue_for_audit : '' }} - - - {{ trans('general.audit_overdue') }} - - + @endcan + + @can('checkin', \App\Models\Asset::class) + + + {{ trans('general.checkin_due') }} + {{ (isset($total_due_and_overdue_for_checkin)) ? $total_due_and_overdue_for_checkin : '' }} + + @endcan
  •  
  • diff --git a/resources/views/licenses/index.blade.php b/resources/views/licenses/index.blade.php index 8fa52fd1fc..82e34b1909 100755 --- a/resources/views/licenses/index.blade.php +++ b/resources/views/licenses/index.blade.php @@ -13,6 +13,9 @@ {{ trans('general.create') }} @endcan +@can('view', \App\Models\License::class) + {{ trans('general.export') }} +@endcan @stop {{-- Page content --}} diff --git a/resources/views/notifications/markdown/asset-acceptance.blade.php b/resources/views/notifications/markdown/asset-acceptance.blade.php index 50191d4a37..93292375a5 100644 --- a/resources/views/notifications/markdown/asset-acceptance.blade.php +++ b/resources/views/notifications/markdown/asset-acceptance.blade.php @@ -13,6 +13,9 @@ @if (isset($declined_date)) | **{{ ucfirst(trans('general.declined')) }}** | {{ $declined_date }} | @endif +@if (isset($note)) +| **{{ trans('general.notes') }}** | {{ $note }} | +@endif @if ((isset($item_tag)) && ($item_tag!='')) | **{{ trans('mail.asset_tag') }}** | {{ $item_tag }} | @endif diff --git a/resources/views/notifications/markdown/report-expected-checkins.blade.php b/resources/views/notifications/markdown/report-expected-checkins.blade.php index 3715188d42..08b81350e4 100644 --- a/resources/views/notifications/markdown/report-expected-checkins.blade.php +++ b/resources/views/notifications/markdown/report-expected-checkins.blade.php @@ -10,7 +10,7 @@ @php $checkin = Helper::getFormattedDateObject($asset->expected_checkin, 'date'); @endphp -| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assigned->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assigned->id]) }}) | {{ $checkin['formatted'] }} +| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assignedTo->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assignedTo->id]) }}) | {{ $checkin['formatted'] }} @endforeach @endcomponent diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index a3d6b6df2d..8b01ce7865 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -92,6 +92,9 @@ exportTypes: ['xlsx', 'excel', 'csv', 'pdf','json', 'xml', 'txt', 'sql', 'doc' ], onLoadSuccess: function () { $('[data-tooltip="true"]').tooltip(); // Needed to attach tooltips after ajax call + }, + formatNoMatches: function () { + return '{{ trans('table.no_matching_records') }}'; } }); diff --git a/resources/views/partials/forms/edit/image-upload.blade.php b/resources/views/partials/forms/edit/image-upload.blade.php index 2e9ac38558..fca1068844 100644 --- a/resources/views/partials/forms/edit/image-upload.blade.php +++ b/resources/views/partials/forms/edit/image-upload.blade.php @@ -32,7 +32,10 @@ -

    {{ trans('general.image_filetypes_help', ['size' => Helper::file_upload_max_size_readable()]) }}

    +

    {{ trans('general.image_filetypes_help', ['size' => Helper::file_upload_max_size_readable()]) }} {{ $help_text ?? '' }}

    + + + {!! $errors->first('image', '') !!}
    - {{ trans('general.back') }} - @@ -448,8 +446,8 @@
    -
    -
    + + diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 3dbd9ff531..f79270d18c 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -89,9 +89,9 @@ - @if ($user->managedLocations()->count() >= 0 ) + @if ($user->managedLocations->count() >= 0 )
  • - +
  • @endif - @can('update', $user) + @if ($user->managesUsers->count() >= 0 ) +
  • + + + +
  • + @endif + + + @can('update', $user)
    -
    +
    @include('partials.locations-bulk-actions') @@ -1046,6 +1062,39 @@
    + +
    + + @include('partials.locations-bulk-actions') + + + +
    + +
    diff --git a/routes/api.php b/routes/api.php index c462e27ca4..234a59a84a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -495,13 +495,27 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi )->name('api.assets.show.byserial') ->where('any', '.*'); - Route::get('audit/{audit}', + // LEGACY URL - Get assets that are due or overdue for audit + Route::get('audit/{status}', [ Api\AssetsController::class, 'index' ] )->name('api.asset.to-audit'); + + + // This gets the "due or overdue" API endpoints for audits and checkins + Route::get('{action}/{upcoming_status}', + [ + Api\AssetsController::class, + 'index' + ] + )->name('api.assets.list-upcoming') + ->where(['action' => 'audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']); + + + Route::post('audit', [ Api\AssetsController::class, diff --git a/routes/web/hardware.php b/routes/web/hardware.php index d4f2892281..7833f0fdaa 100644 --- a/routes/web/hardware.php +++ b/routes/web/hardware.php @@ -50,26 +50,10 @@ Route::group( [AssetsController::class, 'dueForAudit'] )->name('assets.audit.due'); - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - - Route::get('audit/due', - [AssetsController::class, 'dueForAudit'] - )->name('assets.audit.due'); - - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - - Route::get('audit/due', - [AssetsController::class, 'dueForAudit'] - )->name('assets.audit.due'); - - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - + Route::get('checkins/due', + [AssetsController::class, 'dueForCheckin'] + )->name('assets.checkins.due'); + Route::get('audit/{id}', [AssetsController::class, 'audit'] )->name('asset.audit.create'); diff --git a/routes/web/licenses.php b/routes/web/licenses.php index b70347793c..7212a47648 100644 --- a/routes/web/licenses.php +++ b/routes/web/licenses.php @@ -48,6 +48,13 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () { '{licenseId}/showfile/{fileId}/{download?}', [Licenses\LicenseFilesController::class, 'show'] )->name('show.licensefile'); + Route::get( + 'export', + [ + Licenses\LicensesController::class, + 'getExportLicensesCsv' + ] + )->name('licenses.export'); }); Route::resource('licenses', Licenses\LicensesController::class, [ diff --git a/tests/Feature/Api/Assets/AssetIndexTest.php b/tests/Feature/Api/Assets/AssetIndexTest.php index 3175db6953..0a21e13f26 100644 --- a/tests/Feature/Api/Assets/AssetIndexTest.php +++ b/tests/Feature/Api/Assets/AssetIndexTest.php @@ -7,10 +7,10 @@ use App\Models\Company; use App\Models\User; use Illuminate\Testing\Fluent\AssertableJson; use Tests\TestCase; - +use Carbon\Carbon; class AssetIndexTest extends TestCase { - public function testAssetIndexReturnsExpectedAssets() + public function testAssetApiIndexReturnsExpectedAssets() { Asset::factory()->count(3)->create(); @@ -30,7 +30,102 @@ class AssetIndexTest extends TestCase ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); } - public function testAssetIndexAdheresToCompanyScoping() + public function testAssetApiIndexReturnsDisplayUpcomingAuditsDue() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]); + + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsOverdueForAudit() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + + public function testAssetApiIndexReturnsDueOrOverdueForAudit() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]); + Asset::factory()->count(2)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due-or-overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc()); + } + + + + public function testAssetApiIndexReturnsDueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due']) + ) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsOverdueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsDueOrOverdueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]); + Asset::factory()->count(2)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due-or-overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc()); + } + + public function testAssetApiIndexAdheresToCompanyScoping() { [$companyA, $companyB] = Company::factory()->count(2)->create(); diff --git a/tests/Feature/Api/Assets/AssetStoreTest.php b/tests/Feature/Api/Assets/AssetStoreTest.php index c874745510..006a5738f7 100644 --- a/tests/Feature/Api/Assets/AssetStoreTest.php +++ b/tests/Feature/Api/Assets/AssetStoreTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Api\Assets; +use App\Helpers\Helper; use App\Models\Asset; use App\Models\AssetModel; use App\Models\Company; @@ -12,6 +13,7 @@ use App\Models\Supplier; use App\Models\User; use Illuminate\Support\Facades\Crypt; use Illuminate\Testing\Fluent\AssertableJson; +use Illuminate\Testing\TestResponse; use Tests\TestCase; class AssetStoreTest extends TestCase @@ -278,6 +280,50 @@ class AssetStoreTest extends TestCase ->assertStatusMessageIs('error'); } + public function testStoresPeriodAsDecimalSeparatorForPurchaseCost() + { + $this->settings->set([ + 'default_currency' => 'USD', + 'digit_separator' => '1,234.56', + ]); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => 'random-string', + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->create()->id, + // API accepts float + 'purchase_cost' => 12.34, + ]) + ->assertStatusMessageIs('success'); + + $asset = Asset::find($response['payload']['id']); + + $this->assertEquals(12.34, $asset->purchase_cost); + } + + public function testStoresPeriodAsCommaSeparatorForPurchaseCost() + { + $this->settings->set([ + 'default_currency' => 'EUR', + 'digit_separator' => '1.234,56', + ]); + + $response = $this->actingAsForApi(User::factory()->superuser()->create()) + ->postJson(route('api.assets.store'), [ + 'asset_tag' => 'random-string', + 'model_id' => AssetModel::factory()->create()->id, + 'status_id' => Statuslabel::factory()->create()->id, + // API also accepts string for comma separated values + 'purchase_cost' => '12,34', + ]) + ->assertStatusMessageIs('success'); + + $asset = Asset::find($response['payload']['id']); + + $this->assertEquals(12.34, $asset->purchase_cost); + } + public function testUniqueSerialNumbersIsEnforcedWhenEnabled() { $model = AssetModel::factory()->create(); diff --git a/tests/Feature/Api/Users/UpdateUserApiTest.php b/tests/Feature/Api/Users/UpdateUserApiTest.php index 1728de82cd..37ebce4a46 100644 --- a/tests/Feature/Api/Users/UpdateUserApiTest.php +++ b/tests/Feature/Api/Users/UpdateUserApiTest.php @@ -127,27 +127,27 @@ class UpdateUserApiTest extends TestCase { $groupToJoin = Group::factory()->create(); - $normalUser = User::factory()->editUsers()->create(); + $userWhoCanEditUsers = User::factory()->editUsers()->create(); $superUser = User::factory()->superuser()->create(); - $oneUserToUpdate = User::factory()->create(); - $anotherUserToUpdate = User::factory()->create(); + $userToUpdateByUserWhoCanEditUsers = User::factory()->create(); + $userToUpdateByToUserBySuperuser = User::factory()->create(); - $this->actingAsForApi($normalUser) - ->patchJson(route('api.users.update', $oneUserToUpdate), [ + $this->actingAsForApi($userWhoCanEditUsers) + ->patchJson(route('api.users.update', $userToUpdateByUserWhoCanEditUsers), [ 'groups' => [$groupToJoin->id], ]); $this->actingAsForApi($superUser) - ->patchJson(route('api.users.update', $anotherUserToUpdate), [ + ->patchJson(route('api.users.update', $userToUpdateByToUserBySuperuser), [ 'groups' => [$groupToJoin->id], ]); - $this->assertFalse( - $oneUserToUpdate->refresh()->groups->contains($groupToJoin), + $this->assertFalse($userToUpdateByUserWhoCanEditUsers->refresh()->groups->contains($groupToJoin), 'Non-super-user was able to modify user group' ); - $this->assertTrue($anotherUserToUpdate->refresh()->groups->contains($groupToJoin)); + + $this->assertTrue($userToUpdateByToUserBySuperuser->refresh()->groups->contains($groupToJoin)); } public function testUserGroupsCanBeClearedBySuperUser() @@ -175,4 +175,73 @@ class UpdateUserApiTest extends TestCase $this->assertTrue($oneUserToUpdate->refresh()->groups->contains($joinedGroup)); $this->assertFalse($anotherUserToUpdate->refresh()->groups->contains($joinedGroup)); } + + public function testNonSuperuserCannotUpdateOwnGroups() + { + $groupToJoin = Group::factory()->create(); + $user = User::factory()->editUsers()->create(); + + $this->actingAsForApi($user) + ->patchJson(route('api.users.update', $user), [ + 'groups' => [$groupToJoin->id], + ]); + + $this->assertFalse($user->refresh()->groups->contains($groupToJoin), + 'Non-super-user was able to modify user group' + ); + + } + + public function testNonSuperuserCannotUpdateGroups() + { + $user = User::factory()->editUsers()->create(); + $group = Group::factory()->create(); + $user->groups()->sync([$group->id]); + $newGroupToJoin = Group::factory()->create(); + + $this->actingAsForApi($user) + ->patchJson(route('api.users.update', $user), [ + 'groups' => [$newGroupToJoin->id], + ]); + + + $this->assertFalse($user->refresh()->groups->contains($newGroupToJoin), + 'Non-super-user was able to modify user group membership' + ); + + $this->assertTrue($user->refresh()->groups->contains($group)); + + } + + public function testUsersGroupsAreNotClearedIfNoGroupPassedBySuperUser() + { + $user = User::factory()->create(); + $superUser = User::factory()->superuser()->create(); + + $group = Group::factory()->create(); + $user->groups()->sync([$group->id]); + + $this->actingAsForApi($superUser) + ->patchJson(route('api.users.update', $user), []); + + $this->assertTrue($user->refresh()->groups->contains($group)); + } + + public function testMultipleGroupsUpdateBySuperUser() + { + $user = User::factory()->create(); + $superUser = User::factory()->superuser()->create(); + + $groupA = Group::factory()->create(['name'=>'Group A']); + $groupB = Group::factory()->create(['name'=>'Group B']); + + $this->actingAsForApi($superUser) + ->patchJson(route('api.users.update', $user), [ + 'groups' => [$groupA->id, $groupB->id], + ])->json(); + + $this->assertTrue($user->refresh()->groups->contains($groupA)); + $this->assertTrue($user->refresh()->groups->contains($groupB)); + } + } diff --git a/tests/Feature/Api/Users/UsersDeleteTest.php b/tests/Feature/Api/Users/UsersDeleteTest.php new file mode 100644 index 0000000000..cbdba83278 --- /dev/null +++ b/tests/Feature/Api/Users/UsersDeleteTest.php @@ -0,0 +1,42 @@ +create(['first_name' => 'Manager', 'last_name' => 'McManagerson']); + User::factory()->create(['first_name' => 'Lowly', 'last_name' => 'Worker', 'manager_id' => $manager->id]); + $this->actingAs(User::factory()->deleteUsers()->create())->assertFalse($manager->isDeletable()); + } + + public function testDisallowUserDeletionIfStillManagingLocations() + { + $manager = User::factory()->create(['first_name' => 'Manager', 'last_name' => 'McManagerson']); + Location::factory()->create(['manager_id' => $manager->id]); + $this->actingAs(User::factory()->deleteUsers()->create())->assertFalse($manager->isDeletable()); + } + + public function testAllowUserDeletionIfNotManagingLocations() + { + $manager = User::factory()->create(['first_name' => 'Manager', 'last_name' => 'McManagerson']); + $this->actingAs(User::factory()->deleteUsers()->create())->assertTrue($manager->isDeletable()); + } + + public function testDisallowUserDeletionIfNoDeletePermissions() + { + $manager = User::factory()->create(['first_name' => 'Manager', 'last_name' => 'McManagerson']); + Location::factory()->create(['manager_id' => $manager->id]); + $this->actingAs(User::factory()->editUsers()->create())->assertFalse($manager->isDeletable()); + } + + +} diff --git a/tests/Feature/Assets/AssetsBulkEditTest.php b/tests/Feature/Assets/AssetsBulkEditTest.php new file mode 100644 index 0000000000..70918b3595 --- /dev/null +++ b/tests/Feature/Assets/AssetsBulkEditTest.php @@ -0,0 +1,208 @@ +viewAssets()->editAssets()->create(); + $assets = Asset::factory()->count(2)->create(); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs($user)->post('/hardware/bulkedit', [ + 'ids' => $id_array, + 'order' => 'asc', + 'bulk_actions' => 'edit', + 'sort' => 'id' + ])->assertStatus(200); + } + + public function testStandardUserCannotAccessPage() + { + $user = User::factory()->create(); + $assets = Asset::factory()->count(2)->create(); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs($user)->post('/hardware/bulkedit', [ + 'ids' => $id_array, + 'order' => 'asc', + 'bulk_actions' => 'edit', + 'sort' => 'id' + ])->assertStatus(403); + } + + public function testBulkEditAssetsAcceptsAllPossibleAttributes() + { + // sets up all needed models and attributes on the assets + // this test does not deal with custom fields - will be dealt with in separate cases + $status1 = Statuslabel::factory()->create(); + $status2 = Statuslabel::factory()->create(); + $model1 = AssetModel::factory()->create(); + $model2 = AssetModel::factory()->create(); + $supplier1 = Supplier::factory()->create(); + $supplier2 = Supplier::factory()->create(); + $company1 = Company::factory()->create(); + $company2 = Company::factory()->create(); + $assets = Asset::factory()->count(10)->create([ + 'purchase_date' => '2023-01-01', + 'expected_checkin' => '2023-01-01', + 'status_id' => $status1->id, + 'model_id' => $model1->id, + // skipping locations on this test, it deserves it's own test + 'purchase_cost' => 1234.90, + 'supplier_id' => $supplier1->id, + 'company_id' => $company1->id, + 'order_number' => '123456', + 'warranty_months' => 24, + 'next_audit_date' => '2024-06-01', + 'requestable' => false + ]); + + // gets the ids together to submit to the endpoint + $id_array = $assets->pluck('id')->toArray(); + + // submits the ids and new values for each attribute + $this->actingAs(User::factory()->editAssets()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + 'purchase_date' => '2024-01-01', + 'expected_checkin' => '2024-01-01', + 'status_id' => $status2->id, + 'model_id' => $model2->id, + 'purchase_cost' => 5678.92, + 'supplier_id' => $supplier2->id, + 'company_id' => $company2->id, + 'order_number' => '7890', + 'warranty_months' => 36, + 'next_audit_date' => '2025-01-01', + 'requestable' => true + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors(); + + // asserts that each asset has the updated values + Asset::findMany($id_array)->each(function (Asset $asset) use ($status2, $model2, $supplier2, $company2) { + $this->assertEquals('2024-01-01', $asset->purchase_date->format('Y-m-d')); + $this->assertEquals('2024-01-01', $asset->expected_checkin->format('Y-m-d')); + $this->assertEquals($status2->id, $asset->status_id); + $this->assertEquals($model2->id, $asset->model_id); + $this->assertEquals(5678.92, $asset->purchase_cost); + $this->assertEquals($supplier2->id, $asset->supplier_id); + $this->assertEquals($company2->id, $asset->company_id); + $this->assertEquals(7890, $asset->order_number); + $this->assertEquals(36, $asset->warranty_months); + $this->assertEquals('2025-01-01', $asset->next_audit_date); + // shouldn't requestable be cast as a boolean??? it's not. + $this->assertEquals(1, $asset->requestable); + }); + } + + public function testBulkEditAssetsAcceptsAndUpdatesUnencryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + CustomField::factory()->ram()->create(); + CustomField::factory()->cpu()->create(); + + // when getting the custom field directly from the factory the field has not been fully created yet + // so we have to do a query afterwards to get the actual model :shrug: + + $ram = CustomField::where('name', 'RAM')->first(); + $cpu = CustomField::where('name', 'CPU')->first(); + + $assets = Asset::factory()->count(10)->hasMultipleCustomFields([$ram, $cpu])->create([ + $ram->db_column => 8, + $cpu->db_column => '2.1', + ]); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs(User::factory()->editAssets()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + $ram->db_column => 16, + $cpu->db_column => '4.1', + ])->assertStatus(302); + + Asset::findMany($id_array)->each(function (Asset $asset) use ($ram, $cpu) { + $this->assertEquals(16, $asset->{$ram->db_column}); + $this->assertEquals('4.1', $asset->{$cpu->db_column}); + }); + } + + public function testBulkEditAssetsAcceptsAndUpdatesEncryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + CustomField::factory()->testEncrypted()->create(); + + $encrypted = CustomField::where('name', 'Test Encrypted')->first(); + + $assets = Asset::factory()->count(10)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs(User::factory()->admin()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + Asset::findMany($id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('New Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + } + + public function testBulkEditAssetsRequiresAdminUserToUpdateEncryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on mysql'); + $edit_user = User::factory()->editAssets()->create(); + $admin_user = User::factory()->admin()->create(); + + CustomField::factory()->testEncrypted()->create(); + + $encrypted = CustomField::where('name', 'Test Encrypted')->first(); + + $admin_assets = Asset::factory()->count(5)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $standard_assets = Asset::factory()->count(5)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $admin_id_array = $admin_assets->pluck('id')->toArray(); + $standard_id_array = $standard_assets->pluck('id')->toArray(); + + $this->actingAs($admin_user)->post(route('hardware/bulksave'), [ + 'ids' => $admin_id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + // do we want to return an error when this happens??? + $this->actingAs($edit_user)->post(route('hardware/bulksave'), [ + 'ids' => $standard_id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + Asset::findMany($admin_id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('New Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + + Asset::findMany($standard_id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('Original Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + } +} diff --git a/tests/Unit/Helpers/HelperTest.php b/tests/Unit/Helpers/HelperTest.php index 0b5fba986c..cfc8c7fac2 100644 --- a/tests/Unit/Helpers/HelperTest.php +++ b/tests/Unit/Helpers/HelperTest.php @@ -16,4 +16,13 @@ class HelperTest extends TestCase { $this->assertIsString(Helper::defaultChartColors(-1)); } + + public function testParseCurrencyMethod() + { + $this->settings->set(['default_currency' => 'USD']); + $this->assertSame(12.34, Helper::ParseCurrency('USD 12.34')); + + $this->settings->set(['digit_separator' => '1.234,56']); + $this->assertSame(12.34, Helper::ParseCurrency('12,34')); + } } diff --git a/upgrade.php b/upgrade.php index 0ac33856f3..798d9d3c8f 100644 --- a/upgrade.php +++ b/upgrade.php @@ -87,7 +87,7 @@ if($upgrade_requirements){ // done fetching requirements if (!$no_interactive) { - $yesno = readline("\nProceed with upgrade? [Y/n]: "); + $yesno = readline("\nProceed with upgrade? [y/N]: "); } else { $yesno = "yes"; } @@ -248,6 +248,7 @@ $required_exts_array = [ 'bcmath', 'curl', + 'exif', 'fileinfo', 'gd|imagick', 'json',