Compare commits

..

13 Commits

Author SHA1 Message Date
snipe
df38d7e3ed Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-02-27 12:22:30 +00:00
snipe
44dd061619 Merge remote-tracking branch 'origin/develop' 2025-02-26 20:55:57 +00:00
snipe
7603a932b1 Merge remote-tracking branch 'origin/develop' 2025-02-26 20:29:42 +00:00
snipe
138e7acc13 Merge remote-tracking branch 'origin/develop' 2025-02-26 12:47:54 +00:00
snipe
e863d3e7e5 Merge remote-tracking branch 'origin/develop' 2025-02-26 12:02:00 +00:00
snipe
c8e401f5ed Merge remote-tracking branch 'origin/develop' 2025-02-26 11:59:53 +00:00
snipe
3ba20a8e28 Merge remote-tracking branch 'origin/develop' 2025-02-26 11:39:10 +00:00
snipe
ebae63752f Merge remote-tracking branch 'origin/develop' 2025-02-26 10:25:18 +00:00
snipe
8bc73901cf Merge remote-tracking branch 'origin/develop' 2025-02-26 08:25:35 +00:00
snipe
b4f70d9244 Merge remote-tracking branch 'origin/develop' 2025-02-26 07:16:59 +00:00
snipe
21e9f2bba3 Merge remote-tracking branch 'origin/develop' 2025-02-26 07:11:22 +00:00
snipe
881f4e3d6a Merge remote-tracking branch 'origin/develop' 2025-02-25 14:43:57 +00:00
snipe
b141945add Updated branch
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 12:18:52 +00:00
2521 changed files with 15680 additions and 62615 deletions

View File

@@ -2590,10 +2590,10 @@
]
},
{
"login": "qveensi",
"login": "QveenSi",
"name": "Yevhenii Huzii",
"avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4",
"profile": "https://github.com/qveensi",
"profile": "https://github.com/QveenSi",
"contributions": [
"code"
]
@@ -2607,6 +2607,15 @@
"code"
]
},
{
"login": "QveenSi",
"name": "Yevhenii Huzii",
"avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4",
"profile": "https://github.com/QveenSi",
"contributions": [
"code"
]
},
{
"login": "chrisweirich",
"name": "Christian Weirich",
@@ -3280,915 +3289,6 @@
"contributions": [
"code"
]
},
{
"login": "azmcnutt",
"name": "James M",
"avatar_url": "https://avatars.githubusercontent.com/u/31522486?v=4",
"profile": "https://github.com/azmcnutt",
"contributions": [
"code"
]
},
{
"login": "Fiala06",
"name": "Fiala06",
"avatar_url": "https://avatars.githubusercontent.com/u/5183146?v=4",
"profile": "https://github.com/Fiala06",
"contributions": [
"code"
]
},
{
"login": "ntaylor-86",
"name": "Nathan Taylor",
"avatar_url": "https://avatars.githubusercontent.com/u/28693782?v=4",
"profile": "https://github.com/ntaylor-86",
"contributions": [
"code"
]
},
{
"login": "fvollmer",
"name": "fvollmer",
"avatar_url": "https://avatars.githubusercontent.com/u/16699443?v=4",
"profile": "https://github.com/fvollmer",
"contributions": [
"code"
]
},
{
"login": "36864",
"name": "36864",
"avatar_url": "https://avatars.githubusercontent.com/u/109086466?v=4",
"profile": "https://github.com/36864",
"contributions": [
"code"
]
},
{
"login": "CloCkWeRX",
"name": "Daniel O'Connor",
"avatar_url": "https://avatars.githubusercontent.com/u/365751?v=4",
"profile": "http://clockwerx.blogspot.com/",
"contributions": [
"code"
]
},
{
"login": "BeatSpark",
"name": "BeatSpark",
"avatar_url": "https://avatars.githubusercontent.com/u/102852568?v=4",
"profile": "https://github.com/BeatSpark",
"contributions": [
"code"
]
},
{
"login": "mrdahbi",
"name": "mrdahbi",
"avatar_url": "https://avatars.githubusercontent.com/u/59203607?v=4",
"profile": "https://github.com/mrdahbi",
"contributions": [
"code"
]
},
{
"login": "chfsx",
"name": "Fabian Schmid",
"avatar_url": "https://avatars.githubusercontent.com/u/6661332?v=4",
"profile": "http://sr.solutions",
"contributions": [
"code"
]
},
{
"login": "realchrisolin",
"name": "Chris Olin",
"avatar_url": "https://avatars.githubusercontent.com/u/1288116?v=4",
"profile": "https://www.chrisolin.com",
"contributions": [
"code"
]
},
{
"login": "mnemonicly",
"name": "Dan",
"avatar_url": "https://avatars.githubusercontent.com/u/3803132?v=4",
"profile": "https://github.com/mnemonicly",
"contributions": [
"code"
]
},
{
"login": "NebelKreis",
"name": "Nebel",
"avatar_url": "https://avatars.githubusercontent.com/u/43917728?v=4",
"profile": "https://github.com/NebelKreis",
"contributions": [
"code"
]
},
{
"login": "test1337ahp",
"name": "test1337ahp",
"avatar_url": "https://avatars.githubusercontent.com/u/132433803?v=4",
"profile": "https://github.com/test1337ahp",
"contributions": [
"code"
]
},
{
"login": "JonathonReinhart",
"name": "Jonathon Reinhart",
"avatar_url": "https://avatars.githubusercontent.com/u/1916566?v=4",
"profile": "https://github.com/JonathonReinhart",
"contributions": [
"code"
]
},
{
"login": "aranar-pro",
"name": "aranar-pro",
"avatar_url": "https://avatars.githubusercontent.com/u/484742?v=4",
"profile": "https://github.com/aranar-pro",
"contributions": [
"code"
]
},
{
"login": "phil-flip",
"name": "Phil",
"avatar_url": "https://avatars.githubusercontent.com/u/27019397?v=4",
"profile": "https://github.com/phil-flip",
"contributions": [
"code"
]
},
{
"login": "fe80",
"name": "Steffy Fort",
"avatar_url": "https://avatars.githubusercontent.com/u/6473460?v=4",
"profile": "https://fe80.fr/",
"contributions": [
"code"
]
},
{
"login": "sorvani",
"name": "Jared Busch",
"avatar_url": "https://avatars.githubusercontent.com/u/3302372?v=4",
"profile": "https://github.com/sorvani",
"contributions": [
"code"
]
},
{
"login": "seanborg-codethink",
"name": "seanborg-codethink",
"avatar_url": "https://avatars.githubusercontent.com/u/111956991?v=4",
"profile": "https://github.com/seanborg-codethink",
"contributions": [
"code"
]
},
{
"login": "dkaatz",
"name": "dkaatz",
"avatar_url": "https://avatars.githubusercontent.com/u/160669961?v=4",
"profile": "https://github.com/dkaatz",
"contributions": [
"code"
]
},
{
"login": "DanielRuf",
"name": "Daniel Ruf",
"avatar_url": "https://avatars.githubusercontent.com/u/827205?v=4",
"profile": "https://threema.id/74SF7MW6?text=",
"contributions": [
"code"
]
},
{
"login": "ahpaleus",
"name": "ahpaleus",
"avatar_url": "https://avatars.githubusercontent.com/u/38883201?v=4",
"profile": "https://github.com/ahpaleus",
"contributions": [
"code"
]
},
{
"login": "mink-adao-duy",
"name": "Anh DAO-DUY",
"avatar_url": "https://avatars.githubusercontent.com/u/22906055?v=4",
"profile": "https://github.com/mink-adao-duy",
"contributions": [
"code"
]
},
{
"login": "Serdnad",
"name": "Andres Gutierrez",
"avatar_url": "https://avatars.githubusercontent.com/u/4723453?v=4",
"profile": "https://github.com/Serdnad",
"contributions": [
"code"
]
},
{
"login": "wewhite",
"name": "Warren White",
"avatar_url": "https://avatars.githubusercontent.com/u/111083379?v=4",
"profile": "https://github.com/wewhite",
"contributions": [
"code"
]
},
{
"login": "robintemme",
"name": "Robin Temme",
"avatar_url": "https://avatars.githubusercontent.com/u/2809241?v=4",
"profile": "https://robintemme.de/",
"contributions": [
"code"
]
},
{
"login": "herroworrd",
"name": "herroworrd",
"avatar_url": "https://avatars.githubusercontent.com/u/47008367?v=4",
"profile": "https://github.com/herroworrd",
"contributions": [
"code"
]
},
{
"login": "vicleos",
"name": "vicleos",
"avatar_url": "https://avatars.githubusercontent.com/u/28558609?v=4",
"profile": "https://mubiu.com/",
"contributions": [
"code"
]
},
{
"login": "thinkl33t",
"name": "Bob Clough",
"avatar_url": "https://avatars.githubusercontent.com/u/1016780?v=4",
"profile": "http://thinkl33t.co.uk/",
"contributions": [
"code"
]
},
{
"login": "brandon-bailey",
"name": "Brandon Daniel Bailey",
"avatar_url": "https://avatars.githubusercontent.com/u/10648463?v=4",
"profile": "https://github.com/brandon-bailey",
"contributions": [
"code"
]
},
{
"login": "marcquark",
"name": "Marc Bartelt",
"avatar_url": "https://avatars.githubusercontent.com/u/23556080?v=4",
"profile": "https://github.com/marcquark",
"contributions": [
"code"
]
},
{
"login": "manu-crealytics",
"name": "manu-crealytics",
"avatar_url": "https://avatars.githubusercontent.com/u/18286893?v=4",
"profile": "https://github.com/manu-crealytics",
"contributions": [
"code"
]
},
{
"login": "Galaxy102",
"name": "Konstantin Köhring",
"avatar_url": "https://avatars.githubusercontent.com/u/18245993?v=4",
"profile": "https://www.galaxy102.de/",
"contributions": [
"code"
]
},
{
"login": "deloz",
"name": "Deloz",
"avatar_url": "https://avatars.githubusercontent.com/u/685167?v=4",
"profile": "https://deloz.net/",
"contributions": [
"code"
]
},
{
"login": "mbrrg",
"name": "Martin Berg",
"avatar_url": "https://avatars.githubusercontent.com/u/2682426?v=4",
"profile": "https://github.com/mbrrg",
"contributions": [
"code"
]
},
{
"login": "Nothing4You",
"name": "Richard Schwab",
"avatar_url": "https://avatars.githubusercontent.com/u/3694534?v=4",
"profile": "https://github.com/Nothing4You",
"contributions": [
"code"
]
},
{
"login": "rickheil",
"name": "Rick Heil",
"avatar_url": "https://avatars.githubusercontent.com/u/8959676?v=4",
"profile": "https://rickheil.com/",
"contributions": [
"code"
]
},
{
"login": "rosscdh",
"name": "Ross Crawford-d'Heureuse",
"avatar_url": "https://avatars.githubusercontent.com/u/397106?v=4",
"profile": "https://github.com/rosscdh",
"contributions": [
"code"
]
},
{
"login": "McG800",
"name": "Ryan McGuire",
"avatar_url": "https://avatars.githubusercontent.com/u/1621107?v=4",
"profile": "https://github.com/McG800",
"contributions": [
"code"
]
},
{
"login": "SBrown2021",
"name": "SBrown2021",
"avatar_url": "https://avatars.githubusercontent.com/u/77835667?v=4",
"profile": "https://github.com/SBrown2021",
"contributions": [
"code"
]
},
{
"login": "serkanerip",
"name": "Serkan",
"avatar_url": "https://avatars.githubusercontent.com/u/8780913?v=4",
"profile": "https://github.com/serkanerip",
"contributions": [
"code"
]
},
{
"login": "Shankschn",
"name": "Shanks",
"avatar_url": "https://avatars.githubusercontent.com/u/63188620?v=4",
"profile": "https://www.yudelei.com/",
"contributions": [
"code"
]
},
{
"login": "cendai-mis",
"name": "cendai-mis",
"avatar_url": "https://avatars.githubusercontent.com/u/198525698?v=4",
"profile": "https://github.com/cendai-mis",
"contributions": [
"code"
]
},
{
"login": "smcpeck",
"name": "Shaun McPeck",
"avatar_url": "https://avatars.githubusercontent.com/u/8724583?v=4",
"profile": "https://smcpeck.github.io/",
"contributions": [
"code"
]
},
{
"login": "snazy2000",
"name": "Stephen",
"avatar_url": "https://avatars.githubusercontent.com/u/1378836?v=4",
"profile": "https://github.com/snazy2000",
"contributions": [
"code"
]
},
{
"login": "Nevets82",
"name": "Steven",
"avatar_url": "https://avatars.githubusercontent.com/u/4462739?v=4",
"profile": "http://nevets82.github.io/",
"contributions": [
"code"
]
},
{
"login": "Mateus-Romera",
"name": "Mateus Villar",
"avatar_url": "https://avatars.githubusercontent.com/u/29017267?v=4",
"profile": "https://mateusvillar.com/",
"contributions": [
"code"
]
},
{
"login": "mzack5020",
"name": "Matthew Zackschewski",
"avatar_url": "https://avatars.githubusercontent.com/u/12749393?v=4",
"profile": "https://github.com/mzack5020",
"contributions": [
"code"
]
},
{
"login": "firefrei",
"name": "Matthias Frei",
"avatar_url": "https://avatars.githubusercontent.com/u/12660103?v=4",
"profile": "https://www.frei.media/",
"contributions": [
"code"
]
},
{
"login": "nticaric",
"name": "Nenad Ticaric",
"avatar_url": "https://avatars.githubusercontent.com/u/824840?v=4",
"profile": "https://github.com/nticaric",
"contributions": [
"code"
]
},
{
"login": "Scorcher",
"name": "Nikolay Didenko",
"avatar_url": "https://avatars.githubusercontent.com/u/706439?v=4",
"profile": "https://github.com/Scorcher",
"contributions": [
"code"
]
},
{
"login": "nunomaduro",
"name": "Nuno Maduro",
"avatar_url": "https://avatars.githubusercontent.com/u/5457236?v=4",
"profile": "https://nunomaduro.com/sponsorships",
"contributions": [
"code"
]
},
{
"login": "owalerys",
"name": "Oliver Walerys",
"avatar_url": "https://avatars.githubusercontent.com/u/8883074?v=4",
"profile": "https://tektikhq.com/",
"contributions": [
"code"
]
},
{
"login": "rcmcdonald91",
"name": "R. Christian McDonald",
"avatar_url": "https://avatars.githubusercontent.com/u/3102039?v=4",
"profile": "https://keybase.io/rcmcdonald91",
"contributions": [
"code"
]
},
{
"login": "nixn",
"name": "nix",
"avatar_url": "https://avatars.githubusercontent.com/u/1525581?v=4",
"profile": "https://nnix.net/",
"contributions": [
"code"
]
},
{
"login": "octobunny",
"name": "octobunny",
"avatar_url": "https://avatars.githubusercontent.com/u/55462380?v=4",
"profile": "https://github.com/octobunny",
"contributions": [
"code"
]
},
{
"login": "sreyemnayr",
"name": "Ryan",
"avatar_url": "https://avatars.githubusercontent.com/u/8558670?v=4",
"profile": "https://github.com/sreyemnayr",
"contributions": [
"code"
]
},
{
"login": "p3nj",
"name": "p3nj",
"avatar_url": "https://avatars.githubusercontent.com/u/1501022?v=4",
"profile": "https://benji.ltd/",
"contributions": [
"code"
]
},
{
"login": "timwsuqld",
"name": "Tim White",
"avatar_url": "https://avatars.githubusercontent.com/u/6201617?v=4",
"profile": "https://github.com/timwsuqld",
"contributions": [
"code"
]
},
{
"login": "yannikp",
"name": "yannikp",
"avatar_url": "https://avatars.githubusercontent.com/u/22473767?v=4",
"profile": "https://github.com/yannikp",
"contributions": [
"code"
]
},
{
"login": "viclou",
"name": "victoria",
"avatar_url": "https://avatars.githubusercontent.com/u/20525448?v=4",
"profile": "https://github.com/viclou",
"contributions": [
"code"
]
},
{
"login": "valentyntu",
"name": "Valentyn Tulub",
"avatar_url": "https://avatars.githubusercontent.com/u/40685314?v=4",
"profile": "https://github.com/valentyntu",
"contributions": [
"code"
]
},
{
"login": "Wouter0100",
"name": "Wouter van Os",
"avatar_url": "https://avatars.githubusercontent.com/u/864520?v=4",
"profile": "http://wouter0100.nl/",
"contributions": [
"code"
]
},
{
"login": "xWyatt",
"name": "Wyatt Teeter",
"avatar_url": "https://avatars.githubusercontent.com/u/3946540?v=4",
"profile": "https://www.linkedin.com/in/wyatt-teeter",
"contributions": [
"code"
]
},
{
"login": "terwey",
"name": "Yorick Terweijden",
"avatar_url": "https://avatars.githubusercontent.com/u/1596124?v=4",
"profile": "https://github.com/terwey",
"contributions": [
"code"
]
},
{
"login": "bmkalle",
"name": "bmkalle",
"avatar_url": "https://avatars.githubusercontent.com/u/69298836?v=4",
"profile": "https://github.com/bmkalle",
"contributions": [
"code"
]
},
{
"login": "bricelabelle",
"name": "bricelabelle",
"avatar_url": "https://avatars.githubusercontent.com/u/28403467?v=4",
"profile": "https://github.com/bricelabelle",
"contributions": [
"code"
]
},
{
"login": "corydlamb",
"name": "corydlamb",
"avatar_url": "https://avatars.githubusercontent.com/u/97770090?v=4",
"profile": "https://github.com/corydlamb",
"contributions": [
"code"
]
},
{
"login": "splashx",
"name": "Diogenes S. Jesus",
"avatar_url": "https://avatars.githubusercontent.com/u/1154133?v=4",
"profile": "http://twitter.com/splash",
"contributions": [
"code"
]
},
{
"login": "dkmansion",
"name": "D M",
"avatar_url": "https://avatars.githubusercontent.com/u/5826629?v=4",
"profile": "https://github.com/dkmansion",
"contributions": [
"code"
]
},
{
"login": "Jarli01",
"name": "Dustin B",
"avatar_url": "https://avatars.githubusercontent.com/u/14837699?v=4",
"profile": "https://github.com/Jarli01",
"contributions": [
"code"
]
},
{
"login": "fabiang",
"name": "Fabian Grutschus",
"avatar_url": "https://avatars.githubusercontent.com/u/348344?v=4",
"profile": "https://github.com/fabiang",
"contributions": [
"code"
]
},
{
"login": "MelonSmasher",
"name": "MelonSmasher",
"avatar_url": "https://avatars.githubusercontent.com/u/1491053?v=4",
"profile": "https://github.com/MelonSmasher",
"contributions": [
"code"
]
},
{
"login": "AlexanderWPapyrus",
"name": "AlexanderWPapyrus",
"avatar_url": "https://avatars.githubusercontent.com/u/80526133?v=4",
"profile": "https://github.com/AlexanderWPapyrus",
"contributions": [
"code"
]
},
{
"login": "disc",
"name": "Alexandr Hacicheant",
"avatar_url": "https://avatars.githubusercontent.com/u/306231?v=4",
"profile": "https://github.com/disc",
"contributions": [
"code"
]
},
{
"login": "hex128",
"name": "Hex",
"avatar_url": "https://avatars.githubusercontent.com/u/3032891?v=4",
"profile": "https://hex128.io/",
"contributions": [
"code"
]
},
{
"login": "arukompas",
"name": "Arunas Skirius",
"avatar_url": "https://avatars.githubusercontent.com/u/8697942?v=4",
"profile": "https://github.com/arukompas",
"contributions": [
"code"
]
},
{
"login": "benperiton",
"name": "Ben Periton",
"avatar_url": "https://avatars.githubusercontent.com/u/104396?v=4",
"profile": "https://github.com/benperiton",
"contributions": [
"code"
]
},
{
"login": "byronwolfman",
"name": "Byron Wolfman",
"avatar_url": "https://avatars.githubusercontent.com/u/11906832?v=4",
"profile": "https://wolfman.dev/",
"contributions": [
"code"
]
},
{
"login": "CalvinSchwartz",
"name": "Calvin",
"avatar_url": "https://avatars.githubusercontent.com/u/56485508?v=4",
"profile": "https://github.com/CalvinSchwartz",
"contributions": [
"code"
]
},
{
"login": "juanfont",
"name": "Juan Font",
"avatar_url": "https://avatars.githubusercontent.com/u/181059?v=4",
"profile": "https://github.com/juanfont",
"contributions": [
"code"
]
},
{
"login": "juhotaipale",
"name": "Juho Taipale",
"avatar_url": "https://avatars.githubusercontent.com/u/13137708?v=4",
"profile": "https://github.com/juhotaipale",
"contributions": [
"code"
]
},
{
"login": "KorvinSzanto",
"name": "Korvin Szanto",
"avatar_url": "https://avatars.githubusercontent.com/u/1007419?v=4",
"profile": "https://github.com/KorvinSzanto",
"contributions": [
"code"
]
},
{
"login": "sniff122",
"name": "Lewis Foster",
"avatar_url": "https://avatars.githubusercontent.com/u/8513053?v=4",
"profile": "https://lewisfoster.foo/",
"contributions": [
"code"
]
},
{
"login": "loganswartz",
"name": "Logan Swartzendruber",
"avatar_url": "https://avatars.githubusercontent.com/u/33877541?v=4",
"profile": "https://github.com/loganswartz",
"contributions": [
"code"
]
},
{
"login": "lopezio",
"name": "Lorenzo P.",
"avatar_url": "https://avatars.githubusercontent.com/u/1156208?v=4",
"profile": "https://github.com/lopezio",
"contributions": [
"code"
]
},
{
"login": "m4us1ne",
"name": "Lukas Jung",
"avatar_url": "https://avatars.githubusercontent.com/u/33946590?v=4",
"profile": "https://github.com/m4us1ne",
"contributions": [
"code"
]
},
{
"login": "LeafedFox",
"name": "Ellie",
"avatar_url": "https://avatars.githubusercontent.com/u/10965027?v=4",
"profile": "https://leafedfox.xyz/",
"contributions": [
"code"
]
},
{
"login": "gastamper",
"name": "GA Stamper",
"avatar_url": "https://avatars.githubusercontent.com/u/20960555?v=4",
"profile": "https://github.com/gastamper",
"contributions": [
"code"
]
},
{
"login": "gl-pup",
"name": "Guillaume Lefranc",
"avatar_url": "https://avatars.githubusercontent.com/u/206553556?v=4",
"profile": "https://github.com/gl-pup",
"contributions": [
"code"
]
},
{
"login": "dasjoe",
"name": "Hajo Möller",
"avatar_url": "https://avatars.githubusercontent.com/u/733892?v=4",
"profile": "https://github.com/dasjoe",
"contributions": [
"code"
]
},
{
"login": "pottom",
"name": "Istvan Basa",
"avatar_url": "https://avatars.githubusercontent.com/u/3420063?v=4",
"profile": "https://github.com/pottom",
"contributions": [
"code"
]
},
{
"login": "jjasghar",
"name": "JJ Asghar",
"avatar_url": "https://avatars.githubusercontent.com/u/810824?v=4",
"profile": "https://jjasghar.github.io/",
"contributions": [
"code"
]
},
{
"login": "JemCdo",
"name": "James E. Msenga",
"avatar_url": "https://avatars.githubusercontent.com/u/40404495?v=4",
"profile": "https://github.com/JemCdo",
"contributions": [
"code"
]
},
{
"login": "jfwiebe",
"name": "Jan Felix Wiebe",
"avatar_url": "https://avatars.githubusercontent.com/u/6865786?v=4",
"profile": "https://github.com/jfwiebe",
"contributions": [
"code"
]
},
{
"login": "drexljo",
"name": "Jo Drexl",
"avatar_url": "https://avatars.githubusercontent.com/u/43412008?v=4",
"profile": "https://www.nfon.com/",
"contributions": [
"code"
]
},
{
"login": "austinsasko",
"name": "Austin Sasko",
"avatar_url": "https://avatars.githubusercontent.com/u/4807843?v=4",
"profile": "https://github.com/austinsasko",
"contributions": [
"code"
]
},
{
"login": "JassonCordones",
"name": "Jasson",
"avatar_url": "https://avatars.githubusercontent.com/u/4875039?v=4",
"profile": "http://jassoncordones.github.io",
"contributions": [
"code"
]
},
{
"login": "Tinyblargon",
"name": "Okean",
"avatar_url": "https://avatars.githubusercontent.com/u/76069640?v=4",
"profile": "https://github.com/Tinyblargon",
"contributions": [
"code"
]
},
{
"login": "amedranogil",
"name": "Alejandro Medrano",
"avatar_url": "https://avatars.githubusercontent.com/u/6515064?v=4",
"profile": "https://www.lst.tfo.upm.es/alejandro-medrano/",
"contributions": [
"code"
]
},
{
"login": "lukaskraic",
"name": "Lukas Kraic",
"avatar_url": "https://avatars.githubusercontent.com/u/58696401?v=4",
"profile": "https://github.com/lukaskraic",
"contributions": [
"code"
]
}
]
}

View File

@@ -35,7 +35,6 @@ DB_USERNAME=snipeit
DB_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
@@ -79,13 +78,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
# --------------------------------------------
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
# --------------------------------------------
#PHP_UPLOAD_LIMIT=10
#PHP_POST_MAX_SIZE=10
#PHP_UPLOAD_MAX_FILESIZE=10
#PHP_MEMORY_LIMIT=10
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS

View File

@@ -35,7 +35,6 @@ DB_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
@@ -84,15 +83,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
# --------------------------------------------
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
# --------------------------------------------
#PHP_UPLOAD_LIMIT=10
#PHP_POST_MAX_SIZE=10
#PHP_UPLOAD_MAX_FILESIZE=10
#PHP_MEMORY_LIMIT=10
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------

View File

@@ -30,7 +30,6 @@ DB_USERNAME=null
DB_PASSWORD=null
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=false
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false
@@ -179,7 +178,6 @@ PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
# OPTIONAL: MISC
# --------------------------------------------
LOG_CHANNEL=single
LOG_DEPRECATIONS=false
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC

View File

@@ -10,10 +10,10 @@ name: Codacy Security Scan
on:
push:
branches: [ develop ]
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
branches: [ master ]
schedule:
- cron: '36 23 * * 3'
@@ -22,11 +22,11 @@ permissions:
jobs:
codacy-security-scan:
# Ensure schedule job never runs on forked repos. It's only executed for 'grokability/snipe-it'
# Ensure schedule job never runs on forked repos. It's only executed for 'snipe/snipe-it'
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule'))
if: (github.repository == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule'))
name: Codacy Security Scan
runs-on: ubuntu-latest
steps:

View File

@@ -20,8 +20,8 @@ permissions:
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
if: github.repository == 'snipe/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
@@ -32,7 +32,7 @@ jobs:
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
type=semver,pattern=v{{major}}-latest-alpine
type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.

View File

@@ -1,5 +1,5 @@
# Snipe-IT Docker image build for hub.docker.com
name: Docker images (Ubuntu)
name: Docker images
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
@@ -20,8 +20,8 @@ permissions:
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
if: github.repository == 'snipe/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
@@ -32,7 +32,7 @@ jobs:
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.

View File

@@ -1,240 +0,0 @@
{
"standard": "WCAG2AA",
"level": "error",
"defaults": {
"useIncognitoBrowserContext": false,
"timeout": 500000,
"wait": 5000,
"ignore" : [
"WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail",
"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail"
],
"viewport": {
"width": 1280,
"height": 1024
}
},
"urls": [
{
"__NOTE" : "this should always be FIRST (if browser context is preserved)",
"url": "https://snipe-it.test/login",
"actions": [
"navigate to https://snipe-it.test/login",
"screen capture tests/pa11y/login.png",
"set field input[name='username'] to admin",
"set field input[name='password'] to password",
"click element button[type=submit]",
"wait for url to be https://snipe-it.test/",
"screen capture tests/pa11y/dashboard.png"
]
},
{
"url" : "https://snipe-it.test/admin",
"actions" : [
"navigate to https://snipe-it.test/admin",
"screen capture tests/pa11y/admin-settings.png"
]
},
{
"url" : "https://snipe-it.test/admin/branding",
"actions" : [
"navigate to https://snipe-it.test/admin/branding",
"screen capture tests/pa11y/admin-branding.png"
]
},
{
"url" : "https://snipe-it.test/admin/general",
"actions" : [
"navigate to https://snipe-it.test/admin/general",
"screen capture tests/pa11y/admin-general.png"
]
},
{
"url" : "https://snipe-it.test/hardware/create",
"actions" : [
"navigate to https://snipe-it.test/hardware/create",
"screen capture tests/pa11y/asset-create.png"
]
},
{
"url" : "https://snipe-it.test/hardware",
"actions" : [
"navigate to https://snipe-it.test/hardware",
"screen capture tests/pa11y/asset-list.png"
]
},
{
"url" : "https://snipe-it.test/hardware/1",
"actions" : [
"navigate to https://snipe-it.test/hardware/1",
"screen capture tests/pa11y/asset-detail.png"
]
},
{
"url" : "https://snipe-it.test/account/view-assets",
"actions" : [
"navigate to https://snipe-it.test/account/view-assets",
"screen capture tests/pa11y/profile.png"
]
},
{
"url" : "https://snipe-it.test/licences",
"actions" : [
"navigate to https://snipe-it.test/licenses",
"screen capture tests/pa11y/license-list.png"
]
},
{
"url" : "https://snipe-it.test/licences/create",
"actions" : [
"navigate to https://snipe-it.test/licenses/create",
"screen capture tests/pa11y/license-create.png"
]
},
{
"url" : "https://snipe-it.test/licences/1",
"actions" : [
"navigate to https://snipe-it.test/licenses/1",
"screen capture tests/pa11y/license-view.png"
]
},
{
"url" : "https://snipe-it.test/consumables",
"actions" : [
"navigate to https://snipe-it.test/consumables",
"screen capture tests/pa11y/consumable-list.png"
]
},
{
"url" : "https://snipe-it.test/consumables/create",
"actions" : [
"navigate to https://snipe-it.test/consumables/create",
"screen capture tests/pa11y/consumable-create.png"
]
},
{
"url" : "https://snipe-it.test/consumables/1",
"actions" : [
"navigate to https://snipe-it.test/consumables/1",
"screen capture tests/pa11y/consumable-view.png"
]
},
{
"url" : "https://snipe-it.test/accessories",
"actions" : [
"navigate to https://snipe-it.test/accessories",
"screen capture tests/pa11y/accessory-list.png"
]
},
{
"url" : "https://snipe-it.test/accessories/create",
"actions" : [
"navigate to https://snipe-it.test/accessories/create",
"screen capture tests/pa11y/accessory-create.png"
]
},
{
"url" : "https://snipe-it.test/accessories/1",
"actions" : [
"navigate to https://snipe-it.test/accessories/1",
"screen capture tests/pa11y/accessory-view.png"
]
},
{
"url" : "https://snipe-it.test/locations",
"actions" : [
"navigate to https://snipe-it.test/locations",
"screen capture tests/pa11y/location-list.png"
]
},
{
"url" : "https://snipe-it.test/locations/create",
"actions" : [
"navigate to https://snipe-it.test/locations/create",
"screen capture tests/pa11y/location-create.png"
]
},
{
"url" : "https://snipe-it.test/locations/1",
"actions" : [
"navigate to https://snipe-it.test/locations/1",
"screen capture tests/pa11y/location-view.png"
]
},
{
"url" : "https://snipe-it.test/models",
"actions" : [
"navigate to https://snipe-it.test/models",
"screen capture tests/pa11y/model-list.png"
]
},
{
"url" : "https://snipe-it.test/models/create",
"actions" : [
"navigate to https://snipe-it.test/models/create",
"screen capture tests/pa11y/model-create.png"
]
},
{
"url" : "https://snipe-it.test/models/1",
"actions" : [
"navigate to https://snipe-it.test/models/1",
"screen capture tests/pa11y/model-view.png"
]
},
{
"url" : "https://snipe-it.test/companies",
"actions" : [
"navigate to https://snipe-it.test/companies",
"screen capture tests/pa11y/company-list.png"
]
},
{
"url" : "https://snipe-it.test/companies/create",
"actions" : [
"navigate to https://snipe-it.test/companies/create",
"screen capture tests/pa11y/company-create.png"
]
},
{
"url" : "https://snipe-it.test/companies/1",
"actions" : [
"navigate to https://snipe-it.test/companies/1",
"screen capture tests/pa11y/company-view.png"
]
},
{
"url" : "https://snipe-it.test/departments",
"actions" : [
"navigate to https://snipe-it.test/departments",
"screen capture tests/pa11y/department-list.png"
]
},
{
"url" : "https://snipe-it.test/departments/create",
"actions" : [
"navigate to https://snipe-it.test/departments/create",
"screen capture tests/pa11y/department-create.png"
]
},
{
"url" : "https://snipe-it.test/departments/1",
"actions" : [
"navigate to https://snipe-it.test/departments/1",
"screen capture tests/pa11y/department-view.png"
]
},
{
"url" : "https://snipe-it.test/invalid-url",
"actions" : [
"navigate to https://snipe-it.test/invalid-url",
"screen capture tests/pa11y/404.png"
]
}
]
}

View File

@@ -42,32 +42,18 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/qveensi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") |
| [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") |
| [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") |
| [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3302372?v=4" width="110px;"/><br /><sub>Jared Busch</sub>](https://github.com/sorvani)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [<img src="https://avatars.githubusercontent.com/u/111956991?v=4" width="110px;"/><br /><sub>seanborg-codethink</sub>](https://github.com/seanborg-codethink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [<img src="https://avatars.githubusercontent.com/u/160669961?v=4" width="110px;"/><br /><sub>dkaatz</sub>](https://github.com/dkaatz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [<img src="https://avatars.githubusercontent.com/u/827205?v=4" width="110px;"/><br /><sub>Daniel Ruf</sub>](https://threema.id/74SF7MW6?text=)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [<img src="https://avatars.githubusercontent.com/u/38883201?v=4" width="110px;"/><br /><sub>ahpaleus</sub>](https://github.com/ahpaleus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [<img src="https://avatars.githubusercontent.com/u/22906055?v=4" width="110px;"/><br /><sub>Anh DAO-DUY</sub>](https://github.com/mink-adao-duy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [<img src="https://avatars.githubusercontent.com/u/4723453?v=4" width="110px;"/><br /><sub>Andres Gutierrez</sub>](https://github.com/Serdnad)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111083379?v=4" width="110px;"/><br /><sub>Warren White</sub>](https://github.com/wewhite)<br />[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [<img src="https://avatars.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://robintemme.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [<img src="https://avatars.githubusercontent.com/u/47008367?v=4" width="110px;"/><br /><sub>herroworrd</sub>](https://github.com/herroworrd)<br />[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [<img src="https://avatars.githubusercontent.com/u/28558609?v=4" width="110px;"/><br /><sub>vicleos</sub>](https://mubiu.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [<img src="https://avatars.githubusercontent.com/u/1016780?v=4" width="110px;"/><br /><sub>Bob Clough</sub>](http://thinkl33t.co.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [<img src="https://avatars.githubusercontent.com/u/10648463?v=4" width="110px;"/><br /><sub>Brandon Daniel Bailey</sub>](https://github.com/brandon-bailey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [<img src="https://avatars.githubusercontent.com/u/23556080?v=4" width="110px;"/><br /><sub>Marc Bartelt</sub>](https://github.com/marcquark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") |
| [<img src="https://avatars.githubusercontent.com/u/18286893?v=4" width="110px;"/><br /><sub>manu-crealytics</sub>](https://github.com/manu-crealytics)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [<img src="https://avatars.githubusercontent.com/u/18245993?v=4" width="110px;"/><br /><sub>Konstantin Köhring</sub>](https://www.galaxy102.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [<img src="https://avatars.githubusercontent.com/u/685167?v=4" width="110px;"/><br /><sub>Deloz</sub>](https://deloz.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [<img src="https://avatars.githubusercontent.com/u/2682426?v=4" width="110px;"/><br /><sub>Martin Berg</sub>](https://github.com/mbrrg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [<img src="https://avatars.githubusercontent.com/u/3694534?v=4" width="110px;"/><br /><sub>Richard Schwab</sub>](https://github.com/Nothing4You)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [<img src="https://avatars.githubusercontent.com/u/8959676?v=4" width="110px;"/><br /><sub>Rick Heil</sub>](https://rickheil.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [<img src="https://avatars.githubusercontent.com/u/397106?v=4" width="110px;"/><br /><sub>Ross Crawford-d'Heureuse</sub>](https://github.com/rosscdh)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1621107?v=4" width="110px;"/><br /><sub>Ryan McGuire</sub>](https://github.com/McG800)<br />[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [<img src="https://avatars.githubusercontent.com/u/77835667?v=4" width="110px;"/><br /><sub>SBrown2021</sub>](https://github.com/SBrown2021)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [<img src="https://avatars.githubusercontent.com/u/8780913?v=4" width="110px;"/><br /><sub>Serkan</sub>](https://github.com/serkanerip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [<img src="https://avatars.githubusercontent.com/u/63188620?v=4" width="110px;"/><br /><sub>Shanks</sub>](https://www.yudelei.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [<img src="https://avatars.githubusercontent.com/u/198525698?v=4" width="110px;"/><br /><sub>cendai-mis</sub>](https://github.com/cendai-mis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [<img src="https://avatars.githubusercontent.com/u/8724583?v=4" width="110px;"/><br /><sub>Shaun McPeck</sub>](https://smcpeck.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [<img src="https://avatars.githubusercontent.com/u/1378836?v=4" width="110px;"/><br /><sub>Stephen</sub>](https://github.com/snazy2000)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/4462739?v=4" width="110px;"/><br /><sub>Steven</sub>](http://nevets82.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [<img src="https://avatars.githubusercontent.com/u/29017267?v=4" width="110px;"/><br /><sub>Mateus Villar</sub>](https://mateusvillar.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [<img src="https://avatars.githubusercontent.com/u/12749393?v=4" width="110px;"/><br /><sub>Matthew Zackschewski</sub>](https://github.com/mzack5020)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [<img src="https://avatars.githubusercontent.com/u/12660103?v=4" width="110px;"/><br /><sub>Matthias Frei</sub>](https://www.frei.media/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [<img src="https://avatars.githubusercontent.com/u/824840?v=4" width="110px;"/><br /><sub>Nenad Ticaric</sub>](https://github.com/nticaric)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [<img src="https://avatars.githubusercontent.com/u/706439?v=4" width="110px;"/><br /><sub>Nikolay Didenko</sub>](https://github.com/Scorcher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [<img src="https://avatars.githubusercontent.com/u/5457236?v=4" width="110px;"/><br /><sub>Nuno Maduro</sub>](https://nunomaduro.com/sponsorships)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") |
| [<img src="https://avatars.githubusercontent.com/u/8883074?v=4" width="110px;"/><br /><sub>Oliver Walerys</sub>](https://tektikhq.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [<img src="https://avatars.githubusercontent.com/u/3102039?v=4" width="110px;"/><br /><sub>R. Christian McDonald</sub>](https://keybase.io/rcmcdonald91)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [<img src="https://avatars.githubusercontent.com/u/1525581?v=4" width="110px;"/><br /><sub>nix</sub>](https://nnix.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [<img src="https://avatars.githubusercontent.com/u/55462380?v=4" width="110px;"/><br /><sub>octobunny</sub>](https://github.com/octobunny)<br />[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [<img src="https://avatars.githubusercontent.com/u/8558670?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/sreyemnayr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>p3nj</sub>](https://benji.ltd/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [<img src="https://avatars.githubusercontent.com/u/6201617?v=4" width="110px;"/><br /><sub>Tim White</sub>](https://github.com/timwsuqld)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") |
| [<img src="https://avatars.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>yannikp</sub>](https://github.com/yannikp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [<img src="https://avatars.githubusercontent.com/u/20525448?v=4" width="110px;"/><br /><sub>victoria</sub>](https://github.com/viclou)<br />[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [<img src="https://avatars.githubusercontent.com/u/40685314?v=4" width="110px;"/><br /><sub>Valentyn Tulub</sub>](https://github.com/valentyntu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [<img src="https://avatars.githubusercontent.com/u/864520?v=4" width="110px;"/><br /><sub>Wouter van Os</sub>](http://wouter0100.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [<img src="https://avatars.githubusercontent.com/u/3946540?v=4" width="110px;"/><br /><sub>Wyatt Teeter</sub>](https://www.linkedin.com/in/wyatt-teeter)<br />[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [<img src="https://avatars.githubusercontent.com/u/1596124?v=4" width="110px;"/><br /><sub>Yorick Terweijden</sub>](https://github.com/terwey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [<img src="https://avatars.githubusercontent.com/u/69298836?v=4" width="110px;"/><br /><sub>bmkalle</sub>](https://github.com/bmkalle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28403467?v=4" width="110px;"/><br /><sub>bricelabelle</sub>](https://github.com/bricelabelle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [<img src="https://avatars.githubusercontent.com/u/97770090?v=4" width="110px;"/><br /><sub>corydlamb</sub>](https://github.com/corydlamb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [<img src="https://avatars.githubusercontent.com/u/1154133?v=4" width="110px;"/><br /><sub>Diogenes S. Jesus</sub>](http://twitter.com/splash)<br />[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [<img src="https://avatars.githubusercontent.com/u/5826629?v=4" width="110px;"/><br /><sub>D M</sub>](https://github.com/dkmansion)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [<img src="https://avatars.githubusercontent.com/u/14837699?v=4" width="110px;"/><br /><sub>Dustin B</sub>](https://github.com/Jarli01)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [<img src="https://avatars.githubusercontent.com/u/348344?v=4" width="110px;"/><br /><sub>Fabian Grutschus</sub>](https://github.com/fabiang)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [<img src="https://avatars.githubusercontent.com/u/1491053?v=4" width="110px;"/><br /><sub>MelonSmasher</sub>](https://github.com/MelonSmasher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") |
| [<img src="https://avatars.githubusercontent.com/u/80526133?v=4" width="110px;"/><br /><sub>AlexanderWPapyrus</sub>](https://github.com/AlexanderWPapyrus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [<img src="https://avatars.githubusercontent.com/u/306231?v=4" width="110px;"/><br /><sub>Alexandr Hacicheant</sub>](https://github.com/disc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [<img src="https://avatars.githubusercontent.com/u/3032891?v=4" width="110px;"/><br /><sub>Hex</sub>](https://hex128.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [<img src="https://avatars.githubusercontent.com/u/8697942?v=4" width="110px;"/><br /><sub>Arunas Skirius</sub>](https://github.com/arukompas)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [<img src="https://avatars.githubusercontent.com/u/104396?v=4" width="110px;"/><br /><sub>Ben Periton</sub>](https://github.com/benperiton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [<img src="https://avatars.githubusercontent.com/u/11906832?v=4" width="110px;"/><br /><sub>Byron Wolfman</sub>](https://wolfman.dev/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [<img src="https://avatars.githubusercontent.com/u/56485508?v=4" width="110px;"/><br /><sub>Calvin</sub>](https://github.com/CalvinSchwartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") |
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -2,7 +2,7 @@ FROM ubuntu:24.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference:
# - https://github.com/grokability/snipe-it/pull/9201
# - https://github.com/snipe/snipe-it/pull/9201
# - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
RUN export DEBIAN_FRONTEND=noninteractive; \
@@ -110,7 +110,7 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Get dependencies
USER docker
RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html && rm -rf /var/www/html/vendor/*/*/.git
RUN composer install --no-dev --working-dir=/var/www/html
USER root
############### APPLICATION INSTALL/INIT #################

View File

@@ -70,7 +70,7 @@ COPY --from=composer /usr/bin/composer /usr/local/bin
ARG COMPOSER_ALLOW_SUPERUSER=1
RUN set -eux; \
# Download and extract snipeit tarball
curl -o snipeit.tar.gz -fL "https://github.com/grokability/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
curl -o snipeit.tar.gz -fL "https://github.com/snipe/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
tar -xzf snipeit.tar.gz --strip-components=1 -C /var/www/html/; \
rm snipeit.tar.gz; \
# Install composer php dependencies

View File

@@ -1,6 +1,6 @@
![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
![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/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/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/) [![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
@@ -9,7 +9,7 @@ This is a FOSS project for asset management in IT Operations. Knowing who has wh
It is built on [Laravel 11](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/grokability/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
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, 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.
@@ -44,7 +44,7 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
-----
### Bug Reports & Feature Requests
Feel free to check out the [GitHub Issues for this project](https://github.com/grokability/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
> [!IMPORTANT]
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
@@ -76,37 +76,26 @@ Since the release of the JSON REST API, several third-party developers have been
> [!NOTE]
> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
#### Libraries & Modules
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://www.edtechirl.com/p/snipe-it-and-azure-asset-management) originally by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
- [InQRy (archived)](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [Marksman (archived)](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Python Module (archived)](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks.
#### Mobile Apps
We're currently working on our own mobile app, but in the meantime, check out these third-party apps that work with Snipe-IT:
- [SnipeMate](https://snipemate.app/) (iOS, Google Play, Huawei AppGallery) by Mars Technology
- [Snipe-Scan](https://apps.apple.com/do/app/snipe-scan/id6744179400?uo=2) (iOS) by Nicolas Maton
- [Snipe-IT Assets Management](https://play.google.com/store/apps/details?id=com.diegogarciadev.assetsmanager.snipeit&hl=en&pli=1) (Google Play) by DiegoGarciaDEV
- [AssetX](https://apps.apple.com/my/app/assetx-for-snipe-it/id6741996196?uo=2) (iOS) for Snipe-IT by Rishi Gupta
-----
### Join the Community!
@@ -133,15 +122,9 @@ The ERD is available [online here](https://drawsql.app/templates/snipe-it).
Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years!
-----
### Star History
[![Star History Chart](https://api.star-history.com/svg?repos=grokability/snipe-it&type=Date)](https://www.star-history.com/#grokability/snipe-it&Date)
------
### Announcement List
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
We also usually make smaller announcements on our social accounts, our Discord, and our blog, so be sure to subscribe to those if you're looking for more granular announcements.

View File

@@ -11,7 +11,6 @@ make it impossible to backport security fixes on older versions.
| Version | Supported |
|---------| ------------------ |
| 8.x | :white_check_mark: |
| 7.x | :white_check_mark: |
| 6.x | :x: |
| 5.1.x | :x: |

2
Vagrantfile vendored
View File

@@ -1,7 +1,7 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh"
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
Vagrant.configure("2") do |config|

View File

@@ -6,7 +6,7 @@
"it asset"
],
"website": "https://snipeitapp.com/",
"repository": "https://github.com/grokability/snipe-it",
"repository": "https://github.com/snipe/snipe-it",
"logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg",
"success_url": "/setup",
"env": {

View File

@@ -1,48 +0,0 @@
<?php
namespace App\Actions\CheckoutRequests;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetCancelation;
use Illuminate\Auth\Access\AuthorizationException;
class CancelCheckoutRequestAction
{
public static function run(Asset $asset, User $user)
{
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
}
$asset->cancelRequest();
$asset->decrement('requests_counter', 1);
$data['item'] = $asset;
$data['target'] = $user;
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
$logaction->location_id = $user->location_id ?? null;
$logaction->logaction('request canceled');
try {
$settings->notify(new RequestAssetCancelation($data));
} catch (\Exception $e) {
\Log::warning($e);
}
return true;
}
}

View File

@@ -1,54 +0,0 @@
<?php
namespace App\Actions\CheckoutRequests;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetNotification;
use Illuminate\Auth\Access\AuthorizationException;
use Log;
class CreateCheckoutRequestAction
{
/**
* @throws AssetNotRequestable
* @throws AuthorizationException
*/
public static function run(Asset $asset, User $user): string
{
if (is_null(Asset::RequestableAssets()->find($asset->id))) {
throw new AssetNotRequestable($asset);
}
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
}
$data['item'] = $asset;
$data['target'] = $user;
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
$logaction->location_id = $user->location_id ?? null;
$logaction->logaction('requested');
$asset->request();
$asset->increment('requests_counter', 1);
try {
$settings->notify(new RequestAssetNotification($data));
} catch (\Exception $e) {
Log::warning($e);
}
return true;
}
}

View File

@@ -1,53 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Setting;
use Illuminate\Console\Command;
class DisableSAML extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:saml-disable';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This is a rescue command that can be used to turn off SAML settings in the event that you managed to lock yourself out using bad SAML settings.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if ($this->confirm("\n****************************************************\nThis will disable SAML support. You will not be able \nto login with an account that does not exist \nlocally in the Snipe-IT local database. \n****************************************************\n\nDo you wish to continue? [y|N]")) {
$setting = Setting::getSettings();
$setting->saml_enabled = 0;
if ($setting->save()) {
$this->info('SAML has been set to disabled.');
} else {
$this->info('Unable to disable SAML.');
}
} else {
$this->info('Canceled. No actions taken.');
}
}
}

View File

@@ -1,151 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Accessory;
use App\Models\Actionlog;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
class FixBulkAccessoryCheckinActionLogEntries extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:fix-bulk-accessory-action-log-entries {--dry-run : Run the sync process but don\'t update the database} {--skip-backup : Skip pre-execution backup}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This script attempts to fix timestamps and missing created_by values for bulk checkin entries in the log table';
private bool $dryrun = false;
private bool $skipBackup = false;
/**
* Execute the console command.
*/
public function handle()
{
$this->skipBackup = $this->option('skip-backup');
$this->dryrun = $this->option('dry-run');
if ($this->dryrun) {
$this->info('This is a DRY RUN - no changes will be saved.');
$this->newLine();
}
$logs = Actionlog::query()
// only look for accessory checkin logs
->where('item_type', Accessory::class)
// that were part of a bulk checkin
->where('note', 'Bulk checkin items')
// logs that were improperly timestamped should have created_at in the 1970s
->whereYear('created_at', '1970')
->get();
if ($logs->isEmpty()) {
$this->info('No logs found with incorrect timestamps.');
return 0;
}
$this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:');
$this->table(
['ID', 'Created By', 'Created At', 'Updated At'],
$logs->map(function ($log) {
return [
$log->id,
$log->created_by,
$log->created_at,
$log->updated_at,
];
})
);
if (!$this->dryrun && !$this->confirm('Update these logs?')) {
return 0;
}
if (!$this->dryrun && !$this->skipBackup) {
$this->info('Backing up the database before making changes...');
$this->call('snipeit:backup');
}
if ($this->dryrun) {
$this->newLine();
$this->info('DRY RUN. NOT ACTUALLY UPDATING LOGS.');
}
foreach ($logs as $log) {
$this->newLine();
$this->info('Processing log id:' . $log->id);
// created_by was not being set for accessory bulk checkins
// so let's see if there was another bulk checkin log
// with the same timestamp and a created_by value we can use.
if (is_null($log->created_by)) {
$createdByFromSimilarLog = $this->getCreatedByAttributeFromSimilarLog($log);
if ($createdByFromSimilarLog) {
$this->line(vsprintf('Updating log id:%s created_by to %s', [$log->id, $createdByFromSimilarLog]));
$log->created_by = $createdByFromSimilarLog;
} else {
$this->warn(vsprintf('No created_by found for log id:%s', [$log->id]));
$this->warn('Skipping updating this log since no similar log was found to update created_by from.');
// If we can't find a similar log then let's skip updating it
continue;
}
}
$this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at]));
$log->created_at = $log->updated_at;
if (!$this->dryrun) {
Model::withoutTimestamps(function () use ($log) {
$log->saveQuietly();
});
}
}
$this->newLine();
if ($this->dryrun) {
$this->info('DRY RUN. NO CHANGES WERE ACTUALLY MADE.');
}
return 0;
}
/**
* Hopefully the bulk checkin included other items like assets or licenses
* so we can use one of those logs to get the correct created_by value.
*
* This method attempts to find a bulk check in log that was
* created at the same time as the log passed in.
*/
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int
{
$similarLog = Actionlog::query()
->whereNotNull('created_by')
->where([
'action_type' => 'checkin from',
'note' => 'Bulk checkin items',
'target_id' => $log->target_id,
'target_type' => $log->target_type,
'created_at' => $log->updated_at,
])
->first();
if ($similarLog) {
return $similarLog->created_by;
}
return null;
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class FixUpAssignedTypeWithoutAssignedTo extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:assigned-type-fixup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fixes up assets that have an assigned_type but no assigned_to';
/**
* Execute the console command.
*/
public function handle()
{
DB::table('assets')->whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]);
$this->info("Assets with an assigned_type but no assigned_to are fixed");
}
}

View File

@@ -2,9 +2,11 @@
namespace App\Console\Commands;
use App\Helpers\Helper;
use Illuminate\Console\Command;
use App\Models\User;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\DB;
class GeneratePersonalAccessToken extends Command
@@ -41,8 +43,9 @@ class GeneratePersonalAccessToken extends Command
*
* @return void
*/
public function __construct(TokenRepository $tokenRepository)
public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
{
$this->validation = $validation;
$this->tokenRepository = $tokenRepository;
parent::__construct();
}
@@ -73,7 +76,7 @@ class GeneratePersonalAccessToken extends Command
} else {
$this->warn('Your API Token has been created. Be sure to copy this token now, as it WILL NOT be accessible again.');
$this->warn('Your API Token has been created. Be sure to copy this token now, as it will not be accessible again.');
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) {
$this->info('API Token ID: '.$token->id);

View File

@@ -182,7 +182,7 @@ class LdapSync extends Command
// Inject location information fields
for ($i = 0; $i < $results['count']; $i++) {
$results[$i]['ldap_location_override'] = false;
$results[$i]['location_id'] = null;
$results[$i]['location_id'] = 0;
}
// Grab subsets based on location-specific DNs, and overwrite location for these users.
@@ -361,15 +361,9 @@ class LdapSync extends Command
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
$user->activated = !($filter_var ?? $boolean_cast);
}else{
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
}
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
@@ -434,12 +428,8 @@ class LdapSync extends Command
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
// Check if the relationship already exists
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
}
$user->groups()->attach($ldap_default_group);
}
//updates assets location based on user's location
if ($user->wasChanged('location_id')) {
foreach ($user->assets as $asset) {

View File

@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use Illuminate\Support\Facades\Schema;
use Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
@@ -51,7 +51,8 @@ class PaveIt extends Command
}
// List all the tables in the database so we don't have to worry about missing some as the app grows
$tables = Schema::getTables();
$tables = DB::connection()->getDoctrineSchemaManager()->listTableNames();
$except_tables = [
'oauth_access_tokens',
'oauth_clients',
@@ -66,15 +67,14 @@ class PaveIt extends Command
foreach ($custom_fields as $custom_field) {
$this->info('DROP the '.$custom_field->db_column.' column from assets as well.');
if (Schema::hasColumn('assets', $custom_field->db_column)) {
Schema::table('assets', function ($table) use ($custom_field) {
if (\Schema::hasColumn('assets', $custom_field->db_column)) {
\Schema::table('assets', function ($table) use ($custom_field) {
$table->dropColumn($custom_field->db_column);
});
}
}
foreach ($tables as $table_obj) {
$table = $table_obj['name'];
foreach ($tables as $table) {
if (in_array($table, $except_tables)) {
$this->info($table. ' is SKIPPED.');
} else {
@@ -84,8 +84,8 @@ class PaveIt extends Command
}
// Leave in the demo oauth keys so we don't have to reset them every day in the demos
DB::statement('delete from oauth_clients WHERE id > 2');
DB::statement('delete from oauth_access_tokens WHERE user_id > 2');
\DB::statement('delete from oauth_clients WHERE id > 2');
\DB::statement('delete from oauth_access_tokens WHERE id > 2');
}
}

View File

@@ -249,7 +249,6 @@ class RestoreFromBackup extends Command
'storage/private_uploads/consumables',
'storage/private_uploads/eula-pdfs',
'storage/private_uploads/imports',
'storage/private_uploads/locations',
'storage/private_uploads/licenses',
'storage/private_uploads/signatures',
'storage/private_uploads/users',
@@ -290,7 +289,6 @@ class RestoreFromBackup extends Command
$interesting_files = [];
$boring_files = [];
$unsafe_files = [];
for ($i = 0; $i < $za->numFiles; $i++) {
$stat_results = $za->statIndex($i);
@@ -329,9 +327,8 @@ class RestoreFromBackup extends Command
}
}
}
$good_extensions = config('filesystems.allowed_upload_extensions_array');
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
foreach (array_merge($private_files, $public_files) as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
@@ -341,9 +338,7 @@ class RestoreFromBackup extends Command
if ($last_pos !== false) {
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
if (!in_array($extension, $good_extensions)) {
// gathering potentially unsafe files here to return at exit
$unsafe_files[] = $raw_path;
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
$this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
$boring_files[] = $raw_path;
continue 2;
}
@@ -377,7 +372,6 @@ class RestoreFromBackup extends Command
if ($this->option('sanitize-guess-prefix')) {
$prefix = SQLStreamer::guess_prefix($sql_contents);
$this->line($prefix);
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitize your SQL.");
}
@@ -511,11 +505,6 @@ class RestoreFromBackup extends Command
} else {
$this->info(count($interesting_files).' files were succesfully transferred');
}
if (count($unsafe_files) > 0) {
foreach ($unsafe_files as $unsafe_file) {
$this->warn('Potentially unsafe file '.$unsafe_file.' was skipped');
}
}
foreach ($boring_files as $boring_file) {
$this->warn($boring_file.' was skipped.');
}

View File

@@ -70,26 +70,23 @@ class SendAcceptanceReminder extends Command
// The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user.
// Collapsing and flattening the collection doesn't work above.
$acceptance = $unacceptedAssetGroup[0]['acceptance'];
$locale = $acceptance->assignedTo?->locale;
$email = $acceptance->assignedTo?->email;
if(!$email){
$no_email_list[] = [
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->present()->fullName(),
'id' => $acceptance->assignedTo->id,
'name' => $acceptance->assignedTo->present()->fullName(),
];
} else {
$count++;
}
$item_count = $unacceptedAssetGroup->count();
if ($locale && $email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count)));
}
$count++;
}
$this->info($count.' users notified.');
@@ -99,11 +96,8 @@ class SendAcceptanceReminder extends Command
foreach ($no_email_list as $user) {
$rows[] = [$user['id'], $user['name']];
}
if (!empty($rows)) {
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
}
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
return 0;
}

View File

@@ -42,28 +42,27 @@ class SendExpirationAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
$alert_interval = $settings->alert_interval;
$threshold = $settings->alert_interval;
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item)) // Trim each email
->filter(fn($item) => !empty($item))
->all();
// Expiring Assets
$assets = Asset::getExpiringWarrantee($alert_interval);
$assets = Asset::getExpiringWarrantee($threshold);
if ($assets->count() > 0) {
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $threshold));
}
// Expiring licenses
$licenses = License::getExpiringLicenses($alert_interval);
$licenses = License::getExpiringLicenses($threshold);
if ($licenses->count() > 0) {
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $threshold));
}
} else {
if ($settings->alert_email == '') {

View File

@@ -2,12 +2,13 @@
namespace App\Console\Commands;
use App\Mail\SendUpcomingAuditMail;
use App\Models\Asset;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class SendUpcomingAuditReport extends Command
{
@@ -47,20 +48,19 @@ class SendUpcomingAuditReport extends Command
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$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');
$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 ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
}
}

View File

@@ -1,51 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Helpers\Helper;
use Illuminate\Console\Command;
class TestLocationsFMCS extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:test-locations-fmcs {--location_id=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Test for company ID inconsistencies if FullMultipleCompanySupport with scoped locations will be used.';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('This script checks for company ID inconsistencies if Full Multiple Company Support with scoped locations will be used.');
$this->info('This could take a few moments if have a very large dataset.');
$this->newLine();
// if parameter location_id is set, only test this location
$location_id = null;
if ($this->option('location_id')) {
$location_id = $this->option('location_id');
}
$mismatched = Helper::test_locations_fmcs(true, $location_id);
$this->warn(trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)));
$this->newLine();
$this->info('Edit your locations to associate them with the correct company.');
$header = ['Type', 'ID', 'Name', 'Checkout Type', 'Company ID', 'Item Company', 'Item Location', 'Location Company', 'Location Company ID'];
sort($mismatched);
$this->table($header, $mismatched);
}
}

View File

@@ -5,7 +5,6 @@ namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use App\Models\Setting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -19,14 +18,12 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
if(Setting::getSettings()?->alerts_enabled === 1) {
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
$schedule->command('saml:clear_expired_nonces')->weekly();
}

View File

@@ -28,7 +28,7 @@ class CheckoutableCheckedIn
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
$this->action_date = $action_date ?? date('Y-m-d H:i:s');
$this->action_date = $action_date ?? date('Y-m-d');
$this->originalValues = $originalValues;
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class AssetNotRequestable extends Exception
{
}

View File

@@ -11,7 +11,6 @@ use Illuminate\Support\Facades\Log;
use Throwable;
use JsonException;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
class Handler extends ExceptionHandler
{
@@ -108,34 +107,25 @@ class Handler extends ExceptionHandler
$statusCode = $e->getStatusCode();
// API throttle requests are handled in the RouteServiceProvider configureRateLimiting() method, so we don't need to handle them here
switch ($e->getStatusCode()) {
case '404':
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404);
case '429':
return response()->json(Helper::formatStandardApiResponse('error', null, 'Too many requests'), 429);
case '405':
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
default:
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode);
}
}
// This handles API validation exceptions that happen at the Form Request level, so they
// never even get to the controller where we normally nicely format JSON responses
if ($e instanceof ValidationException) {
$response = $this->invalidJson($request, $e);
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
}
}
// This is traaaaash but it handles models that are not found while using route model binding :(
// The only alternative is to set that at *each* route, which is crazypants
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
// This gets the MVC model name from the exception and formats in a way that's less fugly
$model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))));
$model_name = last(explode('\\', $e->getModel()));
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
// Sigh.
@@ -147,14 +137,6 @@ class Handler extends ExceptionHandler
$route = 'models.index';
} elseif ($route == 'predefinedkits.index') {
$route = 'kits.index';
} elseif ($route == 'assetmaintenances.index') {
$route = 'maintenances.index';
} elseif ($route === 'licenseseats.index') {
$route = 'licenses.index';
} elseif ($route === 'customfields.index') {
$route = 'fields.index';
} elseif ($route === 'customfieldsets.index') {
$route = 'fields.index';
}
return redirect()
@@ -213,9 +195,8 @@ class Handler extends ExceptionHandler
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
}
}

View File

@@ -1,10 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class UserDoestExistException extends Exception
{
}

View File

@@ -12,8 +12,6 @@ use App\Models\Depreciation;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\License;
use App\Models\Location;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
use Carbon\Carbon;
@@ -723,8 +721,8 @@ class Helper
// The check and message that the user is still using the deprecated version
$deprecations = [
'ms_teams_deprecated' => array(
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
];
// if item of concern is being used and its being used with the deprecated values return the notification array.
@@ -870,49 +868,7 @@ class Helper
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif') || ($filetype == 'image/webp')) {
return $filetype;
}
return false;
}
/**
* Check if the file is a video, so we can show a preview
*
* @param File $file
* @return string | Boolean
* @author [B. Wetherington] [<bwetherington@grokability.com>]
* @since [v8.1.18]
*/
public static function checkUploadIsVideo($file)
{
$finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
if (($filetype == 'video/mp4') || ($filetype == 'video/quicktime') || ($filetype == 'video/mpeg') || ($filetype == 'video/ogg') || ($filetype == 'video/webm') || ($filetype == 'video/x-msvide')) {
return $filetype;
}
return false;
}
/**
* Check if the file is audio, so we can show a preview
*
* @param File $file
* @return string | Boolean
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
*/
public static function checkUploadIsAudio($file)
{
$finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
if (($filetype == 'audio/mpeg') || ($filetype == 'audio/ogg')) {
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
return $filetype;
}
@@ -939,12 +895,6 @@ class Helper
public static function selectedPermissionsArray($permissions, $selected_arr = [])
{
$permissions_arr = [];
if (is_array($permissions)) {
$permissions = json_encode($permissions);
}
// Set default to empty JSON if the value is null
$permissions = json_decode($permissions ?? '{}', JSON_OBJECT_AS_ARRAY);
foreach ($permissions as $permission) {
for ($x = 0; $x < count($permission); $x++) {
@@ -955,13 +905,13 @@ class Helper
if (is_array($selected_arr)) {
if (array_key_exists($permission_name, $selected_arr)) {
$permissions_arr[$permission_name] = (int) $selected_arr[$permission_name];
$permissions_arr[$permission_name] = $selected_arr[$permission_name];
} else {
$permissions_arr[$permission_name] = 0;
$permissions_arr[$permission_name] = '0';
}
} else {
$permissions_arr[$permission_name] = 0;
$permissions_arr[$permission_name] = '0';
}
}
}
@@ -1213,15 +1163,6 @@ class Helper
// Misc
'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save',
// video
'mov' => 'fa-solid fa-video',
'mp4' => 'fa-solid fa-video',
// audio
'ogg' => 'fa-solid fa-file-audio',
'mp3' => 'fa-solid fa-file-audio',
'wav' => 'fa-solid fa-file-audio',
];
if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
@@ -1365,24 +1306,25 @@ class Helper
switch ($item) {
case 'asset':
return 'fas fa-barcode';
break;
case 'accessory':
return 'fas fa-keyboard';
break;
case 'component':
return 'fas fa-hdd';
break;
case 'consumable':
return 'fas fa-tint';
break;
case 'license':
return 'far fa-save';
break;
case 'location':
return 'fas fa-map-marker-alt';
break;
case 'user':
return 'fas fa-user';
case 'supplier':
return 'fa-solid fa-store';
case 'manufacturer':
return 'fa-solid fa-building';
case 'category':
return 'fa-solid fa-table-columns';
break;
}
}
@@ -1532,173 +1474,59 @@ class Helper
}
static public function getRedirectOption($request, $id, $table, $item_id = null) : RedirectResponse
static public function getRedirectOption($request, $id, $table, $item_id = null)
{
$redirect_option = Session::get('redirect_option') ?? $request->redirect_option;
$checkout_to_type = Session::get('checkout_to_type') ?? null;
$checkedInFrom = Session::get('checkedInFrom');
$other_redirect = Session::get('other_redirect');
$backUrl = Session::pull('back_url', route('home'));
// return to previous page
if ($redirect_option === 'back') {
if ($backUrl === route('home')) {
return redirect()->to($backUrl)
->with('warning', trans('general.page_error'));
}
return redirect()->to($backUrl);
}
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
// return to index
if ($redirect_option == 'index') {
return match ($table) {
'Assets' => redirect()->route('hardware.index'),
'Users' => redirect()->route('users.index'),
'Licenses' => redirect()->route('licenses.index'),
'Accessories' => redirect()->route('accessories.index'),
'Components' => redirect()->route('components.index'),
'Consumables' => redirect()->route('consumables.index'),
};
switch ($table) {
case "Assets":
return route('hardware.index');
case "Users":
return route('users.index');
case "Licenses":
return route('licenses.index');
case "Accessories":
return route('accessories.index');
case "Components":
return route('components.index');
case "Consumables":
return route('consumables.index');
}
}
// return to thing being assigned
if ($redirect_option == 'item') {
return match ($table) {
'Assets' => redirect()->route('hardware.show', $id ?? $item_id),
'Users' => redirect()->route('users.show', $id ?? $item_id),
'Licenses' => redirect()->route('licenses.show', $id ?? $item_id),
'Accessories' => redirect()->route('accessories.show', $id ?? $item_id),
'Components' => redirect()->route('components.show', $id ?? $item_id),
'Consumables' => redirect()->route('consumables.show', $id ?? $item_id),
};
switch ($table) {
case "Assets":
return route('hardware.show', $id ?? $item_id);
case "Users":
return route('users.show', $id ?? $item_id);
case "Licenses":
return route('licenses.show', $id ?? $item_id);
case "Accessories":
return route('accessories.show', $id ?? $item_id);
case "Components":
return route('components.show', $id ?? $item_id);
case "Consumables":
return route('consumables.show', $id ?? $item_id);
}
}
// return to assignment target
if ($redirect_option == 'target') {
return match ($checkout_to_type) {
'user' => redirect()->route('users.show', $request->assigned_user ?? $checkedInFrom),
'location' => redirect()->route('locations.show', $request->assigned_location ?? $checkedInFrom),
'asset' => redirect()->route('hardware.show', $request->assigned_asset ?? $checkedInFrom),
};
switch ($checkout_to_type) {
case 'user':
return route('users.show', $request->assigned_user);
case 'location':
return route('locations.show', $request->assigned_location);
case 'asset':
return route('hardware.show', $request->assigned_asset);
}
}
// return to somewhere else
if ($redirect_option == 'other_redirect') {
return match ($other_redirect) {
'audit' => redirect()->route('assets.audit.due'),
'model' => redirect()->route('models.show', $request->model_id),
};
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
}
/**
* Check for inconsistencies before activating scoped locations with FullMultipleCompanySupport
* If there are locations with different companies than related objects unforseen problems could arise
*
* @author T. Regnery <tobias.regnery@gmail.com>
* @since 7.0
*
* @param $artisan when false, bail out on first inconsistent entry
* @param $location_id when set, only test this specific location
* @param $new_company_id in case of updating a location, this is the newly requested company_id
* @return string []
*/
static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) {
$mismatched = [];
if ($location_id) {
$location = Location::find($location_id);
if ($location) {
$locations = collect([])->push(Location::find($location_id));
}
} else {
$locations = Location::all();
}
// Bail out early if there are no locations
if ($locations->count() == 0) {
return [];
}
foreach($locations as $location) {
// in case of an update of a single location, use the newly requested company_id
if ($new_company_id) {
$location_company = $new_company_id;
} else {
$location_company = $location->company_id;
}
// Depending on the relationship, we must use different operations to retrieve the objects
$keywords_relation = [
'many' => [
'accessories',
'assets',
'assignedAccessories',
'assignedAssets',
'components',
'consumables',
'rtd_assets',
'users',
],
'one' => [
'manager',
'parent',
]];
// In case of a single location, the children must be checked as well, because we don't walk every location
if ($location_id) {
$keywords_relation['many'][] = 'children';
}
foreach ($keywords_relation as $relation => $keywords) {
foreach($keywords as $keyword) {
if ($relation == 'many') {
$items = $location->{$keyword}->all();
} else {
$items = collect([])->push($location->$keyword);
}
$count = 0;
foreach ($items as $item) {
if ($item && $item->company_id != $location_company) {
$mismatched[] = [
class_basename(get_class($item)),
$item->id,
$item->name ?? $item->asset_tag ?? $item->serial ?? $item->username,
$item->assigned_type ? str_replace('App\\Models\\', '', $item->assigned_type) : null,
$item->company_id ?? null,
$item->company->name ?? null,
// $item->defaultLoc->id ?? null,
// $item->defaultLoc->name ?? null,
// $item->defaultLoc->company->id ?? null,
// $item->defaultLoc->company->name ?? null,
$item->location->name ?? null,
$item->location->company->name ?? null,
$location_company ?? null,
];
$count++;
// Bail early if this is not being run via artisan
if ((!$artisan) && ($count > 0)) {
return $mismatched;
}
}
}
}
}
}
return $mismatched;
}
}

View File

@@ -16,14 +16,14 @@ class StorageHelper
$disk = config('filesystems.default');
}
switch (config("filesystems.disks.$disk.driver")) {
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
default:
return Storage::disk($disk)->download($filename);
default:
return Storage::disk($disk)->download($filename);
}
}
@@ -33,29 +33,21 @@ class StorageHelper
* to determine that they are safe to display inline.
*
* @author <A. Gianotto> [<snipe@snipe.net]>
* @since v7.0.14
* @param $file_with_path
* @since v7.0.14
* @param $file_with_path
* @return bool
*/
public static function allowSafeInline($file_with_path)
{
public static function allowSafeInline($file_with_path) {
$allowed_inline = [
'avif',
'gif',
'gif',
'jpg',
'mov',
'mp3',
'mp4',
'ogg',
'pdf',
'png',
'svg',
'jpg',
'gif',
'svg',
'wav',
'webm',
'avif',
'webp',
'png',
];
@@ -67,24 +59,10 @@ class StorageHelper
}
public static function getFiletype($file_with_path)
{
// The file exists and is allowed to be displayed inline
if (Storage::exists($file_with_path)) {
return pathinfo($file_with_path, PATHINFO_EXTENSION);
}
return null;
}
/**
* Decide whether to show the file inline or download it.
*/
public static function showOrDownloadFile($file, $filename)
{
public static function showOrDownloadFile($file, $filename) {
$headers = [];

View File

@@ -83,8 +83,7 @@ class AccessoriesController extends Controller
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
@@ -113,13 +112,13 @@ class AccessoriesController extends Controller
{
$this->authorize('create', Accessory::class);
$cloned = clone $accessory;
$cloned->id = null;
$cloned->deleted_at = '';
$cloned->location_id = null;
$accessory = clone $accessory;
$accessory->id = null;
$accessory->location_id = null;
return view('accessories/edit')
->with('item', $cloned);
->with('item', $accessory);
}
@@ -168,8 +167,7 @@ class AccessoriesController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($accessory->save()) {
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
@@ -186,15 +184,15 @@ class AccessoriesController extends Controller
*/
public function destroy($accessoryId) : RedirectResponse
{
if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) {
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize($accessory);
if ($accessory->checkouts_count > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
if ($accessory->hasUsers() > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()]));
}
if ($accessory->image) {
@@ -222,10 +220,7 @@ class AccessoriesController extends Controller
*/
public function show(Accessory $accessory) : View | RedirectResponse
{
$accessory->loadCount('checkouts as checkouts_count');
$accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id);
$this->authorize('view', $accessory);
return view('accessories.view', compact('accessory'));
}

View File

@@ -7,6 +7,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use \Illuminate\Contracts\View\View;
@@ -29,17 +30,9 @@ class AccessoryCheckinController extends Controller
}
$accessory = Accessory::find($accessory_user->accessory_id);
//based on what the accessory is checked out to the target redirect option will be displayed accordingly.
$target_option = match ($accessory_user->assigned_type) {
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]),
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
}
/**
@@ -58,14 +51,8 @@ class AccessoryCheckinController extends Controller
$accessory = Accessory::find($accessory_checkout->accessory_id);
session()->put('checkedInFrom', $accessory_checkout->assigned_to);
session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) {
'App\Models\User' => 'user',
'App\Models\Location' => 'location',
'App\Models\Asset' => 'asset',
});
$this->authorize('checkin', $accessory);
$checkin_hours = date('H:i:s');
$checkin_at = date('Y-m-d H:i:s');
if ($request->filled('checkin_at')) {
@@ -78,8 +65,7 @@ class AccessoryCheckinController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));

View File

@@ -97,7 +97,7 @@ class AccessoryCheckoutController extends Controller
// Redirect to the new accessory page
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
->with('success', trans('admin/accessories/message.checkout.success'));
}
}

View File

@@ -7,7 +7,6 @@ use App\Events\CheckoutDeclined;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Http\Controllers\Controller;
use App\Mail\CheckoutAcceptanceResponseMail;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
@@ -21,12 +20,9 @@ use App\Models\License;
use App\Models\Component;
use App\Models\Consumable;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
@@ -152,8 +148,6 @@ class AcceptanceController extends Controller
}
}
$assigned_user = User::find($acceptance->assigned_to_id);
// this is horrible
switch($acceptance->checkoutable_type){
case 'App\Models\Asset':
@@ -163,30 +157,35 @@ class AcceptanceController extends Controller
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
$display_model = $asset_model->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Accessory':
$pdf_view_route ='account.accept.accept-accessory-eula';
$accessory = Accessory::find($item->id);
$display_model = $accessory->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\LicenseSeat':
$pdf_view_route ='account.accept.accept-license-eula';
$license = License::find($item->license_id);
$display_model = $license->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Component':
$pdf_view_route ='account.accept.accept-component-eula';
$component = Component::find($item->id);
$display_model = $component->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
case 'App\Models\Consumable':
$pdf_view_route ='account.accept.accept-consumable-eula';
$consumable = Consumable::find($item->id);
$display_model = $consumable->name;
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
break;
}
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
@@ -209,12 +208,9 @@ class AcceptanceController extends Controller
*/
$branding_settings = SettingsController::getPDFBranding();
$path_logo = "";
// Check for the PDF logo path and use that, otherwise use the regular logo path
if (!is_null($branding_settings->acceptance_pdf_logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
} elseif (!is_null($branding_settings->logo)) {
if (is_null($branding_settings->logo)){
$path_logo = "";
} else {
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
}
@@ -227,7 +223,7 @@ class AcceptanceController extends Controller
'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_user->present()->fullName,
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => $path_logo,
@@ -241,19 +237,6 @@ class AcceptanceController extends Controller
}
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
// Send the PDF to the signing user
if (($request->input('send_copy') == '1') && ($assigned_user->email !='')) {
// Add the attachment for the signing user into the $data array
$data['file'] = $pdf_filename;
try {
$assigned_user->notify(new AcceptanceAssetAcceptedToUserNotification($data));
} catch (\Exception $e) {
Log::warning($e);
}
}
try {
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
} catch (\Exception $e) {
@@ -351,21 +334,6 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.declined');
}
if ($acceptance->alert_on_response_id) {
try {
$recipient = User::find($acceptance->alert_on_response_id);
if ($recipient) {
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
$acceptance,
$recipient,
$request->input('asset_acceptance') === 'accepted',
));
}
} catch (Exception $e) {
Log::warning($e);
}
}
return redirect()->to('account/accept')->with('success', $return_msg);

View File

@@ -66,7 +66,7 @@ class AccessoriesController extends Controller
}
if ($request->filled('company_id')) {
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
$accessories->where('company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -249,11 +249,11 @@ class AccessoriesController extends Controller
public function destroy($id)
{
$this->authorize('delete', Accessory::class);
$accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
$accessory = Accessory::findOrFail($id);
$this->authorize($accessory);
if ($accessory->checkouts_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/general.delete_disabled')));
if ($accessory->hasUsers() > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()])));
}
$accessory->delete();

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function list($assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that there are some uploads on this asset that can be listed
if ($asset->uploads->count() > 0) {
$files = array();
foreach ($asset->uploads as $upload) {
array_push($files, $upload);
}
// Give the list of files back to the user
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/hardware/message.upload.success')));
}
// There are no files.
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
}
/**
* Check for permissions and display the file.
*
* @param int $assetId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assets/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetId
* @param int $fileId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function destroy($assetId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assets';
// the asset is valid
if (isset($asset->id)) {
$this->authorize('update', $asset);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
}

View File

@@ -75,7 +75,6 @@ class AssetMaintenancesController extends Controller
'serial',
'created_by',
'supplier',
'location',
'is_warranty',
'status_label',
];
@@ -99,9 +98,6 @@ class AssetMaintenancesController extends Controller
case 'serial':
$maintenances = $maintenances->OrderByAssetSerial($order);
break;
case 'location':
$maintenances = $maintenances->OrderLocationName($order);
break;
case 'status_label':
$maintenances = $maintenances->OrderStatusName($order);
break;

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\AssetModel;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\AssetModelsTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetModelFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $assetModel);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
$assetModel->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetmodel
* @since [v7.0.12]
* @author [r-xyz]
*/
public function list($assetmodel_id) : JsonResponse | array
{
$assetmodel = AssetModel::with('uploads')->find($assetmodel_id);
$this->authorize('view', $assetmodel);
return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count());
}
/**
* Check for permissions and display the file.
*
* @param int $assetModelId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assetmodels/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetModelId
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('update', $assetModel);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
}

View File

@@ -85,12 +85,6 @@ class AssetModelsController extends Controller
$assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number'));
}
if ($request->input('requestable') == 'true') {
$assetmodels = $assetmodels->where('models.requestable', '=', '1');
} elseif ($request->input('requestable') == 'false') {
$assetmodels = $assetmodels->where('models.requestable', '=', '0');
}
if ($request->filled('notes')) {
$assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes'));
}
@@ -154,7 +148,7 @@ class AssetModelsController extends Controller
$assetmodel = $request->handleImages($assetmodel);
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
@@ -207,7 +201,7 @@ class AssetModelsController extends Controller
$assetmodel = AssetModel::findOrFail($id);
$assetmodel->fill($request->all());
$assetmodel = $request->handleImages($assetmodel);
/**
* Allow custom_fieldset_id to override and populate fieldset_id.
* This is stupid, but required for legacy API support.
@@ -222,7 +216,7 @@ class AssetModelsController extends Controller
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.update.success')));
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));

View File

@@ -34,7 +34,6 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
@@ -114,7 +113,6 @@ class AssetsController extends Controller
'byod',
'asset_eol_date',
'requestable',
'jobtitle',
];
$filter = [];
@@ -299,15 +297,9 @@ class AssetsController extends Controller
if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->filled('model_id')) {
// If model_id is already an array, just use it as-is
if (is_array($request->input('model_id'))) {
$assets->InModelList($request->input('model_id'));
} else {
// Otherwise, turn it into an array
$assets->InModelList([$request->input('model_id')]);
}
$assets->InModelList([$request->input('model_id')]);
}
if ($request->filled('category_id')) {
@@ -396,9 +388,6 @@ class AssetsController extends Controller
case 'assigned_to':
$assets->OrderAssigned($order);
break;
case 'jobtitle':
$assets->OrderByJobTitle($order);
break;
case 'created_by':
$assets->OrderByCreatedByName($order);
break;
@@ -446,6 +435,12 @@ class AssetsController extends Controller
}]);
}
/**
* Here we're just determining which Transformer (via $transformer) to use based on the
* variables we set earlier on in this method - we default to AssetsTransformer.
*/
return (new $transformer)->transformAssets($assets, $total, $request);
}
@@ -496,32 +491,15 @@ class AssetsController extends Controller
public function showBySerial(Request $request, $serial): JsonResponse | array
{
$this->authorize('index', Asset::class);
$assets = Asset::where('serial', $serial)->with([
'assetstatus',
'assignedTo',
'company',
'defaultLoc',
'location',
'model.category',
'model.depreciation',
'model.fieldset',
'model.manufacturer',
'supplier',
]);
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
// Check if they've passed ?deleted=true
if ($request->input('deleted', 'false') == 'true') {
$assets = $assets->withTrashed();
}
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
if (($assets) && ($assets->count()) > 0) {
return (new AssetsTransformer)->transformAssets($assets, $total);
if (($assets = $assets->get()) && ($assets->count()) > 0) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
// If there are 0 results, return the "no such asset" response
@@ -578,12 +556,7 @@ class AssetsController extends Controller
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id',
])->with('model', 'assetstatus', 'assignedTo')
->NotArchived();
if ((Setting::getSettings()->full_multiple_companies_support=='1') && ($request->filled('companyId'))) {
$assets->where('assets.company_id', $request->input('companyId'));
}
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
@@ -593,6 +566,7 @@ class AssetsController extends Controller
$assets = $assets->AssignedSearch($request->input('search'));
}
$assets = $assets->paginate(50);
// Loop through and set some custom properties for the transformer to use.
@@ -705,9 +679,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
// below is what we want the _eventual_ return to look like - in a more standardized format.
// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
@@ -1075,7 +1047,7 @@ class AssetsController extends Controller
* @param int $id
* @since [v4.0]
*/
public function audit(Request $request, Asset $asset): JsonResponse
public function audit(Request $request): JsonResponse
{
$this->authorize('audit', Asset::class);
@@ -1083,15 +1055,36 @@ class AssetsController extends Controller
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
// Allow the asset tag to be passed in the payload (legacy method)
if ($request->filled('asset_tag')) {
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
// 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) {
$originalValues = $asset->getRawOriginal();
/**
* 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;
if ($request->filled('next_audit_date')) {
@@ -1106,89 +1099,33 @@ class AssetsController extends Controller
$asset->last_audit_date = date('Y-m-d H:i:s');
// Set up the payload for re-display in the API response
$payload = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
/**
* Update custom fields in the database.
* Validation for these fields is handled through the AssetRequest form request
* $model = AssetModel::find($request->get('model_id'));
*/
if (($asset->model) && ($asset->model->fieldset)) {
$payload['custom_fields'] = [];
foreach ($asset->model->fieldset->fields as $field) {
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
}
}
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors()));
}
/**
* 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()
* @see \App\Models\Asset::save()
*/
$asset->unsetEventDispatcher();
/**
* 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'), null, $originalValues);
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
$asset->logAudit(request('note'), request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag' => e($asset->asset_tag),
'note' => e($request->input('note')),
'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);
}
// No matching asset for the asset tag that was passed.
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
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);
}

View File

@@ -56,7 +56,7 @@ class CategoriesController extends Controller
'notes',
])
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
/*
@@ -212,7 +212,7 @@ class CategoriesController extends Controller
public function destroy($id) : JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
if (! $category->isDeletable()) {
return response()->json(

View File

@@ -1,44 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Exceptions\AssetNotRequestable;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Exception;
class CheckoutRequest extends Controller
{
public function store(Asset $asset): JsonResponse
{
try {
CreateCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success')));
} catch (AssetNotRequestable $e) {
return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable'));
} catch (AuthorizationException $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}
public function destroy(Asset $asset): JsonResponse
{
try {
CancelCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled')));
} catch (AuthorizationException $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}
}

View File

@@ -43,10 +43,7 @@ class CompaniesController extends Controller
$companies = Company::withCount(['assets as assets_count' => function ($query) {
$query->AssetsForShow();
}])
->with('adminuser')
->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
}])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
@@ -122,7 +119,6 @@ class CompaniesController extends Controller
{
$this->authorize('view', Company::class);
$company = Company::findOrFail($id);
$this->authorize('view', $company);
return (new CompaniesTransformer)->transformCompany($company);
}
@@ -140,7 +136,6 @@ class CompaniesController extends Controller
{
$this->authorize('update', Company::class);
$company = Company::findOrFail($id);
$this->authorize('update', $company);
$company->fill($request->all());
$company = $request->handleImages($company);
@@ -193,7 +188,6 @@ class CompaniesController extends Controller
'companies.image',
]);
if ($request->filled('search')) {
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%');
}

View File

@@ -60,7 +60,7 @@ class ComponentsController extends Controller
}
if ($request->filled('company_id')) {
$components->where('components.company_id', '=', $request->input('company_id'));
$components->where('company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {

View File

@@ -40,7 +40,7 @@ class ConsumablesController extends Controller
}
if ($request->filled('company_id')) {
$consumables->where('consumables.company_id', '=', $request->input('company_id'));
$consumables->where('company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {

View File

@@ -76,7 +76,7 @@ class GroupsController extends Controller
$this->authorize('superadmin');
$group = new Group;
// Get all the available permissions
$permissions = json_encode(config('permissions'));
$permissions = config('permissions');
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$group->name = $request->input('name');

View File

@@ -66,41 +66,25 @@ class ImportController extends Controller
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
if (function_exists('iconv')) {
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = false;
try {
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
} catch (\Exception $e) {
$transliterated = false; //blank out the partially-decoded string
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
),
422
);
}
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = iconv($encoding, 'UTF-8', $file_contents);
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
if ($tmpresults !== false) {
$transliterated = null; //save on memory?
if ($tmpresults !== false) {
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
}
}
$file_contents = null; //try to save on memory, I guess?
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$file_contents = null; //try to save on memory, I guess?
try {
$import->header_row = $reader->fetchOne(0);
@@ -234,15 +218,6 @@ class ImportController extends Controller
case 'location':
$redirectTo = 'locations.index';
break;
case 'supplier':
$redirectTo = 'suppliers.index';
break;
case 'manufacturer':
$redirectTo = 'manufacturers.index';
break;
case 'category':
$redirectTo = 'categories.index';
break;
}
if ($errors) { //Failure

View File

@@ -29,21 +29,12 @@ class LicenseSeatsController extends Controller
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
->where('license_seats.license_id', $licenseId);
if ($request->input('status') == 'available') {
$seats->whereNull('license_seats.assigned_to');
}
if ($request->input('status') == 'assigned') {
$seats->ByAssigned();
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->input('sort') == 'department') {
$seats->OrderDepartments($order);
} else {
$seats->orderBy('updated_at', $order);
$seats->orderBy('id', $order);
}
$total = $seats->count();
@@ -145,13 +136,13 @@ class LicenseSeatsController extends Controller
if ($licenseSeat->save()) {
if ($is_checkin) {
$licenseSeat->logCheckin($target, $request->input('notes'));
$licenseSeat->logCheckin($target, $request->input('note'));
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
$licenseSeat->logCheckout($request->input('notes'), $target);
$licenseSeat->logCheckout($request->input('note'), $target);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}

View File

@@ -12,9 +12,7 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -48,7 +46,6 @@ class LocationsController extends Controller
'id',
'image',
'ldap_ou',
'company_id',
'manager_id',
'name',
'rtd_assets_count',
@@ -77,23 +74,15 @@ class LocationsController extends Controller
'locations.image',
'locations.ldap_ou',
'locations.currency',
'locations.company_id',
'locations.notes',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->with('adminuser');
// Only scope locations if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$locations = Company::scopeCompanyables($locations);
}
->withCount('users as users_count');
if ($request->filled('search')) {
$locations = $locations->TextSearch($request->input('search'));
@@ -127,10 +116,6 @@ class LocationsController extends Controller
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
}
if ($request->filled('company_id')) {
$locations->where('locations.company_id', '=', $request->input('company_id'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -147,9 +132,6 @@ class LocationsController extends Controller
case 'manager':
$locations->OrderManager($order);
break;
case 'company':
$locations->OrderCompany($order);
break;
default:
$locations->orderBy($sort, $order);
break;
@@ -177,15 +159,6 @@ class LocationsController extends Controller
$location->fill($request->all());
$location = $request->handleImages($location);
// Only scope location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
// check if parent is set and has a different company
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
}
}
if ($location->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success')));
}
@@ -203,7 +176,7 @@ class LocationsController extends Controller
public function show($id) : JsonResponse | array
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children', 'company')
$location = Location::with('parent', 'manager', 'children')
->select([
'locations.id',
'locations.name',
@@ -219,7 +192,6 @@ class LocationsController extends Controller
'locations.updated_at',
'locations.image',
'locations.currency',
'locations.company_id',
'locations.notes',
])
->withCount('assignedAssets as assigned_assets_count')
@@ -248,19 +220,6 @@ class LocationsController extends Controller
$location->fill($request->all());
$location = $request->handleImages($location);
if ($request->filled('company_id')) {
// Only scope location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
// check if there are related objects with different company
if (Helper::test_locations_fmcs(false, $id, $location->company_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations'));
}
} else {
$location->company_id = $request->get('company_id');
}
}
if ($location->isValid()) {
$location->save();
@@ -381,11 +340,6 @@ class LocationsController extends Controller
'locations.image',
]);
// Only scope locations if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$locations = Company::scopeCompanyables($locations);
}
$page = 1;
if ($request->filled('page')) {
$page = $request->input('page');

View File

@@ -4,19 +4,15 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ProfileTransformer;
use App\Models\CheckoutRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ProfileController extends Controller
{
@@ -171,22 +167,6 @@ class ProfileController extends Controller
}
/**
* Display the EULAs accepted by the user.
*
* @param \App\Http\Transformers\ActionlogsTransformer $transformer
* @return \Illuminate\Http\JsonResponse
*@since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas(ProfileTransformer $transformer)
{
// Only return this user's EULAs
$eulas = auth()->user()->eulas;
return response()->json(
$transformer->transformFiles($eulas, $eulas->count())
);
}
}

View File

@@ -5,8 +5,6 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ActionlogsTransformer;
use App\Models\Actionlog;
use App\Models\Company;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
@@ -20,11 +18,10 @@ class ReportsController extends Controller
*/
public function index(Request $request) : JsonResponse | array
{
$this->authorize('activity.view');
$this->authorize('reports.view');
$actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location');
if ($request->filled('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
}

View File

@@ -24,15 +24,10 @@ class SuppliersController extends Controller
public function index(Request $request): array
{
$this->authorize('view', Supplier::class);
$allowed_columns = [
'id',
$allowed_columns = ['
id',
'name',
'address',
'address2',
'city',
'state',
'country',
'zip',
'phone',
'contact',
'fax',
@@ -44,24 +39,21 @@ class SuppliersController extends Controller
'components_count',
'consumables_count',
'url',
'notes',
];
$suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip'])
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url'])
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
->withCount('components as components_count')
->withCount('consumables as consumables_count')
->with('adminuser');
->withCount('consumables as consumables_count');
if ($request->filled('search')) {
$suppliers->TextSearch($request->input('search'));
$suppliers = $suppliers->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$suppliers->where('name', '=', $request->input('name'));
}
@@ -108,15 +100,7 @@ class SuppliersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($request->input('sort')) {
case 'created_by':
$suppliers->OrderByCreatedByName($order);
break;
default:
$suppliers->orderBy($sort, $order);
break;
}
$suppliers->orderBy($sort, $order);
$total = $suppliers->count();
$suppliers = $suppliers->skip($offset)->take($limit)->get();

View File

@@ -1,258 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\UploadedFilesTransformer;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/assetmodels/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
/**
* List files for an object
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to list files for
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function index(Request $request, $object_type, $id) : JsonResponse | array
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Columns allowed for sorting
$allowed_columns =
[
'id',
'filename',
'action_type',
'note',
'created_at',
];
$uploads = $object->uploads();
$offset = ($request->input('offset') > $object->count()) ? $object->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'action_logs.created_at';
// Text search on action_logs fields
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searcghing across all relations
// And we generally won't need that here
if ($request->filled('search')) {
$uploads->where(
function ($query) use ($request) {
$query->where('filename', 'LIKE', '%' . $request->input('search') . '%')
->orWhere('note', 'LIKE', '%' . $request->input('search') . '%');
}
);
}
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
}
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to store so we can check permisisons
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $object_type, $id) : JsonResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// If the file storage directory doesn't exist, create it
if (! Storage::exists(self::$map_storage_path[$object_type])) {
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
}
if ($request->hasFile('file')) {
// Loop over the attached files and add them to the object
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files))));
}
// No files were submitted
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.nofiles')));
}
/**
* Check for permissions and display the file.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check that the file being requested exists for the object
if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id)
) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_id')), 200);
}
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.file_not_found'), 200));
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
}
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
}
/**
* Delete the associated file
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function destroy($object_type, $id, $file_id) : JsonResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check for the file
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)->first();
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
}
// Delete the record of the file
if ($log->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
}
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
}
}

View File

@@ -6,7 +6,6 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\SaveUserRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\ActionlogsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\LicensesTransformer;
@@ -81,7 +80,7 @@ class UsersController extends Controller
'users.autoassign_licenses',
'users.website',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
->withCount([
'assets as assets_count' => function(Builder $query) {
$query->withoutTrashed();
@@ -207,11 +206,11 @@ class UsersController extends Controller
}
if ($request->filled('manages_users_count')) {
$users->has('managesUsers', '=', $request->input('manages_users_count'));
$users->has('manages_users_count', '=', $request->input('manages_users_count'));
}
if ($request->filled('manages_locations_count')) {
$users->has('managedLocations', '=', $request->input('manages_locations_count'));
$users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
}
if ($request->filled('autoassign_licenses')) {
@@ -340,7 +339,6 @@ class UsersController extends Controller
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
});
}
@@ -677,6 +675,7 @@ class UsersController extends Controller
$this->authorize('view', License::class);
if ($user = User::where('id', $id)->withTrashed()->first()) {
$this->authorize('update', $user);
$licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
@@ -736,25 +735,6 @@ class UsersController extends Controller
return (new UsersTransformer)->transformUser($request->user());
}
/**
* Display the EULAs accepted by the user.
*
* @param \App\Models\User $user
* @param \App\Http\Transformers\ActionlogsTransformer $transformer
* @return \Illuminate\Http\JsonResponse
*@since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas(User $user, ActionlogsTransformer $transformer)
{
$this->authorize('view', User::class);
$eulas = $user->eulas;
return response()->json(
$transformer->transformActionlogs($eulas, $eulas->count())
);
}
/**
* Restore a soft-deleted user.
*

View File

@@ -19,6 +19,19 @@ use \Illuminate\Http\RedirectResponse;
*/
class AssetMaintenancesController extends Controller
{
/**
* Checks for permissions for this action.
*
* @todo This should be replaced with middleware and/or policies
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
*/
private static function getInsufficientPermissionsRedirect(): RedirectResponse
{
return redirect()->route('maintenances.index')
->with('error', trans('general.insufficient_permissions'));
}
/**
* Returns a view that invokes the ajax tables which actually contains
@@ -54,10 +67,16 @@ class AssetMaintenancesController extends Controller
// We have to set this so that the correct property is set in the select2 ajax dropdown
$asset->asset_id = $asset->id;
}
// Prepare Asset Maintenance Type List
$assetMaintenanceType = [
'' => 'Select an asset maintenance type',
] + AssetMaintenance::getImprovementOptions();
// Mark the selected asset, if it came in
return view('asset_maintenances/edit')
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
->with('asset', $asset)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', new AssetMaintenance);
}
@@ -72,45 +91,43 @@ class AssetMaintenancesController extends Controller
public function store(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
// create a new model instance
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$asset = Asset::find($request->input('asset_id'));
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
// Loop through the selected assets
foreach ($assets as $asset) {
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
// Save the asset maintenance data
$assetMaintenance->asset_id = $asset->id;
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id();
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
if (!$assetMaintenance->save()) {
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
}
if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) {
return static::getInsufficientPermissionsRedirect();
}
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.create.success'));
// Save the asset maintenance data
$assetMaintenance->asset_id = $request->input('asset_id');
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id();
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
// Redirect to the new asset maintenance page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
}
/**
@@ -118,19 +135,26 @@ class AssetMaintenancesController extends Controller
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
*/
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
// Prepare Improvement Type List
$assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions();
return view('asset_maintenances/edit')
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
->with('asset_ids', request()->input('asset_ids', []))
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
->with('item', $maintenance);
->with('selectedAsset', null)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', $maintenance);
}
/**
@@ -146,21 +170,33 @@ class AssetMaintenancesController extends Controller
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
$maintenance->supplier_id = $request->input('supplier_id');
$maintenance->is_warranty = $request->input('is_warranty', 0);
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id'));
if (! Company::isCurrentUserHasAccess($asset)) {
return static::getInsufficientPermissionsRedirect();
}
// Save the asset maintenance data
$maintenance->asset_id = $request->input('asset_id');
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->title = $request->input('title');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
// Todo - put this in a getter/setter?
if (($maintenance->completion_date == null))
{
if (($maintenance->completion_date == null)
) {
if (($maintenance->asset_maintenance_time !== 0)
|| (! is_null($maintenance->asset_maintenance_time))
) {
@@ -174,10 +210,13 @@ class AssetMaintenancesController extends Controller
) {
$startDate = Carbon::parse($maintenance->start_date);
$completionDate = Carbon::parse($maintenance->completion_date);
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
$maintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
if ($maintenance->save()) {
// Redirect to the new asset maintenance page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.edit.success'));
}
@@ -193,12 +232,21 @@ class AssetMaintenancesController extends Controller
* @version v1.0
* @since [v1.8]
*/
public function destroy(AssetMaintenance $maintenance) : RedirectResponse
public function destroy($assetMaintenanceId) : RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
// Delete the asset maintenance
$maintenance->delete();
$assetMaintenance->delete();
// Redirect to the asset_maintenance management page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.delete.success'));
@@ -214,6 +262,11 @@ class AssetMaintenancesController extends Controller
*/
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
}
}

View File

@@ -14,7 +14,6 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Validator;
class AssetCheckinController extends Controller
{
@@ -42,19 +41,7 @@ class AssetCheckinController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
}
$target_option = match ($asset->assigned_type) {
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset_previous')]),
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
return view('hardware/checkin', compact('asset', 'target_option'))
return view('hardware/checkin', compact('asset'))
->with('item', $asset)
->with('statusLabel_list', Helper::statusLabelList())
->with('backto', $backto)
@@ -88,14 +75,12 @@ class AssetCheckinController extends Controller
$this->authorize('checkin', $asset);
session()->put('checkedInFrom', $asset->assignedTo->id);
session()->put('checkout_to_type', match ($asset->assigned_type) {
'App\Models\User' => 'user',
'App\Models\Location' => 'location',
'App\Models\Asset' => 'asset',
});
if ($asset->assignedType() == Asset::USER) {
$user = $asset->assignedTo;
}
$asset->expected_checkin = null;
$asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = $request->get('name');
@@ -122,14 +107,11 @@ class AssetCheckinController extends Controller
$originalValues = $asset->getRawOriginal();
// Handle last checkin date
$checkin_at = date('Y-m-d H:i:s');
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
$checkin_at = $request->get('checkin_at');
}
$asset->last_checkin = $checkin_at;
$asset->licenseseats->each(function (LicenseSeat $seat) {
$seat->update(['assigned_to' => null]);
@@ -153,8 +135,7 @@ class AssetCheckinController extends Controller
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
return Helper::getRedirectOption($request, $asset->id, 'Assets')
->with('success', trans('admin/hardware/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success'));
}
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());

View File

@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Session;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Validator;
class AssetCheckoutController extends Controller
{
@@ -37,14 +36,6 @@ class AssetCheckoutController extends Controller
->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
}
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'))
->with('statusLabel_list', Helper::deployableStatusLabelList())
@@ -123,7 +114,7 @@ class AssetCheckoutController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return Helper::getRedirectOption($request, $asset->id, 'Assets')
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error

View File

@@ -6,7 +6,6 @@ use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Log;
@@ -149,7 +148,7 @@ class AssetsController extends Controller
$asset->byod = request('byod', 0);
if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString();
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
}
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
@@ -188,31 +187,14 @@ class AssetsController extends Controller
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {
$target = null;
$location = null;
if ($userId = request('assigned_user')) {
$target = User::find($userId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
if (request('assigned_user')) {
$target = User::find(request('assigned_user'));
$location = $target->location_id;
} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
} elseif (request('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
$location = $target->location_id;
} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
}
} elseif (request('assigned_location')) {
$target = Location::find(request('assigned_location'));
$location = $target->id;
}
@@ -233,18 +215,18 @@ class AssetsController extends Controller
if ($successes) {
if ($failures) {
//some succeeded, some failed
return Helper::getRedirectOption($request, $asset->id, 'Assets') //FIXME - not tested
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]))
->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)]));
} else {
if (count($successes) == 1) {
//the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed
//and re-translated
return Helper::getRedirectOption($request, $asset->id, 'Assets')
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)]));
} else {
//multi-success
return Helper::getRedirectOption($request, $asset->id, 'Assets')
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]));
}
}
@@ -265,7 +247,6 @@ class AssetsController extends Controller
public function edit(Asset $asset) : View | RedirectResponse
{
$this->authorize($asset);
session()->put('back_url', url()->previous());
return view('hardware/edit')
->with('item', $asset)
->with('statuslabel_list', Helper::statusLabelList())
@@ -337,7 +318,7 @@ class AssetsController extends Controller
$asset->eol_explicit = false;
} elseif ($request->filled('asset_eol_date')) {
$asset->asset_eol_date = $request->input('asset_eol_date', null);
$months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true);
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($asset->model->eol) {
if($months != $asset->model->eol > 0) {
$asset->eol_explicit = true;
@@ -369,6 +350,11 @@ class AssetsController extends Controller
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
}
if ($asset->assigned_to == '') {
$asset->location_id = $request->input('rtd_location_id', null);
}
if ($request->filled('image_delete')) {
try {
unlink(public_path().'/uploads/assets/'.$asset->image);
@@ -404,39 +390,35 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
// Update custom fields in the database.
// Validation for these fields is handlded through the AssetRequest form request
// FIXME: No idea why this is returning a Builder error on db_column_name.
// Need to investigate and fix. Using static method for now.
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
session()->put([
'redirect_option' => $request->get('redirect_option'),
'checkout_to_type' => $request->get('checkout_to_type'),
'other_redirect' => $request->get('redirect_option') === 'other_redirect' ? 'model' : null,
]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->save()) {
return Helper::getRedirectOption($request, $asset->id, 'Assets')
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.update.success'));
}
@@ -468,7 +450,7 @@ class AssetsController extends Controller
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues));
DB::table('assets')
->where('id', $asset->id)
->update(['assigned_to' => null, 'assigned_type' => null]);
->update(['assigned_to' => null]);
}
@@ -480,7 +462,6 @@ class AssetsController extends Controller
}
}
$asset->delete();
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success'));
@@ -542,7 +523,7 @@ class AssetsController extends Controller
{
$settings = Setting::getSettings();
if ($settings->label2_2d_type !== 'none') {
if (($settings->qr_code == '1') && ($settings->label2_2d_type !== 'none')) {
if ($asset) {
$size = Helper::barcodeDimensions($settings->label2_2d_type);
@@ -884,6 +865,13 @@ class AssetsController extends Controller
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
public function audit(Asset $asset)
{
$settings = Setting::getSettings();
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function dueForAudit()
{
@@ -900,72 +888,19 @@ class AssetsController extends Controller
}
public function audit(Asset $asset): View | RedirectResponse
{
$this->authorize('audit', Asset::class);
$settings = Setting::getSettings();
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
}
$dt = Carbon::now()->addMonths( (int) $settings->audit_interval)->toDateString();
return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function auditStore(UploadFileRequest $request, Asset $asset)
{
$this->authorize('audit', Asset::class);
session()->put('redirect_option', $request->get('redirect_option'));
session()->put('other_redirect', 'audit');
$rules = [
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable',
];
$validator = Validator::make($request->all(), $rules);
$originalValues = $asset->getRawOriginal();
$asset->next_audit_date = $request->input('next_audit_date');
$asset->last_audit_date = date('Y-m-d H:i:s');
// 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') {
$asset->location_id = $request->input('location_id');
}
// Update custom fields in the database
if (($asset->model) && ($asset->model->fieldset)) {
foreach ($asset->model->fieldset->fields as $field) {
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return redirect()->back()->withInput()->withErrors($asset->getErrors());
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
/**
@@ -982,11 +917,18 @@ class AssetsController extends Controller
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
* @see \App\Models\Asset::save()
*/
$asset->unsetEventDispatcher();
$asset->next_audit_date = $request->input('next_audit_date');
$asset->last_audit_date = date('Y-m-d H:i:s');
// 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') {
$asset->location_id = $request->input('location_id');
}
/**
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
@@ -1000,8 +942,8 @@ class AssetsController extends Controller
$file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
}
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
return Helper::getRedirectOption($request, $asset->id, 'Assets')->with('success', trans('admin/hardware/message.audit.success'));
$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());

View File

@@ -52,26 +52,11 @@ class BulkAssetsController extends Controller
}
$asset_ids = $request->input('ids');
if ($request->input('bulk_actions') === 'checkout') {
$status_check =$this->hasUndeployableStatus($asset_ids);
if($status_check && $status_check['status'] === true){
$asset_tags = implode(', ', array_column($status_check['tags'], 'asset_tag'));
$asset_ids = $status_check['asset_ids'];
session()->flash('warning', trans('admin/hardware/message.undeployable', ['asset_tags' => $asset_tags]));
}
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('hardware.bulkcheckout.show');
}
if ($request->input('bulk_actions') === 'maintenance') {
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('maintenances.create');
}
// Figure out where we need to send the user after the update is complete, and store that in the session
$bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]);
@@ -112,47 +97,11 @@ class BulkAssetsController extends Controller
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
$query = Asset::with('assignedTo', 'location', 'model')
$assets = Asset::with('assignedTo', 'location', 'model')
->whereIn('assets.id', $asset_ids)
->withTrashed();
switch ($sort_override) {
case 'model':
$query->OrderModels($order);
break;
case 'model_number':
$query->OrderModelNumber($order);
break;
case 'category':
$query->OrderCategory($order);
break;
case 'manufacturer':
$query->OrderManufacturer($order);
break;
case 'company':
$query->OrderCompany($order);
break;
case 'location':
$query->OrderLocation($order);
break;
case 'rtd_location':
$query->OrderRtdLocation($order);
break;
case 'status_label':
$query->OrderStatus($order);
break;
case 'supplier':
$query->OrderSupplier($order);
break;
case 'assigned_to':
$query->OrderAssigned($order);
break;
default:
$query->orderBy($column_sort, $order);
break;
}
$assets = $query->get();
$assets = $assets->get();
if ($assets->isEmpty()) {
Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
@@ -205,7 +154,40 @@ class BulkAssetsController extends Controller
}
}
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
break;
case 'model_number':
$assets->OrderModelNumber($order);
break;
case 'category':
$assets->OrderCategory($order);
break;
case 'manufacturer':
$assets->OrderManufacturer($order);
break;
case 'company':
$assets->OrderCompany($order);
break;
case 'location':
$assets->OrderLocation($order);
case 'rtd_location':
$assets->OrderRtdLocation($order);
break;
case 'status_label':
$assets->OrderStatus($order);
break;
case 'supplier':
$assets->OrderSupplier($order);
break;
case 'assigned_to':
$assets->OrderAssigned($order);
break;
default:
$assets->orderBy($column_sort, $order);
break;
}
return redirect()->back()->with('error', 'No action selected');
}
@@ -232,21 +214,6 @@ class BulkAssetsController extends Controller
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
// find custom field input attributes that start with 'null_'
$null_custom_fields_inputs = array_filter($request->all(), function ($key) {
// filter out all keys that start with 'null_'
return (strpos($key, 'null_') === 0);
}, ARRAY_FILTER_USE_KEY);;
// remove 'null' from the keys
$custom_fields_to_null = [];
foreach ($null_custom_fields_inputs as $key => $value) {
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
}
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
@@ -284,9 +251,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
|| ($request->filled('null_asset_eol_date'))
|| ($request->filled('null_notes'))
|| ($request->anyFilled($custom_field_columns))
|| ($request->anyFilled(array_keys($null_custom_fields_inputs)))
) {
// Let's loop through those assets and build an update array
@@ -309,14 +274,10 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date')
->conditionallyAddItem('asset_eol_date')
->conditionallyAddItem('notes');
->conditionallyAddItem('asset_eol_date');
foreach ($custom_field_columns as $key => $custom_field_column) {
$this->conditionallyAddItem($custom_field_column);
}
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
$this->conditionallyAddItem($key);
}
if (!($asset->eol_explicit)) {
if ($request->filled('model_id')) {
@@ -367,10 +328,6 @@ class BulkAssetsController extends Controller
}
}
if ($request->input('null_notes')=='1') {
$this->update_array['notes'] = null;
}
if ($request->filled('purchase_cost')) {
@@ -411,12 +368,10 @@ class BulkAssetsController extends Controller
// This could probably be added to a form request.
// If the asset isn't assigned, we don't care what the status is.
// Otherwise we need to make sure the status type is still a deployable one.
$unassigned = $asset->assigned_to == '';
$deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1';
$pending = $updated_status->pending === 1;
if ($unassigned || $deployable || $pending) {
if (
($asset->assigned_to == '')
|| ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1')
) {
$this->update_array['status_id'] = $updated_status->id;
}
@@ -468,7 +423,6 @@ class BulkAssetsController extends Controller
}
/**
*
* Start all the custom fields shenanigans
*/
@@ -476,15 +430,6 @@ class BulkAssetsController extends Controller
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// null custom fields
if ($custom_fields_to_null) {
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
if ($field->db_column == $key) {
$this->update_array[$field->db_column] = null;
}
}
}
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
if (Gate::allows('admin')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
@@ -617,10 +562,7 @@ class BulkAssetsController extends Controller
public function showCheckout() : View
{
$this->authorize('checkout', Asset::class);
$do_not_change = ['' => trans('general.do_not_change')];
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list);
return view('hardware/bulk-checkout');
}
/**
@@ -628,6 +570,7 @@ class BulkAssetsController extends Controller
*/
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
$this->authorize('checkout', Asset::class);
try {
@@ -641,8 +584,6 @@ class BulkAssetsController extends Controller
$asset_ids = array_filter($request->get('selected_assets'));
$assets = Asset::findOrFail($asset_ids);
if (request('checkout_to_type') == 'asset') {
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
@@ -652,25 +593,21 @@ class BulkAssetsController extends Controller
}
$checkout_at = date('Y-m-d H:i:s');
if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) {
$checkout_at = $request->get('checkout_at');
$checkout_at = e($request->get('checkout_at'));
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = $request->get('expected_checkin');
$expected_checkin = e($request->get('expected_checkin'));
}
$errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
foreach ($assets as $asset) {
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset);
// See if there is a status label passed
if ($request->filled('status_id')) {
$asset->status_id = $request->get('status_id');
}
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
//TODO - I think this logic is duplicated in the checkOut method?
@@ -695,7 +632,7 @@ class BulkAssetsController extends Controller
// Redirect to the asset management page with error
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
} catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
}
}
@@ -714,25 +651,4 @@ class BulkAssetsController extends Controller
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
}
}
public function hasUndeployableStatus (array $asset_ids)
{
$undeployable = Asset::whereIn('id', $asset_ids)
->undeployable()
->get();
$undeployableTags = $undeployable->map(function ($asset) {
return [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
];
})->toArray();
$undeployableIds = array_column($undeployableTags, 'id');
$filtered_ids = array_diff($asset_ids, $undeployableIds);
if($undeployable->isNotEmpty()) {
return ['status' => true, 'tags' => $undeployableTags, 'asset_ids' => $filtered_ids];
}
return false;
}
}

View File

@@ -206,7 +206,6 @@ class LoginController extends Controller
$user->password = bcrypt($request->input('password'));
}
$user->last_login = \Carbon::now();
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
@@ -227,7 +226,7 @@ class LoginController extends Controller
$strip_prefixes = [
// IIS/AD
// https://github.com/grokability/snipe-it/pull/5862
// https://github.com/snipe/snipe-it/pull/5862
'\\',
// Google Cloud IAP
@@ -284,11 +283,8 @@ class LoginController extends Controller
return redirect()->back()->withInput()->withErrors($validator);
}
// Set the custom lockout attempts from the env and sett the custom lockout throttle from the env.
// We divide decayMinutes by 60 here to get minutes, since Laravel changed the default from minutes
// to seconds, and we don't want to break limits on existing systems
$this->maxAttempts = config('auth.passwords.users.throttle.max_attempts');
$this->decayMinutes = (config('auth.passwords.users.throttle.lockout_duration') / 60);
$this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
$this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
@@ -358,7 +354,7 @@ class LoginController extends Controller
// We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
// but let's check anyway in case there's a browser history or back button thing.
// but let's check check anyway in case there's a browser history or back button thing.
// While you can access this page directly, enrolling a device when 2FA isn't enforced
// won't cause any harm.
@@ -436,7 +432,6 @@ class LoginController extends Controller
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->last_login = \Carbon::now();
$user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);
@@ -484,7 +479,6 @@ class LoginController extends Controller
}
$request->session()->regenerate(true);
$request->session()->forget('2fa_authed');
if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){
$request->session()->remove('password_hash_'.Auth::getDefaultDriver());
@@ -525,6 +519,45 @@ class LoginController extends Controller
return 'username';
}
/**
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
$minutes = round($seconds / 60);
$message = trans('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([$this->username() => $message]);
}
/**
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
$lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
$maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request),
$maxLoginAttempts,
$lockoutTime
);
}
public function legacyAuthRedirect()
{

View File

@@ -71,28 +71,20 @@ class BulkAssetModelsController extends Controller
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) {
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
}
if (($request->filled('category_id') && ($request->input('category_id') != 'NC'))) {
$update_array['category_id'] = $request->input('category_id');
}
if ($request->input('fieldset_id') != 'NC') {
$update_array['fieldset_id'] = $request->input('fieldset_id');
}
if ($request->input('depreciation_id') != 'NC') {
$update_array['depreciation_id'] = $request->input('depreciation_id');
}
if ($request->input('requestable') != '') {
if ($request->filled('requestable') != '') {
$update_array['requestable'] = $request->input('requestable');
}
if ($request->filled('min_amt')) {
$update_array['min_amt'] = $request->input('min_amt');
}
if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array);

View File

@@ -68,7 +68,6 @@ class CategoriesController extends Controller
$category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->notes = $request->input('notes');
$category->created_by = auth()->id();
@@ -122,7 +121,6 @@ class CategoriesController extends Controller
$category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->notes = $request->input('notes');
@@ -147,7 +145,7 @@ class CategoriesController extends Controller
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
if (is_null($category = Category::findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
@@ -157,6 +155,7 @@ class CategoriesController extends Controller
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
// Redirect to the locations management page
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}

View File

@@ -123,13 +123,11 @@ final class CompaniesController extends Controller
*/
public function destroy($companyId) : RedirectResponse
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.not_found'));
}
$this->authorize('delete', $company);
if (! $company->isDeletable()) {
return redirect()->route('companies.index')

View File

@@ -100,8 +100,8 @@ class ComponentCheckinController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
trans('admin/components/message.checkin.success'));
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));

View File

@@ -120,7 +120,6 @@ class ComponentCheckoutController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.checkout.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
}
}

View File

@@ -91,8 +91,7 @@ class ComponentsController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) {
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -112,7 +111,6 @@ class ComponentsController extends Controller
{
$this->authorize('update', $component);
session()->put('back_url', url()->previous());
return view('components/edit')
->with('item', $component)
->with('category_type', 'component');
@@ -166,8 +164,7 @@ class ComponentsController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) {
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());

View File

@@ -111,7 +111,6 @@ class ConsumableCheckoutController extends Controller
// Redirect to the new consumable page
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
->with('success', trans('admin/consumables/message.checkout.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success'));
}
}

View File

@@ -90,8 +90,7 @@ class ConsumablesController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) {
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
->with('success', trans('admin/consumables/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -108,7 +107,6 @@ class ConsumablesController extends Controller
public function edit(Consumable $consumable) : View | RedirectResponse
{
$this->authorize($consumable);
session()->put('back_url', url()->previous());
return view('consumables/edit')
->with('item', $consumable)
->with('category_type', 'consumable');
@@ -162,8 +160,7 @@ class ConsumablesController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) {
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
->with('success', trans('admin/consumables/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());

View File

@@ -83,30 +83,29 @@ class CustomFieldsController extends Controller
{
$this->authorize('create', CustomField::class);
$show_in_email = $request->input("show_in_email", 0);
$display_in_user_view = $request->input("display_in_user_view", 0);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
// Override the display settings if the field is encrypted
if ($request->input("field_encrypted") == '1') {
if ($request->get("field_encrypted") == '1') {
$show_in_email = '0';
$display_in_user_view = '0';
}
$field = new CustomField([
"name" => trim($request->input("name")),
"element" => $request->input("element"),
"help_text" => $request->input("help_text"),
"field_values" => $request->input("field_values"),
"field_encrypted" => $request->input("field_encrypted", 0),
"name" => trim($request->get("name")),
"element" => $request->get("element"),
"help_text" => $request->get("help_text"),
"field_values" => $request->get("field_values"),
"field_encrypted" => $request->get("field_encrypted", 0),
"show_in_email" => $show_in_email,
"is_unique" => $request->input("is_unique", 0),
"is_unique" => $request->get("is_unique", 0),
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->input("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->input("show_in_listview", 0),
"show_in_requestable_list" => $request->input("show_in_requestable_list", 0),
"display_checkin" => $request->input("display_checkin", 0),
"display_checkout" => $request->input("display_checkout", 0),
"display_audit" => $request->input("display_audit", 0),
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"display_checkin" => $request->get("display_checkin", 0),
"display_checkout" => $request->get("display_checkout", 0),
"created_by" => auth()->id()
]);
@@ -238,8 +237,8 @@ class CustomFieldsController extends Controller
$display_in_user_view = '0';
}
$field->name = trim($request->get("name"));
$field->element = $request->get("element");
$field->name = trim(e($request->get("name")));
$field->element = e($request->get("element"));
$field->field_values = $request->get("field_values");
$field->created_by = auth()->id();
$field->help_text = $request->get("help_text");
@@ -251,12 +250,11 @@ class CustomFieldsController extends Controller
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
$field->display_checkin = $request->get("display_checkin", 0);
$field->display_checkout = $request->get("display_checkout", 0);
$field->display_audit = $request->get("display_audit", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = $request->get('custom_format');
$field->format = e($request->get('custom_format'));
} else {
$field->format = $request->get('format');
$field->format = e($request->get('format'));
}
if ($field->element == 'checkbox' || $field->element == 'radio'){

View File

@@ -83,10 +83,6 @@ class GroupsController extends Controller
{
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}

View File

@@ -38,7 +38,6 @@ class LabelsController extends Controller
$exampleAsset->order_number = '12345';
$exampleAsset->purchase_date = '2023-01-01';
$exampleAsset->status_id = 1;
$exampleAsset->location_id = 1;
$exampleAsset->company = new Company([
'name' => trans('admin/labels/table.example_company'),

View File

@@ -86,8 +86,7 @@ class LicenseCheckinController extends Controller
}
if($licenseSeat->assigned_to != null){
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
session()->put('checkedInFrom', $return_to->id);
$return_to = User::find($licenseSeat->assigned_to);
} else {
$return_to = Asset::find($licenseSeat->asset_id);
}
@@ -98,17 +97,14 @@ class LicenseCheckinController extends Controller
$licenseSeat->notes = $request->input('notes');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($request->get('redirect_option') === 'target'){
session()->put(['checkout_to_type' => 'user']);
}
// Was the asset updated?
if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
return Helper::getRedirectOption($request, $license->id, 'Licenses')
->with('success', trans('admin/licenses/message.checkin.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
}
// Redirect to the license page with error

View File

@@ -89,8 +89,7 @@ class LicenseCheckoutController extends Controller
if ($checkoutTarget) {
return Helper::getRedirectOption($request, $license->id, 'Licenses')
->with('success', trans('admin/licenses/message.checkout.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
}

View File

@@ -105,8 +105,7 @@ class LicensesController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
return Helper::getRedirectOption($request, $license->id, 'Licenses')
->with('success', trans('admin/licenses/message.create.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($license->getErrors());
@@ -126,7 +125,7 @@ class LicensesController extends Controller
{
$this->authorize('update', $license);
session()->put('back_url', url()->previous());
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
@@ -182,8 +181,7 @@ class LicensesController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) {
return Helper::getRedirectOption($request, $license->id, 'Licenses')
->with('success', trans('admin/licenses/message.update.success'));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success'));
}
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors());

View File

@@ -2,13 +2,10 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
@@ -66,7 +63,6 @@ class LocationsController extends Controller
public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Location::class);
$location = new Location();
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
@@ -83,18 +79,6 @@ class LocationsController extends Controller
$location->phone = request('phone');
$location->fax = request('fax');
$location->notes = $request->input('notes');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// Only scope the location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// check if parent is set and has a different company
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
return redirect()->back()->withInput()->withInput()->with('error', 'different company than parent');
}
} else {
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
@@ -147,17 +131,6 @@ class LocationsController extends Controller
$location->manager_id = $request->input('manager_id');
$location->notes = $request->input('notes');
// Only scope the location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// check if there are related objects with different company
if (Helper::test_locations_fmcs(false, $location->id, $location->company_id)) {
return redirect()->back()->withInput()->withInput()->with('error', 'error scoped locations');
}
} else {
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
if ($location->save()) {
@@ -177,7 +150,6 @@ class LocationsController extends Controller
public function destroy($locationId) : RedirectResponse
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -214,8 +186,6 @@ class LocationsController extends Controller
*/
public function show(Location $location) : View | RedirectResponse
{
$this->authorize('view', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
@@ -233,24 +203,20 @@ class LocationsController extends Controller
public function print_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -319,20 +285,13 @@ class LocationsController extends Controller
}
public function print_all_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -346,8 +305,6 @@ class LocationsController extends Controller
*/
public function postBulkDelete(Request $request) : View | RedirectResponse
{
$this->authorize('update', Location::class);
$locations_raw_array = $request->input('ids');
// Make sure some IDs have been selected
@@ -381,8 +338,6 @@ class LocationsController extends Controller
*/
public function postBulkDeleteStore(Request $request) : RedirectResponse
{
$this->authorize('delete', Location::class);
$locations_raw_array = $request->input('ids');
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {

View File

@@ -1,111 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Location;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class LocationsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, Location $location) : RedirectResponse
{
$this->authorize('update', $location);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/locations')) {
Storage::makeDirectory('private_uploads/locations', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file);
$location->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$this->authorize('view', $location);
if (! $log = Actionlog::find($fileId)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file record');
}
$file = 'private_uploads/locations/'.$log->filename;
if (! Storage::exists($file)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file on server');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(Location $location, $fileId = null) : RedirectResponse
{
$rel_path = 'private_uploads/locations';
$this->authorize('update', $location);
$log = Actionlog::find($fileId);
if ($log) {
// This should be moved to purge
// if (Storage::exists($rel_path.'/'.$log->filename)) {
// Storage::delete($rel_path.'/'.$log->filename);
// }
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}

View File

@@ -6,7 +6,6 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
@@ -32,30 +31,7 @@ class ManufacturersController extends Controller
public function index() : View
{
$this->authorize('index', Manufacturer::class);
$manufacturer_count = Manufacturer::withTrashed()->count();
return view('manufacturers/index')->with('manufacturer_count', $manufacturer_count);
}
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the manufacturers listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0]
*/
public function seed() : RedirectResponse
{
$this->authorize('index', Manufacturer::class);
$manufacturers_count = Manufacturer::withTrashed()->count();
if ($manufacturers_count == 0) {
Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']);
return redirect()->route('manufacturers.index')->with('success', trans('general.seeding.manufacturers.success'));
}
return redirect()->route('manufacturers.index')->with('error', trans_choice('general.seeding.manufacturers.error', ['count' => $manufacturers_count]));
return view('manufacturers/index');
}
/**

View File

@@ -41,11 +41,10 @@ class ModalController extends Controller
$view = view("modals.${type}");
if ($type == "statuslabel") {
$view->with('statuslabel_types', Helper::statusTypeList());
}
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
$view->with('kitId', $itemId);
$view->with('statuslabel_types', Helper::statusTypeList());
}
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
$view->with('kitId', $itemId);
}
return $view;
}

View File

@@ -3,21 +3,15 @@
namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\ProfileTransformer;
use App\Models\Actionlog;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This controller handles all actions related to User Profiles for
* the Snipe-IT Asset Management application.
@@ -59,7 +53,7 @@ class ProfileController extends Controller
$user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) {
$user->locale = $request->input('locale');
$user->locale = $request->input('locale', 'en-US');
}
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
@@ -142,7 +136,7 @@ class ProfileController extends Controller
}
// This checks to make sure that the user's password isn't the same as their username,
// email address, first name or last name (see https://github.com/grokability/snipe-it/issues/8661)
// email address, first name or last name (see https://github.com/snipe/snipe-it/issues/8661)
// While this is handled via SaveUserRequest form request in other places, we have to do this manually
// here because we don't have the username, etc form fields available in the profile password change
// form.
@@ -226,7 +220,7 @@ class ProfileController extends Controller
if (!$user = User::find(auth()->id())) {
return redirect()->back()
->with('error', trans('admin/users/message.user_not_found', ['id' => auth()->id()]));
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
}
if (empty($user->email)) {
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
@@ -240,28 +234,4 @@ class ProfileController extends Controller
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
}
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
{
$logentry = Actionlog::where('filename', $filename)->first();
// Make sure the user has permission to view this file
if (auth()->id() != $logentry->target_id) {
return redirect()->route('account')->with('error', trans('general.generic_model_not_found', ['model' => 'file']));
}
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
}
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
}
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
}
}

View File

@@ -184,7 +184,7 @@ class ReportsController extends Controller
$currency = e(Setting::getSettings()->default_currency);
}
$row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false);
$row[] = $asset->purchase_date;
$row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost);
$row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue());
$row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
@@ -243,7 +243,7 @@ class ReportsController extends Controller
$header = [
trans('general.date'),
trans('general.created_by'),
trans('general.admin'),
trans('general.action'),
trans('general.type'),
trans('general.item'),
@@ -485,7 +485,7 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.purchase_date');
}
if ($request->filled('purchase_cost')) {
if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) {
$header[] = trans('admin/hardware/table.purchase_cost');
}
@@ -493,17 +493,6 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.eol');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('order')) {
$header[] = trans('admin/hardware/form.order');
}
@@ -590,6 +579,17 @@ class ReportsController extends Controller
$header[] = trans('general.status');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('checkout_date')) {
$header[] = trans('admin/hardware/table.checkout_date');
}
@@ -737,11 +737,6 @@ class ReportsController extends Controller
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
}
if (($request->filled('last_updated_start')) && ($request->filled('last_updated_end'))) {
$assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]);
}
if ($request->filled('exclude_archived')) {
$assets->notArchived();
}
@@ -810,19 +805,6 @@ class ReportsController extends Controller
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('order')) {
$row[] = ($asset->order_number) ? $asset->order_number : '';
}
@@ -956,6 +938,19 @@ class ReportsController extends Controller
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('checkout_date')) {
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}
@@ -1086,10 +1081,10 @@ class ReportsController extends Controller
$row[] = e($assetMaintenance->start_date);
$row[] = e($assetMaintenance->completion_date);
if (is_null($assetMaintenance->asset_maintenance_time)) {
$improvementTime = (int) Carbon::now()
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
$improvementTime = intval(Carbon::now()
->diffInDays(Carbon::parse($assetMaintenance->start_date)));
} else {
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
$improvementTime = intval($assetMaintenance->asset_maintenance_time);
}
$row[] = $improvementTime;
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
@@ -1180,13 +1175,18 @@ class ReportsController extends Controller
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
// Only send notification if assigned
if ($locale && $email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note))->locale($locale));
if (is_null($email) || $email === '') {
} elseif ($email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)));
}
if ($email == ''){
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}

34
app/Http/Controllers/SettingsController.php Normal file → Executable file
View File

@@ -14,7 +14,6 @@ use App\Http\Requests\StoreLabelSettings;
use App\Http\Requests\StoreSecuritySettings;
use App\Models\CustomField;
use App\Models\Group;
use App\Models\Labels\Label as LabelModel;
use App\Models\Setting;
use App\Models\Asset;
use App\Models\User;
@@ -291,6 +290,7 @@ class SettingsController extends Controller
public function getSettings() : View
{
$setting = Setting::getSettings();
return view('settings/general', compact('setting'));
}
@@ -314,23 +314,7 @@ class SettingsController extends Controller
$setting->modellist_displays = implode(',', $request->input('show_in_model_list'));
}
$old_locations_fmcs = $setting->scope_locations_fmcs;
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0');
// Backward compatibility for locations makes no sense without FullMultipleCompanySupport
if (!$setting->full_multiple_companies_support) {
$setting->scope_locations_fmcs = '0';
}
// check for inconsistencies when activating scoped locations
if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') {
$mismatched = Helper::test_locations_fmcs(false);
if (count($mismatched) != 0) {
return redirect()->back()->withInput()->with('error', trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)).' '.trans('admin/settings/message.location_scoping.not_saved'));
}
}
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
@@ -352,7 +336,6 @@ class SettingsController extends Controller
$setting->dash_chart_type = $request->input('dash_chart_type');
$setting->profile_edit = $request->input('profile_edit', 0);
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
$setting->manager_view_enabled = $request->input('manager_view_enabled', 0);
if ($request->input('per_page') != '') {
@@ -445,13 +428,6 @@ class SettingsController extends Controller
$setting->label_logo = null;
}
// Acceptance PDF upload
$setting = $request->handleImages($setting, 600, 'acceptance_pdf_logo', '', 'acceptance_pdf_logo');
if ('1' == $request->input('clear_acceptance_pdf_logo')) {
$setting = $request->deleteExistingImage($setting, '', 'acceptance_pdf_logo');
$setting->acceptance_pdf_logo = null;
}
// Favicon upload
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
if ('1' == $request->input('clear_favicon')) {
@@ -459,7 +435,6 @@ class SettingsController extends Controller
$setting->favicon = null;
}
// Default avatar upload
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
if ($request->input('clear_default_avatar') == '1') {
@@ -651,7 +626,6 @@ class SettingsController extends Controller
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
$setting->admin_cc_always = $request->validated('admin_cc_always');
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
@@ -774,7 +748,6 @@ class SettingsController extends Controller
$setting->label2_2d_type = $request->input('label2_2d_type');
$setting->label2_2d_target = $request->input('label2_2d_target');
$setting->label2_fields = $request->input('label2_fields');
$setting->label2_empty_row_count = $request->input('label2_empty_row_count');
$setting->labels_per_page = $request->input('labels_per_page');
$setting->labels_width = $request->input('labels_width');
$setting->labels_height = $request->input('labels_height');
@@ -877,8 +850,7 @@ class SettingsController extends Controller
$setting->ldap_fname_field = $request->input('ldap_fname_field');
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
$setting->ldap_version = $request->input('ldap_version', 3);
$setting->ldap_active_flag = $request->input('ldap_active_flag', 0);
$setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag', 0);
$setting->ldap_active_flag = $request->input('ldap_active_flag');
$setting->ldap_emp_num = $request->input('ldap_emp_num');
$setting->ldap_email = $request->input('ldap_email');
$setting->ldap_manager = $request->input('ldap_manager');
@@ -924,7 +896,7 @@ class SettingsController extends Controller
* @since v5.0.0
*/
public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse
{
{
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}

View File

@@ -298,6 +298,7 @@ class BulkUsersController extends Controller
$this->logItemCheckinAndDelete($assets, Asset::class);
$this->logAccessoriesCheckin($accessoryUserRows);
$this->logItemCheckinAndDelete($licenses, License::class);
$this->logConsumablesCheckin($consumableUserRows);
Asset::whereIn('id', $assets->pluck('id'))->update([
'status_id' => e(request('status_id')),
@@ -359,7 +360,21 @@ class BulkUsersController extends Controller
$logAction->item_type = Accessory::class;
$logAction->target_id = $accessoryUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->created_by = auth()->id();
$logAction->created_at = auth()->id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}
}
private function logConsumablesCheckin(Collection $consumableUserRows): void
{
foreach ($consumableUserRows as $consumableUserRow) {
$logAction = new Actionlog();
$logAction->item_id = $consumableUserRow->consumable_id;
$logAction->item_type = Consumable::class;
$logAction->target_id = $consumableUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->created_at = auth()->id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}

View File

@@ -154,8 +154,7 @@ class UsersController extends Controller
$user->notify(new WelcomeNotification($data));
}
return Helper::getRedirectOption($request, $user->id, 'Users')
->with('success', trans('admin/users/message.success.create'));
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
@@ -179,7 +178,7 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param $permissions
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
* @return \Illuminate\Contracts\View\View
* @internal param int $id
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
@@ -187,15 +186,10 @@ class UsersController extends Controller
{
$this->authorize('update', User::class);
session()->put('back_url', url()->previous());
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id);
if ($user) {
if ($user->trashed()) {
return redirect()->route('users.show', $user->id);
}
$permissions = config('permissions');
$groups = Group::pluck('name', 'id');
@@ -204,9 +198,10 @@ class UsersController extends Controller
$userPermissions = Helper::selectedPermissionsArray($permissions, $user->permissions);
$permissions = $this->filterDisplayable($permissions);
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'))->with('item', $user);
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'));
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
}
/**
@@ -314,7 +309,7 @@ class UsersController extends Controller
if ($user->save()) {
// Redirect to the user page
return Helper::getRedirectOption($request, $user->id, 'Users')
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
->with('success', trans('admin/users/message.success.update'));
}
return redirect()->back()->withInput()->withErrors($user->getErrors());
@@ -401,22 +396,13 @@ class UsersController extends Controller
// Make sure the user can view users at all
$this->authorize('view', User::class);
$user = User::with([
'consumables',
'accessories',
'licenses',
'userloc',
])
->withTrashed()
->find($user->id);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user);
return view('users/view', [
'user' => $user,
'settings' => Setting::getSettings(),
]);
$userlog = $user->userlog->load('item');
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
}
@@ -470,10 +456,10 @@ class UsersController extends Controller
->with('user', $user)
->with('groups', Group::pluck('name', 'id'))
->with('userGroups', $userGroups)
->with('clone_user', $user_to_clone)
->with('item', $user);
->with('clone_user', $user_to_clone);
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
}
@@ -512,8 +498,6 @@ class UsersController extends Controller
trans('admin/companies/table.title'),
trans('admin/users/table.title'),
trans('general.employee_number'),
trans('admin/users/table.first_name'),
trans('admin/users/table.last_name'),
trans('admin/users/table.name'),
trans('admin/users/table.username'),
trans('admin/users/table.email'),
@@ -559,8 +543,6 @@ class UsersController extends Controller
($user->company) ? $user->company->name : '',
$user->jobtitle,
$user->employee_num,
$user->first_name,
$user->last_name,
$user->present()->fullName(),
$user->username,
$user->email,
@@ -604,18 +586,18 @@ class UsersController extends Controller
$user = User::where('id', $id)
->with([
'assets.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'),
'assets.assignedAssets.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'),
'assets.assetlog',
'assets.assignedAssets.assetlog',
'assets.assignedAssets.defaultLoc',
'assets.assignedAssets.location',
'assets.assignedAssets.model.category',
'assets.defaultLoc',
'assets.location',
'assets.model.category',
'accessories.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'),
'accessories.assetlog',
'accessories.category',
'accessories.manufacturer',
'consumables.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'),
'consumables.assetlog',
'consumables.category',
'consumables.manufacturer',
'licenses.category',

View File

@@ -2,21 +2,18 @@
namespace App\Http\Controllers;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetCancelation;
use App\Notifications\RequestAssetNotification;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Exception;
use Log;
/**
* This controller handles all actions related to the ability for users
@@ -27,126 +24,50 @@ use Exception;
class ViewAssetsController extends Controller
{
/**
* Extract custom fields that should be displayed in user view.
*
* @param User $user
* @return array
*/
private function extractCustomFields(User $user): array
{
$fieldArray = [];
foreach ($user->assets as $asset) {
if ($asset->model && $asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_in_user_view == '1') {
$fieldArray[$field->db_column] = $field->name;
}
}
}
}
return array_unique($fieldArray);
}
/**
* Get list of users viewable by the current user.
*
* @param User $authUser
* @return \Illuminate\Support\Collection
*/
private function getViewableUsers(User $authUser): \Illuminate\Support\Collection
{
// SuperAdmin sees all users
if ($authUser->isSuperUser()) {
return User::select('id', 'first_name', 'last_name', 'username')
->where('activated', 1)
->orderBy('last_name')
->orderBy('first_name')
->get();
}
// Regular manager sees only their subordinates + self
$managedUsers = $authUser->getAllSubordinates();
// If user has subordinates, show them with self at beginning
if ($managedUsers->count() > 0) {
return collect([$authUser])->merge($managedUsers)
->sortBy('last_name')
->sortBy('first_name');
}
// User has no subordinates, only sees themselves
return collect([$authUser]);
}
/**
* Get the selected user ID from request or default to current user.
*
* @param Request $request
* @param \Illuminate\Support\Collection $subordinates
* @param int $defaultUserId
* @return int
*/
private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int
{
// If no subordinates or no user_id in request, return default
if ($subordinates->count() <= 1 || !$request->filled('user_id')) {
return $defaultUserId;
}
$requestedUserId = (int) $request->input('user_id');
// Validate if the requested user is allowed
if ($subordinates->contains('id', $requestedUserId)) {
return $requestedUserId;
}
// If invalid ID or not authorized, return default
return $defaultUserId;
}
/**
* Show user's assigned assets with optional manager view functionality.
* Redirect to the profile page.
*
*/
public function getIndex(Request $request) : View | RedirectResponse
public function getIndex() : View | RedirectResponse
{
$authUser = auth()->user();
$settings = Setting::getSettings();
$subordinates = collect();
$selectedUserId = $authUser->id;
// Process manager view if enabled
if ($settings->manager_view_enabled) {
$subordinates = $this->getViewableUsers($authUser);
$selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id);
}
// Load the data for the user to be viewed (either auth user or selected subordinate)
$userToView = User::with([
$user = User::with(
'assets',
'assets.model',
'assets.model.fieldset.fields',
'consumables',
'accessories',
'licenses'
])->find($selectedUserId);
'licenses',
)->find(auth()->id());
$field_array = array();
// Loop through all the custom fields that are applied to any model the user has assigned
foreach ($user->assets as $asset) {
// Make sure the model has a custom fieldset before trying to loop through the associated fields
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// check and make sure they're allowed to see the value of the custom field
if ($field->display_in_user_view == '1') {
$field_array[$field->db_column] = $field->name;
}
}
}
// If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error
if (!$userToView) {
return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found'));
}
// Process custom fields for the user being viewed
$fieldArray = $this->extractCustomFields($userToView);
// Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns
array_unique($field_array);
// Pass the necessary data to the view
return view('account/view-assets', [
'user' => $userToView, // Use 'user' for compatibility with the existing view
'field_array' => $fieldArray,
'settings' => $settings,
'subordinates' => $subordinates,
'selectedUserId' => $selectedUserId
]);
if (isset($user->id)) {
return view('account/view-assets', compact('user', 'field_array' ))
->with('settings', Setting::getSettings());
}
// Redirect to the user management page
return redirect()->route('users.index')
->with('error', trans('admin/users/message.user_not_found', $user->id));
}
/**
@@ -160,7 +81,7 @@ class ViewAssetsController extends Controller
return view('account/requestable-assets', compact('assets', 'models'));
}
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null): RedirectResponse
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null) : RedirectResponse
{
$item = null;
$fullItemType = 'App\\Models\\'.studly_case($itemType);
@@ -223,32 +144,62 @@ class ViewAssetsController extends Controller
* Process a specific requested asset
* @param null $assetId
*/
public function store(Asset $asset): RedirectResponse
public function getRequestAsset($assetId = null) : RedirectResponse
{
try {
CreateCheckoutRequestAction::run($asset, auth()->user());
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
} catch (AssetNotRequestable $e) {
return redirect()->back()->with('error', 'Asset is not requestable');
} catch (AuthorizationException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.requests.error'));
} catch (Exception $e) {
report($e);
return redirect()->back()->with('error', trans('general.something_went_wrong'));
}
}
$user = auth()->user();
public function destroy(Asset $asset): RedirectResponse
{
try {
CancelCheckoutRequestAction::run($asset, auth()->user());
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} catch (Exception $e) {
report($e);
return redirect()->back()->with('error', trans('general.something_went_wrong'));
// Check if the asset exists and is requestable
if (is_null($asset = Asset::RequestableAssets()->find($assetId))) {
return redirect()->route('requestable-assets')
->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable'));
}
if (! Company::isCurrentUserHasAccess($asset)) {
return redirect()->route('requestable-assets')
->with('error', trans('general.insufficient_permissions'));
}
}
$data['item'] = $asset;
$data['target'] = auth()->user();
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
if ($user->location_id) {
$logaction->location_id = $user->location_id;
}
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
// If it's already requested, cancel the request.
if ($asset->isRequestedBy(auth()->user())) {
$asset->cancelRequest();
$asset->decrement('requests_counter', 1);
$logaction->logaction('request canceled');
try {
$settings->notify(new RequestAssetCancelation($data));
} catch (\Exception $e) {
Log::warning($e);
}
return redirect()->route('requestable-assets')
->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
}
$logaction->logaction('requested');
$asset->request();
$asset->increment('requests_counter', 1);
try {
$settings->notify(new RequestAssetNotification($data));
} catch (\Exception $e) {
Log::warning($e);
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
}
public function getRequestedAssets() : View
{

View File

@@ -73,7 +73,6 @@ class Kernel extends HttpKernel
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'api-throttle' => \App\Http\Middleware\SetAPIResponseHeaders::class,
'health' => null,
];
}

View File

@@ -1,82 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Symfony\Component\HttpFoundation\Response;
class SetAPIResponseHeaders extends ThrottleRequests
{
/**
* Add the rate limit headers to the response.
*
* This extends the original ThrottleRequests middleware to add the 'X-RateLimit-Reset' and 'Retry-After' headers, even
* if the rate limit is not exceeded.
* @param $maxAttempts
* @param $remainingAttempts
* @param $retryAfter
* @param Response|null $response
* @return array|int[]
*/
protected function getHeaders($maxAttempts, $remainingAttempts, $retryAfter = null, ?Response $response = null)
{
if ($response &&
! is_null($response->headers->get('X-RateLimit-Remaining')) &&
(int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) {
$headers = [];
$headers['Retry-After'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed
return $headers;
}
$headers = [
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $remainingAttempts,
];
if (! is_null($retryAfter)) {
$headers['Retry-After'] = $retryAfter;
$headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed
}
return $headers;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
protected function handleRequest($request, Closure $next, array $limits)
{
foreach ($limits as $limit) {
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
}
$this->limiter->hit($limit->key, $limit->decaySeconds);
}
$response = $next($request);
foreach ($limits as $limit) {
$response = $this->addHeaders(
$response,
$limit->maxAttempts,
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts),
$this->getTimeUntilNextRetry($limit->key) // this is the only line we changed
);
}
return $response;
}
}

View File

@@ -33,9 +33,9 @@ class SaveUserRequest extends FormRequest
public function rules()
{
$rules = [
'department_id' => 'nullable|integer|exists:departments,id',
'department_id' => 'nullable|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
'company_id' => ['nullable', 'integer', 'exists:companies,id']
'company_id' => ['nullable','exists:companies,id']
];
switch ($this->method()) {

View File

@@ -41,7 +41,6 @@ class SettingsSamlRequest extends FormRequest
public function withValidator($validator)
{
$validator->after(function ($validator) {
$setting = Setting::getSettings();
if ($this->input('saml_enabled') == '1') {
$idpMetadata = $this->input('saml_idp_metadata');
if (! empty($idpMetadata)) {
@@ -57,7 +56,7 @@ class SettingsSamlRequest extends FormRequest
}
}
$was_custom_x509cert = strpos($setting->saml_custom_settings, 'sp_x509cert') !== false;
$was_custom_x509cert = strpos(Setting::getSettings()->saml_custom_settings, 'sp_x509cert') !== false;
$custom_x509cert = '';
$custom_privateKey = '';
@@ -127,14 +126,10 @@ class SettingsSamlRequest extends FormRequest
}
if (! (empty($x509cert) && empty($privateKey))) {
// $this->merge([
// 'saml_sp_x509cert' => $x509cert,
// 'saml_sp_privatekey' => $privateKey,
// ]);
$setting->saml_sp_x509cert = $x509cert;
$setting->saml_sp_privatekey = $privateKey;
$setting->save();
$this->merge([
'saml_sp_x509cert' => $x509cert,
'saml_sp_privatekey' => $privateKey,
]);
}
} else {
$validator->errors()->add('saml_integration', 'openssl.cnf is missing/invalid');
@@ -150,21 +145,15 @@ class SettingsSamlRequest extends FormRequest
}
if (! empty($x509certNew)) {
// $this->merge([
// 'saml_sp_x509certNew' => $x509certNew,
// ]);
$setting->saml_sp_x509certNew = $x509certNew;
$setting->save();
$this->merge([
'saml_sp_x509certNew' => $x509certNew,
]);
}
} else {
// $this->merge([
// 'saml_sp_x509certNew' => '',
// ]);
$setting->saml_sp_x509certNew = '';
$setting->save();
$this->merge([
'saml_sp_x509certNew' => '',
]);
}
});
}
}

View File

@@ -19,7 +19,6 @@ class StoreAssetModelRequest extends ImageUploadRequest
public function prepareForValidation(): void
{
parent::prepareForValidation();
if ($this->category_id) {
if ($category = Category::find($this->category_id)) {

View File

@@ -39,6 +39,7 @@ class StoreAssetRequest extends ImageUploadRequest
$this->merge([
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
'company_id' => $idForCurrentUser,
'assigned_to' => $assigned_to ?? null,
]);
}

View File

@@ -29,16 +29,10 @@ class StoreLabelSettings extends FormRequest
return $label->getName();
})->values()->toArray();
if (empty($this->input('label2_template'))) {
$this->merge([
'label2_template' => 'DefaultLabel',
]);
}
return [
'labels_per_page' => 'numeric',
'labels_width' => 'numeric|min:0.1',
'labels_height' => 'numeric|min:0.1',
'labels_width' => 'numeric',
'labels_height' => 'numeric',
'labels_pmargin_left' => 'numeric|nullable',
'labels_pmargin_right' => 'numeric|nullable',
'labels_pmargin_top' => 'numeric|nullable',

View File

@@ -27,6 +27,8 @@ class StoreLdapSettings extends FormRequest
'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1',
'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1',
'ldap_server' => 'nullable|required_if:ldap_enabled,1|starts_with:ldap://,ldaps://',
'ldap_uname' => 'nullable|required_if:ldap_enabled,1',
'ldap_pword' => 'nullable|required_if:ldap_enabled,1',
'ldap_basedn' => 'nullable|required_if:ldap_enabled,1',
'ldap_fname_field' => 'nullable|required_if:ldap_enabled,1',
'custom_forgot_pass_url' => 'nullable|url',

View File

@@ -5,7 +5,6 @@ namespace App\Http\Requests;
use App\Models\Accessory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class StoreNotificationSettings extends FormRequest
{
@@ -27,9 +26,6 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
'admin_cc_always' => [
Rule::in('0', '1'),
],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',

View File

@@ -10,36 +10,19 @@ trait MayContainCustomFields
// this gets called automatically on a form request
public function withValidator($validator)
{
// In case the model is being changed via form
if (request()->has('model_id')!='') {
$asset_model = AssetModel::find(request()->input('model_id'));
// or if we have it available to route-model-binding
} elseif ((request()->route('asset') && (request()->route('asset')->model_id))) {
$asset_model = AssetModel::find(request()->route('asset')->model_id);
} else {
if ($this->method() == 'POST') {
$asset_model = AssetModel::find($this->model_id);
}
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
$asset_model = $this->asset->model;
}
// find the model
if ($this->method() == 'POST') {
$asset_model = AssetModel::find($this->model_id);
}
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
$asset_model = $this->asset->model;
}
// collect the custom fields in the request
$validator->after(function ($validator) use ($asset_model) {
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
return str_starts_with($attributes, '_snipeit_');
});
// if there are custom fields, find the ones that don't exist on the model's fieldset and add an error to the validator's error bag
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
->each(function ($request_field_name) use ($request_fields, $validator) {

View File

@@ -6,7 +6,6 @@ use App\Http\Traits\ConvertsBase64ToFiles;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \App\Helpers\Helper;
class UploadFileRequest extends Request
{
@@ -28,65 +27,44 @@ class UploadFileRequest extends Request
*/
public function rules()
{
$max_file_size = Helper::file_upload_max_size();
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:'.config('filesystems.allowed_upload_extensions_for_validator').'|max:'.$max_file_size,
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
];
}
/**
* Sanitizes (if needed) and Saves a file to the appropriate location
* Returns the 'short' (storage-relative) filename
*
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
* a way to merge them or extend one into the other?
*/
public function handleFile(string $dirname, string $name_prefix, $file): string
{
$extension = $file->getClientOriginalExtension();
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
// Check for SVG and sanitize it
if ($file->getMimeType() === 'image/svg+xml') {
$uploaded_file = $this->handleSVG($file);
Log::debug('This is an SVG');
Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put($dirname.$file_name, $cleanSVG);
} catch (\Exception $e) {
Log::debug('Upload no workie :( ');
Log::debug($e);
}
} else {
$uploaded_file = file_get_contents($file);
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
}
try {
Storage::put($dirname.$file_name, $uploaded_file);
} catch (\Exception $e) {
Log::debug($e);
}
return $file_name;
}
public function handleSVG($file)
{
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
return $sanitizer->sanitize($dirtySVG);
}
/**
* Get the validation error messages that apply to the request, but
* replace the attribute name with the name of the file that was attempted and failed
* to make it clearer to the user which file is the bad one.
*
* @return array
*/
public function attributes(): array
{
$attributes = [];
if ($this->file) {
for ($i = 0; $i < count($this->file); $i++) {
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
}
}
return $attributes;
}
}
}

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