diff --git a/.all-contributorsrc b/.all-contributorsrc
index d7624a4314..ba9f54a99b 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -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,15 +2607,6 @@
"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",
@@ -3136,6 +3127,1059 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "FlorentDotMe",
+ "name": "Florent Bervas",
+ "avatar_url": "https://avatars.githubusercontent.com/u/292081?v=4",
+ "profile": "http://spoontux.net",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "dbakan",
+ "name": "Daniel Albertsen",
+ "avatar_url": "https://avatars.githubusercontent.com/u/4498077?v=4",
+ "profile": "https://ditscheri.com",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "r-xyz",
+ "name": "r-xyz",
+ "avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4",
+ "profile": "https://github.com/r-xyz",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "DrekiDegga",
+ "name": "Steven Mainor",
+ "avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4",
+ "profile": "https://github.com/DrekiDegga",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "arne-kroeger",
+ "name": "arne-kroeger",
+ "avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4",
+ "profile": "https://github.com/arne-kroeger",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Glukose1",
+ "name": "Glukose1",
+ "avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4",
+ "profile": "https://github.com/Glukose1",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Scarzy",
+ "name": "Scarzy",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4",
+ "profile": "https://github.com/Scarzy",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "setpill",
+ "name": "setpill",
+ "avatar_url": "https://avatars.githubusercontent.com/u/37372069?v=4",
+ "profile": "https://github.com/setpill",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "swift2512",
+ "name": "swift2512",
+ "avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
+ "profile": "https://github.com/swift2512",
+ "contributions": [
+ "bug"
+ ]
+ },
+ {
+ "login": "DarrenRainey",
+ "name": "Darren Rainey",
+ "avatar_url": "https://avatars.githubusercontent.com/u/6136439?v=4",
+ "profile": "https://darrenraineys.co.uk",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "maciej-poleszczyk",
+ "name": "maciej-poleszczyk",
+ "avatar_url": "https://avatars.githubusercontent.com/u/133033121?v=4",
+ "profile": "https://github.com/maciej-poleszczyk",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "sgross-emlix",
+ "name": "Sebastian Groß",
+ "avatar_url": "https://avatars.githubusercontent.com/u/143394709?v=4",
+ "profile": "https://github.com/sgross-emlix",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "AnouarTouati",
+ "name": "Anouar Touati",
+ "avatar_url": "https://avatars.githubusercontent.com/u/41107778?v=4",
+ "profile": "https://github.com/AnouarTouati",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "aHVzY2g",
+ "name": "aHVzY2g",
+ "avatar_url": "https://avatars.githubusercontent.com/u/25596663?v=4",
+ "profile": "https://github.com/aHVzY2g",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "brlin-tw",
+ "name": "林博仁 Buo-ren Lin",
+ "avatar_url": "https://avatars.githubusercontent.com/u/13408130?v=4",
+ "profile": "https://brlin.me",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "addex12",
+ "name": "Adugna Gizaw",
+ "avatar_url": "https://avatars.githubusercontent.com/u/18550946?v=4",
+ "profile": "https://orbalia.pythonanywhere.com/",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "jostrander",
+ "name": "Jesse Ostrander",
+ "avatar_url": "https://avatars.githubusercontent.com/u/760989?v=4",
+ "profile": "https://github.com/jostrander",
+ "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"
+ ]
}
]
}
diff --git a/.env.dev.docker b/.env.dev.docker
index 7b9e2000cf..f47e69ce4b 100644
--- a/.env.dev.docker
+++ b/.env.dev.docker
@@ -1,6 +1,8 @@
# --------------------------------------------
# REQUIRED: DB SETUP
# --------------------------------------------
+# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/
+
MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit
MYSQL_PASSWORD=changeme1234
@@ -9,12 +11,12 @@ MYSQL_ROOT_PASSWORD=changeme1234
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
APP_ENV=develop
-APP_DEBUG=false
+APP_DEBUG=true
# please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE=en-US
MAX_RESULTS=500
# --------------------------------------------
@@ -33,6 +35,7 @@ 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
@@ -76,6 +79,13 @@ 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
@@ -156,7 +166,7 @@ RESET_PASSWORD_LINK_EXPIRES=900
# --------------------------------------------
# OPTIONAL: MISC
# --------------------------------------------
-LOG_CHANNEL=stderr
+LOG_CHANNEL=single
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC
diff --git a/.env.docker b/.env.docker
index 35c10bfde1..88b65b8f79 100644
--- a/.env.docker
+++ b/.env.docker
@@ -1,7 +1,7 @@
# --------------------------------------------
# REQUIRED: DOCKER SPECIFIC SETTINGS
# --------------------------------------------
-APP_VERSION=v6.4.1
+APP_VERSION=
APP_PORT=8000
# --------------------------------------------
@@ -9,12 +9,12 @@ APP_PORT=8000
# --------------------------------------------
APP_ENV=production
APP_DEBUG=false
-# Please regenerate the APP_KEY value by calling `docker compose run --rm snipeit php artisan key:generate --show`. Copy paste the value here
+# Please regenerate the APP_KEY value by calling `docker compose run --rm app php artisan key:generate --show`. Copy paste the value here
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE=en-US
MAX_RESULTS=500
# --------------------------------------------
@@ -35,6 +35,7 @@ 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
@@ -83,6 +84,15 @@ 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
# --------------------------------------------
@@ -97,7 +107,7 @@ API_TOKEN_EXPIRATION_YEARS=40
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
# --------------------------------------------
-APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.0.0.0/8
+APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.16.0.0/12
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
diff --git a/.env.dusk.example b/.env.dusk.example
index 2f16f44c7a..e306a06cf8 100644
--- a/.env.dusk.example
+++ b/.env.dusk.example
@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk=
APP_URL=http://127.0.0.1:8000
APP_TIMEZONE='US/Eastern'
-APP_LOCALE=en
+APP_LOCALE=en-US
APP_LOCKED=false
MAX_RESULTS=200
diff --git a/.env.example b/.env.example
index c92dc22b71..81de24ead1 100644
--- a/.env.example
+++ b/.env.example
@@ -30,8 +30,11 @@ 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
+
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS
@@ -78,6 +81,12 @@ MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
ALLOW_BACKUP_DELETE=false
ALLOW_DATA_PURGE=false
+ALL_BACKUP_KEEP_DAYS=7
+DAILY_BACKUP_KEEP_DAYS=16
+WEEKLY_BACKUP_KEEP_WEEKS=8
+MONTHLY_BACKUP_KEEP_MONTHS=4
+YEARLY_BACKUP_KEEP_YEARS=2
+BACKUP_PURGE_OLDEST_AT_MEGS=5000
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
@@ -87,10 +96,11 @@ SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false
ENCRYPT=false
COOKIE_NAME=snipeit_session
+PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
-BS_TABLE_STORAGE=cookieStorage
+BS_TABLE_STORAGE=localStorage
BS_TABLE_DEEPLINK=true
# --------------------------------------------
@@ -169,6 +179,7 @@ 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
@@ -183,6 +194,7 @@ REPORT_TIME_LIMIT=12000
REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
+LIVEWIRE_URL_PREFIX=null
# --------------------------------------------
# OPTIONAL: HASHING
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index b5f067cc68..0000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,38 +0,0 @@
-#### Expected Behavior (or desired behavior if a feature request)
-
-(what you expect to happen goes here)
-
------
-
-#### Actual Behavior
-
-(what actually happens goes here)
-
------
-
-#### Please confirm you have done the following before posting your bug report:
-
-- [ ] I have enabled debug mode
-- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
-
------
-#### Provide answers to these questions:
-
-- Is this a fresh install or an upgrade?
-- Version of Snipe-IT you're running
-- Version of PHP you're running
-- Version of MySQL/MariaDB you're running
-- What OS and web server you're running Snipe-IT on
-- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
-- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
-- What specific Snipe-IT page you're on, and what specific element you're interacting with to trigger the error
-- If a stacktrace is provided in the error, include that too.
-- Any errors that appear in your browser's error console.
-- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
-- Include any additional information you can find in `storage/logs` and your webserver's logs.
-- Include what you've done so far in the installation, and if you got any error messages along the way.
-- Indicate whether or not you've manually edited any data directly in the database
-
-Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.
-
-https://snipe-it.readme.io/docs/getting-help
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
deleted file mode 100644
index 5cc00c2a31..0000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ /dev/null
@@ -1,129 +0,0 @@
-name: Bug Report
-description: Create a report to help us improve
-body:
- - type: checkboxes
- attributes:
- label: Debug mode
- description: Please confirm you have done the following before posting your bug report
- options:
- - label: I have enabled debug mode
- required: true
- - label: I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
- required: true
- - type: textarea
- attributes:
- label: Describe the bug
- description: A clear and concise description of what the bug is.
- validations:
- required: true
- - type: textarea
- attributes:
- label: Reproduction steps
- description: Steps to reproduce the behavior.
- value: |
- 1.
- 2.
- 3.
- ...
- validations:
- required: true
- - type: textarea
- attributes:
- label: Expected behavior
- description: A clear and concise description of what you expected to happen.
- validations:
- required: true
- - type: textarea
- attributes:
- label: Screenshots
- description: 'If applicable, add screenshots to help explain your problem.'
- - type: markdown
- attributes:
- value: "### Server"
- - type: input
- attributes:
- label: Snipe-IT Version
- validations:
- required: true
- - type: input
- id: server_operatingSystem
- attributes:
- label: Operating System
- description: 'e.g. Ubuntu, Windows'
- validations:
- required: true
- - type: input
- attributes:
- label: Web Server
- description: 'e.g. Apache, IIS'
- validations:
- required: true
- - type: input
- attributes:
- label: PHP Version
- validations:
- required: true
- - type: markdown
- attributes:
- value: "### Desktop"
- - type: input
- id: desktop_operatingSystem
- attributes:
- label: Operating System
- description: 'e.g. Ubuntu, Windows'
- - type: input
- id: desktop_browser
- attributes:
- label: Browser
- description: 'e.g. Google Chrome, Safari'
- - type: input
- id: desktop_version
- attributes:
- label: Version
- description: 'e.g. 93'
- - type: markdown
- attributes:
- value: "### Mobile"
- - type: input
- attributes:
- label: Device
- description: 'e.g. iPhone 6, Pixel 4a'
- - type: input
- id: mobile_operatingSystem
- attributes:
- label: Operating System
- description: 'e.g. iOS 8.1, Android 9'
- - type: input
- id: mobile_browser
- attributes:
- label: Browser
- description: 'e.g. Google Chrome, Safari'
- - type: input
- id: mobile_version
- attributes:
- label: Version
- description: 'e.g. 93'
- - type: textarea
- attributes:
- label: Error messages
- description: |
- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
- If a stacktrace is provided in the error, include that too.
- Any errors that appear in your browser's error console.
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `storage/logs` and your webserver's logs.
- Include the output from `php -m` (this should display what modules you have enabled.)
- render: shell
- - type: textarea
- attributes:
- label: Additional context
- description: |
- Is this a fresh install or an upgrade?
- What OS and web server you're running Snipe-IT on
- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
- Include what you've done so far in the installation, and if you got any error messages along the way.
- Indicate whether or not you've manually edited any data directly in the database
- Add any other context about the problem here.
- - type: markdown
- attributes:
- value: Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0cec..0000000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
deleted file mode 100644
index dec3a8fe08..0000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Feature Request
-description: Suggest an idea for this project
-title: "[Feature Request]: "
-labels: ["feature request"]
-body:
- - type: textarea
- attributes:
- label: Is your feature request related to a problem? Please describe.
- description: A clear and concise description of what the problem is. The more information you can provide about your use-case, the more liklely we are to consider your feature.
- validations:
- required: true
- - type: textarea
- attributes:
- label: Describe the solution you'd like
- description: A clear and concise description of what you want to happen.
- validations:
- required: true
- - type: textarea
- attributes:
- label: Describe alternatives you've considered
- description: A clear and concise description of any alternative solutions or features you've considered.
- - type: textarea
- attributes:
- label: Additional context
- description: Add any other context or screenshots about the feature request here.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
deleted file mode 100644
index 7ef56a0d5f..0000000000
--- a/.github/pull_request_template.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Description
-
-Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context, providing screenshots where practical. List any dependencies that are required for this change.
-
-Fixes # (issue)
-
-## Type of change
-
-Please delete options that are not relevant.
-
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
-- [ ] This change requires a documentation update
-
-# How Has This Been Tested?
-
-Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
-
-- [ ] Test A
-- [ ] Test B
-
-**Test Configuration**:
-* PHP version:
-* MySQL version
-* Webserver version
-* OS version
-
-
-# Checklist:
-
-- [ ] I have read the Contributing documentation available here: https://snipe-it.readme.io/docs/contributing-overview
-- [ ] I have formatted this PR according to the project guidelines: https://snipe-it.readme.io/docs/contributing-overview#pull-request-guidelines
-- [ ] My code follows the style guidelines of this project
-- [ ] I have performed a self-review of my own code
-- [ ] I have commented my code, particularly in hard-to-understand areas
-- [ ] I have made corresponding changes to the documentation
-- [ ] My changes generate no new warnings
-- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] New and existing unit tests pass locally with my changes
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index 53acc282e4..0000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 60
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 7
-# Issues with these labels will never be considered stale
-exemptLabels:
- - pinned
- - security
- - :woman_technologist: ready for dev
- - :moneybag: bounty
- - :hand: bug
- - "🔐 security"
- - "👩💻 ready for dev"
- - "💰 bounty"
- - "✋ bug"
-
-exemptMilestones: true
-
-# Label to use when marking an issue as stale
-staleLabel: stale
-
-only: issues
-
-# Comment to post when removing the stale label.
-unmarkComment: >
- Okay, it looks like this issue or feature request might still be important. We'll re-open
- it for now. Thank you for letting us know!
-
-# Comment to post when marking an issue as stale. Set to `false` to disable
-markComment: >
- Is this still relevant? We haven't heard from anyone in a bit. If so,
- please comment with any updates or additional detail.
-
- This issue has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs. Don't
- take it personally, we just need to keep a handle on things. Thank you
- for your contributions!
-# Comment to post when closing a stale issue. Set to `false` to disable
-closeComment: >
- This issue has been automatically closed because it has not had
- recent activity. If you believe this is still an issue, please confirm that
- this issue is still happening in the most recent version of Snipe-IT and reply
- to this thread to re-open it.
diff --git a/.github/travis-memory.ini b/.github/travis-memory.ini
deleted file mode 100644
index 6bd8019ec4..0000000000
--- a/.github/travis-memory.ini
+++ /dev/null
@@ -1 +0,0 @@
-memory_limit= 2048M
diff --git a/.github/weekly-digest.yml b/.github/weekly-digest.yml
deleted file mode 100644
index fe502fbc98..0000000000
--- a/.github/weekly-digest.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Configuration for weekly-digest - https://github.com/apps/weekly-digest
-publishDay: sun
-canPublishIssues: true
-canPublishPullRequests: true
-canPublishContributors: true
-canPublishStargazers: true
-canPublishCommits: true
diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml
index f8368dff83..a11509d69e 100644
--- a/.github/workflows/codacy-analysis.yml
+++ b/.github/workflows/codacy-analysis.yml
@@ -10,10 +10,10 @@ name: Codacy Security Scan
on:
push:
- branches: [ master ]
+ branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
- branches: [ master ]
+ branches: [ develop ]
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 'snipe/snipe-it'
+ # Ensure schedule job never runs on forked repos. It's only executed for 'grokability/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 == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule'))
+ if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule'))
name: Codacy Security Scan
runs-on: ubuntu-latest
steps:
@@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
- uses: codacy/codacy-analysis-cli-action@v4.4.1
+ uses: codacy/codacy-analysis-cli-action@v4.4.5
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations
diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml
index bd46f9567b..86e0c022e2 100644
--- a/.github/workflows/docker-alpine.yml
+++ b/.github/workflows/docker-alpine.yml
@@ -20,8 +20,8 @@ permissions:
jobs:
docker:
- # Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
- if: github.repository == 'snipe/snipe-it'
+ # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
+ if: github.repository == 'grokability/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.
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker-ubuntu.yml
similarity index 95%
rename from .github/workflows/docker.yml
rename to .github/workflows/docker-ubuntu.yml
index adb87f3a5d..a78ba7413c 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker-ubuntu.yml
@@ -1,5 +1,5 @@
# Snipe-IT Docker image build for hub.docker.com
-name: Docker images
+name: Docker images (Ubuntu)
# 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 'snipe/snipe-it'
- if: github.repository == 'snipe/snipe-it'
+ # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
+ if: github.repository == 'grokability/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.
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000000..5a4042aee4
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,40 @@
+name: 'Close stale issues'
+on:
+ schedule:
+ - cron: '30 1 * * *'
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ permissions:
+ # contents: write # only for delete-branch option
+ issues: write
+ # pull-requests: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ debug-only: true
+ ascending: true
+ operations-per-run: 1000 # just while we're debugging
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ days-before-stale: 60
+ days-before-close: 7
+ exempt-all-milestones: true
+ stale-issue-message: >
+ Is this still relevant? We haven't heard from anyone in a bit. If so,
+ please comment with any updates or additional detail.
+
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Don't
+ take it personally, we just need to keep a handle on things. Thank you
+ for your contributions!
+ close-issue-message: >
+ This issue has been automatically closed because it has not had
+ recent activity. If you believe this is still an issue, please confirm that
+ this issue is still happening in the most recent version of Snipe-IT and reply
+ to this thread to re-open it.
+ # There doesn't seem to be a 'reopen issue message'?
+ # Since there is no 'stale-pr-message' - PR's should not be stale'd
+ stale-issue-label: stale
+ exempt-issue-labels: >
+ pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩💻 ready for dev,💰 bounty,✋ bug
\ No newline at end of file
diff --git a/.github/workflows/tests-mysql.yml b/.github/workflows/tests-mysql.yml
index 737a86dca3..16190900b0 100644
--- a/.github/workflows/tests-mysql.yml
+++ b/.github/workflows/tests-mysql.yml
@@ -25,9 +25,9 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "8.1"
- "8.2"
- "8.3"
+ - "8.4"
name: PHP ${{ matrix.php-version }}
@@ -67,7 +67,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
- php artisan passport:install
+ php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
@@ -76,4 +76,4 @@ jobs:
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
- run: php artisan test --parallel
+ run: php artisan test
diff --git a/.github/workflows/tests-postgres.yml b/.github/workflows/tests-postgres.yml
index b12fdebbc4..d111fc87a5 100644
--- a/.github/workflows/tests-postgres.yml
+++ b/.github/workflows/tests-postgres.yml
@@ -22,6 +22,9 @@ jobs:
matrix:
php-version:
- "8.2"
+ - "8.3"
+ - "8.4"
+
name: PHP ${{ matrix.php-version }}
@@ -62,7 +65,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
- php artisan passport:install
+ php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
@@ -72,4 +75,4 @@ jobs:
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
DB_USERNAME: snipeit
DB_PASSWORD: password
- run: php artisan test --parallel
+ run: php artisan test
diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml
index 49c7c92d8e..cd06805e01 100644
--- a/.github/workflows/tests-sqlite.yml
+++ b/.github/workflows/tests-sqlite.yml
@@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "8.1.1"
+ - "8.3"
name: PHP ${{ matrix.php-version }}
@@ -43,6 +43,9 @@ jobs:
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
+ - name: Create database file
+ run: touch database/database.sqlite
+
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
@@ -57,5 +60,5 @@ jobs:
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
- DB_CONNECTION: sqlite_testing
- run: php artisan test --parallel
+ DB_CONNECTION: sqlite
+ run: php artisan test
diff --git a/.gitignore b/.gitignore
index bf8360ba24..17a7d28dab 100755
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ storage/private_uploads/users/*
tests/_data/scenarios
tests/_output/*
tests/_support/_generated/*
+tests/coverage/*
/npm-debug.log
/storage/oauth-private.key
/storage/oauth-public.key
@@ -67,3 +68,6 @@ _ide_helper_models.php
/.phplint-cache
storage/ldap_client_tls.cert
storage/ldap_client_tls.key
+/storage/framework/testing
+
+/.phpunit.cache
\ No newline at end of file
diff --git a/.pa11yci.json b/.pa11yci.json
new file mode 100644
index 0000000000..73b84a8140
--- /dev/null
+++ b/.pa11yci.json
@@ -0,0 +1,240 @@
+{
+ "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"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.upgrade_requirements.json b/.upgrade_requirements.json
index 53d7337b62..9c4ece093b 100644
--- a/.upgrade_requirements.json
+++ b/.upgrade_requirements.json
@@ -3,8 +3,8 @@
"DOC2": "In other words, what you see locally are the requirements for your _current_ install",
"DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version",
"DOC4": "You should really just ignore it and run upgrade.php. Really",
- "php_min_version": "8.1.0",
- "php_max_major_minor": "8.3",
- "php_max_wontwork": "8.4.0",
- "current_snipeit_version": "7.0"
+ "php_min_version": "8.2.0",
+ "php_max_major_minor": "8.4",
+ "php_max_wontwork": "8.5.0",
+ "current_snipeit_version": "8.0"
}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index c0ca7951dd..7ab3810780 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,461 +1,73 @@
Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
-
-
-
-
-
-
-
+| [
snipe](http://www.snipe.net)
[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
+| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
+| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
+| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
+| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
+| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
+| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") |
+| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") |
+| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") |
+| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
+| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
+| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
+| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
+| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
+| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
+| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
+| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") |
+| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") |
+| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") |
+| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") |
+| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") |
+| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") |
+| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") |
+| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") |
+| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") |
+| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") |
+| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
+| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
+| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
+| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
+| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
+| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
+| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
+| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
+| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") |
+| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
+| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
+| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
+| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
+| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
+| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
+| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/qveensi)
[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") |
+| [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") |
+| [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") |
+| [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") |
+| [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") |
+| [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") |
+| [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
+| [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
+| [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
+| [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
+| [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [
Darren Rainey](https://darrenraineys.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [
maciej-poleszczyk](https://github.com/maciej-poleszczyk)
[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [
Sebastian Groß](https://github.com/sgross-emlix)
[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [
Anouar Touati](https://github.com/AnouarTouati)
[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
+| [
aHVzY2g](https://github.com/aHVzY2g)
[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [
林博仁 Buo-ren Lin](https://brlin.me)
[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [
Adugna Gizaw](https://orbalia.pythonanywhere.com/)
[🌍](#translation-addex12 "Translation") | [
Jesse Ostrander](https://github.com/jostrander)
[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [
James M](https://github.com/azmcnutt)
[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [
Fiala06](https://github.com/Fiala06)
[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [
Nathan Taylor](https://github.com/ntaylor-86)
[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
+| [
fvollmer](https://github.com/fvollmer)
[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [
36864](https://github.com/36864)
[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [
Daniel O'Connor](http://clockwerx.blogspot.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [
BeatSpark](https://github.com/BeatSpark)
[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [
mrdahbi](https://github.com/mrdahbi)
[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [
Fabian Schmid](http://sr.solutions)
[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [
Chris Olin](https://www.chrisolin.com)
[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
+| [
Dan](https://github.com/mnemonicly)
[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [
Nebel](https://github.com/NebelKreis)
[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [
test1337ahp](https://github.com/test1337ahp)
[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [
Jonathon Reinhart](https://github.com/JonathonReinhart)
[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [
aranar-pro](https://github.com/aranar-pro)
[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [
Phil](https://github.com/phil-flip)
[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [
Steffy Fort](https://fe80.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
+| [
Jared Busch](https://github.com/sorvani)
[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [
seanborg-codethink](https://github.com/seanborg-codethink)
[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [
dkaatz](https://github.com/dkaatz)
[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [
Daniel Ruf](https://threema.id/74SF7MW6?text=)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [
ahpaleus](https://github.com/ahpaleus)
[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [
Anh DAO-DUY](https://github.com/mink-adao-duy)
[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [
Andres Gutierrez](https://github.com/Serdnad)
[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") |
+| [
Warren White](https://github.com/wewhite)
[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [
Robin Temme](https://robintemme.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [
herroworrd](https://github.com/herroworrd)
[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [
vicleos](https://mubiu.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [
Bob Clough](http://thinkl33t.co.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [
Brandon Daniel Bailey](https://github.com/brandon-bailey)
[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [
Marc Bartelt](https://github.com/marcquark)
[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") |
+| [
manu-crealytics](https://github.com/manu-crealytics)
[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [
Konstantin Köhring](https://www.galaxy102.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [
Deloz](https://deloz.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [
Martin Berg](https://github.com/mbrrg)
[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [
Richard Schwab](https://github.com/Nothing4You)
[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [
Rick Heil](https://rickheil.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [
Ross Crawford-d'Heureuse](https://github.com/rosscdh)
[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") |
+| [
Ryan McGuire](https://github.com/McG800)
[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [
SBrown2021](https://github.com/SBrown2021)
[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [
Serkan](https://github.com/serkanerip)
[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [
Shanks](https://www.yudelei.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [
cendai-mis](https://github.com/cendai-mis)
[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [
Shaun McPeck](https://smcpeck.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [
Stephen](https://github.com/snazy2000)
[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") |
+| [
Steven](http://nevets82.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [
Mateus Villar](https://mateusvillar.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [
Matthew Zackschewski](https://github.com/mzack5020)
[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [
Matthias Frei](https://www.frei.media/)
[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [
Nenad Ticaric](https://github.com/nticaric)
[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [
Nikolay Didenko](https://github.com/Scorcher)
[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [
Nuno Maduro](https://nunomaduro.com/sponsorships)
[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") |
+| [
Oliver Walerys](https://tektikhq.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [
R. Christian McDonald](https://keybase.io/rcmcdonald91)
[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [
nix](https://nnix.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [
octobunny](https://github.com/octobunny)
[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [
Ryan](https://github.com/sreyemnayr)
[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [
p3nj](https://benji.ltd/)
[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [
Tim White](https://github.com/timwsuqld)
[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") |
+| [
yannikp](https://github.com/yannikp)
[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [
victoria](https://github.com/viclou)
[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [
Valentyn Tulub](https://github.com/valentyntu)
[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [
Wouter van Os](http://wouter0100.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [
Wyatt Teeter](https://www.linkedin.com/in/wyatt-teeter)
[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [
Yorick Terweijden](https://github.com/terwey)
[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [
bmkalle](https://github.com/bmkalle)
[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") |
+| [
bricelabelle](https://github.com/bricelabelle)
[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [
corydlamb](https://github.com/corydlamb)
[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [
Diogenes S. Jesus](http://twitter.com/splash)
[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [
D M](https://github.com/dkmansion)
[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [
Dustin B](https://github.com/Jarli01)
[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [
Fabian Grutschus](https://github.com/fabiang)
[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [
MelonSmasher](https://github.com/MelonSmasher)
[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") |
+| [
AlexanderWPapyrus](https://github.com/AlexanderWPapyrus)
[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [
Alexandr Hacicheant](https://github.com/disc)
[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [
Hex](https://hex128.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [
Arunas Skirius](https://github.com/arukompas)
[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [
Ben Periton](https://github.com/benperiton)
[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [
Byron Wolfman](https://wolfman.dev/)
[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [
Calvin](https://github.com/CalvinSchwartz)
[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") |
+| [
Juan Font](https://github.com/juanfont)
[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [
Juho Taipale](https://github.com/juhotaipale)
[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [
Korvin Szanto](https://github.com/KorvinSzanto)
[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [
Lewis Foster](https://lewisfoster.foo/)
[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [
Logan Swartzendruber](https://github.com/loganswartz)
[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [
Lorenzo P.](https://github.com/lopezio)
[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [
Lukas Jung](https://github.com/m4us1ne)
[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
+| [
Ellie](https://leafedfox.xyz/)
[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo Möller](https://github.com/dasjoe)
[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
+| [
Jan Felix Wiebe](https://github.com/jfwiebe)
[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") |
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
diff --git a/Dockerfile b/Dockerfile
index bd363ccd18..b4c4e8a357 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,8 @@
-FROM ubuntu:22.04
+FROM ubuntu:24.04
LABEL maintainer="Brady Wetherington "
# No need to add `apt-get clean` here, reference:
-# - https://github.com/snipe/snipe-it/pull/9201
+# - https://github.com/grokability/snipe-it/pull/9201
# - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
RUN export DEBIAN_FRONTEND=noninteractive; \
@@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \
apache2 \
apache2-bin \
-libapache2-mod-php8.1 \
-php8.1-curl \
-php8.1-ldap \
-php8.1-mysql \
-php8.1-gd \
-php8.1-xml \
-php8.1-mbstring \
-php8.1-zip \
-php8.1-bcmath \
-php8.1-redis \
+libapache2-mod-php8.3 \
+php8.3-curl \
+php8.3-ldap \
+php8.3-mysql \
+php8.3-gd \
+php8.3-xml \
+php8.3-mbstring \
+php8.3-zip \
+php8.3-bcmath \
+php8.3-redis \
php-memcached \
patch \
curl \
@@ -40,8 +40,7 @@ autoconf \
libc-dev \
libldap-common \
pkg-config \
-libmcrypt-dev \
-php8.1-dev \
+php8.3-dev \
ca-certificates \
unzip \
dnsutils \
@@ -51,18 +50,13 @@ dnsutils \
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
RUN php go-pear.phar
-RUN pecl install mcrypt
-
-RUN bash -c "echo extension=/usr/lib/php/20210902/mcrypt.so > /etc/php/8.1/mods-available/mcrypt.ini"
-
-RUN phpenmod mcrypt
RUN phpenmod gd
RUN phpenmod bcmath
-RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/apache2/php.ini
-RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/cli/php.ini
+RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/apache2/php.ini
+RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/cli/php.ini
-RUN useradd -m --uid 1000 --gid 50 docker
+RUN useradd -m --uid 10000 --gid 50 docker
RUN echo export APACHE_RUN_USER=docker >> /etc/apache2/envvars
RUN echo export APACHE_RUN_GROUP=staff >> /etc/apache2/envvars
@@ -116,7 +110,7 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Get dependencies
USER docker
-RUN composer install --no-dev --working-dir=/var/www/html
+RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html && rm -rf /var/www/html/vendor/*/*/.git
USER root
############### APPLICATION INSTALL/INIT #################
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
index 6d68e7ad9e..5df4b6d06a 100644
--- a/Dockerfile.alpine
+++ b/Dockerfile.alpine
@@ -1,35 +1,35 @@
-FROM alpine:3.18.6
+FROM alpine:3.19
# Apache + PHP
RUN apk add --no-cache \
apache2 \
- php81 \
- php81-common \
- php81-apache2 \
- php81-curl \
- php81-ldap \
- php81-mysqli \
- php81-gd \
- php81-xml \
- php81-mbstring \
- php81-zip \
- php81-ctype \
- php81-tokenizer \
- php81-pdo_mysql \
- php81-openssl \
- php81-bcmath \
- php81-phar \
- php81-json \
- php81-iconv \
- php81-fileinfo \
- php81-simplexml \
- php81-session \
- php81-dom \
- php81-xmlwriter \
- php81-xmlreader \
- php81-sodium \
- php81-redis \
- php81-pecl-memcached \
- php81-exif \
+ php82 \
+ php82-common \
+ php82-apache2 \
+ php82-curl \
+ php82-ldap \
+ php82-mysqli \
+ php82-gd \
+ php82-xml \
+ php82-mbstring \
+ php82-zip \
+ php82-ctype \
+ php82-tokenizer \
+ php82-pdo_mysql \
+ php82-openssl \
+ php82-bcmath \
+ php82-phar \
+ php82-json \
+ php82-iconv \
+ php82-fileinfo \
+ php82-simplexml \
+ php82-session \
+ php82-dom \
+ php82-xmlwriter \
+ php82-xmlreader \
+ php82-sodium \
+ php82-redis \
+ php82-pecl-memcached \
+ php82-exif \
curl \
wget \
vim \
@@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
-RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
+RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite
@@ -73,18 +73,18 @@ RUN mkdir -p /var/www/.composer && chown apache /var/www/.composer
# Install dependencies
USER apache
-RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html
+RUN COMPOSER_CACHE_DIR=/dev/null composer install --working-dir=/var/www/html
USER root
VOLUME ["/var/lib/snipeit"]
-# Entrypoints
-COPY docker/entrypoint_alpine.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
+# Startup script
+COPY docker/startup_alpine.sh /startup.sh
+RUN chmod +x /startup.sh
ENTRYPOINT ["/sbin/tini", "--"]
-CMD ["/entrypoint.sh"]
+CMD ["/startup.sh"]
EXPOSE 80
diff --git a/Dockerfile.fpm-alpine b/Dockerfile.fpm-alpine
index 77302524d4..0b89672267 100644
--- a/Dockerfile.fpm-alpine
+++ b/Dockerfile.fpm-alpine
@@ -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/snipe/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
+ curl -o snipeit.tar.gz -fL "https://github.com/grokability/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
@@ -97,7 +97,7 @@ RUN set -eux; \
VOLUME [ "/var/lib/snipeit" ]
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
-COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
+COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
-ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
-CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
+ENTRYPOINT [ "/startup.sh" ]
+CMD [ "/startup.sh", "php-fpm" ]
diff --git a/README.md b/README.md
index 6d2c45ac71..062be60453 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,34 @@
-
+
-[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
+[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[](#contributing) [](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
-It is built on [Laravel 10](http://laravel.com).
+It is built on [Laravel 11](http://laravel.com).
-Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
+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/).)
> [!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.
+-----
+
+### Table of Contents
+* [Installation](#installation)
+* [User's Manual](#users-manual)
+* [Bug Reports & Feature Requests](#bug-reports--feature-requests)
+* [Security](#security)
+* [Upgrading](#upgrading)
+* [Translations!](#translations-)
+* [Libraries, Modules & Related Projects](#libraries-modules--related-projects)
+* [Join the Community!](#join-the-community)
+* [Contributing](#contributing)
+* [Announcement List](#announcement-list)
+
+
-----
### Installation
@@ -22,8 +37,6 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
-
-
-----
### User's Manual
For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview).
@@ -31,24 +44,25 @@ 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/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.
+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.
> [!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.**
->
+
-----
+### Security
+
+> [!IMPORTANT]
+> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
+-----
+
+
### Upgrading
Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT.
------
-### 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.
-
-------
-
### Translations!
Please see the [translations documentation](https://snipe-it.readme.io/docs/translations) for information about available languages and how to add translations to Snipe-IT.
@@ -62,38 +76,72 @@ 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. :)
-- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
+#### Libraries & Modules
+
- [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
+- [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://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
+- [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.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
-- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
+- [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!
+
+- **[Join our Discord](https://discord.gg/yZFtShAcKk)!** It’s full of great people. We even wrote about it [here](https://grokstar.dev/culture/2024/06/the-unlikely-rise-of-discord-as-a-support-channel/)!
+- **Follow us on Bluesky** at [@snipeitapp.com](https://bsky.app/profile/snipeitapp.com)
+- **Follow us on Mastodon** at [hachyderm.io/@grokability](https://hachyderm.io/@grokability)
+- **Follow our blog** at [Grokstar.Dev](https://grokstar.dev)
+- **Subscribe here** on Github for notifications about new releases. (We recommend selecting "Releases" only for most users - this repo can get noisy.)
-----
### Contributing
-Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
+**Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them.**
-Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
+Contributions should follow from a human-to-human discussion in the form of an issue for the best chances of being merged into the core project. (Sometimes we might already be working on that feature, sometimes we've decided against )
+
+Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
+
+This project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
-[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT.
+Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years!
-----
-### Security
+### Star History
-> [!IMPORTANT]
-> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
+[](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.
+
+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.
diff --git a/SECURITY.md b/SECURITY.md
index dcbfd3e6f0..e052527bd1 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -10,10 +10,13 @@ however there are times when library dependencies and/or PHP/MySQL dependencies
make it impossible to backport security fixes on older versions.
| Version | Supported |
-| ------- | ------------------ |
-| 5.1.x | :white_check_mark: |
+|---------| ------------------ |
+| 8.x | :white_check_mark: |
+| 7.x | :white_check_mark: |
+| 6.x | :x: |
+| 5.1.x | :x: |
| 5.0.x | :x: |
-| 4.0.x | :white_check_mark: |
+| 4.0.x | :x: |
| < 4.0 | :x: |
## Reporting a Vulnerability
diff --git a/TESTING.md b/TESTING.md
index 92f3184eec..002cf27d3a 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -20,7 +20,7 @@ APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
-APP_LOCALE=en
+APP_LOCALE=en-US
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
diff --git a/Vagrantfile b/Vagrantfile
index 27efef7b1a..7c12ed642b 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -1,7 +1,7 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
-SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
+SNIPEIT_SH_URL= "https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh"
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
Vagrant.configure("2") do |config|
diff --git a/app.json b/app.json
index 0bf523f5e4..24d874114a 100644
--- a/app.json
+++ b/app.json
@@ -6,7 +6,7 @@
"it asset"
],
"website": "https://snipeitapp.com/",
- "repository": "https://github.com/snipe/snipe-it",
+ "repository": "https://github.com/grokability/snipe-it",
"logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg",
"success_url": "/setup",
"env": {
diff --git a/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php b/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php
new file mode 100644
index 0000000000..2d6dd68a08
--- /dev/null
+++ b/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php
@@ -0,0 +1,48 @@
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php b/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php
new file mode 100644
index 0000000000..6870cfba2d
--- /dev/null
+++ b/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php
@@ -0,0 +1,54 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Commands/DisableSAML.php b/app/Console/Commands/DisableSAML.php
new file mode 100644
index 0000000000..795e65f1c7
--- /dev/null
+++ b/app/Console/Commands/DisableSAML.php
@@ -0,0 +1,53 @@
+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.');
+ }
+ }
+}
diff --git a/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php b/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php
new file mode 100644
index 0000000000..694d8d3b30
--- /dev/null
+++ b/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php
@@ -0,0 +1,151 @@
+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;
+ }
+}
diff --git a/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php
new file mode 100644
index 0000000000..f368929f16
--- /dev/null
+++ b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php
@@ -0,0 +1,32 @@
+whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]);
+ $this->info("Assets with an assigned_type but no assigned_to are fixed");
+ }
+}
diff --git a/app/Console/Commands/FixupAssignedToWithoutAssignedType.php b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php
new file mode 100644
index 0000000000..ea3a55439a
--- /dev/null
+++ b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php
@@ -0,0 +1,66 @@
+whereNotNull("assigned_to")->withTrashed();
+ $this->withProgressBar($assets->get(), function (Asset $asset) {
+ //now check each action log, from the most recent backwards, to find the last checkin or checkout
+ foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
+ if($this->option("debug")) {
+ $this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
+ }
+ switch($action_log->action_type) {
+ case 'checkin from':
+ if($this->option("debug")) {
+ $this->info("Doing a checkin for ".$asset->id);
+ }
+ $asset->assigned_to = null;
+ // if you have a required custom field, we still want to save, and we *don't* want an action_log
+ $asset->saveQuietly();
+ return;
+
+ case 'checkout':
+ if($this->option("debug")) {
+ $this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
+ }
+ if($asset->assigned_to != $action_log->target_id) {
+ $this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
+ //FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
+ }
+ $asset->assigned_type = $action_log->target_type;
+ $asset->saveQuietly(); // see above
+ return;
+ }
+ }
+ $asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
+ $asset->saveQuietly(); //see above
+ });
+ $this->newLine();
+ $this->info("Assets assigned_type are fixed");
+ }
+}
diff --git a/app/Console/Commands/GeneratePersonalAccessToken.php b/app/Console/Commands/GeneratePersonalAccessToken.php
index c5f531213c..63b246978e 100644
--- a/app/Console/Commands/GeneratePersonalAccessToken.php
+++ b/app/Console/Commands/GeneratePersonalAccessToken.php
@@ -2,11 +2,9 @@
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
@@ -43,9 +41,8 @@ class GeneratePersonalAccessToken extends Command
*
* @return void
*/
- public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
+ public function __construct(TokenRepository $tokenRepository)
{
- $this->validation = $validation;
$this->tokenRepository = $tokenRepository;
parent::__construct();
}
@@ -76,7 +73,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);
diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php
old mode 100755
new mode 100644
index 1856bf10fc..f90aeafd8d
--- a/app/Console/Commands/LdapSync.php
+++ b/app/Console/Commands/LdapSync.php
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
+use App\Models\Asset;
use App\Models\Department;
use App\Models\Group;
use Illuminate\Console\Command;
@@ -53,18 +54,22 @@ class LdapSync extends Command
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
- $ldap_result_username = Setting::getSettings()->ldap_username_field;
- $ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
- $ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
- $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
- $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
- $ldap_result_email = Setting::getSettings()->ldap_email;
- $ldap_result_phone = Setting::getSettings()->ldap_phone_field;
- $ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle;
- $ldap_result_country = Setting::getSettings()->ldap_country;
- $ldap_result_location = Setting::getSettings()->ldap_location;
- $ldap_result_dept = Setting::getSettings()->ldap_dept;
- $ldap_result_manager = Setting::getSettings()->ldap_manager;
+
+ $ldap_map = [
+ "username" => Setting::getSettings()->ldap_username_field,
+ "last_name" => Setting::getSettings()->ldap_lname_field,
+ "first_name" => Setting::getSettings()->ldap_fname_field,
+ "active_flag" => Setting::getSettings()->ldap_active_flag,
+ "emp_num" => Setting::getSettings()->ldap_emp_num,
+ "email" => Setting::getSettings()->ldap_email,
+ "phone" => Setting::getSettings()->ldap_phone_field,
+ "jobtitle" => Setting::getSettings()->ldap_jobtitle,
+ "country" => Setting::getSettings()->ldap_country,
+ "location" => Setting::getSettings()->ldap_location,
+ "dept" => Setting::getSettings()->ldap_dept,
+ "manager" => Setting::getSettings()->ldap_manager,
+ ];
+
$ldap_default_group = Setting::getSettings()->ldap_default_group;
$search_base = Setting::getSettings()->ldap_base_dn;
@@ -107,14 +112,25 @@ class LdapSync extends Command
}
/**
- * If a filter has been specified, use that
+ * If a filter has been specified, use that, otherwise default to null
*/
if ($this->option('filter') != '') {
- $results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
+ $filter = $this->option('filter');
} else {
- $results = Ldap::findLdapUsers($search_base);
+ $filter = null;
}
-
+
+ /**
+ * We only need to request the LDAP attributes that we process
+ */
+ $attributes = array_values(array_filter($ldap_map));
+
+ if (Setting::getSettings()->is_ad === 1 && is_null($ldap_map['active_flag'])) {
+ $attributes[] = 'useraccountcontrol';
+ }
+
+ $results = Ldap::findLdapUsers($search_base, -1, $filter, $attributes);
+
} catch (\Exception $e) {
if ($this->option('json_summary')) {
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
@@ -126,23 +142,24 @@ class LdapSync extends Command
}
/* Determine which location to assign users to by default. */
- $location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
+ $default_location = null;
if ($this->option('location') != '') {
- if ($location = Location::where('name', '=', $this->option('location'))->first()) {
+ if ($default_location = Location::where('name', '=', $this->option('location'))->first()) {
Log::debug('Location name ' . $this->option('location') . ' passed');
- Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
+ Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
} elseif ($this->option('location_id')) {
+ //TODO - figure out how or why this is an array?
foreach($this->option('location_id') as $location_id) {
- if ($location = Location::where('id', '=', $location_id)->first()) {
+ if ($default_location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
- Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
+ Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
}
}
- if (! isset($location)) {
+ if (!isset($default_location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
@@ -183,17 +200,17 @@ class LdapSync extends Command
}
$usernames = [];
for ($i = 0; $i < $location_users['count']; $i++) {
- if (array_key_exists($ldap_result_username, $location_users[$i])) {
+ if (array_key_exists($ldap_map["username"], $location_users[$i])) {
$location_users[$i]['ldap_location_override'] = true;
$location_users[$i]['location_id'] = $ldap_loc['id'];
- $usernames[] = $location_users[$i][$ldap_result_username][0];
+ $usernames[] = $location_users[$i][$ldap_map["username"]][0];
}
}
// Delete located users from the general group.
foreach ($results as $key => $generic_entry) {
- if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) {
- if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
+ if ((is_array($generic_entry)) && (array_key_exists($ldap_map["username"], $generic_entry))) {
+ if (in_array($generic_entry[$ldap_map["username"]][0], $usernames)) {
unset($results[$key]);
}
}
@@ -218,76 +235,78 @@ class LdapSync extends Command
for ($i = 0; $i < $results['count']; $i++) {
- $item = [];
- $item['username'] = $results[$i][$ldap_result_username][0] ?? '';
- $item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? '';
- $item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? '';
- $item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? '';
- $item['email'] = $results[$i][$ldap_result_email][0] ?? '';
- $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
- $item['location_id'] = $results[$i]['location_id'] ?? '';
- $item['telephone'] = $results[$i][$ldap_result_phone][0] ?? '';
- $item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? '';
- $item['country'] = $results[$i][$ldap_result_country][0] ?? '';
- $item['department'] = $results[$i][$ldap_result_dept][0] ?? '';
- $item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
- $item['location'] = $results[$i][$ldap_result_location][0] ?? '';
+ $item = [];
+ $item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
+ $item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
+ $item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
+ $item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
+ $item['email'] = $results[$i][$ldap_map["email"]][0] ?? '';
+ $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
+ $item['location_id'] = $results[$i]['location_id'] ?? '';
+ $item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
+ $item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
+ $item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
+ $item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
+ $item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
+ $item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
+ $location = $default_location; //initially, set '$location' to the default_location (which may just be `null`)
- // ONLY if you are using the "ldap_location" option *AND* you have an actual result
- if ($ldap_result_location && $item['location']) {
- $location = Location::firstOrCreate([
- 'name' => $item['location'],
- ]);
- }
- $department = Department::firstOrCreate([
- 'name' => $item['department'],
+ // ONLY if you are using the "ldap_location" option *AND* you have an actual result
+ if ($ldap_map["location"] && $item['location']) {
+ $location = Location::firstOrCreate([
+ 'name' => $item['location'],
]);
+ }
+ $department = Department::firstOrCreate([
+ 'name' => $item['department'],
+ ]);
- $user = User::where('username', $item['username'])->first();
- if ($user) {
- // Updating an existing user.
- $item['createorupdate'] = 'updated';
- } else {
- // Creating a new user.
- $user = new User;
- $user->password = $user->noPassword();
- $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
- $item['createorupdate'] = 'created';
- }
+ $user = User::where('username', $item['username'])->first();
+ if ($user) {
+ // Updating an existing user.
+ $item['createorupdate'] = 'updated';
+ } else {
+ // Creating a new user.
+ $user = new User;
+ $user->password = $user->noPassword();
+ $user->locale = app()->getLocale();
+ $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
+ $item['createorupdate'] = 'created';
+ }
//If a sync option is not filled in on the LDAP settings don't populate the user field
- if($ldap_result_username != null){
+ if($ldap_map["username"] != null){
$user->username = $item['username'];
}
- if($ldap_result_last_name != null){
+ if($ldap_map["last_name"] != null){
$user->last_name = $item['lastname'];
}
- if($ldap_result_first_name != null){
+ if($ldap_map["first_name"] != null){
$user->first_name = $item['firstname'];
}
- if($ldap_result_emp_num != null){
+ if($ldap_map["emp_num"] != null){
$user->employee_num = e($item['employee_number']);
}
- if($ldap_result_email != null){
+ if($ldap_map["email"] != null){
$user->email = $item['email'];
}
- if($ldap_result_phone != null){
+ if($ldap_map["phone"] != null){
$user->phone = $item['telephone'];
}
- if($ldap_result_jobtitle != null){
+ if($ldap_map["jobtitle"] != null){
$user->jobtitle = $item['jobtitle'];
}
- if($ldap_result_country != null){
+ if($ldap_map["country"] != null){
$user->country = $item['country'];
}
- if($ldap_result_dept != null){
+ if($ldap_map["dept"] != null){
$user->department_id = $department->id;
}
- if($ldap_result_location != null){
- $user->location_id = $location ? $location->id : null;
+ if($ldap_map["location"] != null){
+ $user->location_id = $location?->id;
}
- if($ldap_result_manager != null){
+ if($ldap_map["manager"] != null){
if($item['manager'] != null) {
// Check Cache first
if (isset($manager_cache[$item['manager']])) {
@@ -304,63 +323,76 @@ class LdapSync extends Command
$ldap_manager = [
"count" => 1,
0 => [
- $ldap_result_username => [$item['manager']]
+ $ldap_map["username"] => [$item['manager']]
]
];
}
-
+
+ $add_manager_to_cache = true;
if ($ldap_manager["count"] > 0) {
+ try {
+ // Get the Manager's username
+ // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
+ $ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0];
- // Get the Manager's username
- // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
- $ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0];
+ // Get User from Manager username.
+ $ldap_manager = User::where('username', $ldapManagerUsername)->first();
- // Get User from Manager username.
- $ldap_manager = User::where('username', $ldapManagerUsername)->first();
-
- if ($ldap_manager && isset($ldap_manager->id)) {
- // Link user to manager id.
- $user->manager_id = $ldap_manager->id;
+ if ($ldap_manager && isset($ldap_manager->id)) {
+ // Link user to manager id.
+ $user->manager_id = $ldap_manager->id;
+ }
+ } catch (\Exception $e) {
+ $add_manager_to_cache = false;
+ \Log::warning('Handling ldap manager ' . $item['manager'] . ' caused an exception: ' . $e->getMessage() . '. Continuing synchronization.');
}
}
- $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
+ if ($add_manager_to_cache) {
+ $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
+ }
}
}
}
- // Sync activated state for Active Directory.
- if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
- // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
- // (Specifically, we don't handle a value of '0.0' correctly)
- $raw_value = @$results[$i][$ldap_result_active_flag][0];
- $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
- $boolean_cast = (bool)$raw_value;
-
+ // Sync activated state for Active Directory.
+ if (!empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set....
+ // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
+ // (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
+ }
- } elseif (array_key_exists('useraccountcontrol', $results[$i]) ) {
- // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
- // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
+ } elseif (array_key_exists('useraccountcontrol', $results[$i])) {
+ // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
+ // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
- /* The following is _probably_ the correct logic, but we can't use it because
- some users may have been dependent upon the previous behavior, and this
- could cause additional access to be available to users they don't want
- to allow to log in.
+ /* The following is _probably_ the correct logic, but we can't use it because
+ some users may have been dependent upon the previous behavior, and this
+ could cause additional access to be available to users they don't want
+ to allow to log in.
- $useraccountcontrol = $results[$i]['useraccountcontrol'][0];
- if(
- // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
- ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
- !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
- !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
- ) {
- $user->activated = 1;
- } else {
- $user->activated = 0;
- } */
- $enabled_accounts = [
+ $useraccountcontrol = $results[$i]['useraccountcontrol'][0];
+ if(
+ // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
+ ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
+ !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
+ !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
+ ) {
+ $user->activated = 1;
+ } else {
+ $user->activated = 0;
+ } */
+ $enabled_accounts = [
'512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
@@ -373,44 +405,59 @@ class LdapSync extends Command
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
- ];
- $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
+ ];
+ $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
// If we're not using AD, and there isn't an activated flag set, activate all users
- } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
- already-existing accounts will be however the administrator has set them */
+ } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
+ already-existing accounts will be however the administrator has set them */
- if ($item['ldap_location_override'] == true) {
- $user->location_id = $item['location_id'];
- } elseif ((isset($location)) && (! empty($location))) {
- if ((is_array($location)) && (array_key_exists('id', $location))) {
- $user->location_id = $location['id'];
- } elseif (is_object($location)) {
- $user->location_id = $location->id;
+ if ($item['ldap_location_override'] == true) {
+ $user->location_id = $item['location_id'];
+ } elseif ((isset($location)) && (!empty($location))) {
+ if ((is_array($location)) && (array_key_exists('id', $location))) {
+ $user->location_id = $location['id'];
+ } elseif (is_object($location)) {
+ $user->location_id = $location->id; //THIS is the magic line, this should do it.
+ }
+ }
+ // TODO - should we be NULLING locations if $location is really `null`, and that's what we came up with?
+ // will that conflict with any overriding setting that the user set? Like, if they moved someone from
+ // the 'null' location to somewhere, we wouldn't want to try to override that, right?
+ $location = null;
+ $user->ldap_import = 1;
+
+ $errors = '';
+
+ if ($user->save()) {
+ $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);
}
}
- $location = null;
- $user->ldap_import = 1;
-
- $errors = '';
-
- if ($user->save()) {
- $item['note'] = $item['createorupdate'];
- $item['status'] = 'success';
- if ( $item['createorupdate'] === 'created' && $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) {
+ $asset->location_id = $user->location_id;
+ // TODO: somehow add note? "Asset Location Changed because of thing"
+ $asset->save();
}
-
- } else {
- foreach ($user->getErrors()->getMessages() as $key => $err) {
- $errors .= $err[0];
- }
- $item['note'] = $errors;
- $item['status'] = 'error';
}
- array_push($summary, $item);
+ } else {
+ foreach ($user->getErrors()->getMessages() as $key => $err) {
+ $errors .= $err[0];
+ }
+ $item['note'] = $errors;
+ $item['status'] = 'error';
+ }
+
+ array_push($summary, $item);
}
if ($this->option('summary')) {
diff --git a/app/Console/Commands/MergeUsersByUsername.php b/app/Console/Commands/MergeUsersByUsername.php
index 390942708d..0c5e966ab9 100644
--- a/app/Console/Commands/MergeUsersByUsername.php
+++ b/app/Console/Commands/MergeUsersByUsername.php
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
+use App\Events\UserMerged;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
@@ -51,7 +52,7 @@ class MergeUsersByUsername extends Command
$bad_users = User::where('username', '=', trim($parts[0]))
->whereNull('deleted_at')
- ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations')
+ ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')
->get();
@@ -105,10 +106,26 @@ class MergeUsersByUsername extends Command
$managedLocation->save();
}
+ foreach ($bad_user->uploads as $upload) {
+ $this->info('Updating upload log record '.$upload->id.' to user '.$user->id);
+ $upload->item_id = $user->id;
+ $upload->save();
+ }
+
+ foreach ($bad_user->acceptances as $acceptance) {
+ $this->info('Updating acceptance log record '.$acceptance->id.' to user '.$user->id);
+ $acceptance->item_id = $user->id;
+ $acceptance->save();
+ }
+
// Mark the user as deleted
$this->info('Marking the user as deleted');
$bad_user->deleted_at = Carbon::now()->timestamp;
$bad_user->save();
+
+ event(new UserMerged($bad_user, $user, null));
+
+
}
}
}
diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php
index 8370e7c050..b79949521a 100644
--- a/app/Console/Commands/ObjectImportCommand.php
+++ b/app/Console/Commands/ObjectImportCommand.php
@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
+use Symfony\Component\Console\Helper\ProgressIndicator;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
@@ -29,6 +30,11 @@ class ObjectImportCommand extends Command
*/
protected $description = 'Import Items from CSV';
+ /**
+ * The progress indicator instance.
+ */
+ protected ProgressIndicator $progressIndicator;
+
/**
* Create a new command instance.
*
@@ -39,8 +45,6 @@ class ObjectImportCommand extends Command
parent::__construct();
}
- private $bar;
-
/**
* Execute the console command.
*
@@ -48,12 +52,14 @@ class ObjectImportCommand extends Command
*/
public function handle()
{
+ $this->progressIndicator = new ProgressIndicator($this->output);
+
$filename = $this->argument('filename');
$class = title_case($this->option('item-type'));
$classString = "App\\Importer\\{$class}Importer";
$importer = new $classString($filename);
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
- ->setUserId($this->option('user_id'))
+ ->setCreatedBy($this->option('user_id'))
->setUpdating($this->option('update'))
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format'));
@@ -61,46 +67,25 @@ class ObjectImportCommand extends Command
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile');
// Log::useFiles($logFile);
- $this->comment('======= Importing Items from '.$filename.' =========');
+ $this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
+
$importer->import();
- $this->bar = null;
-
- if (! empty($this->errors)) {
- $this->comment('The following Errors were encountered.');
- foreach ($this->errors as $asset => $error) {
- $this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error));
- }
- } else {
- $this->comment('All Items imported successfully!');
- }
- $this->comment('');
+ $this->progressIndicator->finish('Import finished.');
}
- public function errorCallback($item, $field, $errorString)
+ public function errorCallback($item, $field, $error)
{
- $this->errors[$item->name][$field] = $errorString;
+ $this->output->write("\x0D\x1B[2K");
+
+ $this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error));
}
- public function progress($count)
+ public function progress($importedItemsCount)
{
- if (! $this->bar) {
- $this->bar = $this->output->createProgressBar($count);
- }
- static $index = 0;
- $index++;
- if ($index < $count) {
- $this->bar->advance();
- } else {
- $this->bar->finish();
- }
+ $this->progressIndicator->advance();
}
- // Tracks the current item for error messages
- private $updating;
- // An array of errors encountered while parsing
- private $errors;
-
/**
* Log a message to file, configurable by the --log-file parameter.
* If a warning message is passed, we'll spit it to the console as well.
diff --git a/app/Console/Commands/PaveIt.php b/app/Console/Commands/PaveIt.php
index 09c846ea68..ef69d25a5b 100644
--- a/app/Console/Commands/PaveIt.php
+++ b/app/Console/Commands/PaveIt.php
@@ -51,8 +51,7 @@ 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 = DB::connection()->getDoctrineSchemaManager()->listTableNames();
-
+ $tables = Schema::getTables();
$except_tables = [
'oauth_access_tokens',
'oauth_clients',
@@ -74,7 +73,8 @@ class PaveIt extends Command
}
}
- foreach ($tables as $table) {
+ foreach ($tables as $table_obj) {
+ $table = $table_obj['name'];
if (in_array($table, $except_tables)) {
$this->info($table. ' is SKIPPED.');
} else {
diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php
index 7abd83c85c..1dd2aaa51d 100644
--- a/app/Console/Commands/Purge.php
+++ b/app/Console/Commands/Purge.php
@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Accessory;
+use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
@@ -15,6 +16,8 @@ use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
class Purge extends Command
{
@@ -141,6 +144,20 @@ class Purge extends Command
$this->info($users->count().' users purged.');
$user_assoc = 0;
foreach ($users as $user) {
+
+ $rel_path = 'private_uploads/users';
+ $filenames = Actionlog::where('action_type', 'uploaded')
+ ->where('item_id', $user->id)
+ ->pluck('filename');
+ foreach($filenames as $filename) {
+ try {
+ if (Storage::exists($rel_path . '/' . $filename)) {
+ Storage::delete($rel_path . '/' . $filename);
+ }
+ } catch (\Exception $e) {
+ Log::info('An error occurred while deleting files: ' . $e->getMessage());
+ }
+ }
$this->info('- User "'.$user->username.'" deleted.');
$user_assoc += $user->userlog()->count();
$user->userlog()->forceDelete();
diff --git a/app/Console/Commands/RecryptFromMcrypt.php b/app/Console/Commands/RecryptFromMcrypt.php
deleted file mode 100644
index 33c8ae65c9..0000000000
--- a/app/Console/Commands/RecryptFromMcrypt.php
+++ /dev/null
@@ -1,157 +0,0 @@
-error('ERROR: You do not have a LEGACY_APP_KEY set in your .env file. Please locate your old APP_KEY and ADD a line to your .env file like: LEGACY_APP_KEY=YOUR_OLD_APP_KEY');
-
- return false;
- }
-
- // Do some basic legacy app key length checks
- if (strlen($legacy_key) == 32) {
- $legacy_length_check = true;
- } elseif (array_key_exists('1', $key_parts) && (strlen($key_parts[1]) == 44)) {
- $legacy_key = base64_decode($key_parts[1], true);
- $legacy_length_check = true;
- } else {
- $legacy_length_check = false;
- }
-
- // Check that the app key is 32 characters
- if ($legacy_length_check === true) {
- $this->comment('INFO: Your LEGACY_APP_KEY looks correct. Okay to continue.');
- } else {
- $this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters or base64 followed by 44 characters for later versions). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.');
-
- return false;
- }
-
- $this->error('================================!!!! WARNING !!!!================================');
- $this->error('================================!!!! WARNING !!!!================================');
- $this->comment("This tool will attempt to decrypt your old Snipe-IT (mcrypt, now deprecated) encrypted data and re-encrypt it using OpenSSL. \n\nYou should only continue if you have backed up any and all old APP_KEYs and have backed up your data.");
-
- $force = ($this->option('force')) ? true : false;
-
- if ($force || ($this->confirm('Are you SURE you wish to continue?'))) {
- $backup_file = 'backups/env-backups/'.'app_key-'.date('Y-m-d-gis');
-
- try {
- Storage::disk('local')->put($backup_file, 'APP_KEY: '.config('app.key'));
- Storage::disk('local')->append($backup_file, 'LEGACY_APP_KEY: '.$legacy_key);
- } catch (\Exception $e) {
- $this->info('WARNING: Could not backup app keys');
- }
-
- if ($legacy_cipher) {
- $mcrypter = new McryptEncrypter($legacy_key, $legacy_cipher);
- } else {
- $mcrypter = new McryptEncrypter($legacy_key);
- }
- $settings = Setting::getSettings();
-
- if ($settings->ldap_pword == '') {
- $this->comment('INFO: No LDAP password found. Skipping... ');
- } else {
- $decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword);
- $settings->ldap_pword = Crypt::encrypt($decrypted_ldap_pword);
- $settings->save();
- }
- /** @var CustomField[] $custom_fields */
- $custom_fields = CustomField::where('field_encrypted', '=', 1)->get();
- $this->comment('INFO: Retrieving encrypted custom fields...');
-
- $query = Asset::withTrashed();
-
- foreach ($custom_fields as $custom_field) {
- $this->comment('FIELD TO RECRYPT: '.$custom_field->name.' ('.$custom_field->db_column.')');
- $query->orWhereNotNull($custom_field->db_column);
- }
-
- // Get all assets with a value in any of the fields that were encrypted
- /** @var Asset[] $assets */
- $assets = $query->get();
-
- $bar = $this->output->createProgressBar(count($assets));
-
- foreach ($assets as $asset) {
- foreach ($custom_fields as $encrypted_field) {
- $columnName = $encrypted_field->db_column;
-
- // Make sure the value isn't null
- if ($asset->{$columnName} != '') {
- // Try to decrypt the payload using the legacy app key
- try {
- $decrypted_field = $mcrypter->decrypt($asset->{$columnName});
- $asset->{$columnName} = Crypt::encrypt($decrypted_field);
- $this->comment($decrypted_field);
- } catch (\Exception $e) {
- $errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
- }
- }
- }
- $asset->save();
- $bar->advance();
- }
-
- $bar->finish();
-
- if (count($errors) > 0) {
- $this->comment("\n\n");
- $this->error("The decrypter encountered some errors: \n");
- foreach ($errors as $error) {
- $this->error($error);
- }
- }
- }
- }
-}
diff --git a/app/Console/Commands/RemoveExplicitEols.php b/app/Console/Commands/RemoveExplicitEols.php
new file mode 100644
index 0000000000..f5164c2ee0
--- /dev/null
+++ b/app/Console/Commands/RemoveExplicitEols.php
@@ -0,0 +1,60 @@
+option('model_name') == 'all') {
+ $assets = Asset::all();
+ $this->updateAssets($assets);
+ } else {
+ $assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first();
+
+ if ($assetModel) {
+ $assets = Asset::where('model_id', '=', $assetModel->id)->get();
+ $this->updateAssets($assets);
+ } else {
+ $this->error('Asset model not found');
+ }
+ }
+ $endTime = microtime(true);
+ $executionTime = ($endTime - $startTime);
+ $this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
+ }
+
+ private function updateAssets($assets)
+ {
+ foreach ($assets as $asset) {
+ $asset->eol_explicit = 0;
+ $asset->asset_eol_date = null;
+ $asset->save();
+ }
+
+ $this->info($assets->count() . ' Assets updated successfully');
+ }
+}
diff --git a/app/Console/Commands/ResetDemoSettings.php b/app/Console/Commands/ResetDemoSettings.php
index 9ddade9efb..8eda65240b 100644
--- a/app/Console/Commands/ResetDemoSettings.php
+++ b/app/Console/Commands/ResetDemoSettings.php
@@ -50,12 +50,12 @@ class ResetDemoSettings extends Command
$settings->alert_email = 'service@snipe-it.io';
$settings->login_note = 'Use `admin` / `password` to login to the demo.';
$settings->header_color = null;
- $settings->barcode_type = 'QRCODE';
+ $settings->label2_2d_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 2;
$settings->ldap_enabled = 0;
$settings->full_multiple_companies_support = 0;
- $settings->alt_barcode = 'C128';
+ $settings->label2_1d_type = 'C128';
$settings->skin = '';
$settings->email_domain = 'snipeitapp.com';
$settings->email_format = 'filastname';
@@ -65,7 +65,7 @@ class ResetDemoSettings extends Command
$settings->thumbnail_max_h = '30';
$settings->locale = 'en-US';
$settings->version_footer = 'on';
- $settings->support_footer = null;
+ $settings->support_footer = 'on';
$settings->saml_enabled = '0';
$settings->saml_sp_x509cert = null;
$settings->saml_idp_metadata = null;
@@ -73,6 +73,7 @@ class ResetDemoSettings extends Command
$settings->saml_forcelogin = '0';
$settings->saml_slo = null;
$settings->saml_custom_settings = null;
+ $settings->default_avatar = 'default.png';
$settings->save();
diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php
index 1cc9b280e1..35e67a82e7 100644
--- a/app/Console/Commands/RestoreFromBackup.php
+++ b/app/Console/Commands/RestoreFromBackup.php
@@ -30,8 +30,11 @@ class SQLStreamer {
public function parse_sql(string $line): string {
// take into account the 'start of line or not' setting as an instance variable?
// 'continuation' lines for a permitted statement are PERMITTED.
+ // remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
+ // Windows dumps
+ $line = trim($line, "\r\n");
if($this->statement_is_permitted && $line[0] === ' ') {
- return $line;
+ return $line . "\n"; //re-add the newline
}
$table_regex = '`?([a-zA-Z0-9_]+)`?';
@@ -42,8 +45,14 @@ class SQLStreamer {
"/^(INSERT INTO )$table_regex(.*)$/" => false,
"/^UNLOCK TABLES/" => false,
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
- "/^\\)[a-zA-Z0-9_= ]*;$/" => false
- // ^^^^^^ that bit should *exit* the 'perimitted' black
+ "/^\\)[a-zA-Z0-9_= ]*;$/" => false,
+ // ^^^^^^ that bit should *exit* the 'permitted' block
+ "/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
+ /* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
+ a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
+ assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
+ "<^/\*!40101 SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, //using weird delimiters (<,>) for readability. allow quoted or unquoted charsets
+ "<^/\*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' \*/;$>" => false, //same, now handle zero-values
];
foreach($allowed_statements as $statement => $statechange) {
@@ -67,7 +76,7 @@ class SQLStreamer {
}
//how do we *replace* the tablename?
// print "RETURNING LINE: $line";
- return $line;
+ return $line . "\n"; //re-add newline
}
}
// all that is not allowed is denied.
@@ -85,7 +94,7 @@ class SQLStreamer {
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
- //can't use 'users' because the 'accessories_users' table?
+ //can't use 'users' because the 'accessories_checkout' table?
// can't use 'assets' because 'ver1_components_assets'
foreach($check_tables as $check_table => $_ignore) {
foreach ($parser->tablenames as $tablename => $_count) {
@@ -164,7 +173,8 @@ class RestoreFromBackup extends Command
{filename : The zip file to be migrated}
{--no-progress : Don\'t show a progress bar}
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
- {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
+ {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}
+ {--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }';
/**
* The console command description.
@@ -239,6 +249,7 @@ 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',
@@ -279,6 +290,7 @@ class RestoreFromBackup extends Command
$interesting_files = [];
$boring_files = [];
+ $unsafe_files = [];
for ($i = 0; $i < $za->numFiles; $i++) {
$stat_results = $za->statIndex($i);
@@ -318,7 +330,8 @@ class RestoreFromBackup extends Command
}
}
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
- 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
+ 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', 'avif'
+ ];
foreach (array_merge($private_files, $public_files) as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
@@ -328,7 +341,9 @@ class RestoreFromBackup extends Command
if ($last_pos !== false) {
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
if (!in_array($extension, $good_extensions)) {
- $this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
+ // gathering potentially unsafe files here to return at exit
+ $unsafe_files[] = $raw_path;
+ Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
$boring_files[] = $raw_path;
continue 2;
}
@@ -362,7 +377,17 @@ 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 sanitze your SQL.");
+
+ return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitize your SQL.");
+ }
+
+ // If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
+ if ($this->option('sql-stdout-only')) {
+ $sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
+ $bytes_read = $sql_importer->line_aware_piping();
+ return $this->warn("$bytes_read total bytes read");
+ //TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
+ // which would be good for redirecting to a file, and not having to trim the last line off of it
}
//how to invoke the restore?
@@ -466,6 +491,9 @@ class RestoreFromBackup extends Command
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
$fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
+ if (!is_dir($file_details['dest'])) {
+ mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
+ }
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
fwrite($migrated_file, $buffer);
@@ -483,6 +511,11 @@ 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.');
}
diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php
new file mode 100644
index 0000000000..c1cf4ffda2
--- /dev/null
+++ b/app/Console/Commands/SendAcceptanceReminder.php
@@ -0,0 +1,111 @@
+where('checkoutable_type', 'App\Models\Asset')
+ ->whereHas('checkoutable', function($query) {
+ $query->where('accepted_at', null)
+ ->where('declined_at', null);
+ })
+ ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
+ ->get();
+
+ $count = 0;
+ $unacceptedAssetGroups = $pending
+ ->filter(function($acceptance) {
+ return $acceptance->checkoutable_type == 'App\Models\Asset';
+ })
+ ->map(function($acceptance) {
+ return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
+ })
+ ->groupBy(function($item) {
+ return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
+ });
+ $no_email_list= [];
+
+ foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
+ // 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(),
+ ];
+ } 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)));
+ }
+
+ }
+
+ $this->info($count.' users notified.');
+ $headers = ['ID', 'Name'];
+ $rows = [];
+
+ 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);
+ }
+
+ return 0;
+ }
+
+}
diff --git a/app/Console/Commands/SendExpirationAlerts.php b/app/Console/Commands/SendExpirationAlerts.php
index 025ba2ce23..64aff907fb 100644
--- a/app/Console/Commands/SendExpirationAlerts.php
+++ b/app/Console/Commands/SendExpirationAlerts.php
@@ -2,13 +2,13 @@
namespace App\Console\Commands;
+use App\Mail\ExpiringAssetsMail;
+use App\Mail\ExpiringLicenseMail;
use App\Models\Asset;
use App\Models\License;
-use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
-use App\Notifications\ExpiringAssetsNotification;
-use App\Notifications\ExpiringLicenseNotification;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Mail;
class SendExpirationAlerts extends Command
{
@@ -42,27 +42,28 @@ class SendExpirationAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
- $threshold = $settings->alert_interval;
+ $alert_interval = $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(function ($item, $key) {
- return new AlertRecipient($item);
- });
-
+ $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($threshold);
+ $assets = Asset::getExpiringWarrantee($alert_interval);
+
if ($assets->count() > 0) {
- $this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
- \Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
+ $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));
}
// Expiring licenses
- $licenses = License::getExpiringLicenses($threshold);
+ $licenses = License::getExpiringLicenses($alert_interval);
if ($licenses->count() > 0) {
- $this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
- \Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
+ $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));
}
} else {
if ($settings->alert_email == '') {
diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php
index 7c81a37d3a..854488adc6 100644
--- a/app/Console/Commands/SendUpcomingAuditReport.php
+++ b/app/Console/Commands/SendUpcomingAuditReport.php
@@ -2,13 +2,12 @@
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
{
@@ -48,19 +47,20 @@ 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 (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
+ if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
- $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
- return new AlertRecipient($item);
- });
+ $recipients = collect(explode(',', $settings->alert_email))
+ ->map(fn($item) => trim($item))
+ ->filter(fn($item) => !empty($item))
+ ->all();
- $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));
}
}
diff --git a/app/Console/Commands/TestLocationsFMCS.php b/app/Console/Commands/TestLocationsFMCS.php
new file mode 100644
index 0000000000..70eface2de
--- /dev/null
+++ b/app/Console/Commands/TestLocationsFMCS.php
@@ -0,0 +1,51 @@
+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);
+
+ }
+
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 8d512f303b..ca85459b67 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -5,6 +5,7 @@ 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;
@@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
- $schedule->command('snipeit:inventory-alerts')->daily();
- $schedule->command('snipeit:expiring-alerts')->daily();
- $schedule->command('snipeit:expected-checkin')->daily();
+ 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: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();
}
diff --git a/app/Events/CheckoutableCheckedIn.php b/app/Events/CheckoutableCheckedIn.php
index 48aed2a64d..fedbd49fbe 100644
--- a/app/Events/CheckoutableCheckedIn.php
+++ b/app/Events/CheckoutableCheckedIn.php
@@ -28,7 +28,7 @@ class CheckoutableCheckedIn
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
- $this->action_date = $action_date ?? date('Y-m-d');
+ $this->action_date = $action_date ?? date('Y-m-d H:i:s');
$this->originalValues = $originalValues;
}
}
diff --git a/app/Events/UserMerged.php b/app/Events/UserMerged.php
index b045fdef03..3a7f4d6a2c 100644
--- a/app/Events/UserMerged.php
+++ b/app/Events/UserMerged.php
@@ -15,7 +15,7 @@ class UserMerged
*
* @return void
*/
- public function __construct(User $from_user, User $to_user, User $admin)
+ public function __construct(User $from_user, User $to_user, ?User $admin)
{
$this->merged_from = $from_user;
$this->merged_to = $to_user;
diff --git a/app/Exceptions/AssetNotRequestable.php b/app/Exceptions/AssetNotRequestable.php
new file mode 100644
index 0000000000..4067dda385
--- /dev/null
+++ b/app/Exceptions/AssetNotRequestable.php
@@ -0,0 +1,9 @@
+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:
@@ -122,6 +122,37 @@ class Handler extends ExceptionHandler
}
+ // 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())))));
+ $route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
+
+ // Sigh.
+ if ($route == 'assets.index') {
+ $route = 'hardware.index';
+ } elseif ($route == 'reporttemplates.index') {
+ $route = 'reports/custom';
+ } elseif ($route == 'assetmodels.index') {
+ $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()
+ ->route($route)
+ ->withError(trans('general.generic_model_not_found', ['model' => $model_name]));
+ }
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {
@@ -174,8 +205,9 @@ class Handler extends ExceptionHandler
*/
public function register()
{
+
$this->reportable(function (Throwable $e) {
//
});
}
-}
\ No newline at end of file
+}
diff --git a/app/Exceptions/UserDoestExistException.php b/app/Exceptions/UserDoestExistException.php
new file mode 100644
index 0000000000..e14069dca4
--- /dev/null
+++ b/app/Exceptions/UserDoestExistException.php
@@ -0,0 +1,10 @@
+ 'mn-MN', // Mongolian
'ms' => 'ms-MY', // Malay
'nl' => 'nl-NL', // Dutch
- 'no' => 'no-NO', // Norwegian
+ 'no' => 'nb-NO', // Norwegian Bokmål
'pl' => 'pl-PL', // Polish
+ 'pt' => 'pt-PT', // Portuguese
'ro' => 'ro-RO', // Romanian
'ru' => 'ru-RU', // Russian
'sk' => 'sk-SK', // Slovak
@@ -707,6 +710,28 @@ class Helper
return $randomString;
}
+ /**
+ * A method to be used to handle deprecations notifications, currently handling MS Teams. more can be added when needed.
+ *
+ *
+ * @author [Godfrey Martinez]
+ * @since [v7.0.14]
+ * @return array
+ */
+ public static function deprecationCheck() : array {
+ // 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. Change webhook endpoint'),
+ ];
+
+ // if item of concern is being used and its being used with the deprecated values return the notification array.
+ if(Setting::getSettings()->webhook_selected === 'microsoft' && $deprecations['ms_teams_deprecated']['check']) {
+ return $deprecations;
+ }
+ return [];
+ }
/**
* This nasty little method gets the low inventory info for the
@@ -720,7 +745,7 @@ class Helper
{
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
- $accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
+ $accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
$licenses = License::where('min_amt', '>', 0)->get();
@@ -748,7 +773,7 @@ class Helper
}
foreach ($accessories as $accessory) {
- $avail = $accessory->qty - $accessory->users_count;
+ $avail = $accessory->qty - $accessory->checkouts_count;
if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0);
@@ -844,7 +869,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')) {
+ if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif') || ($filetype == 'image/webp')) {
return $filetype;
}
@@ -871,6 +896,12 @@ 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++) {
@@ -881,13 +912,13 @@ class Helper
if (is_array($selected_arr)) {
if (array_key_exists($permission_name, $selected_arr)) {
- $permissions_arr[$permission_name] = $selected_arr[$permission_name];
+ $permissions_arr[$permission_name] = (int) $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;
}
}
}
@@ -913,13 +944,22 @@ class Helper
$rules = $class::rules();
foreach ($rules as $rule_name => $rule) {
if ($rule_name == $field) {
- if (strpos($rule, 'required') === false) {
- return false;
+ if (is_array($rule)) {
+ if (in_array('required', $rule)) {
+ return true;
+ } else {
+ return false;
+ }
} else {
- return true;
- }
+ if (strpos($rule, 'required') === false) {
+ return false;
+ } else {
+ return true;
+ }
+ }
}
}
+ return false;
}
/**
@@ -1113,6 +1153,7 @@ class Helper
'png' => 'far fa-image',
'webp' => 'far fa-image',
'avif' => 'far fa-image',
+ 'svg' => 'fas fa-vector-square',
// word
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
@@ -1125,7 +1166,7 @@ class Helper
//Text
'txt' => 'far fa-file-alt',
'rtf' => 'far fa-file-alt',
- 'xml' => 'far fa-file-alt',
+ 'xml' => 'fas fa-code',
// Misc
'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save',
@@ -1138,41 +1179,7 @@ class Helper
return 'far fa-file';
}
- public static function show_file_inline($filename)
- {
- $extension = substr(strrchr($filename, '.'), 1);
- if ($extension) {
- switch ($extension) {
- case 'jpg':
- case 'jpeg':
- case 'gif':
- case 'png':
- case 'webp':
- case 'avif':
- return true;
- break;
- default:
- return false;
- }
- }
-
- return false;
- }
-
- /**
- * Generate a random encrypted password.
- *
- * @author Wes Hulette
- *
- * @since 5.0.0
- *
- * @return string
- */
- public static function generateEncyrptedPassword(): string
- {
- return bcrypt(self::generateUnencryptedPassword());
- }
/**
* Get a random unencrypted password.
@@ -1306,25 +1313,24 @@ 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';
- break;
+ case 'supplier':
+ return 'fa-solid fa-store';
+ case 'manufacturer':
+ return 'fa-solid fa-building';
+ case 'category':
+ return 'fa-solid fa-table-columns';
}
}
@@ -1440,7 +1446,6 @@ class Helper
foreach (self::$language_map as $legacy => $new) {
if ($language_code == $legacy) {
- Log::debug('Current language is '.$legacy.', using '.$new.' instead');
return $new;
}
}
@@ -1451,6 +1456,7 @@ class Helper
public static function mapBackToLegacyLocale($new_locale = null)
{
+
if (strlen($new_locale) <= 4) {
return $new_locale; //"new locale" apparently wasn't quite so new
}
@@ -1458,44 +1464,193 @@ class Helper
// This does a *reverse* search against our new language map array - given the value, find the *key* for it
$legacy_locale = array_search($new_locale, self::$language_map);
- if($legacy_locale !== false) {
+ if ($legacy_locale !== false) {
return $legacy_locale;
}
return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
}
+ public static function determineLanguageDirection() {
+ return in_array(app()->getLocale(),
+ [
+ 'ar-SA',
+ 'fa-IR',
+ 'he-IL'
+ ]) ? 'rtl' : 'ltr';
+ }
- static public function getRedirectOption($request, $id, $table, $asset_id = null)
+
+ static public function getRedirectOption($request, $id, $table, $item_id = null)
{
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
+ $checkedInFrom = Session::get('checkedInFrom');
+ $other_redirect = Session::get('other_redirect');
- //return to index
- if ($redirect_option == '0') {
+ // return to index
+ if ($redirect_option == 'index') {
switch ($table) {
case "Assets":
- return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
+ 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 == '1') {
+
+ // return to thing being assigned
+ if ($redirect_option == 'item') {
switch ($table) {
case "Assets":
- return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success'));
+ 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 thing being assigned to
- if ($redirect_option == '2') {
+
+ // return to assignment target
+ if ($redirect_option == 'target') {
switch ($checkout_to_type) {
case 'user':
- return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success'));
+ return route('users.show', $request->assigned_user ?? $checkedInFrom);
case 'location':
- return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success'));
+ return route('locations.show', $request->assigned_location ?? $checkedInFrom);
case 'asset':
- return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success'));
+ return route('hardware.show', $request->assigned_asset ?? $checkedInFrom);
}
}
+
+ // return to somewhere else
+ if ($redirect_option == 'other_redirect') {
+ switch ($other_redirect) {
+ case 'audit':
+ return route('assets.audit.due');
+ }
+
+ }
+
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
+ * @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;
+ }
}
diff --git a/app/Helpers/IconHelper.php b/app/Helpers/IconHelper.php
new file mode 100644
index 0000000000..7c8e2a7456
--- /dev/null
+++ b/app/Helpers/IconHelper.php
@@ -0,0 +1,196 @@
+download($filename);
}
}
+
+
+ /**
+ * This determines the file types that should be allowed inline and checks their fileinfo extension
+ * to determine that they are safe to display inline.
+ *
+ * @author [
+ * @since v7.0.14
+ * @param $file_with_path
+ * @return bool
+ */
+ public static function allowSafeInline($file_with_path) {
+
+ $allowed_inline = [
+ 'pdf',
+ 'svg',
+ 'jpg',
+ 'gif',
+ 'svg',
+ 'avif',
+ 'webp',
+ 'png',
+ ];
+
+
+ // The file exists and is allowed to be displayed inline
+ if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) {
+ return true;
+ }
+ return false;
+
+ }
+
+ /**
+ * Decide whether to show the file inline or download it.
+ */
+ public static function showOrDownloadFile($file, $filename) {
+
+ $headers = [];
+
+ if (request('inline') == 'true') {
+
+ $headers = [
+ 'Content-Disposition' => 'inline',
+ ];
+
+ // This is NOT allowed as inline - force it to be displayed as text in the browser
+ if (self::allowSafeInline($file) != true) {
+ $headers = array_merge($headers, ['Content-Type' => 'text/plain']);
+ }
+ }
+
+ // Everything else seems okay, but the file doesn't exist on the server.
+ if (Storage::missing($file)) {
+ throw new FileNotFoundException();
+ }
+
+ return Storage::download($file, $filename, $headers);
+
+ }
}
diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php
index 81d5d692c5..66a914e9a9 100755
--- a/app/Http/Controllers/Accessories/AccessoriesController.php
+++ b/app/Http/Controllers/Accessories/AccessoriesController.php
@@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Accessory;
use App\Models\Company;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
-use Redirect;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
/** This controller handles all actions related to Accessories for
@@ -27,24 +27,19 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] []
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('index', Accessory::class);
-
- return view('accessories/index');
+ return view('accessories.index');
}
/**
* Returns a view with a form to create a new Accessory.
*
* @author [A. Gianotto] []
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Accessory::class);
$category_type = 'accessory';
@@ -58,10 +53,8 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize(Accessory::class);
@@ -80,16 +73,17 @@ class AccessoriesController extends Controller
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
- $accessory->user_id = Auth::user()->id;
+ $accessory->created_by = auth()->id();
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
- return redirect()->route('accessories.index')->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());
@@ -100,20 +94,11 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param int $accessoryId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($accessoryId = null)
+ public function edit(Accessory $accessory) : View | RedirectResponse
{
-
- if ($item = Accessory::find($accessoryId)) {
- $this->authorize($item);
-
- return view('accessories/edit', compact('item'))->with('category_type', 'accessory');
- }
-
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
-
+ $this->authorize('update', Accessory::class);
+ return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
}
/**
@@ -122,26 +107,18 @@ class AccessoriesController extends Controller
* @author [J. Vinsmoke]
* @param int $accessoryId
* @since [v6.0]
- * @return View
*/
- public function getClone($accessoryId = null)
+ public function getClone(Accessory $accessory) : View | RedirectResponse
{
$this->authorize('create', Accessory::class);
-
- // Check if the asset exists
- if (is_null($accessory_to_clone = Accessory::find($accessoryId))) {
- // Redirect to the asset management page
- return redirect()->route('accessories.index')
- ->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryId]));
- }
-
- $accessory = clone $accessory_to_clone;
- $accessory->id = null;
- $accessory->location_id = null;
+ $cloned = clone $accessory;
+ $cloned->id = null;
+ $cloned->deleted_at = '';
+ $cloned->location_id = null;
return view('accessories/edit')
- ->with('item', $accessory);
+ ->with('item', $cloned);
}
@@ -151,17 +128,15 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] []
* @param ImageUploadRequest $request
* @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $accessoryId = null)
+ public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse
{
- if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
+ if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
- "qty" => "required|numeric|min:$accessory->users_count"
+ "qty" => "required|numeric|min:$accessory->checkouts_count"
]);
if ($validator->fails()) {
@@ -189,9 +164,10 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
- // Was the accessory updated?
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($accessory->save()) {
- return redirect()->route('accessories.index')->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'));
@@ -205,20 +181,18 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] []
* @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($accessoryId)
+ public function destroy($accessoryId) : RedirectResponse
{
- if (is_null($accessory = Accessory::find($accessoryId))) {
+ if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize($accessory);
- if ($accessory->hasUsers() > 0) {
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()]));
+ if ($accessory->checkouts_count > 0) {
+ return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
}
if ($accessory->image) {
@@ -243,17 +217,14 @@ class AccessoriesController extends Controller
* @param int $accessoryID
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($accessoryID = null)
+ public function show(Accessory $accessory) : View | RedirectResponse
{
- $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
- $this->authorize('view', $accessory);
- if (isset($accessory->id)) {
- return view('accessories/view', compact('accessory'));
- }
+ $accessory->loadCount('checkouts as checkouts_count');
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID]));
+ $accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
+
+ $this->authorize('view', $accessory);
+ return view('accessories.view', compact('accessory'));
}
}
diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
index 75fa0b2f12..9dbb16d83a 100644
--- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php
+++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php
@@ -7,10 +7,13 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
-use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
-use Symfony\Accessory\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Response;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
class AccessoriesFilesController extends Controller
{
@@ -19,20 +22,17 @@ class AccessoriesFilesController extends Controller
*
* @param UploadFileRequest $request
* @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
- *@author [A. Gianotto] []
+ * @author [A. Gianotto] []
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
- public function store(UploadFileRequest $request, $accessoryId = null)
+ public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
}
-
$accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) {
@@ -51,15 +51,15 @@ class AccessoriesFilesController extends Controller
}
- return redirect()->route('accessories.show', $accessory->id)->with('success', trans('general.file_upload_success'));
+ return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
- return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded'));
+ return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
- return redirect()->route('accessories.index')
- ->with('error', trans('general.file_does_not_exist'));
+ return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
+
}
/**
@@ -69,35 +69,30 @@ class AccessoriesFilesController extends Controller
* @since [v1.0]
* @param int $accessoryId
* @param int $fileId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($accessoryId = null, $fileId = null)
+ public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
{
- $accessory = Accessory::find($accessoryId);
-
- // the asset is valid
- if (isset($accessory->id)) {
+ if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('update', $accessory);
- $log = Actionlog::find($fileId);
- // Remove the file if one exists
- if (Storage::exists('accessories/'.$log->filename)) {
- try {
- Storage::delete('accessories/'.$log->filename);
- } catch (\Exception $e) {
- Log::debug($e);
+ if ($log = Actionlog::find($fileId)) {
+
+ if (Storage::exists('private_uploads/accessories/'.$log->filename)) {
+ try {
+ Storage::delete('private_uploads/accessories/' . $log->filename);
+ $log->delete();
+ return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
+ } catch (\Exception $e) {
+ Log::debug($e);
+ return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
+ }
}
+
}
-
- $log->delete();
-
- return redirect()->back()
- ->with('success', trans('admin/hardware/message.deletefile.success'));
+ return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
}
- // Redirect to the licence management page
- return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
+ return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
@@ -107,53 +102,31 @@ class AccessoriesFilesController extends Controller
* @since [v1.4]
* @param int $accessoryId
* @param int $fileId
- * @return \Symfony\Accessory\HttpFoundation\Response
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($accessoryId = null, $fileId = null, $download = true)
+ public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{
- Log::debug('Private filesystem is: '.config('filesystems.default'));
- $accessory = Accessory::find($accessoryId);
-
-
// the accessory is valid
- if (isset($accessory->id)) {
+ if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
- if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
- return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
- }
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
+ $file = 'private_uploads/accessories/'.$log->filename;
- $file = 'private_uploads/accessories/'.$log->filename;
-
- if (Storage::missing($file)) {
- Log::debug('FILE DOES NOT EXISTS for '.$file);
- Log::debug('URL should be '.Storage::url($file));
-
- return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
- ->header('Content-Type', 'text/plain');
- } else {
-
- // Display the file inline
- if (request('inline') == 'true') {
- $headers = [
- 'Content-Disposition' => 'inline',
- ];
- return Storage::download($file, $log->filename, $headers);
- }
-
-
- // We have to override the URL stuff here, since local defaults in Laravel's Flysystem
- // won't work, as they're not accessible via the web
- if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
- return StorageHelper::downloader($file);
+ try {
+ return StorageHelper::showOrDownloadFile($file, $log->filename);
+ } catch (\Exception $e) {
+ return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
}
}
+
+ return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
+
}
- return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
+ return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
+
}
}
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
index 29c6065057..ab24200d78 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php
@@ -3,12 +3,14 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
-use App\Models\User;
+use App\Models\AccessoryCheckout;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AccessoryCheckinController extends Controller
{
@@ -19,22 +21,25 @@ class AccessoryCheckinController extends Controller
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
- * @return View
- * @internal param int $accessoryId
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($accessoryUserId = null, $backto = null)
+ public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
- // Redirect to the accessory management page with error
+ if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$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'))->with('backto', $backto);
+ return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
+
}
/**
@@ -42,24 +47,25 @@ class AccessoryCheckinController extends Controller
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] []
- * @param null $accessoryUserId
+ * @param null $accessoryCheckoutId
* @param string $backto
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
- * @internal param int $accessoryId
*/
- public function store(Request $request, $accessoryUserId = null, $backto = null)
+ public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
- // Redirect to the accessory management page with error
+ if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
- $accessory = Accessory::find($accessory_user->accessory_id);
+ $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')) {
@@ -67,12 +73,12 @@ class AccessoryCheckinController extends Controller
}
// Was the accessory updated?
- if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
- $return_to = e($accessory_user->assigned_to);
+ if ($accessory_checkout->delete()) {
+ event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at));
- event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at));
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
- return redirect()->route('accessories.show', $accessory->id)->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'));
diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
index 679b8d4251..58ce787245 100644
--- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
+++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php
@@ -3,29 +3,34 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
+use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
+use App\Http\Requests\AccessoryCheckoutRequest;
use App\Models\Accessory;
+use App\Models\AccessoryCheckout;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Input;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class AccessoryCheckoutController extends Controller
{
+
+ use CheckInOutRequest;
+
/**
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] []
* @param int $id
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($id)
+ public function create($id) : View | RedirectResponse
{
- if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
+ if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
$this->authorize('checkout', $accessory);
@@ -58,46 +63,41 @@ class AccessoryCheckoutController extends Controller
*
* @author [A. Gianotto] []
* @param Request $request
- * @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @param Accessory $accessory
*/
- public function store(Request $request, $accessoryId)
+ public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
- // Redirect to the accessory management page with error
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
- }
-
+
$this->authorize('checkout', $accessory);
- if (!$user = User::find($request->input('assigned_to'))) {
- return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
+ $target = $this->determineCheckoutTarget();
+
+ $accessory->checkout_qty = $request->input('checkout_qty', 1);
+
+ for ($i = 0; $i < $accessory->checkout_qty; $i++) {
+
+ $accessory_checkout = new AccessoryCheckout([
+ 'accessory_id' => $accessory->id,
+ 'created_at' => Carbon::now(),
+ 'assigned_to' => $target->id,
+ 'assigned_type' => $target::class,
+ 'note' => $request->input('note'),
+ ]);
+
+ $accessory_checkout->created_by = auth()->id();
+ $accessory_checkout->save();
}
- // Make sure there is at least one available to checkout
- if ($accessory->numRemaining() <= 0){
- return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
- }
+ event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
+ $request->request->add(['checkout_to_type' => request('checkout_to_type')]);
+ $request->request->add(['assigned_to' => $target->id]);
- // Update the accessory data
- $accessory->assigned_to = e($request->input('assigned_to'));
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
- $accessory->users()->attach($accessory->id, [
- 'accessory_id' => $accessory->id,
- 'created_at' => Carbon::now(),
- 'user_id' => Auth::id(),
- 'assigned_to' => $request->get('assigned_to'),
- 'note' => $request->input('note'),
- ]);
-
- DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
-
- event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
// Redirect to the new accessory page
- return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
+ return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
+ ->with('success', trans('admin/accessories/message.checkout.success'));
}
}
diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php
index fafb6aeda5..b20f7d97e8 100644
--- a/app/Http/Controllers/Account/AcceptanceController.php
+++ b/app/Http/Controllers/Account/AcceptanceController.php
@@ -28,20 +28,18 @@ use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
-use phpDocumentor\Reflection\Types\Compound;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
class AcceptanceController extends Controller
{
/**
* Show a listing of pending checkout acceptances for the current user
- *
- * @return View
*/
- public function index()
+ public function index() : View
{
- $acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
-
+ $acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get();
return view('account/accept.index', compact('acceptances'));
}
@@ -49,9 +47,8 @@ class AcceptanceController extends Controller
* Shows a form to either accept or decline the checkout acceptance
*
* @param int $id
- * @return mixed
*/
- public function create($id)
+ public function create($id) : View | RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
@@ -64,7 +61,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
- if (! $acceptance->isCheckedOutTo(Auth::user())) {
+ if (! $acceptance->isCheckedOutTo(auth()->user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
@@ -80,9 +77,8 @@ class AcceptanceController extends Controller
*
* @param Request $request
* @param int $id
- * @return \Illuminate\Http\RedirectResponse
*/
- public function store(Request $request, $id)
+ public function store(Request $request, $id) : RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
@@ -94,7 +90,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
- if (! $acceptance->isCheckedOutTo(Auth::user())) {
+ if (! $acceptance->isCheckedOutTo(auth()->user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
@@ -212,9 +208,12 @@ class AcceptanceController extends Controller
*/
$branding_settings = SettingsController::getPDFBranding();
- if (is_null($branding_settings->logo)){
- $path_logo = "";
- } else {
+ $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)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
}
@@ -222,6 +221,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
+ 'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
@@ -240,7 +240,11 @@ class AcceptanceController extends Controller
}
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
- $acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
+ try {
+ $acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
+ } catch (\Exception $e) {
+ Log::warning($e);
+ }
event(new CheckoutAccepted($acceptance));
$return_msg = trans('admin/users/message.accepted');
@@ -312,6 +316,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag,
'item_model' => $display_model,
'item_serial' => $item->serial,
+ 'item_status' => $item->assetstatus?->name,
'note' => $request->input('note'),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
@@ -336,4 +341,5 @@ class AcceptanceController extends Controller
return redirect()->to('account/accept')->with('success', $return_msg);
}
+
}
diff --git a/app/Http/Controllers/ActionlogController.php b/app/Http/Controllers/ActionlogController.php
index 2d194b7ca6..057ca465bc 100644
--- a/app/Http/Controllers/ActionlogController.php
+++ b/app/Http/Controllers/ActionlogController.php
@@ -3,13 +3,14 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
-use App\Models\Actionlog;
-use Illuminate\Support\Facades\Response;
+use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
+use \Illuminate\Http\Response;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ActionlogController extends Controller
{
- public function displaySig($filename)
+ public function displaySig($filename) : RedirectResponse | Response | bool
{
// PHP doesn't let you handle file not found errors well with
// file_get_contents, so we set the error reporting for just this class
@@ -17,6 +18,7 @@ class ActionlogController extends Controller
$disk = config('filesystems.default');
switch (config("filesystems.disks.$disk.driver")) {
+
case 's3':
$file = 'private_uploads/signatures/'.$filename;
return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5)));
@@ -30,15 +32,24 @@ class ActionlogController extends Controller
Log::warning('File '.$file.' not found');
return false;
} else {
- return Response::make($contents)->header('Content-Type', $filetype);
+ return response()->make($contents)->header('Content-Type', $filetype);
}
}
}
- public function getStoredEula($filename){
+ public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
+ {
$this->authorize('view', \App\Models\Asset::class);
- $file = config('app.private_uploads').'/eula-pdfs/'.$filename;
- return Response::download($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'));
+
}
}
diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php
index eb74a9e0f0..90486b40f2 100644
--- a/app/Http/Controllers/Api/AccessoriesController.php
+++ b/app/Http/Controllers/Api/AccessoriesController.php
@@ -4,20 +4,27 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
+use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
+use App\Http\Requests\AccessoryCheckoutRequest;
+use App\Http\Requests\StoreAccessoryRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
use App\Models\Company;
use App\Models\User;
+use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
+use App\Models\AccessoryCheckout;
class AccessoriesController extends Controller
{
+ use CheckInOutRequest;
+
/**
* Display a listing of the resource.
*
@@ -45,20 +52,21 @@ class AccessoriesController extends Controller
'min_amt',
'company_id',
'notes',
- 'users_count',
+ 'checkouts_count',
'qty',
];
- $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
- ->withCount('users as users_count');
+ $accessories = Accessory::select('accessories.*')
+ ->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
+ ->withCount('checkouts as checkouts_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
}
if ($request->filled('company_id')) {
- $accessories->where('company_id', '=', $request->input('company_id'));
+ $accessories->where('accessories.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -104,7 +112,10 @@ class AccessoriesController extends Controller
break;
case 'supplier':
$accessories = $accessories->OrderSupplier($order);
- break;
+ break;
+ case 'created_by':
+ $accessories = $accessories->OrderByCreatedByName($order);
+ break;
default:
$accessories = $accessories->orderBy($column_sort, $order);
break;
@@ -120,14 +131,13 @@ class AccessoriesController extends Controller
/**
* Store a newly created resource in storage.
*
+ * @param \App\Http\Requests\ImageUploadRequest $request
+ * @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] []
* @since [v4.0]
- * @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAccessoryRequest $request)
{
- $this->authorize('create', Accessory::class);
$accessory = new Accessory;
$accessory->fill($request->all());
$accessory = $request->handleImages($accessory);
@@ -143,15 +153,15 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
+ * @param int $id
+ * @return array
* @author [A. Gianotto] []
* @since [v4.0]
- * @param int $id
- * @return \Illuminate\Http\Response
*/
public function show($id)
{
$this->authorize('view', Accessory::class);
- $accessory = Accessory::withCount('users as users_count')->findOrFail($id);
+ $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
@@ -160,10 +170,10 @@ class AccessoriesController extends Controller
/**
* Display the specified resource.
*
+ * @param int $id
+ * @return array
* @author [A. Gianotto] []
* @since [v4.0]
- * @param int $id
- * @return \Illuminate\Http\Response
*/
public function accessory_detail($id)
{
@@ -175,47 +185,33 @@ class AccessoriesController extends Controller
/**
- * Display the specified resource.
+ * Get the list of checkouts for a specific accessory
*
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
+ * @return | array
*/
- public function checkedout($id, Request $request)
+ public function checkedout(Request $request, $id)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::with('lastCheckout')->findOrFail($id);
- if (! Company::isCurrentUserHasAccess($accessory)) {
- return ['total' => 0, 'rows' => []];
- }
-
$offset = request('offset', 0);
$limit = request('limit', 50);
- $accessory_users = $accessory->users;
- $total = $accessory_users->count();
-
- if ($total < $offset) {
- $offset = 0;
- }
-
- $accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
+ // Total count of all checkouts for this asset
+ $accessory_checkouts = $accessory->checkouts();
+ // Check for search text in the request
if ($request->filled('search')) {
- $accessory_users = $accessory->users()
- ->where(function ($query) use ($request) {
- $search_str = '%' . $request->input('search') . '%';
- $query->where('first_name', 'like', $search_str)
- ->orWhere('last_name', 'like', $search_str)
- ->orWhere('note', 'like', $search_str);
- })
- ->get();
- $total = $accessory_users->count();
+ $accessory_checkouts = $accessory_checkouts->TextSearch($request->input('search'));
}
- return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
+ $total = $accessory_checkouts->count();
+ $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
+
+ return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory_checkouts, $total);
}
@@ -226,7 +222,7 @@ class AccessoriesController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@@ -248,16 +244,16 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
$this->authorize('delete', Accessory::class);
- $accessory = Accessory::findOrFail($id);
+ $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
$this->authorize($accessory);
- if ($accessory->hasUsers() > 0) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()])));
+ if ($accessory->checkouts_count > 0) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/general.delete_disabled')));
}
$accessory->delete();
@@ -272,43 +268,34 @@ class AccessoriesController extends Controller
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
- * @author [A. Gianotto] []
* @param int $accessoryId
- * @return \Illuminate\Http\RedirectResponse
+ * @return \Illuminate\Http\JsonResponse
+ * @author [A. Gianotto] []
*/
- public function checkout(Request $request, $accessoryId)
+ public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
{
- // Check if the accessory exists
- if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
- }
-
$this->authorize('checkout', $accessory);
+ $target = $this->determineCheckoutTarget();
+ $accessory->checkout_qty = $request->input('checkout_qty', 1);
+ for ($i = 0; $i < $accessory->checkout_qty; $i++) {
- if ($accessory->numRemaining() > 0) {
-
- if (! $user = User::find($request->input('assigned_to'))) {
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
- }
-
- // Update the accessory data
- $accessory->assigned_to = $request->input('assigned_to');
-
- $accessory->users()->attach($accessory->id, [
+ $accessory_checkout = new AccessoryCheckout([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
- 'user_id' => Auth::id(),
- 'assigned_to' => $request->get('assigned_to'),
- 'note' => $request->get('note'),
+ 'assigned_to' => $target->id,
+ 'assigned_type' => $target::class,
+ 'note' => $request->input('note'),
]);
- event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
-
- return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
+ $accessory_checkout->created_by = auth()->id();
+ $accessory_checkout->save();
}
- return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
+ // Set this value to be able to pass the qty through to the event
+ event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
+
+ return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
@@ -325,29 +312,21 @@ class AccessoriesController extends Controller
*/
public function checkin(Request $request, $accessoryUserId = null)
{
- if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
+ if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
- $accessory = Accessory::find($accessory_user->accessory_id);
+ $accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
- $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
+ $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note'));
// Was the accessory updated?
- if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
- if (! is_null($accessory_user->assigned_to)) {
- $user = User::find($accessory_user->assigned_to);
+ if ($accessory_checkout->delete()) {
+ if (! is_null($accessory_checkout->assigned_to)) {
+ $user = User::find($accessory_checkout->assigned_to);
}
- $data['log_id'] = $logaction->id;
- $data['first_name'] = $user->first_name;
- $data['last_name'] = $user->last_name;
- $data['item_name'] = $accessory->name;
- $data['checkin_date'] = $logaction->created_at;
- $data['item_tag'] = '';
- $data['note'] = $logaction->note;
-
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
}
diff --git a/app/Http/Controllers/Api/AssetFilesController.php b/app/Http/Controllers/Api/AssetFilesController.php
index d8bc5e3841..fabe9ebbb3 100644
--- a/app/Http/Controllers/Api/AssetFilesController.php
+++ b/app/Http/Controllers/Api/AssetFilesController.php
@@ -3,29 +3,18 @@
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
+use App\Http\Transformers\UploadedFilesTransformer;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Crypt;
-use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
-use App\Models\AssetModel;
use App\Models\Actionlog;
-use \Illuminate\Support\Facades\Auth;
-use Carbon\Carbon;
-use DB;
-use Illuminate\Http\Request;
use App\Http\Requests\UploadFileRequest;
+use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
-use Input;
-use Paginator;
-use Slack;
-use Str;
-use TCPDF;
-use Validator;
-use Route;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Illuminate\Http\Request;
/**
@@ -44,12 +33,10 @@ class AssetFilesController extends Controller
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId
- * @return \Illuminate\Http\JsonResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] []
*/
- public function store(UploadFileRequest $request, $assetId = null)
+ public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
@@ -59,7 +46,7 @@ class AssetFilesController extends Controller
// Make sure we are allowed to update this asset
$this->authorize('update', $asset);
- if ($request->hasFile('file')) {
+ 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);
@@ -84,38 +71,40 @@ class AssetFilesController extends Controller
* List the files for an asset.
*
* @param int $assetId
- * @return \Illuminate\Http\JsonResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] []
*/
- public function list($assetId = null)
+ public function list(Asset $asset, Request $request) : JsonResponse | array
{
- // 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')));
- }
+ $this->authorize('view', $asset);
- // There are no files.
- return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
+ $allowed_columns =
+ [
+ 'id',
+ 'filename',
+ 'eol',
+ 'notes',
+ 'created_at',
+ 'updated_at',
+ ];
+
+ $files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id);
+
+ if ($request->filled('search')) {
+ $files = $files->TextSearch($request->input('search'));
}
- // Send back an error message
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
+ // Make sure the offset and limit are actually integers and do not exceed system limits
+ $offset = ($request->input('offset') > $files->count()) ? $files->count() : abs($request->input('offset'));
+ $limit = app('api_limit_value');
+ $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+ $files = $files->orderBy($sort, $order);
+
+ $files = $files->skip($offset)->take($limit)->get();
+ return (new UploadedFilesTransformer())->transformFiles($files, $files->count());
+
}
/**
@@ -128,12 +117,8 @@ class AssetFilesController extends Controller
* @since [v6.0]
* @author [T. Scarsbrook] []
*/
- public function show($assetId = null, $fileId = null)
+ public function show(Asset $asset, $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)) {
@@ -146,7 +131,7 @@ class AssetFilesController extends Controller
// Form the full filename with path
$file = 'private_uploads/assets/'.$log->filename;
- \Log::debug('Checking for '.$file);
+ Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
@@ -178,17 +163,11 @@ class AssetFilesController extends Controller
*
* @param int $assetId
* @param int $fileId
- * @return \Illuminate\Http\JsonResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] []
*/
- public function destroy($assetId = null, $fileId = null)
+ public function destroy(Asset $asset, $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';
@@ -198,12 +177,14 @@ class AssetFilesController extends Controller
// 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
+
+ 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
diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php
index 1ecb3be257..b4e9b44196 100644
--- a/app/Http/Controllers/Api/AssetMaintenancesController.php
+++ b/app/Http/Controllers/Api/AssetMaintenancesController.php
@@ -9,9 +9,8 @@ use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
-use Carbon\Carbon;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Input;
+use Illuminate\Http\JsonResponse;
/**
* This controller handles all actions related to Asset Maintenance for
@@ -22,7 +21,6 @@ use Illuminate\Support\Facades\Input;
class AssetMaintenancesController extends Controller
{
-
/**
* Generates the JSON response for asset maintenances listing view.
*
@@ -30,14 +28,13 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return string JSON
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*')
- ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
+ ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@@ -51,6 +48,10 @@ class AssetMaintenancesController extends Controller
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
}
+ if ($request->filled('created_by')) {
+ $maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by'));
+ }
+
if ($request->filled('asset_maintenance_type')) {
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
}
@@ -72,7 +73,7 @@ class AssetMaintenancesController extends Controller
'asset_tag',
'asset_name',
'serial',
- 'user_id',
+ 'created_by',
'supplier',
'is_warranty',
'status_label',
@@ -82,8 +83,8 @@ class AssetMaintenancesController extends Controller
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
- case 'user_id':
- $maintenances = $maintenances->OrderAdmin($order);
+ case 'created_by':
+ $maintenances = $maintenances->OrderByCreatedBy($order);
break;
case 'supplier':
$maintenances = $maintenances->OrderBySupplier($order);
@@ -120,15 +121,14 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return string JSON
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse | array
{
$this->authorize('update', Asset::class);
// create a new model instance
$maintenance = new AssetMaintenance();
$maintenance->fill($request->all());
- $maintenance->user_id = Auth::id();
+ $maintenance->created_by = auth()->id();
// Was the asset maintenance created?
if ($maintenance->save()) {
@@ -148,9 +148,8 @@ class AssetMaintenancesController extends Controller
* @param int $request
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse | array
{
$this->authorize('update', Asset::class);
@@ -186,17 +185,13 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function destroy($assetMaintenanceId)
+ public function destroy($assetMaintenanceId) : JsonResponse | array
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
- $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
- if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset'));
- }
+ $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
$assetMaintenance->delete();
@@ -212,9 +207,8 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
- * @return string JSON
*/
- public function show($assetMaintenanceId)
+ public function show($assetMaintenanceId) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
diff --git a/app/Http/Controllers/Api/AssetModelFilesController.php b/app/Http/Controllers/Api/AssetModelFilesController.php
new file mode 100644
index 0000000000..d0a4747bf4
--- /dev/null
+++ b/app/Http/Controllers/Api/AssetModelFilesController.php
@@ -0,0 +1,184 @@
+
+ *
+ * @version v1.0
+ * @author [T. Scarsbrook] []
+ */
+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
+ {
+ // Start by checking if the asset being acted upon exists
+ if (! $assetModel = AssetModel::find($assetmodel_id)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
+ }
+
+ $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);
+ }
+}
diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php
index f1fa5369d1..3b6b287334 100644
--- a/app/Http/Controllers/Api/AssetModelsController.php
+++ b/app/Http/Controllers/Api/AssetModelsController.php
@@ -4,15 +4,16 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
+use App\Http\Requests\StoreAssetModelRequest;
use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Http\Request;
-use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
/**
* This class controls all actions related to asset models for
@@ -28,9 +29,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', AssetModel::class);
$allowed_columns =
@@ -48,36 +48,61 @@ class AssetModelsController extends Controller
'assets_count',
'category',
'fieldset',
+ 'deleted_at',
+ 'updated_at',
];
$assetmodels = AssetModel::select([
'models.id',
'models.image',
'models.name',
- 'model_number',
- 'min_amt',
- 'eol',
- 'requestable',
+ 'models.model_number',
+ 'models.min_amt',
+ 'models.eol',
+ 'models.created_by',
+ 'models.requestable',
'models.notes',
'models.created_at',
- 'category_id',
- 'manufacturer_id',
- 'depreciation_id',
- 'fieldset_id',
+ 'models.category_id',
+ 'models.manufacturer_id',
+ 'models.depreciation_id',
+ 'models.fieldset_id',
'models.deleted_at',
'models.updated_at',
])
- ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues')
+ ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
->withCount('assets as assets_count');
if ($request->input('status')=='deleted') {
$assetmodels->onlyTrashed();
}
+ if ($request->filled('name')) {
+ $assetmodels = $assetmodels->where('models.name', '=', $request->input('name'));
+ }
+
+ if ($request->filled('model_number')) {
+ $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'));
+ }
+
if ($request->filled('category_id')) {
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
}
+ if ($request->filled('depreciation_id')) {
+ $assetmodels = $assetmodels->where('models.depreciation_id', '=', $request->input('depreciation_id'));
+ }
+
if ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search'));
}
@@ -89,7 +114,7 @@ class AssetModelsController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';
- switch ($sort) {
+ switch ($request->input('sort')) {
case 'manufacturer':
$assetmodels->OrderManufacturer($order);
break;
@@ -99,6 +124,9 @@ class AssetModelsController extends Controller
case 'fieldset':
$assetmodels->OrderFieldset($order);
break;
+ case 'created_by':
+ $assetmodels->OrderByCreatedByName($order);
+ break;
default:
$assetmodels->orderBy($sort, $order);
break;
@@ -116,10 +144,9 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
+ * @param \App\Http\Requests\StoreAssetModelRequest $request
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAssetModelRequest $request) : JsonResponse
{
$this->authorize('create', AssetModel::class);
$assetmodel = new AssetModel;
@@ -140,9 +167,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', AssetModel::class);
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
@@ -156,9 +182,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function assets($id)
+ public function assets($id) : array
{
$this->authorize('view', AssetModel::class);
$assets = Asset::where('model_id', '=', $id)->get();
@@ -176,7 +201,7 @@ class AssetModelsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(StoreAssetModelRequest $request, $id) : JsonResponse
{
$this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -209,9 +234,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -241,7 +265,7 @@ class AssetModelsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php
index dc20588b75..57f188d92f 100644
--- a/app/Http/Controllers/Api/AssetsController.php
+++ b/app/Http/Controllers/Api/AssetsController.php
@@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
+use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
+use App\Models\AccessoryCheckout;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
@@ -25,18 +27,14 @@ use App\Models\License;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
-use \Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
-use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Log;
-use Paginator;
-use Slack;
-use Str;
-use TCPDF;
-use Validator;
use Illuminate\Support\Facades\Route;
+use App\View\Label;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Validator;
/**
@@ -56,11 +54,15 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function index(Request $request, $action = null, $upcoming_status = null)
+ public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
{
+
+ // This handles the legacy audit endpoints :(
+ if ($action == 'audit') {
+ $action = 'audits';
+ }
$filter_non_deprecable_assets = false;
/**
@@ -80,10 +82,10 @@ class AssetsController extends Controller
$this->authorize('reports.view');
} else {
$transformer = 'App\Http\Transformers\AssetsTransformer';
- $this->authorize('index', Asset::class);
+ $this->authorize('index', Asset::class);
}
-
-
+
+
$settings = Setting::getSettings();
$allowed_columns = [
@@ -126,8 +128,20 @@ class AssetsController extends Controller
}
$assets = Asset::select('assets.*')
- ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo',
- 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
+ ->with(
+ 'model',
+ 'location',
+ 'assetstatus',
+ 'company',
+ 'defaultLoc',
+ 'assignedTo',
+ 'adminuser',
+ 'model.depreciation',
+ 'model.category',
+ 'model.manufacturer',
+ 'model.fieldset',
+ 'supplier'
+ ); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
if ($filter_non_deprecable_assets) {
@@ -159,8 +173,8 @@ class AssetsController extends Controller
* Handle due and overdue audits and checkin dates
*/
switch ($action) {
+ // Audit (singular) is left over from earlier legacy APIs
case 'audits':
-
switch ($upcoming_status) {
case 'due':
$assets->DueForAudit($settings);
@@ -187,7 +201,7 @@ class AssetsController extends Controller
break;
}
break;
- }
+ }
/**
* End handling due and overdue audits and checkin dates
@@ -265,7 +279,6 @@ class AssetsController extends Controller
$join->on('status_alias.id', '=', 'assets.status_id');
});
}
-
}
@@ -285,9 +298,15 @@ class AssetsController extends Controller
if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1');
}
-
+
if ($request->filled('model_id')) {
- $assets->InModelList([$request->input('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')]);
+ }
}
if ($request->filled('category_id')) {
@@ -345,7 +364,7 @@ class AssetsController extends Controller
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
-
+
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
@@ -376,8 +395,32 @@ class AssetsController extends Controller
case 'assigned_to':
$assets->OrderAssigned($order);
break;
+ case 'created_by':
+ $assets->OrderByCreatedByName($order);
+ break;
default:
- $assets->orderBy($column_sort, $order);
+ $numeric_sort = false;
+
+ // Search through the custom fields array to see if we're sorting on a custom field
+ if (array_search($column_sort, $all_custom_fields->pluck('db_column')->toArray()) !== false) {
+
+ // Check to see if this is a numeric field type
+ foreach ($all_custom_fields as $field) {
+ if (($field->db_column == $sort_override) && ($field->format == 'NUMERIC')) {
+ $numeric_sort = true;
+ break;
+ }
+ }
+
+ // This may not work for all databases, but it works for MySQL
+ if ($numeric_sort) {
+ $assets->orderByRaw(DB::getTablePrefix() . 'assets.' . $sort_override . ' * 1 ' . $order);
+ } else {
+ $assets->orderBy($sort_override, $order);
+ }
+ } else {
+ $assets->orderBy($column_sort, $order);
+ }
break;
}
@@ -388,23 +431,17 @@ class AssetsController extends Controller
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
-
+
/**
* Include additional associated relationships
- */
+ */
if ($request->input('components')) {
$assets->loadMissing(['components' => function ($query) {
$query->orderBy('created_at', 'desc');
}]);
}
-
-
- /**
- * 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);
}
@@ -415,9 +452,8 @@ class AssetsController extends Controller
* @param string $tag
* @since [v4.2.1]
* @author [A. Gianotto] []
- * @return \Illuminate\Http\JsonResponse
*/
- public function showByTag(Request $request, $tag)
+ public function showByTag(Request $request, $tag): JsonResponse | array
{
$this->authorize('index', Asset::class);
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
@@ -439,12 +475,10 @@ class AssetsController extends Controller
} else {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
-
}
// If there are 0 results, return the "no such asset" response
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
-
}
/**
@@ -455,23 +489,39 @@ class AssetsController extends Controller
* @since [v4.2.1]
* @return \Illuminate\Http\JsonResponse
*/
- public function showBySerial(Request $request, $serial)
+ public function showBySerial(Request $request, $serial): JsonResponse | array
{
$this->authorize('index', Asset::class);
- $assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
+ $assets = Asset::where('serial', $serial)->with([
+ 'assetstatus',
+ 'assignedTo',
+ 'company',
+ 'defaultLoc',
+ 'location',
+ 'model.category',
+ 'model.depreciation',
+ 'model.fieldset',
+ 'model.manufacturer',
+ 'supplier',
+ ]);
// Check if they've passed ?deleted=true
if ($request->input('deleted', 'false') == 'true') {
$assets = $assets->withTrashed();
}
-
- if (($assets = $assets->get()) && ($assets->count()) > 0) {
- return (new AssetsTransformer)->transformAssets($assets, $assets->count());
+
+ $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 there are 0 results, return the "no such asset" response
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
-
}
/**
@@ -482,19 +532,20 @@ class AssetsController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/
- public function show(Request $request, $id)
+ public function show(Request $request, $id): JsonResponse | array
{
- if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
- ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
+ if ($asset = Asset::with('assetstatus')
+ ->with('assignedTo')->withTrashed()
+ ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)
+ ) {
$this->authorize('view', $asset);
- return (new AssetsTransformer)->transformAsset($asset, $request->input('components') );
+ return (new AssetsTransformer)->transformAsset($asset, $request->input('components'));
}
-
-
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
- public function licenses(Request $request, $id)
+ public function licenses(Request $request, $id): array
{
$this->authorize('view', Asset::class);
$this->authorize('view', License::class);
@@ -502,7 +553,7 @@ class AssetsController extends Controller
$licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
- }
+ }
/**
@@ -511,9 +562,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
- * @return \Illuminate\Http\JsonResponse
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request): array
{
$assets = Asset::select([
@@ -524,7 +574,12 @@ class AssetsController extends Controller
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id',
- ])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
+ ])->with('model', 'assetstatus', 'assignedTo')
+ ->NotArchived();
+
+ if ((Setting::getSettings()->full_multiple_companies_support=='1') && ($request->filled('companyId'))) {
+ $assets->where('assets.company_id', $request->input('companyId'));
+ }
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
@@ -534,7 +589,6 @@ 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.
@@ -546,12 +600,12 @@ class AssetsController extends Controller
$asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
- $asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
+ $asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
}
if ($asset->assetstatus->getStatuslabelType() == 'pending') {
- $asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
+ $asset->use_text .= '(' . $asset->assetstatus->getStatuslabelType() . ')';
}
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
@@ -574,15 +628,15 @@ class AssetsController extends Controller
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->fill($request->validated());
- $asset->user_id = Auth::id();
+ $asset->created_by = auth()->id();
/**
- * this is here just legacy reasons. Api\AssetController
- * used image_source once to allow encoded image uploads.
- */
+ * this is here just legacy reasons. Api\AssetController
+ * used image_source once to allow encoded image uploads.
+ */
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
- }
+ }
$asset = $request->handleImages($asset);
@@ -599,16 +653,16 @@ class AssetsController extends Controller
// If input value is null, use custom field's default value
if ($field_val == null) {
- Log::debug('Field value for '.$field->db_column.' is null');
+ Log::debug('Field value for ' . $field->db_column . ' is null');
$field_val = $field->defaultValue($request->get('model_id'));
- Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
+ Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->get('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
if ($field->field_encrypted == '1') {
Log::debug('This model field is encrypted in this fieldset.');
- if (Gate::allows('admin')) {
+ if (Gate::allows('assets.view.encrypted_custom_fields')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
@@ -619,7 +673,7 @@ class AssetsController extends Controller
}
}
if ($field->element == 'checkbox') {
- if(is_array($field_val)) {
+ if (is_array($field_val)) {
$field_val = implode(',', $field_val);
}
}
@@ -638,7 +692,7 @@ class AssetsController extends Controller
$target = Location::find(request('assigned_location'));
}
if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
}
if ($asset->image) {
@@ -647,7 +701,9 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
- return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($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('error', null, $asset->getErrors()), 200);
@@ -658,95 +714,91 @@ class AssetsController extends Controller
* Accepts a POST request to update an asset
*
* @author [A. Gianotto] []
- * @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
{
- $this->authorize('update', Asset::class);
+ $asset->fill($request->validated());
- if ($asset = Asset::find($id)) {
- $asset->fill($request->all());
-
- ($request->filled('model_id')) ?
- $asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
- ($request->filled('rtd_location_id')) ?
- $asset->location_id = $request->get('rtd_location_id') : '';
- ($request->filled('company_id')) ?
- $asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
-
- ($request->filled('rtd_location_id')) ?
- $asset->location_id = $request->get('rtd_location_id') : null;
-
- /**
- * this is here just legacy reasons. Api\AssetController
- * used image_source once to allow encoded image uploads.
- */
- if ($request->has('image_source')) {
- $request->offsetSet('image', $request->offsetGet('image_source'));
- }
-
- $asset = $request->handleImages($asset);
- $model = AssetModel::find($asset->model_id);
-
- // Update custom fields
- $problems_updating_encrypted_custom_fields = false;
- if (($model) && (isset($model->fieldset))) {
- foreach ($model->fieldset->fields as $field) {
- $field_val = $request->input($field->db_column, null);
-
- if ($request->has($field->db_column)) {
- if ($field->element == 'checkbox') {
- if(is_array($field_val)) {
- $field_val = implode(',', $field_val);
- }
- }
- if ($field->field_encrypted == '1') {
- if (Gate::allows('admin')) {
- $field_val = Crypt::encrypt($field_val);
- } else {
- $problems_updating_encrypted_custom_fields = true;
- continue;
- }
- }
- $asset->{$field->db_column} = $field_val;
- }
- }
- }
-
-
- if ($asset->save()) {
- if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
- $location = $target->location_id;
- } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
- $location = $target->location_id;
-
- Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id)
- ->update(['location_id' => $target->location_id]);
- } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
- $location = $target->id;
- }
-
- if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
- }
-
- if ($asset->image) {
- $asset->image = $asset->getImageUrl();
- }
-
- if ($problems_updating_encrypted_custom_fields) {
- return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
- } else {
- return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
- }
- }
-
- return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
+ if ($request->has('model_id')) {
+ $asset->model()->associate(AssetModel::find($request->validated()['model_id']));
+ }
+ if ($request->has('company_id')) {
+ $asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
+ }
+ if ($request->has('rtd_location_id') && !$request->has('location_id')) {
+ $asset->location_id = $request->validated()['rtd_location_id'];
+ }
+ if ($request->input('last_audit_date')) {
+ $asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
}
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
+ /**
+ * this is here just legacy reasons. Api\AssetController
+ * used image_source once to allow encoded image uploads.
+ */
+ if ($request->has('image_source')) {
+ $request->offsetSet('image', $request->offsetGet('image_source'));
+ }
+
+ $asset = $request->handleImages($asset);
+ $model = $asset->model;
+
+ // Update custom fields
+ $problems_updating_encrypted_custom_fields = false;
+ if (($model) && (isset($model->fieldset))) {
+ foreach ($model->fieldset->fields as $field) {
+ $field_val = $request->input($field->db_column, null);
+
+ if ($request->has($field->db_column)) {
+ if ($field->element == 'checkbox') {
+ if (is_array($field_val)) {
+ $field_val = implode(',', $field_val);
+ }
+ }
+ if ($field->field_encrypted == '1') {
+ if (Gate::allows('assets.view.encrypted_custom_fields')) {
+ $field_val = Crypt::encrypt($field_val);
+ } else {
+ $problems_updating_encrypted_custom_fields = true;
+ continue;
+ }
+ }
+ $asset->{$field->db_column} = $field_val;
+ }
+ }
+ }
+ if ($asset->save()) {
+ if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
+ $location = $target->location_id;
+ } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
+ $location = $target->location_id;
+
+ Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
+ ->update(['location_id' => $target->location_id]);
+ } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
+ $location = $target->id;
+ }
+
+ if (isset($target)) {
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
+ }
+
+ if ($asset->image) {
+ $asset->image = $asset->getImageUrl();
+ }
+
+ if ($problems_updating_encrypted_custom_fields) {
+ return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
+ // Below is the *correct* return since it uses the transformer, but we have to use the old, flat return for now until we can update Jamf2Snipe and Kanji2Snipe
+ // return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.encrypted_warning')));
+ } else {
+ return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
+ // Below is the *correct* return since it uses the transformer, but we have to use the old, flat return for now until we can update Jamf2Snipe and Kanji2Snipe
+ /// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success')));
+ }
+ }
+ return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
@@ -756,18 +808,24 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function destroy($id)
+ public function destroy($id): JsonResponse
{
$this->authorize('delete', Asset::class);
if ($asset = Asset::find($id)) {
$this->authorize('delete', $asset);
- DB::table('assets')
- ->where('id', $asset->id)
- ->update(['assigned_to' => null]);
+ if ($asset->assignedTo) {
+
+ $target = $asset->assignedTo;
+ $checkin_at = date('Y-m-d H:i:s');
+ $originalValues = $asset->getRawOriginal();
+ event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues));
+ DB::table('assets')
+ ->where('id', $asset->id)
+ ->update(['assigned_to' => null]);
+ }
$asset->delete();
@@ -777,7 +835,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
-
+
/**
* Restore a soft-deleted asset.
@@ -785,9 +843,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v5.1.18]
- * @return \Illuminate\Http\JsonResponse
*/
- public function restore(Request $request, $assetId = null)
+ public function restore(Request $request, $assetId = null): JsonResponse
{
if ($asset = Asset::withTrashed()->find($assetId)) {
@@ -806,7 +863,6 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
-
}
/**
@@ -815,9 +871,8 @@ class AssetsController extends Controller
* @author [N. Butler]
* @param string $tag
* @since [v6.0.5]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkoutByTag(AssetCheckoutRequest $request, $tag)
+ public function checkoutByTag(AssetCheckoutRequest $request, $tag): JsonResponse
{
if ($asset = Asset::where('asset_tag', $tag)->first()) {
return $this->checkout($request, $asset->id);
@@ -831,15 +886,14 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkout(AssetCheckoutRequest $request, $asset_id)
+ public function checkout(AssetCheckoutRequest $request, $asset_id): JsonResponse
{
$this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id);
if (! $asset->availableForCheckout()) {
- return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available')));
+ return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available')));
}
$this->authorize('checkout', $asset);
@@ -856,14 +910,12 @@ class AssetsController extends Controller
$asset->location_id = ($target) ? $target->id : '';
$error_payload['target_id'] = $request->input('assigned_location');
$error_payload['target_type'] = 'location';
-
} elseif (request('checkout_to_type') == 'asset') {
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
// Override with the asset's location_id if it has one
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset');
$error_payload['target_type'] = 'asset';
-
} elseif (request('checkout_to_type') == 'user') {
// Fetch the target and set the asset's new location_id
$target = User::find(request('assigned_user'));
@@ -877,7 +929,7 @@ class AssetsController extends Controller
}
if (! isset($target)) {
- return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
+ return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset ' . e($asset->asset_tag) . ' is invalid - ' . $error_payload['target_type'] . ' does not exist.'));
}
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
@@ -891,15 +943,15 @@ class AssetsController extends Controller
// TODO: Follow up here. WTF. Commented out for now.
-// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
-// $asset->location_id = $target->rtd_location_id;
-// }
+ // if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
+ // $asset->location_id = $target->rtd_location_id;
+ // }
- if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
- return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
+ if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
+ return response()->json(Helper::formatStandardApiResponse('success', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
- return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
+ return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
}
@@ -909,9 +961,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkin(Request $request, $asset_id)
+ public function checkin(Request $request, $asset_id): JsonResponse
{
$asset = Asset::with('model')->findOrFail($asset_id);
$this->authorize('checkin', $asset);
@@ -919,7 +970,7 @@ class AssetsController extends Controller
$target = $asset->assignedTo;
if (is_null($target)) {
return response()->json(Helper::formatStandardApiResponse('error', [
- 'asset_tag'=> e($asset->asset_tag),
+ 'asset_tag' => e($asset->asset_tag),
'model' => e($asset->model->name),
'model_number' => e($asset->model->model_number)
], trans('admin/hardware/message.checkin.already_checked_in')));
@@ -942,16 +993,16 @@ class AssetsController extends Controller
if ($request->filled('location_id')) {
$asset->location_id = $request->input('location_id');
- if ($request->input('update_default_location')){
+ if ($request->input('update_default_location')) {
$asset->rtd_location_id = $request->input('location_id');
}
}
- if ($request->has('status_id')) {
+ if ($request->filled('status_id')) {
$asset->status_id = $request->input('status_id');
}
-
- $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
+
+ $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at') . ' ' . date('H:i:s') : date('Y-m-d H:i:s');
$originalValues = $asset->getRawOriginal();
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
@@ -969,23 +1020,24 @@ class AssetsController extends Controller
[Asset::class],
function (Builder $query) use ($asset) {
$query->where('id', $asset->id);
- })
+ }
+ )
->get()
->map(function ($acceptance) {
$acceptance->delete();
});
if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
+ event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
return response()->json(Helper::formatStandardApiResponse('success', [
- 'asset_tag'=> e($asset->asset_tag),
+ 'asset_tag' => e($asset->asset_tag),
'model' => e($asset->model->name),
'model_number' => e($asset->model->model_number)
], trans('admin/hardware/message.checkin.success')));
}
- return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
+ return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
}
/**
@@ -993,12 +1045,11 @@ class AssetsController extends Controller
*
* @author [A. Janes] []
* @since [v6.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function checkinByTag(Request $request, $tag = null)
+ public function checkinByTag(Request $request, $tag = null): JsonResponse
{
$this->authorize('checkin', Asset::class);
- if(null == $tag && null !== ($request->input('asset_tag'))) {
+ if (null == $tag && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag');
}
$asset = Asset::where('asset_tag', $tag)->first();
@@ -1008,8 +1059,8 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('error', [
- 'asset'=> e($tag)
- ], 'Asset with tag '.e($tag).' not found'));
+ 'asset' => e($tag)
+ ], 'Asset with tag ' . e($tag) . ' not found'));
}
@@ -1019,9 +1070,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function audit(Request $request)
+ public function audit(Request $request, Asset $asset): JsonResponse
{
$this->authorize('audit', Asset::class);
@@ -1029,36 +1079,15 @@ class AssetsController extends Controller
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
- // No tag passed - return an error
- if (!$request->filled('asset_tag')) {
- return response()->json(Helper::formatStandardApiResponse('error', [
- 'asset_tag'=> '',
- 'error'=> trans('admin/hardware/message.no_tag'),
- ], trans('admin/hardware/message.no_tag')), 200);
+ // 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();
}
-
- $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
-
-
if ($asset) {
- /**
- * 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();
+ $originalValues = $asset->getRawOriginal();
+
$asset->next_audit_date = $dt;
if ($request->filled('next_audit_date')) {
@@ -1073,35 +1102,88 @@ 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'));
-
- 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->logAudit(request('note'), request('location_id'), null, $originalValues);
+ return response()->json(Helper::formatStandardApiResponse('success', $payload, 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', [
- '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);
-
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
@@ -1112,9 +1194,8 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function requestable(Request $request)
+ public function requestable(Request $request): JsonResponse | array
{
$this->authorize('viewRequestable', Asset::class);
@@ -1135,8 +1216,18 @@ class AssetsController extends Controller
}
$assets = Asset::select('assets.*')
- ->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
- 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests');
+ ->with(
+ 'location',
+ 'assetstatus',
+ 'assetlog',
+ 'company',
+ 'assignedTo',
+ 'model.category',
+ 'model.manufacturer',
+ 'model.fieldset',
+ 'supplier',
+ 'requests'
+ );
@@ -1144,7 +1235,7 @@ class AssetsController extends Controller
if ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
-
+
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name())) {
@@ -1185,4 +1276,115 @@ class AssetsController extends Controller
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
}
+
+
+ public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
+ {
+
+ return [];
+ // to do
+ }
+
+ public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
+ {
+ $this->authorize('view', Asset::class);
+ $this->authorize('view', $asset);
+ $accessory_checkouts = AccessoryCheckout::AssetsAssigned()
+ ->where('assigned_to', $asset->id)
+ ->with('adminuser')
+ ->with('accessories');
+
+ $offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value');
+ $limit = app('api_limit_value');
+
+ $total = $accessory_checkouts->count();
+ $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
+ return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
+ }
+
+
+ /**
+ * Generate asset labels by tag
+ *
+ * @author [Nebelkreis] [https://github.com/NebelKreis]
+ *
+ * @param Request $request Contains asset_tags array of asset tags to generate labels for
+ * @return JsonResponse Returns base64 encoded PDF on success, error message on failure
+ */
+ public function getLabels(Request $request): JsonResponse
+ {
+ try {
+ $this->authorize('view', Asset::class);
+
+ // Validate that asset tags were provided in the request
+ if (!$request->filled('asset_tags')) {
+ return response()->json(Helper::formatStandardApiResponse('error', null,
+ trans('admin/hardware/message.no_assets_selected')), 400);
+ }
+
+ // Convert asset tags from request into collection and fetch matching assets
+ $asset_tags = collect($request->input('asset_tags'));
+ $assets = Asset::whereIn('asset_tag', $asset_tags)->get();
+
+ // Return error if no assets were found for the provided tags
+ if ($assets->isEmpty()) {
+ return response()->json(Helper::formatStandardApiResponse('error', null,
+ trans('admin/hardware/message.does_not_exist')), 404);
+ }
+
+ try {
+ $settings = Setting::getSettings();
+
+ // Check if logo file exists in storage and disable logo if not found
+ // This prevents errors when trying to include a non-existent logo in the PDF
+ $settings->label_logo = ($original_logo = $settings->label_logo) && !Storage::disk('public')->exists('/' . $original_logo) ? null : $settings->label_logo;
+
+
+ $label = new Label();
+
+ if (!$label) {
+ throw new \Exception('Label object could not be created');
+ }
+
+ // Configure label with assets and settings
+ // bulkedit=false and count=0 are default values for label generation
+ $label = $label->with('assets', $assets)
+ ->with('settings', $settings)
+ ->with('bulkedit', false)
+ ->with('count', 0);
+
+ // Generate PDF using callback function
+ // The callback captures the PDF content in $pdf_content variable
+ $pdf_content = '';
+ $label->render(function($pdf) use (&$pdf_content) {
+ $pdf_content = $pdf->Output('', 'S');
+ return $pdf;
+ });
+
+ // Verify PDF was generated successfully
+ if (empty($pdf_content)) {
+ throw new \Exception('PDF content is empty');
+ }
+
+ $encoded_content = base64_encode($pdf_content);
+
+ return response()->json(Helper::formatStandardApiResponse('success', [
+ 'pdf' => $encoded_content
+ ], trans('admin/hardware/message.labels_generated')));
+
+ } catch (\Exception $e) {
+ return response()->json(Helper::formatStandardApiResponse('error', [
+ 'error_message' => $e->getMessage(),
+ 'error_line' => $e->getLine(),
+ 'error_file' => $e->getFile()
+ ], trans('admin/hardware/message.error_generating_labels')), 500);
+ }
+ } catch (\Exception $e) {
+ return response()->json(Helper::formatStandardApiResponse('error', [
+ 'error_message' => $e->getMessage(),
+ 'error_line' => $e->getLine(),
+ 'error_file' => $e->getFile()
+ ], $e->getMessage()), 500);
+ }
+ }
}
diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php
index 2aa4b3741c..85cb70b126 100644
--- a/app/Http/Controllers/Api/CategoriesController.php
+++ b/app/Http/Controllers/Api/CategoriesController.php
@@ -8,9 +8,9 @@ use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Category;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\Validator;
class CategoriesController extends Controller
{
@@ -21,7 +21,7 @@ class CategoriesController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', Category::class);
$allowed_columns = [
@@ -39,10 +39,12 @@ class CategoriesController extends Controller
'components_count',
'licenses_count',
'image',
+ 'notes',
];
$categories = Category::select([
'id',
+ 'created_by',
'created_at',
'updated_at',
'name', 'category_type',
@@ -50,8 +52,11 @@ class CategoriesController extends Controller
'eula_text',
'require_acceptance',
'checkin_email',
- 'image'
- ])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
+ 'image',
+ 'notes',
+ ])
+ ->with('adminuser')
+ ->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
/*
@@ -91,13 +96,33 @@ class CategoriesController extends Controller
$categories->where('checkin_email', '=', $request->input('checkin_email'));
}
+ if ($request->filled('created_by')) {
+ $categories->where('created_by', '=', $request->input('created_by'));
+ }
+
+ if ($request->filled('created_at')) {
+ $categories->where('created_at', '=', $request->input('created_at'));
+ }
+
+ if ($request->filled('updated_at')) {
+ $categories->where('updated_at', '=', $request->input('updated_at'));
+ }
+
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
- $categories->orderBy($sort, $order);
+ $sort_override = $request->input('sort');
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets_count';
+
+ switch ($sort_override) {
+ case 'created_by':
+ $categories = $categories->OrderByCreatedBy($order);
+ break;
+ default:
+ $categories = $categories->orderBy($column_sort, $order);
+ break;
+ }
$total = $categories->count();
$categories = $categories->skip($offset)->take($limit)->get();
@@ -115,7 +140,7 @@ class CategoriesController extends Controller
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Category::class);
$category = new Category;
@@ -136,9 +161,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', 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')->findOrFail($id);
@@ -156,7 +180,7 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Category::class);
$category = Category::findOrFail($id);
@@ -164,7 +188,7 @@ class CategoriesController extends Controller
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
return response()->json(
- Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
+ Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422)
);
}
$category->fill($request->all());
@@ -185,10 +209,10 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ 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')->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', 'models as models_count')->findOrFail($id);
if (! $category->isDeletable()) {
return response()->json(
@@ -208,7 +232,7 @@ class CategoriesController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request, $category_type = 'asset')
+ public function selectlist(Request $request, $category_type = 'asset') : array
{
$this->authorize('view.selectlists');
$categories = Category::select([
diff --git a/app/Http/Controllers/Api/CheckoutRequest.php b/app/Http/Controllers/Api/CheckoutRequest.php
new file mode 100644
index 0000000000..9b66531ae4
--- /dev/null
+++ b/app/Http/Controllers/Api/CheckoutRequest.php
@@ -0,0 +1,44 @@
+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')));
+ }
+ }
+}
diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php
index 9c667973a3..fd7f57ddce 100644
--- a/app/Http/Controllers/Api/CompaniesController.php
+++ b/app/Http/Controllers/Api/CompaniesController.php
@@ -10,6 +10,7 @@ use App\Models\Company;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class CompaniesController extends Controller
{
@@ -18,9 +19,8 @@ class CompaniesController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Company::class);
@@ -38,6 +38,7 @@ class CompaniesController extends Controller
'accessories_count',
'consumables_count',
'components_count',
+ 'notes',
];
$companies = Company::withCount(['assets as assets_count' => function ($query) {
@@ -56,17 +57,29 @@ class CompaniesController extends Controller
$companies->where('email', '=', $request->input('email'));
}
+ if ($request->filled('created_by')) {
+ $companies->where('created_by', '=', $request->input('created_by'));
+ }
+
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $companies->orderBy($sort, $order);
+ $sort_override = $request->input('sort');
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
+
+ switch ($sort_override) {
+ case 'created_by':
+ $companies = $companies->OrderByCreatedBy($order);
+ break;
+ default:
+ $companies = $companies->orderBy($column_sort, $order);
+ break;
+ }
$total = $companies->count();
+
$companies = $companies->skip($offset)->take($limit)->get();
return (new CompaniesTransformer)->transformCompanies($companies, $total);
@@ -79,9 +92,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Company::class);
$company = new Company;
@@ -102,9 +114,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Company::class);
$company = Company::findOrFail($id);
@@ -120,9 +131,8 @@ class CompaniesController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Company::class);
$company = Company::findOrFail($id);
@@ -144,9 +154,8 @@ class CompaniesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Company::class);
$company = Company::findOrFail($id);
@@ -169,7 +178,7 @@ class CompaniesController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
$companies = Company::select([
diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php
index 4cf8173eac..8881628b39 100644
--- a/app/Http/Controllers/Api/ComponentsController.php
+++ b/app/Http/Controllers/Api/ComponentsController.php
@@ -5,16 +5,17 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ComponentsTransformer;
-use App\Models\Company;
use App\Models\Component;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn;
-use App\Events\ComponentCheckedIn;
use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
class ComponentsController extends Controller
{
@@ -24,9 +25,8 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Component::class);
@@ -38,6 +38,7 @@ class ComponentsController extends Controller
'name',
'min_amt',
'order_number',
+ 'model_number',
'serial',
'purchase_date',
'purchase_cost',
@@ -47,7 +48,8 @@ class ComponentsController extends Controller
];
$components = Component::select('components.*')
- ->with('company', 'location', 'category', 'assets', 'supplier');
+ ->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
+ ->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
@@ -58,7 +60,7 @@ class ComponentsController extends Controller
}
if ($request->filled('company_id')) {
- $components->where('company_id', '=', $request->input('company_id'));
+ $components->where('components.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -69,6 +71,14 @@ class ComponentsController extends Controller
$components->where('supplier_id', '=', $request->input('supplier_id'));
}
+ if ($request->filled('manufacturer_id')) {
+ $components->where('manufacturer_id', '=', $request->input('manufacturer_id'));
+ }
+
+ if ($request->filled('model_number')) {
+ $components->where('model_number', '=', $request->input('model_number'));
+ }
+
if ($request->filled('location_id')) {
$components->where('location_id', '=', $request->input('location_id'));
}
@@ -98,6 +108,12 @@ class ComponentsController extends Controller
case 'supplier':
$components = $components->OrderSupplier($order);
break;
+ case 'manufacturer':
+ $components = $components->OrderManufacturer($order);
+ break;
+ case 'created_by':
+ $components = $components->OrderByCreatedBy($order);
+ break;
default:
$components = $components->orderBy($column_sort, $order);
break;
@@ -116,9 +132,8 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Component::class);
$component = new Component;
@@ -137,9 +152,8 @@ class ComponentsController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Component::class);
$component = Component::findOrFail($id);
@@ -156,9 +170,8 @@ class ComponentsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Component::class);
$component = Component::findOrFail($id);
@@ -179,13 +192,17 @@ class ComponentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Component::class);
$component = Component::findOrFail($id);
$this->authorize('delete', $component);
+
+ if ($component->numCheckedOut() > 0) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.delete.error_qty')));
+ }
+
$component->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.delete.success')));
@@ -198,9 +215,8 @@ class ComponentsController extends Controller
* @since [v4.0]
* @param Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function getAssets(Request $request, $id)
+ public function getAssets(Request $request, $id) : array
{
$this->authorize('view', \App\Models\Asset::class);
@@ -241,10 +257,8 @@ class ComponentsController extends Controller
* @since [v5.1.8]
* @param Request $request
* @param int $componentId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function checkout(Request $request, $componentId)
+ public function checkout(Request $request, $componentId) : JsonResponse
{
// Check if the component exists
if (!$component = Component::find($componentId)) {
@@ -275,9 +289,9 @@ class ComponentsController extends Controller
$component->assets()->attach($component->id, [
'component_id' => $component->id,
- 'created_at' => \Carbon::now(),
+ 'created_at' => Carbon::now(),
'assigned_qty' => $request->get('assigned_qty', 1),
- 'user_id' => \Auth::id(),
+ 'created_by' => auth()->id(),
'asset_id' => $request->get('assigned_to'),
'note' => $request->get('note'),
]);
@@ -297,15 +311,11 @@ class ComponentsController extends Controller
* @since [v5.1.8]
* @param Request $request
* @param $component_asset_id
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function checkin(Request $request, $component_asset_id)
+ public function checkin(Request $request, $component_asset_id) : JsonResponse
{
- if ($component_assets = \DB::table('components_assets')->find($component_asset_id)) {
-
+ if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
-
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found')));
}
@@ -313,17 +323,13 @@ class ComponentsController extends Controller
$max_to_checkin = $component_assets->assigned_qty;
- if ($max_to_checkin > 1) {
-
- $validator = \Validator::make($request->all(), [
- "checkin_qty" => "required|numeric|between:1,$max_to_checkin"
- ]);
-
- if ($validator->fails()) {
- return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin));
- }
+ $validator = Validator::make($request->all(), [
+ "checkin_qty" => "required|numeric|between:1,$max_to_checkin"
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and ' . $max_to_checkin));
}
-
// Validation passed, so let's figure out what we have to do here.
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1));
@@ -333,28 +339,23 @@ class ComponentsController extends Controller
$component_assets->assigned_qty = $qty_remaining_in_checkout;
Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
-
- \DB::table('components_assets')->where('id',
- $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
+
+ DB::table('components_assets')->where('id', $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
- if ($qty_remaining_in_checkout == 0) {
- \DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
+ if ($qty_remaining_in_checkout === 0) {
+ DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
-
$asset = Asset::find($component_assets->asset_id);
- event(new CheckoutableCheckedIn($component, $asset, \Auth::user(), $request->input('note'), \Carbon::now()));
+ event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
-
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
-
-
}
}
diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php
index 956a176580..7bddde070c 100644
--- a/app/Http/Controllers/Api/ConsumablesController.php
+++ b/app/Http/Controllers/Api/ConsumablesController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
+use App\Http\Requests\StoreConsumableRequest;
use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
@@ -12,8 +13,8 @@ use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
class ConsumablesController extends Controller
{
@@ -22,34 +23,13 @@ class ConsumablesController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- *
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('index', Consumable::class);
- // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
- // Relations will be handled in query scopes a little further down.
- $allowed_columns =
- [
- 'id',
- 'name',
- 'order_number',
- 'min_amt',
- 'purchase_date',
- 'purchase_cost',
- 'company',
- 'category',
- 'model_number',
- 'item_no',
- 'qty',
- 'image',
- 'notes',
- ];
-
- $consumables = Consumable::select('consumables.*')
- ->with('company', 'location', 'category', 'users', 'manufacturer');
+ $consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
+ ->withCount('users as consumables_users_count');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
@@ -60,7 +40,7 @@ class ConsumablesController extends Controller
}
if ($request->filled('company_id')) {
- $consumables->where('company_id', '=', $request->input('company_id'));
+ $consumables->where('consumables.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -91,15 +71,9 @@ class ConsumablesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
- $allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort_override = $request->input('sort');
- $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
-
-
- switch ($sort_override) {
+ switch ($request->input('sort')) {
case 'category':
$consumables = $consumables->OrderCategory($order);
break;
@@ -112,11 +86,37 @@ class ConsumablesController extends Controller
case 'company':
$consumables = $consumables->OrderCompany($order);
break;
+ case 'remaining':
+ $consumables = $consumables->OrderRemaining($order);
+ break;
case 'supplier':
- $components = $consumables->OrderSupplier($order);
+ $consumables = $consumables->OrderSupplier($order);
+ break;
+ case 'created_by':
+ $consumables = $consumables->OrderByCreatedBy($order);
break;
default:
- $consumables = $consumables->orderBy($column_sort, $order);
+ // This array is what determines which fields should be allowed to be sorted on ON the table itself.
+ // These must match a column on the consumables table directly.
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'order_number',
+ 'min_amt',
+ 'purchase_date',
+ 'purchase_cost',
+ 'company',
+ 'category',
+ 'model_number',
+ 'item_no',
+ 'manufacturer',
+ 'location',
+ 'qty',
+ 'image'
+ ];
+
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+ $consumables = $consumables->orderBy($sort, $order);
break;
}
@@ -132,9 +132,8 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreConsumableRequest $request) : JsonResponse
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable;
@@ -153,9 +152,8 @@ class ConsumablesController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Consumable::class);
$consumable = Consumable::with('users')->findOrFail($id);
@@ -170,9 +168,8 @@ class ConsumablesController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(StoreConsumableRequest $request, $id) : JsonResponse
{
$this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id);
@@ -192,9 +189,8 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Consumable::class);
$consumable = Consumable::findOrFail($id);
@@ -211,14 +207,13 @@ class ConsumablesController extends Controller
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
- * @return array
*/
- public function getDataView($consumableId)
+ public function getDataView($consumableId) : array
{
$consumable = Consumable::with(['consumableAssignments'=> function ($query) {
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
},
- 'consumableAssignments.admin'=> function ($query) {
+ 'consumableAssignments.adminuser'=> function ($query) {
},
'consumableAssignments.user'=> function ($query) {
},
@@ -236,7 +231,8 @@ class ConsumablesController extends Controller
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
- 'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null,
+ 'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response
+ 'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null,
];
}
@@ -252,9 +248,8 @@ class ConsumablesController extends Controller
* @author [A. Gutierrez] []
* @param int $id
* @since [v4.9.5]
- * @return JsonResponse
*/
- public function checkout(Request $request, $id)
+ public function checkout(Request $request, $id) : JsonResponse
{
// Check if the consumable exists
if (!$consumable = Consumable::with('users')->find($id)) {
@@ -263,6 +258,8 @@ class ConsumablesController extends Controller
$this->authorize('checkout', $consumable);
+ $consumable->checkout_qty = $request->input('checkout_qty', 1);
+
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
@@ -273,27 +270,35 @@ class ConsumablesController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
}
+ // Make sure there is at least one available to checkout
+ if ($consumable->numRemaining() <= 0 || $consumable->checkout_qty > $consumable->numRemaining()) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable', ['requested' => $consumable->checkout_qty, 'remaining' => $consumable->numRemaining() ])));
+ }
+
+
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
if (!$user = User::find($request->input('assigned_to'))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
- Log::debug('No valid user');
}
// Update the consumable data
$consumable->assigned_to = $request->input('assigned_to');
- $consumable->users()->attach($consumable->id,
+ for ($i = 0; $i < $consumable->checkout_qty; $i++) {
+ $consumable->users()->attach($consumable->id,
[
'consumable_id' => $consumable->id,
- 'user_id' => $user->id,
+ 'created_by' => $user->id,
'assigned_to' => $request->input('assigned_to'),
'note' => $request->input('note'),
]
);
+ }
- event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
+
+ event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
@@ -304,7 +309,7 @@ class ConsumablesController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$consumables = Consumable::select([
'consumables.id',
diff --git a/app/Http/Controllers/Api/CustomFieldsController.php b/app/Http/Controllers/Api/CustomFieldsController.php
index 470d672ccc..207a92e274 100644
--- a/app/Http/Controllers/Api/CustomFieldsController.php
+++ b/app/Http/Controllers/Api/CustomFieldsController.php
@@ -8,7 +8,8 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
-use Validator;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Http\JsonResponse;
class CustomFieldsController extends Controller
{
@@ -20,7 +21,7 @@ class CustomFieldsController extends Controller
* @since [v3.0]
* @return array
*/
- public function index()
+ public function index() : array
{
$this->authorize('index', CustomField::class);
$fields = CustomField::get();
@@ -33,9 +34,8 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] []
* @param int $id
* @since [v4.1.10]
- * @return View
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) {
@@ -52,9 +52,8 @@ class CustomFieldsController extends Controller
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id);
@@ -86,9 +85,8 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] []
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', CustomField::class);
$field = new CustomField;
@@ -136,7 +134,7 @@ class CustomFieldsController extends Controller
return $fieldset->fields()->sync($fields);
}
- public function associate(Request $request, $field_id)
+ public function associate(Request $request, $field_id) : JsonResponse
{
$this->authorize('update', CustomFieldset::class);
@@ -155,10 +153,9 @@ class CustomFieldsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
- public function disassociate(Request $request, $field_id)
+ public function disassociate(Request $request, $field_id) : JsonResponse
{
$this->authorize('update', CustomFieldset::class);
-
$field = CustomField::findOrFail($field_id);
$fieldset_id = $request->input('fieldset_id');
@@ -179,9 +176,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy($field_id)
+ public function destroy($field_id) : JsonResponse
{
$field = CustomField::findOrFail($field_id);
diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php
index 2fa22f5491..5dbd507917 100644
--- a/app/Http/Controllers/Api/CustomFieldsetsController.php
+++ b/app/Http/Controllers/Api/CustomFieldsetsController.php
@@ -9,8 +9,7 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Illuminate\Http\Request;
-use Redirect;
-use View;
+use Illuminate\Http\JsonResponse;
/**
* This controller handles all actions related to Custom Asset Fieldsets for
@@ -30,9 +29,8 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson]
* @param int $id
* @since [v1.8]
- * @return View
*/
- public function index()
+ public function index() : array
{
$this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
@@ -46,9 +44,8 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson]
* @param int $id
* @since [v1.8]
- * @return View
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
@@ -65,9 +62,8 @@ class CustomFieldsetsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -86,9 +82,8 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset;
@@ -118,9 +113,8 @@ class CustomFieldsetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -147,7 +141,7 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId
* @return string JSON
*/
- public function fields($id)
+ public function fields($id) : array
{
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id);
@@ -164,14 +158,11 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId
* @return string JSON
*/
- public function fieldsWithDefaultValues($fieldsetId, $modelId)
+ public function fieldsWithDefaultValues($fieldsetId, $modelId) : array
{
$this->authorize('view', CustomField::class);
-
$set = CustomFieldset::findOrFail($fieldsetId);
-
$fields = $set->fields;
-
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
}
}
diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php
index 485e614b25..167d5cbc2a 100644
--- a/app/Http/Controllers/Api/DepartmentsController.php
+++ b/app/Http/Controllers/Api/DepartmentsController.php
@@ -6,12 +6,11 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer;
-use App\Models\Company;
use App\Models\Department;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class DepartmentsController extends Controller
{
@@ -20,12 +19,11 @@ class DepartmentsController extends Controller
*
* @author [Godfrey Martinez] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Department::class);
- $allowed_columns = ['id', 'name', 'image', 'users_count'];
+ $allowed_columns = ['id', 'name', 'image', 'users_count', 'notes'];
$departments = Department::select(
'departments.id',
@@ -37,7 +35,8 @@ class DepartmentsController extends Controller
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
- 'departments.image'
+ 'departments.image',
+ 'departments.notes',
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
@@ -74,6 +73,9 @@ class DepartmentsController extends Controller
case 'manager':
$departments->OrderManager($order);
break;
+ case 'company':
+ $departments->OrderCompany($order);
+ break;
default:
$departments->orderBy($sort, $order);
break;
@@ -91,16 +93,15 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
$department = $request->handleImages($department);
- $department->user_id = Auth::user()->id;
+ $department->created_by = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
if ($department->save()) {
@@ -116,13 +117,11 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Department::class);
$department = Department::findOrFail($id);
-
return (new DepartmentsTransformer)->transformDepartment($department);
}
@@ -133,9 +132,8 @@ class DepartmentsController extends Controller
* @since [v5.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Department::class);
$department = Department::findOrFail($id);
@@ -156,9 +154,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v4.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$department = Department::findOrFail($id);
@@ -180,7 +177,7 @@ class DepartmentsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/DepreciationsController.php b/app/Http/Controllers/Api/DepreciationsController.php
index 502a0741b7..254a72c98e 100644
--- a/app/Http/Controllers/Api/DepreciationsController.php
+++ b/app/Http/Controllers/Api/DepreciationsController.php
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\DepreciationsTransformer;
use App\Models\Depreciation;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
class DepreciationsController extends Controller
{
@@ -15,14 +16,27 @@ class DepreciationsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Depreciation::class);
- $allowed_columns = ['id','name','months','depreciation_min','created_at'];
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'months',
+ 'depreciation_min',
+ 'depreciation_type',
+ 'created_at',
+ 'assets_count',
+ 'models_count',
+ 'licenses_count',
+ ];
- $depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at');
+ $depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','created_at','updated_at', 'created_by')
+ ->with('adminuser')
+ ->withCount('assets as assets_count')
+ ->withCount('models as models_count')
+ ->withCount('licenses as licenses_count');
if ($request->filled('search')) {
$depreciations = $depreciations->TextSearch($request->input('search'));
@@ -31,10 +45,18 @@ class DepreciationsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $depreciations->orderBy($sort, $order);
+ $sort_override = $request->input('sort');
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
+
+ switch ($sort_override) {
+ case 'created_by':
+ $depreciations = $depreciations->OrderByCreatedBy($order);
+ break;
+ default:
+ $depreciations = $depreciations->orderBy($column_sort, $order);
+ break;
+ }
$total = $depreciations->count();
$depreciations = $depreciations->skip($offset)->take($limit)->get();
@@ -48,9 +70,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', Depreciation::class);
$depreciation = new Depreciation;
@@ -69,9 +90,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -86,9 +106,8 @@ class DepreciationsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -107,9 +126,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Depreciation::class);
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);
diff --git a/app/Http/Controllers/Api/GroupsController.php b/app/Http/Controllers/Api/GroupsController.php
index f1eafd49dc..0f474ece83 100644
--- a/app/Http/Controllers/Api/GroupsController.php
+++ b/app/Http/Controllers/Api/GroupsController.php
@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\GroupsTransformer;
use App\Models\Group;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\JsonResponse;
class GroupsController extends Controller
@@ -17,16 +17,14 @@ class GroupsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('superadmin');
$this->authorize('view', Group::class);
- $allowed_columns = ['id', 'name', 'created_at', 'users_count'];
- $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count');
+ $groups = Group::select('id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count');
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
@@ -36,13 +34,29 @@ class GroupsController extends Controller
$groups->where('name', '=', $request->input('name'));
}
- // Make sure the offset and limit are actually integers and do not exceed system limits
+
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $groups->orderBy($sort, $order);
+
+ switch ($request->input('sort')) {
+ case 'created_by':
+ $groups = $groups->OrderByCreatedBy($order);
+ break;
+ default:
+ // This array is what determines which fields should be allowed to be sorted on ON the table itself.
+ // These must match a column on the consumables table directly.
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'created_at',
+ 'users_count',
+ ];
+
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+ $groups = $groups->orderBy($sort, $order);
+ break;
+ }
$total = $groups->count();
$groups = $groups->skip($offset)->take($limit)->get();
@@ -56,18 +70,18 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('superadmin');
$group = new Group;
// Get all the available permissions
- $permissions = config('permissions');
+ $permissions = json_encode(config('permissions'));
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$group->name = $request->input('name');
- $group->created_by = Auth::user()->id;
+ $group->created_by = auth()->id();
+ $group->notes = $request->input('notes');
$group->permissions = json_encode($request->input('permissions', $groupPermissions));
if ($group->save()) {
@@ -83,9 +97,8 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
@@ -99,14 +112,14 @@ class GroupsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
$group->name = $request->input('name');
+ $group->notes = $request->input('notes');
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
if ($group->save()) {
@@ -122,9 +135,8 @@ class GroupsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php
index d5323bfdd4..c18df26683 100644
--- a/app/Http/Controllers/Api/ImportController.php
+++ b/app/Http/Controllers/Api/ImportController.php
@@ -9,27 +9,28 @@ use App\Http\Transformers\ImportsTransformer;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Import;
+use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use League\Csv\Reader;
+use Onnov\DetectEncoding\EncodingDetector;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\JsonResponse;
class ImportController extends Controller
{
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Http\Response
*/
- public function index()
+ public function index() : JsonResponse | array
{
$this->authorize('import');
- $imports = Import::latest()->get();
-
+ $imports = Import::with('adminuser')->latest()->get();
return (new ImportsTransformer)->transformImports($imports);
}
@@ -37,9 +38,8 @@ class ImportController extends Controller
* Process and store a CSV upload file.
*
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\JsonResponse
*/
- public function store()
+ public function store() : JsonResponse
{
$this->authorize('import');
if (! config('app.lock_passwords')) {
@@ -47,6 +47,8 @@ class ImportController extends Controller
$path = config('app.private_uploads').'/imports';
$results = [];
$import = new Import;
+ $detector = new EncodingDetector();
+
foreach ($files as $file) {
if (! in_array($file->getMimeType(), [
'application/vnd.ms-excel',
@@ -57,7 +59,6 @@ class ImportController extends Controller
'text/comma-separated-values',
'text/tsv', ])) {
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
-
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
}
@@ -65,6 +66,40 @@ 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);
+ $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;
+ }
+ }
+ }
+ }
+ $file_contents = null; //try to save on memory, I guess?
+ }
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
try {
@@ -134,7 +169,7 @@ class ImportController extends Controller
}
$import->filesize = filesize($path.'/'.$file_name);
-
+ $import->created_by = auth()->id();
$import->save();
$results[] = $import;
}
@@ -152,9 +187,8 @@ class ImportController extends Controller
* Processes the specified Import.
*
* @param int $import_id
- * @return \Illuminate\Http\Response
*/
- public function process(ItemImportRequest $request, $import_id)
+ public function process(ItemImportRequest $request, $import_id) : JsonResponse
{
$this->authorize('import');
@@ -179,6 +213,9 @@ class ImportController extends Controller
case 'asset':
$redirectTo = 'hardware.index';
break;
+ case 'assetModel':
+ $redirectTo = 'models.index';
+ break;
case 'accessory':
$redirectTo = 'accessories.index';
break;
@@ -197,6 +234,15 @@ 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
@@ -212,9 +258,8 @@ class ImportController extends Controller
* Remove the specified resource from storage.
*
* @param int $import_id
- * @return \Illuminate\Http\Response
*/
- public function destroy($import_id)
+ public function destroy($import_id) : JsonResponse
{
$this->authorize('create', Asset::class);
@@ -231,6 +276,8 @@ class ImportController extends Controller
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
+
}
+ return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
}
diff --git a/app/Http/Controllers/Api/LabelsController.php b/app/Http/Controllers/Api/LabelsController.php
index 126fe99438..0b54235422 100644
--- a/app/Http/Controllers/Api/LabelsController.php
+++ b/app/Http/Controllers/Api/LabelsController.php
@@ -8,7 +8,7 @@ use App\Http\Transformers\LabelsTransformer;
use App\Models\Labels\Label;
use Illuminate\Http\Request;
use Illuminate\Support\ItemNotFoundException;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\JsonResponse;
class LabelsController extends Controller
{
@@ -16,9 +16,8 @@ class LabelsController extends Controller
* Returns JSON listing of all labels.
*
* @author Grant Le Roux
- * @return JsonResponse
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Label::class);
@@ -50,9 +49,8 @@ class LabelsController extends Controller
*
* @author Grant Le Roux
* @param string $labelName
- * @return JsonResponse
*/
- public function show(string $labelName)
+ public function show(string $labelName) : JsonResponse | array
{
$labelName = str_replace('/', '\\', $labelName);
try {
diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php
index 317b098768..babca4a319 100644
--- a/app/Http/Controllers/Api/LicenseSeatsController.php
+++ b/app/Http/Controllers/Api/LicenseSeatsController.php
@@ -9,7 +9,7 @@ use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class LicenseSeatsController extends Controller
@@ -19,11 +19,10 @@ class LicenseSeatsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $licenseId
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request, $licenseId)
+ public function index(Request $request, $licenseId) : JsonResponse | array
{
- //
+
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
@@ -35,7 +34,7 @@ class LicenseSeatsController extends Controller
if ($request->input('sort') == 'department') {
$seats->OrderDepartments($order);
} else {
- $seats->orderBy('id', $order);
+ $seats->orderBy('updated_at', $order);
}
$total = $seats->count();
@@ -64,11 +63,10 @@ class LicenseSeatsController extends Controller
*
* @param int $licenseId
* @param int $seatId
- * @return \Illuminate\Http\Response
*/
- public function show($licenseId, $seatId)
+ public function show($licenseId, $seatId) : JsonResponse | array
{
- //
+
$this->authorize('view', License::class);
// sanity checks:
// 1. does the license seat exist?
@@ -89,19 +87,18 @@ class LicenseSeatsController extends Controller
* @param \Illuminate\Http\Request $request
* @param int $licenseId
* @param int $seatId
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $licenseId, $seatId)
+ public function update(Request $request, $licenseId, $seatId) : JsonResponse | array
{
$this->authorize('checkout', License::class);
- // sanity checks:
- // 1. does the license seat exist?
+
if (! $licenseSeat = LicenseSeat::find($seatId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
- // 2. does the seat belong to the specified license?
- if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
+
+ $license = $licenseSeat->license()->first();
+ if (!$license || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
}
@@ -110,7 +107,7 @@ class LicenseSeatsController extends Controller
// attempt to update the license seat
$licenseSeat->fill($request->all());
- $licenseSeat->user_id = Auth::user()->id;
+ $licenseSeat->created_by = auth()->id();
// check if this update is a checkin operation
// 1. are relevant fields touched at all?
@@ -139,13 +136,13 @@ class LicenseSeatsController extends Controller
if ($licenseSeat->save()) {
if ($is_checkin) {
- $licenseSeat->logCheckin($target, $request->input('note'));
+ $licenseSeat->logCheckin($target, $request->input('notes'));
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('note'), $target);
+ $licenseSeat->logCheckout($request->input('notes'), $target);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php
index c35b669d70..db39f987aa 100644
--- a/app/Http/Controllers/Api/LicensesController.php
+++ b/app/Http/Controllers/Api/LicensesController.php
@@ -4,14 +4,12 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
-use App\Http\Transformers\LicenseSeatsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
-use App\Models\Company;
use App\Models\License;
-use App\Models\LicenseSeat;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
+use Illuminate\Http\JsonResponse;
class LicensesController extends Controller
{
@@ -21,16 +19,15 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', License::class);
- $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
+ $licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) {
- $licenses->where('company_id', '=', $request->input('company_id'));
+ $licenses->where('licenses.company_id', '=', $request->input('company_id'));
}
if ($request->filled('name')) {
@@ -73,6 +70,9 @@ class LicensesController extends Controller
$licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
}
+ if ($request->filled('created_by')) {
+ $licenses->where('created_by', '=', $request->input('created_by'));
+ }
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
$licenses->where('maintained','=',1);
@@ -116,6 +116,9 @@ class LicensesController extends Controller
case 'company':
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break;
+ case 'created_by':
+ $licenses = $licenses->OrderByCreatedBy($order);
+ break;
default:
$allowed_columns =
[
@@ -156,11 +159,9 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
- //
$this->authorize('create', License::class);
$license = new License;
$license->fill($request->all());
@@ -177,12 +178,11 @@ class LicensesController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', License::class);
- $license = License::withCount('freeSeats')->findOrFail($id);
+ $license = License::withCount('freeSeats as free_seats_count')->findOrFail($id);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
return (new LicensesTransformer)->transformLicense($license);
@@ -195,9 +195,8 @@ class LicensesController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse | array
{
//
$this->authorize('update', License::class);
@@ -218,11 +217,9 @@ class LicensesController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
- //
$license = License::findOrFail($id);
$this->authorize('delete', $license);
@@ -248,7 +245,7 @@ class LicensesController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$licenses = License::select([
'licenses.id',
diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php
index 78cf43c671..b5c911a6ca 100644
--- a/app/Http/Controllers/Api/LocationsController.php
+++ b/app/Http/Controllers/Api/LocationsController.php
@@ -3,11 +3,19 @@
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
-use App\Http\Requests\ImageUploadRequest;
use App\Http\Controllers\Controller;
+use App\Http\Requests\ImageUploadRequest;
+use App\Http\Transformers\AccessoriesTransformer;
+use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer;
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;
use Illuminate\Support\Collection;
@@ -21,30 +29,34 @@ class LocationsController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Location::class);
$allowed_columns = [
- 'id',
- 'name',
+ 'accessories_count',
'address',
'address2',
+ 'assets_count',
+ 'assets_count',
+ 'assigned_accessories_count',
+ 'assigned_assets_count',
+ 'assigned_assets_count',
'city',
- 'state',
'country',
- 'zip',
'created_at',
- 'updated_at',
- 'manager_id',
- 'image',
- 'assigned_assets_count',
- 'users_count',
- 'assets_count',
- 'assigned_assets_count',
- 'assets_count',
- 'rtd_assets_count',
'currency',
+ 'id',
+ 'image',
'ldap_ou',
+ 'company_id',
+ 'manager_id',
+ 'name',
+ 'rtd_assets_count',
+ 'state',
+ 'updated_at',
+ 'users_count',
+ 'zip',
+ 'notes',
];
$locations = Location::with('parent', 'manager', 'children')->select([
@@ -65,11 +77,23 @@ class LocationsController extends Controller
'locations.image',
'locations.ldap_ou',
'locations.currency',
- ])->withCount('assignedAssets as assigned_assets_count')
+ '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');
+ ->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);
+ }
if ($request->filled('search')) {
$locations = $locations->TextSearch($request->input('search'));
@@ -103,6 +127,10 @@ 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');
@@ -119,6 +147,9 @@ class LocationsController extends Controller
case 'manager':
$locations->OrderManager($order);
break;
+ case 'company':
+ $locations->OrderCompany($order);
+ break;
default:
$locations->orderBy($sort, $order);
break;
@@ -138,15 +169,23 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Location::class);
$location = new Location;
$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')));
}
@@ -160,12 +199,11 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Location::class);
- $location = Location::with('parent', 'manager', 'children')
+ $location = Location::with('parent', 'manager', 'children', 'company')
->select([
'locations.id',
'locations.name',
@@ -181,6 +219,8 @@ class LocationsController extends Controller
'locations.updated_at',
'locations.image',
'locations.currency',
+ 'locations.company_id',
+ 'locations.notes',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
@@ -199,9 +239,8 @@ class LocationsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\JsonResponse
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Location::class);
$location = Location::findOrFail($id);
@@ -209,6 +248,19 @@ 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();
@@ -224,15 +276,47 @@ class LocationsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
}
+
+ public function assets(Request $request, Location $location) : JsonResponse | array
+ {
+ $this->authorize('view', Asset::class);
+ $this->authorize('view', $location);
+ $assets = Asset::where('location_id', '=', $location->id)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
+ $assets = $assets->get();
+ return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
+ }
+
+ public function assignedAssets(Request $request, Location $location) : JsonResponse | array
+ {
+ $this->authorize('view', Asset::class);
+ $this->authorize('view', $location);
+ $assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
+ $assets = $assets->get();
+ return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
+ }
+
+ public function assignedAccessories(Request $request, Location $location) : JsonResponse | array
+ {
+ $this->authorize('view', Accessory::class);
+ $this->authorize('view', $location);
+ $accessory_checkouts = AccessoryCheckout::LocationAssigned()->where('assigned_to', $location->id)->with('adminuser')->with('accessories');
+
+ $offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value');
+ $limit = app('api_limit_value');
+
+ $total = $accessory_checkouts->count();
+ $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
+ return (new LocationsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
+ }
+
/**
* Remove the specified resource from storage.
*
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count')
@@ -240,6 +324,7 @@ class LocationsController extends Controller
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
+ ->withCount('accessories as accessories_count')
->findOrFail($id);
if (! $location->isDeletable()) {
@@ -280,7 +365,7 @@ class LocationsController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
// If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location.
@@ -296,6 +381,11 @@ 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');
@@ -325,7 +415,6 @@ class LocationsController extends Controller
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
- //return [];
return (new SelectlistTransformer)->transformSelectlist($paginated_results);
}
}
diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php
index 6b5a130229..652fad1cfc 100644
--- a/app/Http/Controllers/Api/ManufacturersController.php
+++ b/app/Http/Controllers/Api/ManufacturersController.php
@@ -10,8 +10,8 @@ use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class ManufacturersController extends Controller
{
@@ -22,14 +22,48 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Manufacturer::class);
- $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'url',
+ 'support_url',
+ 'support_email',
+ 'warranty_lookup_url',
+ 'support_phone',
+ 'created_at',
+ 'updated_at',
+ 'image',
+ 'assets_count',
+ 'consumables_count',
+ 'components_count',
+ 'licenses_count',
+ 'notes',
+ ];
- $manufacturers = Manufacturer::select(
- ['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at']
- )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count');
+ $manufacturers = Manufacturer::select([
+ 'id',
+ 'name',
+ 'url',
+ 'support_url',
+ 'warranty_lookup_url',
+ 'support_email',
+ 'support_phone',
+ 'created_by',
+ 'created_at',
+ 'updated_at',
+ 'image',
+ 'deleted_at',
+ 'notes',
+ ])
+ ->with('adminuser')
+ ->withCount('assets as assets_count')
+ ->withCount('licenses as licenses_count')
+ ->withCount('consumables as consumables_count')
+ ->withCount('accessories as accessories_count')
+ ->withCount('components as components_count');
if ($request->input('deleted') == 'true') {
$manufacturers->onlyTrashed();
@@ -66,10 +100,18 @@ class ManufacturersController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $manufacturers->orderBy($sort, $order);
+ $sort_override = $request->input('sort');
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
+
+ switch ($sort_override) {
+ case 'created_by':
+ $manufacturers = $manufacturers->OrderByCreatedBy($order);
+ break;
+ default:
+ $manufacturers = $manufacturers->orderBy($column_sort, $order);
+ break;
+ }
$total = $manufacturers->count();
$manufacturers = $manufacturers->skip($offset)->take($limit)->get();
@@ -83,9 +125,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
@@ -105,9 +146,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
@@ -122,9 +162,8 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
@@ -144,9 +183,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
@@ -167,10 +205,9 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v6.3.4]
* @param int $id
- * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function restore($id)
+ public function restore($id) : JsonResponse
{
$this->authorize('delete', Manufacturer::class);
@@ -186,7 +223,7 @@ class ManufacturersController extends Controller
$logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s');
- $logaction->user_id = Auth::user()->id;
+ $logaction->created_by = auth()->id();
$logaction->logaction('restore');
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
@@ -206,7 +243,7 @@ class ManufacturersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/PredefinedKitsController.php b/app/Http/Controllers/Api/PredefinedKitsController.php
index b398dbfae2..24f1320185 100644
--- a/app/Http/Controllers/Api/PredefinedKitsController.php
+++ b/app/Http/Controllers/Api/PredefinedKitsController.php
@@ -7,6 +7,8 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\PredefinedKitsTransformer;
use App\Models\PredefinedKit;
use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+use App\Http\Transformers\SelectlistTransformer;
/**
* @author [D. Minaev.] []
@@ -18,12 +20,11 @@ class PredefinedKitsController extends Controller
*
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', PredefinedKit::class);
- $allowed_columns = ['id', 'name'];
- $kits = PredefinedKit::query();
+ $kits = PredefinedKit::query()->with('adminuser');
if ($request->filled('search')) {
$kits = $kits->TextSearch($request->input('search'));
@@ -34,8 +35,25 @@ class PredefinedKitsController extends Controller
$limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name';
- $kits->orderBy($sort, $order);
+
+ switch ($request->input('sort')) {
+ case 'created_by':
+ $kits = $kits->OrderByCreatedBy($order);
+ break;
+ default:
+ // This array is what determines which fields should be allowed to be sorted on ON the table itself.
+ // These must match a column on the consumables table directly.
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'created_at',
+ 'updated_at',
+ ];
+
+ $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+ $kits = $kits->orderBy($sort, $order);
+ break;
+ }
$total = $kits->count();
$kits = $kits->skip($offset)->take($limit)->get();
@@ -47,9 +65,8 @@ class PredefinedKitsController extends Controller
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', PredefinedKit::class);
$kit = new PredefinedKit;
@@ -66,9 +83,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -81,9 +97,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $id kit id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -100,9 +115,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id);
@@ -123,7 +137,7 @@ class PredefinedKitsController extends Controller
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$kits = PredefinedKit::select([
'id',
@@ -145,7 +159,7 @@ class PredefinedKitsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function indexLicenses($kit_id)
+ public function indexLicenses($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -160,7 +174,7 @@ class PredefinedKitsController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
- public function storeLicense(Request $request, $kit_id)
+ public function storeLicense(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -186,9 +200,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateLicense(Request $request, $kit_id, $license_id)
+ public function updateLicense(Request $request, $kit_id, $license_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -205,9 +218,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachLicense($kit_id, $license_id)
+ public function detachLicense($kit_id, $license_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -221,9 +233,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexModels($kit_id)
+ public function indexModels($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -236,9 +247,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function storeModel(Request $request, $kit_id)
+ public function storeModel(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -252,7 +262,7 @@ class PredefinedKitsController extends Controller
$relation = $kit->models();
if ($relation->find($model_id)) {
- return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
+ return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')]));
}
$relation->attach($model_id, ['quantity' => $quantity]);
@@ -264,9 +274,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateModel(Request $request, $kit_id, $model_id)
+ public function updateModel(Request $request, $kit_id, $model_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -283,9 +292,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachModel($kit_id, $model_id)
+ public function detachModel($kit_id, $model_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -299,9 +307,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexConsumables($kit_id)
+ public function indexConsumables($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -314,9 +321,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function storeConsumable(Request $request, $kit_id)
+ public function storeConsumable(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -342,9 +348,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateConsumable(Request $request, $kit_id, $consumable_id)
+ public function updateConsumable(Request $request, $kit_id, $consumable_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -361,9 +366,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachConsumable($kit_id, $consumable_id)
+ public function detachConsumable($kit_id, $consumable_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -377,9 +381,8 @@ class PredefinedKitsController extends Controller
* Display the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function indexAccessories($kit_id)
+ public function indexAccessories($kit_id) : array
{
$this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -392,9 +395,8 @@ class PredefinedKitsController extends Controller
* Store the specified resource.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function storeAccessory(Request $request, $kit_id)
+ public function storeAccessory(Request $request, $kit_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
@@ -420,9 +422,8 @@ class PredefinedKitsController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function updateAccessory(Request $request, $kit_id, $accessory_id)
+ public function updateAccessory(Request $request, $kit_id, $accessory_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
@@ -439,9 +440,8 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage.
*
* @param int $kit_id
- * @return \Illuminate\Http\Response
*/
- public function detachAccessory($kit_id, $accessory_id)
+ public function detachAccessory($kit_id, $accessory_id) : JsonResponse
{
$this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id);
diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php
index b6752dd1ed..f7f91e094e 100644
--- a/app/Http/Controllers/Api/ProfileController.php
+++ b/app/Http/Controllers/Api/ProfileController.php
@@ -6,13 +6,13 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\CheckoutRequest;
use Illuminate\Http\Response;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use Illuminate\Support\Facades\DB;
+use Illuminate\Http\JsonResponse;
class ProfileController extends Controller
{
@@ -42,12 +42,10 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.3.0]
- *
- * @return array
*/
- public function requestedAssets()
+ public function requestedAssets() : array
{
- $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
+ $checkoutRequests = CheckoutRequest::where('user_id', '=', auth()->id())->get();
$results = array();
$show_field = array();
@@ -95,10 +93,9 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function createApiToken(Request $request) {
+ public function createApiToken(Request $request) : JsonResponse
+ {
if (!Gate::allows('self.api')) {
abort(403);
@@ -106,14 +103,14 @@ class ProfileController extends Controller
$accessTokenName = $request->input('name', 'Auth Token');
- if ($accessToken = Auth::user()->createToken($accessTokenName)->accessToken) {
+ if ($accessToken = auth()->user()->createToken($accessTokenName)->accessToken) {
// Get the ID so we can return that with the payload
- $token = DB::table('oauth_access_tokens')->where('user_id', '=', Auth::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
+ $token = DB::table('oauth_access_tokens')->where('user_id', '=', auth()->id())->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
$accessTokenData['id'] = $token->id;
$accessTokenData['token'] = $accessToken;
$accessTokenData['name'] = $accessTokenName;
- return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, 'Personal access token '.$accessTokenName.' created successfully'));
+ return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, trans('account/general.personal_api_keys_success', ['key' => $accessTokenName])));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.'));
@@ -125,17 +122,16 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function deleteApiToken($tokenId) {
+ public function deleteApiToken($tokenId) : Response
+ {
if (!Gate::allows('self.api')) {
abort(403);
}
$token = $this->tokenRepository->findForUser(
- $tokenId, Auth::user()->getAuthIdentifier()
+ $tokenId, auth()->user()->getAuthIdentifier()
);
if (is_null($token)) {
@@ -154,16 +150,15 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.5]
- *
- * @return \Illuminate\Http\Response
*/
- public function showApiTokens(Request $request) {
+ public function showApiTokens() : JsonResponse
+ {
if (!Gate::allows('self.api')) {
abort(403);
}
- $tokens = $this->tokenRepository->forUser(Auth::user()->getAuthIdentifier());
+ $tokens = $this->tokenRepository->forUser(auth()->user()->getAuthIdentifier());
$token_values = $tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked;
})->values();
diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php
index 8761c9442b..c03dddf6b7 100644
--- a/app/Http/Controllers/Api/ReportsController.php
+++ b/app/Http/Controllers/Api/ReportsController.php
@@ -5,7 +5,10 @@ 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;
class ReportsController extends Controller
{
@@ -14,13 +17,13 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function index(Request $request)
+ public function index(Request $request) : JsonResponse | array
{
- $this->authorize('reports.view');
+ $this->authorize('activity.view');
+
+ $actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location');
- $actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location');
if ($request->filled('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
@@ -45,36 +48,40 @@ class ReportsController extends Controller
}
if ($request->filled('action_type')) {
- $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
+ $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'));
}
- if ($request->filled('user_id')) {
- $actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id'));
+ if ($request->filled('created_by')) {
+ $actionlogs = $actionlogs->where('created_by', '=', $request->input('created_by'));
}
if ($request->filled('action_source')) {
- $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
+ $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'));
+ }
+
+ if ($request->filled('remote_ip')) {
+ $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'));
}
- if ($request->filled('remote_ip')) {
- $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc');
- }
if ($request->filled('uploads')) {
- $actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
+ $actionlogs = $actionlogs->whereNotNull('filename');
}
$allowed_columns = [
'id',
'created_at',
'target_id',
- 'user_id',
+ 'created_by',
'accept_signature',
'action_type',
'note',
'remote_ip',
'user_agent',
+ 'target_type',
+ 'item_type',
'action_source',
+ 'action_date',
];
@@ -83,11 +90,19 @@ class ReportsController extends Controller
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
- $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
+ switch ($request->input('sort')) {
+ case 'created_by':
+ $actionlogs->OrderByCreatedBy($order);
+ break;
+ default:
+ $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'action_logs.created_at';
+ $actionlogs = $actionlogs->orderBy($sort, $order);
+ break;
+ }
- $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
+ $actionlogs = $actionlogs->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php
index d9059988f2..7eb28a4815 100644
--- a/app/Http/Controllers/Api/SettingsController.php
+++ b/app/Http/Controllers/Api/SettingsController.php
@@ -9,26 +9,22 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ldap;
use App\Models\Setting;
-use Mail;
-use App\Notifications\SlackTest;
use App\Notifications\MailTest;
-use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\Validator;
-use App\Http\Requests\SlackSettingsRequest;
+use Illuminate\Support\Facades\Validator;
use App\Http\Transformers\LoginAttemptsTransformer;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class SettingsController extends Controller
{
- public function ldaptest()
+ public function ldaptest() : JsonResponse
{
$settings = Setting::getSettings();
@@ -88,7 +84,7 @@ class SettingsController extends Controller
}
- public function ldaptestlogin(Request $request)
+ public function ldaptestlogin(Request $request) : JsonResponse
{
if (Setting::getSettings()->ldap_enabled != '1') {
@@ -148,9 +144,8 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return JsonResponse
*/
- public function ajaxTestEmail()
+ public function ajaxTestEmail() : JsonResponse
{
if (!config('app.lock_passwords')) {
try {
@@ -170,9 +165,8 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] []
* @since [v5.0.0]
- * @return JsonResponse
*/
- public function purgeBarcodes()
+ public function purgeBarcodes() : JsonResponse
{
$file_count = 0;
$files = Storage::disk('public')->files('barcodes');
@@ -211,9 +205,8 @@ class SettingsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0.0]
* @param \Illuminate\Http\Request $request
- * @return array | JsonResponse
*/
- public function showLoginAttempts(Request $request)
+ public function showLoginAttempts(Request $request) : array
{
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at'];
@@ -233,9 +226,9 @@ class SettingsController extends Controller
* Lists backup files
*
* @author [A. Gianotto]
- * @return array | JsonResponse
*/
- public function listBackups() {
+ public function listBackups() : array
+ {
$settings = Setting::getSettings();
$path = 'app/backups';
$backup_files = Storage::files($path);
@@ -276,9 +269,9 @@ class SettingsController extends Controller
* exhausts memory on larger files.
*
* @author [A. Gianotto]
- * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
- public function downloadBackup($file) {
+ public function downloadBackup($file) : JsonResponse | BinaryFileResponse
+ {
$path = storage_path('app/backups');
@@ -296,9 +289,9 @@ class SettingsController extends Controller
*
* @author [A. Gianotto]
* @since [v6.3.1]
- * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
- public function downloadLatestBackup() {
+ public function downloadLatestBackup() : JsonResponse | BinaryFileResponse
+ {
$fileData = collect();
foreach (Storage::files('app/backups') as $file) {
diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php
index 7c8e260c2f..b3503ff152 100644
--- a/app/Http/Controllers/Api/StatuslabelsController.php
+++ b/app/Http/Controllers/Api/StatuslabelsController.php
@@ -8,10 +8,11 @@ use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset;
+use App\Models\Setting;
use App\Models\Statuslabel;
use Illuminate\Http\Request;
use App\Http\Transformers\PieChartTransformer;
-use Illuminate\Support\Arr;
+use Illuminate\Http\JsonResponse;
class StatuslabelsController extends Controller
{
@@ -20,14 +21,21 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', Statuslabel::class);
- $allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label'];
+ $allowed_columns = [
+ 'id',
+ 'name',
+ 'created_at',
+ 'assets_count',
+ 'color',
+ 'notes',
+ 'default_label'
+ ];
- $statuslabels = Statuslabel::withCount('assets as assets_count');
+ $statuslabels = Statuslabel::with('adminuser')->withCount('assets as assets_count');
if ($request->filled('search')) {
$statuslabels = $statuslabels->TextSearch($request->input('search'));
@@ -54,10 +62,18 @@ class StatuslabelsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value');
$limit = app('api_limit_value');
-
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
- $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $statuslabels->orderBy($sort, $order);
+ $sort_override = $request->input('sort');
+ $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
+
+ switch ($sort_override) {
+ case 'created_by':
+ $statuslabels = $statuslabels->OrderByCreatedBy($order);
+ break;
+ default:
+ $statuslabels = $statuslabels->orderBy($column_sort, $order);
+ break;
+ }
$total = $statuslabels->count();
$statuslabels = $statuslabels->skip($offset)->take($limit)->get();
@@ -72,15 +88,15 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
*/
- public function store(Request $request)
+ public function store(Request $request) : JsonResponse
{
$this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending', 'archived');
if (! $request->filled('type')) {
- return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500);
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]));
}
$statuslabel = new Statuslabel;
@@ -108,9 +124,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -126,9 +141,8 @@ class StatuslabelsController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(Request $request, $id)
+ public function update(Request $request, $id) : JsonResponse
{
$this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -163,9 +177,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
@@ -188,13 +201,18 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return array
*/
- public function getAssetCountByStatuslabel()
+ public function getAssetCountByStatuslabel() : array
{
$this->authorize('view', Statuslabel::class);
- $statuslabels = Statuslabel::withCount('assets')->get();
- $total = Array();
+
+ if (Setting::getSettings()->show_archived_in_list == 0 ) {
+ $statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
+ } else {
+ $statuslabels = Statuslabel::withCount('assets')->get();
+ }
+
+ $total = [];
foreach ($statuslabels as $statuslabel) {
@@ -215,9 +233,8 @@ class StatuslabelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.0.11]
- * @return array
*/
- public function getAssetCountByMetaStatus()
+ public function getAssetCountByMetaStatus() : array
{
$this->authorize('view', Statuslabel::class);
@@ -245,9 +262,8 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function assets(Request $request, $id)
+ public function assets(Request $request, $id) : array
{
$this->authorize('view', Statuslabel::class);
$this->authorize('index', Asset::class);
@@ -274,19 +290,20 @@ class StatuslabelsController extends Controller
/**
* Returns a boolean response based on whether the status label
- * is one that is deployable.
+ * is one that is deployable or pending.
*
* This is used by the hardware create/edit view to determine whether
- * we should provide a dropdown of users for them to check the asset out to.
+ * we should provide a dropdown of users for them to check the asset out to,
+ * and whether we show a warning that the asset will be checked in if it's already
+ * assigned but the status is changed to one that isn't pending or deployable
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return bool
*/
- public function checkIfDeployable($id)
+ public function checkIfDeployable($id) : string
{
$statuslabel = Statuslabel::findOrFail($id);
- if ($statuslabel->getStatuslabelType() == 'deployable') {
+ if (($statuslabel->getStatuslabelType() == 'pending') || ($statuslabel->getStatuslabelType() == 'deployable')) {
return '1';
}
@@ -300,7 +317,7 @@ class StatuslabelsController extends Controller
* @since [v6.1.1]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php
index 3e3d637be0..ab0db67909 100644
--- a/app/Http/Controllers/Api/SuppliersController.php
+++ b/app/Http/Controllers/Api/SuppliersController.php
@@ -10,6 +10,7 @@ use App\Models\Supplier;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Http\JsonResponse;
class SuppliersController extends Controller
{
@@ -20,13 +21,18 @@ class SuppliersController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
- public function index(Request $request)
+ 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',
@@ -38,21 +44,24 @@ 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', 'updated_at', 'deleted_at', 'image', 'notes', 'url'])
+ ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip'])
->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');
+ ->withCount('consumables as consumables_count')
+ ->with('adminuser');
if ($request->filled('search')) {
- $suppliers = $suppliers->TextSearch($request->input('search'));
+ $suppliers->TextSearch($request->input('search'));
}
+
if ($request->filled('name')) {
$suppliers->where('name', '=', $request->input('name'));
}
@@ -99,7 +108,15 @@ class SuppliersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
- $suppliers->orderBy($sort, $order);
+
+ switch ($request->input('sort')) {
+ case 'created_by':
+ $suppliers->OrderByCreatedByName($order);
+ break;
+ default:
+ $suppliers->orderBy($sort, $order);
+ break;
+ }
$total = $suppliers->count();
$suppliers = $suppliers->skip($offset)->take($limit)->get();
@@ -114,9 +131,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
- * @return \Illuminate\Http\Response
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : JsonResponse
{
$this->authorize('create', Supplier::class);
$supplier = new Supplier;
@@ -136,9 +152,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function show($id)
+ public function show($id) : array
{
$this->authorize('view', Supplier::class);
$supplier = Supplier::findOrFail($id);
@@ -154,9 +169,8 @@ class SuppliersController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function update(ImageUploadRequest $request, $id)
+ public function update(ImageUploadRequest $request, $id) : JsonResponse
{
$this->authorize('update', Supplier::class);
$supplier = Supplier::findOrFail($id);
@@ -176,9 +190,8 @@ class SuppliersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\Response
*/
- public function destroy($id)
+ public function destroy($id) : JsonResponse
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
@@ -209,7 +222,7 @@ class SuppliersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$this->authorize('view.selectlists');
diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php
index 3374f1907e..994ad15f25 100644
--- a/app/Http/Controllers/Api/UsersController.php
+++ b/app/Http/Controllers/Api/UsersController.php
@@ -6,6 +6,7 @@ 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;
@@ -14,16 +15,19 @@ use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Accessory;
+use App\Models\Company;
use App\Models\Consumable;
use App\Models\License;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Illuminate\Support\Facades\Auth;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use App\Http\Requests\DeleteUserRequest;
+use Illuminate\Http\JsonResponse;
class UsersController extends Controller
{
@@ -35,19 +39,20 @@ class UsersController extends Controller
*
* @return array
*/
- public function index(Request $request)
+ public function index(Request $request) : array
{
$this->authorize('view', User::class);
$users = User::select([
'users.activated',
- 'users.created_by',
'users.address',
'users.avatar',
'users.city',
'users.company_id',
'users.country',
+ 'users.created_by',
'users.created_at',
+ 'users.updated_at',
'users.deleted_at',
'users.department_id',
'users.email',
@@ -66,7 +71,6 @@ class UsersController extends Controller
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
- 'users.updated_at',
'users.username',
'users.zip',
'users.remote',
@@ -77,8 +81,17 @@ class UsersController extends Controller
'users.autoassign_licenses',
'users.website',
- ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
- ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count');
+ ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
+ ->withCount([
+ 'assets as assets_count' => function(Builder $query) {
+ $query->withoutTrashed();
+ },
+ 'licenses as licenses_count',
+ 'accessories as accessories_count',
+ 'consumables as consumables_count',
+ 'managesUsers as manages_users_count',
+ 'managedLocations as manages_locations_count'
+ ]);
if ($request->filled('search') != '') {
@@ -194,17 +207,21 @@ class UsersController extends Controller
}
if ($request->filled('manages_users_count')) {
- $users->has('manages_users_count', '=', $request->input('manages_users_count'));
+ $users->has('managesUsers', '=', $request->input('manages_users_count'));
}
if ($request->filled('manages_locations_count')) {
- $users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
+ $users->has('managedLocations', '=', $request->input('manages_locations_count'));
}
if ($request->filled('autoassign_licenses')) {
$users->where('autoassign_licenses', '=', $request->input('autoassign_licenses'));
}
+ if ($request->filled('locale')) {
+ $users = $users->where('users.locale', '=', $request->input('locale'));
+ }
+
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
@@ -247,13 +264,10 @@ class UsersController extends Controller
'jobtitle',
'username',
'employee_num',
- 'assets',
- 'accessories',
- 'consumables',
- 'licenses',
'groups',
'activated',
'created_at',
+ 'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
@@ -279,6 +293,8 @@ class UsersController extends Controller
'end_date',
'autoassign_licenses',
'website',
+ 'locale',
+ 'notes',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
@@ -305,7 +321,7 @@ class UsersController extends Controller
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*/
- public function selectlist(Request $request)
+ public function selectlist(Request $request) : array
{
$users = User::select(
[
@@ -324,6 +340,7 @@ 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').'%');
});
}
@@ -361,21 +378,21 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param \Illuminate\Http\Request $request
- * @return array | \Illuminate\Http\JsonResponse
*/
- public function store(SaveUserRequest $request)
+ public function store(SaveUserRequest $request) : JsonResponse
{
$this->authorize('create', User::class);
$user = new User;
$user->fill($request->all());
- $user->created_by = Auth::user()->id;
+ $user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
+ $user->created_by = auth()->id();
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
- if (! Auth::user()->isSuperUser()) {
+ if (! auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
}
$user->permissions = $permissions_array;
@@ -408,9 +425,8 @@ class UsersController extends Controller
*
* @author [A. Gianotto] []
* @param int $id
- * @return array | \Illuminate\Http\JsonResponse
*/
- public function show($id)
+ public function show($id) : JsonResponse | array
{
$this->authorize('view', User::class);
@@ -431,15 +447,11 @@ class UsersController extends Controller
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
- * @return \Illuminate\Http\JsonResponse
*/
- public function update(SaveUserRequest $request, $id)
+ public function update(SaveUserRequest $request, User $user): JsonResponse
{
$this->authorize('update', User::class);
- if ($user = User::find($id)) {
-
-
$this->authorize('update', $user);
/**
@@ -449,14 +461,16 @@ class UsersController extends Controller
*
*/
-
- if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
+ if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
}
-
$user->fill($request->all());
+ if ($request->filled('company_id')) {
+ $user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
+ }
+
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
}
@@ -472,25 +486,23 @@ class UsersController extends Controller
$permissions_array = $request->input('permissions');
// Strip out the individual superuser permission if the API user isn't a superadmin
- if (!Auth::user()->isSuperUser()) {
+ if (!auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
}
$user->permissions = $permissions_array;
}
-
- // Update the location of any assets checked out to this user
- Asset::where('assigned_type', User::class)
- ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
-
-
+ if($request->has('location_id')) {
+ // Update the location of any assets checked out to this user
+ Asset::where('assigned_type', User::class)
+ ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
+ }
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
if ($user->save()) {
-
// Check if the request has groups passed and has a value, AND that the user us a superuser
- if (($request->has('groups')) && (Auth::user()->isSuperUser())) {
+ if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
$validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id',
@@ -502,18 +514,10 @@ class UsersController extends Controller
// Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups'));
-
-
}
-
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
-
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
- }
-
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
-
}
/**
@@ -522,9 +526,8 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param int $id
- * @return \Illuminate\Http\JsonResponse
*/
- public function destroy(DeleteUserRequest $request, $id)
+ public function destroy(DeleteUserRequest $request, $id) : JsonResponse
{
$this->authorize('delete', User::class);
@@ -560,9 +563,8 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return array | \Illuminate\Http\JsonResponse
*/
- public function assets(Request $request, $id)
+ public function assets(Request $request, $id) : JsonResponse | array
{
$this->authorize('view', User::class);
$this->authorize('view', Asset::class);
@@ -605,9 +607,9 @@ class UsersController extends Controller
* @since [v6.0.13]
* @param Request $request
* @param $id
- * @return string JSON
*/
- public function emailAssetList(Request $request, $id)
+ public function emailAssetList(Request $request, $id) : JsonResponse
+
{
$this->authorize('update', User::class);
@@ -633,9 +635,8 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return array | \Illuminate\Http\JsonResponse
*/
- public function consumables(Request $request, $id)
+ public function consumables(Request $request, $id) : array
{
$this->authorize('view', User::class);
$this->authorize('view', Consumable::class);
@@ -651,9 +652,8 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v4.6.14]
* @param $userId
- * @return array
*/
- public function accessories($id)
+ public function accessories($id) : array
{
$this->authorize('view', User::class);
$user = User::findOrFail($id);
@@ -670,9 +670,8 @@ class UsersController extends Controller
* @author [N. Mathar] []
* @since [v5.0]
* @param $userId
- * @return array | \Illuminate\Http\JsonResponse
*/
- public function licenses($id)
+ public function licenses($id) : JsonResponse | array
{
$this->authorize('view', User::class);
$this->authorize('view', License::class);
@@ -693,9 +692,8 @@ class UsersController extends Controller
* @author [A. Gianotto] []
* @since [v3.0]
* @param $userId
- * @return string JSON
*/
- public function postTwoFactorReset(Request $request)
+ public function postTwoFactorReset(Request $request) : JsonResponse
{
$this->authorize('update', User::class);
@@ -714,7 +712,7 @@ class UsersController extends Controller
$logaction->item_type = User::class;
$logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s');
- $logaction->user_id = Auth::user()->id;
+ $logaction->created_by = auth()->id();
$logaction->logaction('2FA reset');
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
@@ -733,22 +731,39 @@ class UsersController extends Controller
* @author [Juan Font] []
* @since [v4.4.2]
* @param \Illuminate\Http\Request $request
- * @return array
*/
- public function getCurrentUserInfo(Request $request)
+ public function getCurrentUserInfo(Request $request) : array
{
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] []
+ */
+ public function eulas(User $user, ActionlogsTransformer $transformer)
+ {
+ $this->authorize('view', Asset::class);
+
+ $eulas = $user->eulas;
+ return response()->json(
+ $transformer->transformActionlogs($eulas, $eulas->count())
+ );
+ }
+
/**
* Restore a soft-deleted user.
*
* @author [E. Taylor] []
* @param int $userId
* @since [v6.0.0]
- * @return \Illuminate\Http\JsonResponse
*/
- public function restore($userId)
+ public function restore($userId) : JsonResponse
{
$this->authorize('delete', User::class);
@@ -766,7 +781,7 @@ class UsersController extends Controller
$logaction->item_type = User::class;
$logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s');
- $logaction->user_id = Auth::user()->id;
+ $logaction->created_by = auth()->id();
$logaction->logaction('restore');
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200);
diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php
index c9ffea9a4c..0e46118af7 100644
--- a/app/Http/Controllers/AssetMaintenancesController.php
+++ b/app/Http/Controllers/AssetMaintenancesController.php
@@ -2,17 +2,14 @@
namespace App\Http\Controllers;
-use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Http\Request;
-use Slack;
-use Str;
-use TCPDF;
-use View;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
/**
* This controller handles all actions related to Asset Maintenance for
@@ -22,20 +19,6 @@ use View;
*/
class AssetMaintenancesController extends Controller
{
- /**
- * Checks for permissions for this action.
- *
- * @todo This should be replaced with middleware and/or policies
- * @author Vincent Sposato
- * @version v1.0
- * @since [v1.8]
- * @return View
- */
- private static function getInsufficientPermissionsRedirect()
- {
- return redirect()->route('maintenances.index')
- ->with('error', trans('general.insufficient_permissions'));
- }
/**
* Returns a view that invokes the ajax tables which actually contains
@@ -46,9 +29,8 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return View
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', Asset::class);
return view('asset_maintenances/index');
@@ -63,7 +45,7 @@ class AssetMaintenancesController extends Controller
* @since [v1.8]
* @return mixed
*/
- public function create()
+ public function create() : View
{
$this->authorize('update', Asset::class);
$asset = null;
@@ -72,16 +54,10 @@ 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);
}
@@ -92,48 +68,49 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function store(Request $request)
+ 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'));
- if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) {
- return static::getInsufficientPermissionsRedirect();
+ $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());
+ }
}
- // 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->user_id = Auth::id();
+ return redirect()->route('maintenances.index')
+ ->with('success', trans('admin/asset_maintenances/message.create.success'));
- 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());
}
/**
@@ -141,39 +118,19 @@ class AssetMaintenancesController extends Controller
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato
- * @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function edit($assetMaintenanceId = null)
+ public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
- // Check if the asset maintenance exists
- $this->authorize('update', Asset::class);
- // 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 ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
- // Redirect to the asset maintenance management page
- return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
- } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
- return static::getInsufficientPermissionsRedirect();
- }
+ $this->authorize('update', $maintenance->asset);
-
- // Prepare Improvement Type List
- $assetMaintenanceType = [
- '' => 'Select an improvement type',
- ] + AssetMaintenance::getImprovementOptions();
-
- // Get Supplier List
- // Render the view
return view('asset_maintenances/edit')
- ->with('selectedAsset', null)
- ->with('assetMaintenanceType', $assetMaintenanceType)
- ->with('item', $assetMaintenance);
+ ->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
+ ->with('asset_ids', request()->input('asset_ids', []))
+ ->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
+ ->with('item', $maintenance);
}
/**
@@ -183,69 +140,49 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato
* @param Request $request
* @param int $assetMaintenanceId
- * @return mixed
* @version v1.0
* @since [v1.8]
*/
- public function update(Request $request, $assetMaintenanceId = null)
+ public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
- // 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 ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
- // Redirect to the asset maintenance management page
- return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
- } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
- return static::getInsufficientPermissionsRedirect();
- }
+ $this->authorize('update', $maintenance->asset);
- $assetMaintenance->supplier_id = $request->input('supplier_id');
- $assetMaintenance->is_warranty = $request->input('is_warranty');
- $assetMaintenance->cost = $request->input('cost');
- $assetMaintenance->notes = $request->input('notes');
+ $maintenance->supplier_id = $request->input('supplier_id');
+ $maintenance->is_warranty = $request->input('is_warranty', 0);
+ $maintenance->cost = $request->input('cost');
+ $maintenance->notes = $request->input('notes');
+ $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');
- $asset = Asset::find(request('asset_id'));
- if (! Company::isCurrentUserHasAccess($asset)) {
- return static::getInsufficientPermissionsRedirect();
- }
-
- // 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');
-
- if (($assetMaintenance->completion_date == null)
- ) {
- if (($assetMaintenance->asset_maintenance_time !== 0)
- || (! is_null($assetMaintenance->asset_maintenance_time))
+ // Todo - put this in a getter/setter?
+ if (($maintenance->completion_date == null))
+ {
+ if (($maintenance->asset_maintenance_time !== 0)
+ || (! is_null($maintenance->asset_maintenance_time))
) {
- $assetMaintenance->asset_maintenance_time = null;
+ $maintenance->asset_maintenance_time = null;
}
}
- if (($assetMaintenance->completion_date !== null)
- && ($assetMaintenance->start_date !== '')
- && ($assetMaintenance->start_date !== '0000-00-00')
+ if (($maintenance->completion_date !== null)
+ && ($maintenance->start_date !== '')
+ && ($maintenance->start_date !== '0000-00-00')
) {
- $startDate = Carbon::parse($assetMaintenance->start_date);
- $completionDate = Carbon::parse($assetMaintenance->completion_date);
- $assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
+ $startDate = Carbon::parse($maintenance->start_date);
+ $completionDate = Carbon::parse($maintenance->completion_date);
+ $maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
- // Was the asset maintenance created?
- if ($assetMaintenance->save()) {
-
- // Redirect to the new asset maintenance page
+ if ($maintenance->save()) {
return redirect()->route('maintenances.index')
- ->with('success', trans('admin/asset_maintenances/message.edit.success'));
+ ->with('success', trans('admin/asset_maintenances/message.edit.success'));
}
- return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
+ return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
}
/**
@@ -255,23 +192,13 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return mixed
*/
- public function destroy($assetMaintenanceId)
+ public function destroy(AssetMaintenance $maintenance) : RedirectResponse
{
$this->authorize('update', Asset::class);
- // 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();
- }
-
+ $this->authorize('update', $maintenance->asset);
// Delete the asset maintenance
- $assetMaintenance->delete();
-
+ $maintenance->delete();
// Redirect to the asset_maintenance management page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.delete.success'));
@@ -284,21 +211,9 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
- * @return View
*/
- public function show($assetMaintenanceId)
+ public function show(AssetMaintenance $maintenance) : View | RedirectResponse
{
- $this->authorize('view', Asset::class);
-
- // 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();
- }
-
- return view('asset_maintenances/view')->with('assetMaintenance', $assetMaintenance);
+ return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
}
}
diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php
index a2deb616b0..0795588776 100755
--- a/app/Http/Controllers/AssetModelsController.php
+++ b/app/Http/Controllers/AssetModelsController.php
@@ -4,19 +4,22 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
+use App\Http\Requests\StoreAssetModelRequest;
use App\Models\Actionlog;
-use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CustomField;
+use App\Models\SnipeModel;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
-use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\MessageBag;
+
/**
* This class controls all actions related to asset models for
@@ -27,16 +30,15 @@ use Illuminate\Support\Facades\Log;
*/
class AssetModelsController extends Controller
{
+ protected MessageBag $validatorErrors;
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('index', AssetModel::class);
@@ -48,10 +50,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', AssetModel::class);
@@ -66,16 +66,12 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreAssetModelRequest $request) : RedirectResponse
{
$this->authorize('create', AssetModel::class);
- // Create a new asset model
$model = new AssetModel;
- // Save the model data
$model->eol = $request->input('eol');
$model->depreciation_id = $request->input('depreciation_id');
$model->name = $request->input('name');
@@ -84,7 +80,7 @@ class AssetModelsController extends Controller
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
- $model->user_id = Auth::id();
+ $model->created_by = auth()->id();
$model->requestable = $request->has('requestable');
if ($request->input('fieldset_id') != '') {
@@ -93,7 +89,6 @@ class AssetModelsController extends Controller
$model = $request->handleImages($model);
- // Was it created?
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
@@ -113,21 +108,12 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($modelId = null)
+ public function edit(AssetModel $model) : View | RedirectResponse
{
$this->authorize('update', AssetModel::class);
- if ($item = AssetModel::find($modelId)) {
- $category_type = 'asset';
- $view = View::make('models/edit', compact('item', 'category_type'));
- $view->with('depreciation_list', Helper::depreciationList());
-
- return $view;
- }
-
- return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
+ $category_type = 'asset';
+ return view('models/edit', compact('category_type'))->with('item', $model)->with('depreciation_list', Helper::depreciationList());
}
@@ -142,17 +128,11 @@ class AssetModelsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $modelId = null)
+ public function update(StoreAssetModelRequest $request, AssetModel $model) : RedirectResponse
{
$this->authorize('update', AssetModel::class);
- // Check if the model exists
- if (is_null($model = AssetModel::find($modelId))) {
- // Redirect to the models management page
- return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
- }
$model = $request->handleImages($model);
-
$model->depreciation_id = $request->input('depreciation_id');
$model->eol = $request->input('eol');
$model->name = $request->input('name');
@@ -163,20 +143,17 @@ class AssetModelsController extends Controller
$model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0');
- $this->removeCustomFieldsDefaultValues($model);
-
$model->fieldset_id = $request->input('fieldset_id');
- if ($this->shouldAddDefaultValues($request->input())) {
- if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
- return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
- }
- }
-
-
-
-
if ($model->save()) {
+ $this->removeCustomFieldsDefaultValues($model);
+
+ if ($this->shouldAddDefaultValues($request->input())) {
+ if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))) {
+ return redirect()->back()->withInput()->withErrors($this->validatorErrors);
+ }
+ }
+
if ($model->wasChanged('eol')) {
if ($model->eol > 0) {
$newEol = $model->eol;
@@ -200,30 +177,17 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($modelId)
+ public function destroy(AssetModel $model) : RedirectResponse
{
$this->authorize('delete', AssetModel::class);
- // Check if the model exists
- if (is_null($model = AssetModel::find($modelId))) {
- return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
- }
+
if ($model->assets()->count() > 0) {
// Throw an error that this model is associated with assets
return redirect()->route('models.index')->with('error', trans('admin/models/message.assoc_users'));
}
- if ($model->image) {
- try {
- Storage::disk('public')->delete('models/'.$model->image);
- } catch (\Exception $e) {
- Log::info($e);
- }
- }
-
// Delete the model
$model->delete();
@@ -237,10 +201,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $id
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function getRestore($id)
+ public function getRestore($id) : RedirectResponse
{
$this->authorize('create', AssetModel::class);
@@ -252,10 +214,10 @@ class AssetModelsController extends Controller
if ($model->restore()) {
$logaction = new Actionlog();
- $logaction->item_type = User::class;
+ $logaction->item_type = AssetModel::class;
$logaction->item_id = $model->id;
$logaction->created_at = date('Y-m-d H:i:s');
- $logaction->user_id = Auth::user()->id;
+ $logaction->created_by = auth()->id();
$logaction->logaction('restore');
@@ -282,19 +244,11 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($modelId = null)
+ public function show(AssetModel $model) : View | RedirectResponse
{
$this->authorize('view', AssetModel::class);
- $model = AssetModel::withTrashed()->find($modelId);
-
- if (isset($model->id)) {
- return view('models/view', compact('model'));
- }
-
- return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
+ return view('models/view', compact('model'));
}
/**
@@ -303,25 +257,21 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return View
*/
- public function getClone($modelId = null)
+ public function getClone(AssetModel $model) : View | RedirectResponse
{
$this->authorize('create', AssetModel::class);
- // Check if the model exists
- if (is_null($model_to_clone = AssetModel::find($modelId))) {
- return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
- }
- $model = clone $model_to_clone;
+ $cloned_model = clone $model;
$model->id = null;
+ $model->deleted_at = null;
// Show the page
return view('models/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
- ->with('model_id', $model_to_clone->id)
- ->with('clone_model', $model_to_clone);
+ ->with('model_id', $model->id)
+ ->with('clone_model', $cloned_model);
}
@@ -331,9 +281,8 @@ class AssetModelsController extends Controller
* @author [B. Wetherington] []
* @since [v2.0]
* @param int $modelId
- * @return View
*/
- public function getCustomFields($modelId)
+ public function getCustomFields($modelId) : View
{
return view('models.custom_fields_form')->with('model', AssetModel::find($modelId));
}
@@ -341,13 +290,12 @@ class AssetModelsController extends Controller
/**
- * Returns a view that allows the user to bulk edit model attrbutes
+ * Returns a view that allows the user to bulk edit model attributes
*
* @author [A. Gianotto] []
* @since [v1.7]
- * @return \Illuminate\Contracts\View\View
*/
- public function postBulkEdit(Request $request)
+ public function postBulkEdit(Request $request) : View | RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -389,9 +337,8 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.7]
- * @return \Illuminate\Contracts\View\View
*/
- public function postBulkEditSave(Request $request)
+ public function postBulkEditSave(Request $request) : RedirectResponse
{
$models_raw_array = $request->input('ids');
$update_array = [];
@@ -429,9 +376,8 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $modelId
- * @return \Illuminate\Http\RedirectResponse
*/
- public function postBulkDelete(Request $request)
+ public function postBulkDelete(Request $request) : RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -470,9 +416,8 @@ class AssetModelsController extends Controller
* any default values were entered into the form.
*
* @param array $input
- * @return bool
*/
- private function shouldAddDefaultValues(array $input)
+ private function shouldAddDefaultValues(array $input) : bool
{
return ! empty($input['add_default_values'])
&& ! empty($input['default_values'])
@@ -484,9 +429,8 @@ class AssetModelsController extends Controller
*
* @param AssetModel $model
* @param array $defaultValues
- * @return void
*/
- private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool
+ private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, array $defaultValues): bool
{
$data = array();
foreach ($defaultValues as $customFieldId => $defaultValue) {
@@ -495,22 +439,28 @@ class AssetModelsController extends Controller
$data[$customField->db_column] = $defaultValue;
}
- $fieldsets = $model->fieldset->validation_rules();
+ $allRules = $model->fieldset->validation_rules();
$rules = array();
- foreach ($fieldsets as $fieldset => $validation){
+ foreach ($allRules as $field => $validation) {
// If the field is marked as required, eliminate the rule so it doesn't interfere with the default values
// (we are at model level, the rule still applies when creating a new asset using this model)
$index = array_search('required', $validation);
if ($index !== false){
$validation[$index] = 'nullable';
}
- $rules[$fieldset] = $validation;
+ $rules[$field] = $validation;
}
- $validator = Validator::make($data, $rules);
+ $attributes = [];
+ foreach ($model->fieldset->fields as $field) {
+ $attributes[$field->db_column] = trim(preg_replace('/_+|snipeit|\d+/', ' ', $field->db_column));
+ }
+
+ $validator = Validator::make($data, $rules)->setAttributeNames($attributes);
if($validator->fails()){
+ $this->validatorErrors = $validator->errors();
return false;
}
@@ -527,9 +477,8 @@ class AssetModelsController extends Controller
/**
* Removes all default values
*
- * @return void
*/
- private function removeCustomFieldsDefaultValues(AssetModel $model)
+ private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void
{
$model->defaultValues()->detach();
}
diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php
index c8247ffc15..14b2c1fc0b 100644
--- a/app/Http/Controllers/AssetModelsFilesController.php
+++ b/app/Http/Controllers/AssetModelsFilesController.php
@@ -6,8 +6,11 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
-use Illuminate\Support\Facades\Response;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
+use \Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetModelsFilesController extends Controller
{
@@ -21,7 +24,7 @@ class AssetModelsFilesController extends Controller
*@since [v1.0]
* @author [A. Gianotto] []
*/
- public function store(UploadFileRequest $request, $modelId = null)
+ public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -41,10 +44,10 @@ class AssetModelsFilesController extends Controller
$model->logUpload($file_name, $request->get('notes'));
}
- return redirect()->back()->with('success', trans('general.file_upload_success'));
+ return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
- return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
+ return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
@@ -54,14 +57,10 @@ class AssetModelsFilesController extends Controller
* @param int $modelId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($modelId = null, $fileId = null)
+ public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
- $model = AssetModel::find($modelId);
- // the asset is valid
- if (isset($model->id)) {
+
$this->authorize('view', $model);
if (! $log = Actionlog::find($fileId)) {
@@ -86,12 +85,6 @@ class AssetModelsFilesController extends Controller
}
return StorageHelper::downloader($file);
- }
- // Prepare the error message
- $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
-
- // Redirect to the hardware management page
- return redirect()->route('hardware.index')->with('error', $error);
}
/**
@@ -101,33 +94,22 @@ class AssetModelsFilesController extends Controller
* @param int $modelId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($modelId = null, $fileId = null)
+ public function destroy(AssetModel $model, $fileId = null) : RedirectResponse
{
- $model = AssetModel::find($modelId);
- $this->authorize('update', $model);
$rel_path = 'private_uploads/assetmodels';
-
- // the asset is valid
- if (isset($model->id)) {
- $this->authorize('update', $model);
- $log = Actionlog::find($fileId);
- if ($log) {
- if (Storage::exists($rel_path.'/'.$log->filename)) {
- Storage::delete($rel_path.'/'.$log->filename);
- }
- $log->delete();
-
- return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
+ $this->authorize('update', $model);
+ $log = Actionlog::find($fileId);
+ if ($log) {
+ if (Storage::exists($rel_path.'/'.$log->filename)) {
+ Storage::delete($rel_path.'/'.$log->filename);
}
+ $log->delete();
- return redirect()->back()
- ->with('success', trans('admin/hardware/message.deletefile.success'));
+ return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
- // Redirect to the hardware management page
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
+ return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
+
}
}
diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php
index 73f4838a6a..3e1b75a4d7 100644
--- a/app/Http/Controllers/Assets/AssetCheckinController.php
+++ b/app/Http/Controllers/Assets/AssetCheckinController.php
@@ -11,10 +11,10 @@ use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Session;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Log;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Validator;
class AssetCheckinController extends Controller
{
@@ -26,22 +26,14 @@ class AssetCheckinController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @param string $backto
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function create($assetId, $backto = null)
+ public function create(Asset $asset, $backto = null) : View | RedirectResponse
{
- // Check if the asset exists
- if (is_null($asset = Asset::find($assetId))) {
- // Redirect to the asset management page with error
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
$this->authorize('checkin', $asset);
// This asset is already checked in, redirect
-
if (is_null($asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
@@ -50,7 +42,23 @@ class AssetCheckinController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
- return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
+ // 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'))
+ ->with('item', $asset)
+ ->with('statusLabel_list', Helper::statusLabelList())
+ ->with('backto', $backto)
+ ->with('table_name', 'Assets');
}
/**
@@ -60,11 +68,9 @@ class AssetCheckinController extends Controller
* @param AssetCheckinRequest $request
* @param int $assetId
* @param null $backto
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
+ public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) : RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
@@ -82,13 +88,14 @@ class AssetCheckinController extends Controller
$this->authorize('checkin', $asset);
- if ($asset->assignedType() == Asset::USER) {
- $user = $asset->assignedTo;
- }
+ 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',
+ });
$asset->expected_checkin = null;
- //$asset->last_checkout = null;
- $asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = $request->get('name');
@@ -97,6 +104,9 @@ class AssetCheckinController extends Controller
$asset->status_id = e($request->get('status_id'));
}
+ // Add any custom fields that should be included in the checkout
+ $asset->customFieldsForCheckinCheckout('display_checkin');
+
$this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
@@ -112,11 +122,14 @@ 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]);
@@ -132,12 +145,15 @@ class AssetCheckinController extends Controller
$acceptance->delete();
});
- Session::put('redirect_option', $request->get('redirect_option'));
- // Was the asset updated?
+ session()->put('redirect_option', $request->get('redirect_option'));
+
+ // Add any custom fields that should be included in the checkout
+ $asset->customFieldsForCheckinCheckout('display_checkin');
+
if ($asset->save()) {
- event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
- return Helper::getRedirectOption($asset, $assetId, 'Assets');
+ event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
+ 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());
diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php
index 36f00f0fc6..da2c0a2550 100644
--- a/app/Http/Controllers/Assets/AssetCheckoutController.php
+++ b/app/Http/Controllers/Assets/AssetCheckoutController.php
@@ -9,8 +9,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\Asset;
use Illuminate\Database\Eloquent\ModelNotFoundException;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Illuminate\Support\Facades\Validator;
class AssetCheckoutController extends Controller
{
@@ -23,29 +25,35 @@ class AssetCheckoutController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function create($assetId)
+ public function create(Asset $asset) : View | RedirectResponse
{
- // Check if the asset exists
- if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
$this->authorize('checkout', $asset);
if (!$asset->model) {
- return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ return redirect()->route('hardware.show', $asset)
+ ->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())
- ->with('table_name', 'Assets');
+ ->with('table_name', 'Assets')
+ ->with('item', $asset);
}
-
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
+ return redirect()->route('hardware.index')
+ ->with('error', trans('admin/hardware/message.checkout.not_available'));
}
/**
@@ -53,11 +61,9 @@ class AssetCheckoutController extends Controller
*
* @author [A. Gianotto] []
* @param AssetCheckoutRequest $request
- * @param int $assetId
- * @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
*/
- public function store(AssetCheckoutRequest $request, $assetId)
+ public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
{
try {
// Check if the asset exists
@@ -69,10 +75,10 @@ class AssetCheckoutController extends Controller
$this->authorize('checkout', $asset);
if (!$asset->model) {
- return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
+ return redirect()->route('hardware.show', $asset)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
- $admin = Auth::user();
+ $admin = auth()->user();
$target = $this->determineCheckoutTarget();
@@ -92,6 +98,7 @@ class AssetCheckoutController extends Controller
$asset->status_id = $request->get('status_id');
}
+
if(!empty($asset->licenseseats->all())){
if(request('checkout_to_type') == 'user') {
foreach ($asset->licenseseats as $seat){
@@ -101,22 +108,26 @@ class AssetCheckoutController extends Controller
}
}
+ // Add any custom fields that should be included in the checkout
+ $asset->customFieldsForCheckinCheckout('display_checkout');
+
$settings = \App\Models\Setting::getSettings();
// We have to check whether $target->company_id is null here since locations don't have a company yet
if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
if ($target->company_id != $asset->company_id){
- return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
+ return redirect()->route('hardware.checkout.create', $asset)->with('error', trans('general.error_user_company'));
}
}
- Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+ 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, $assetId, '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
- return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
+ return redirect()->route("hardware.checkout.create", $asset)->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
} catch (ModelNotFoundException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
} catch (CheckoutNotAllowed $e) {
diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php
index 01ff0288ef..cf119edddc 100644
--- a/app/Http/Controllers/Assets/AssetFilesController.php
+++ b/app/Http/Controllers/Assets/AssetFilesController.php
@@ -7,8 +7,12 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
-use Illuminate\Support\Facades\Response;
+use \Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetFilesController extends Controller
{
@@ -22,11 +26,8 @@ class AssetFilesController extends Controller
*@since [v1.0]
* @author [A. Gianotto] []
*/
- public function store(UploadFileRequest $request, $assetId = null)
+ public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse
{
- if (! $asset = Asset::find($assetId)) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
$this->authorize('update', $asset);
@@ -41,7 +42,7 @@ class AssetFilesController extends Controller
$asset->logUpload($file_name, $request->get('notes'));
}
- return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
+ return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
@@ -54,48 +55,30 @@ class AssetFilesController extends Controller
* @param int $assetId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($assetId = null, $fileId = null)
+ public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
- $asset = Asset::find($assetId);
- // the asset is valid
- if (isset($asset->id)) {
- $this->authorize('view', $asset);
- if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
- return response('No matching record for that asset/file', 500)
- ->header('Content-Type', 'text/plain');
- }
+ $this->authorize('view', $asset);
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
- if (! Storage::exists($file)) {
- return response('File '.$file.' not found on server', 404)
- ->header('Content-Type', 'text/plain');
+ try {
+ return StorageHelper::showOrDownloadFile($file, $log->filename);
+ } catch (\Exception $e) {
+ return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found'));
}
- if (request('inline') == 'true') {
-
- $headers = [
- 'Content-Disposition' => 'inline',
- ];
-
- return Storage::download($file, $log->filename, $headers);
- }
-
- return StorageHelper::downloader($file);
}
- // Prepare the error message
- $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
- // Redirect to the hardware management page
- return redirect()->route('hardware.index')->with('error', $error);
+ return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
+
+
}
/**
@@ -105,33 +88,21 @@ class AssetFilesController extends Controller
* @param int $assetId
* @param int $fileId
* @since [v1.0]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($assetId = null, $fileId = null)
+ public function destroy(Asset $asset, $fileId = null) : RedirectResponse
{
- $asset = Asset::find($assetId);
$this->authorize('update', $asset);
$rel_path = 'private_uploads/assets';
- // the asset is valid
- if (isset($asset->id)) {
- $this->authorize('update', $asset);
- $log = Actionlog::find($fileId);
- if ($log) {
- if (Storage::exists($rel_path.'/'.$log->filename)) {
- Storage::delete($rel_path.'/'.$log->filename);
- }
- $log->delete();
-
- return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
+ if ($log = Actionlog::find($fileId)) {
+ if (Storage::exists($rel_path.'/'.$log->filename)) {
+ Storage::delete($rel_path.'/'.$log->filename);
}
-
- return redirect()->back()
- ->with('success', trans('admin/hardware/message.deletefile.success'));
+ $log->delete();
+ return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
- // Redirect to the hardware management page
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
+ return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
+
}
diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php
index bf3a3ec3a8..03486ffe82 100755
--- a/app/Http/Controllers/Assets/AssetsController.php
+++ b/app/Http/Controllers/Assets/AssetsController.php
@@ -2,9 +2,11 @@
namespace App\Http\Controllers\Assets;
+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;
@@ -16,18 +18,20 @@ use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
-use Illuminate\Support\Facades\Auth;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;
-use Illuminate\Support\Facades\Redirect;
+use Illuminate\Http\Response;
+use Illuminate\Contracts\View\View;
+use Illuminate\Http\RedirectResponse;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use TypeError;
/**
* This class controls all actions related to assets for
@@ -55,10 +59,8 @@ class AssetsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @param Request $request
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index(Request $request)
+ public function index(Request $request) : View
{
$this->authorize('index', Asset::class);
$company = Company::find($request->input('company_id'));
@@ -72,13 +74,12 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param Request $request
- * @return View
* @internal param int $model_id
*/
- public function create(Request $request)
+ public function create(Request $request) : View
{
$this->authorize('create', Asset::class);
- $view = View::make('hardware/edit')
+ $view = view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('item', new Asset)
->with('statuslabel_types', Helper::statusTypeList());
@@ -96,9 +97,8 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize(Asset::class);
@@ -113,8 +113,10 @@ class AssetsController extends Controller
$settings = Setting::getSettings();
- $success = false;
+ $successes = [];
+ $failures = [];
$serials = $request->input('serials');
+ $asset = null;
for ($a = 1; $a <= count($asset_tags); $a++) {
$asset = new Asset();
@@ -134,7 +136,7 @@ class AssetsController extends Controller
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
- $asset->user_id = Auth::id();
+ $asset->created_by = auth()->id();
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
@@ -147,7 +149,7 @@ class AssetsController extends Controller
$asset->byod = request('byod', 0);
if (! empty($settings->audit_interval)) {
- $asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
+ $asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString();
}
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
@@ -167,7 +169,7 @@ class AssetsController extends Controller
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
- if (Gate::allows('admin')) {
+ 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 {
@@ -186,61 +188,85 @@ class AssetsController extends Controller
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {
- if (request('assigned_user')) {
- $target = User::find(request('assigned_user'));
+ $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'));
+ }
$location = $target->location_id;
- } elseif (request('assigned_asset')) {
- $target = Asset::find(request('assigned_asset'));
+
+ } 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'));
+ }
$location = $target->location_id;
- } elseif (request('assigned_location')) {
- $target = Location::find(request('assigned_location'));
+
+ } 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'));
+ }
$location = $target->id;
}
if (isset($target)) {
- $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
+ $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
- $success = true;
-
+ $successes[] = "" . e($asset->asset_tag) . "";
+
+ } else {
+ $failures[] = join(",", $asset->getErrors()->all());
}
}
- if ($success) {
- Log::debug(e($asset->asset_tag));
- return redirect()->route('hardware.index')
- ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
-
-
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
+
+ if ($successes) {
+ if ($failures) {
+ //some succeeded, some failed
+ 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 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 redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
+ ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]));
+ }
+ }
+
}
return redirect()->back()->withInput()->withErrors($asset->getErrors());
}
- public function getOptionCookie(Request $request){
- $value = $request->cookie('optional_info');
- echo $value;
- return $value;
- }
/**
* Returns a view that presents a form to edit an existing asset.
*
* @author [A. Gianotto] []
- * @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function edit($assetId = null)
+ public function edit(Asset $asset) : View | RedirectResponse
{
- if (! $item = Asset::find($assetId)) {
- // Redirect to the asset management page with error
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
- //Handles company checks and permissions.
- $this->authorize($item);
-
- return view('hardware/edit', compact('item'))
+ $this->authorize($asset);
+ return view('hardware/edit')
+ ->with('item', $asset)
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList());
}
@@ -252,17 +278,16 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function show($assetId = null)
+ public function show(Asset $asset) : View | RedirectResponse
{
- $asset = Asset::withTrashed()->find($assetId);
$this->authorize('view', $asset);
$settings = Setting::getSettings();
if (isset($asset)) {
$audit_log = Actionlog::where('action_type', '=', 'audit')
- ->where('item_id', '=', $assetId)
+ ->where('item_id', '=', $asset->id)
->where('item_type', '=', Asset::class)
->orderBy('created_at', 'DESC')->first();
@@ -278,7 +303,7 @@ class AssetsController extends Controller
$qr_code = (object) [
'display' => $settings->qr_code == '1',
- 'url' => route('qr_code/hardware', $asset->id),
+ 'url' => route('qr_code/hardware', $asset),
];
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
@@ -292,17 +317,12 @@ class AssetsController extends Controller
* Validate and process asset edit form.
*
* @param int $assetId
- * @return \Illuminate\Http\RedirectResponse|Redirect
* @since [v1.0]
* @author [A. Gianotto] []
*/
- public function update(ImageUploadRequest $request, $assetId = null)
+ public function update(ImageUploadRequest $request, Asset $asset) : RedirectResponse
{
- // Check if the asset exists
- if (! $asset = Asset::find($assetId)) {
- // Redirect to the asset management page with error
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
+
$this->authorize($asset);
$asset->status_id = $request->input('status_id', null);
@@ -316,7 +336,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 = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
+ $months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true);
if($asset->model->eol) {
if($months != $asset->model->eol > 0) {
$asset->eol_explicit = true;
@@ -332,23 +352,22 @@ class AssetsController extends Controller
}
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
-
- // If the box isn't checked, it's not in the request at all.
- $asset->requestable = $request->filled('requestable');
+ $asset->requestable = $request->input('requestable', 0);
$asset->rtd_location_id = $request->input('rtd_location_id', null);
$asset->byod = $request->input('byod', 0);
- $status = Statuslabel::find($asset->status_id);
+ $status = Statuslabel::find($request->input('status_id'));
- if($status->archived){
+ // This is an archived or undeployable - we should check the asset back in.
+ // Pending is allowed here
+ if (($status) && (($status->getStatuslabelType() != 'pending') && ($status->getStatuslabelType() != 'deployable')) && ($target = $asset->assignedTo)) {
+ $originalValues = $asset->getRawOriginal();
$asset->assigned_to = null;
+ $asset->assigned_type = null;
+ $asset->accepted = null;
+ 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);
@@ -359,46 +378,60 @@ class AssetsController extends Controller
}
// Update the asset data
- $asset_tag = $request->input('asset_tags');
+
$serial = $request->input('serials');
+ $asset->serial = $request->input('serials');
+
+ if (is_array($request->input('serials'))) {
+ $asset->serial = $serial[1];
+ }
+
$asset->name = $request->input('name');
- $asset->serial = $serial[1];
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
- $asset->asset_tag = $asset_tag[1];
+
+ $asset_tags = $request->input('asset_tags');
+ $asset->asset_tag = $request->input('asset_tags');
+
+ if (is_array($request->input('asset_tags'))) {
+ $asset->asset_tag = $asset_tags[1];
+ }
+
$asset->notes = $request->input('notes');
$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 ($field->field_encrypted == '1') {
- if (Gate::allows('admin')) {
- if (is_array($request->input($field->db_column))) {
- $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
- } else {
- $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
+ 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 (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);
+ 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')]);
if ($asset->save()) {
- return redirect()->route('hardware.show', $assetId)
+ return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.update.success'));
}
@@ -411,9 +444,8 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy($assetId)
+ public function destroy(Request $request, $assetId) : RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
@@ -423,9 +455,17 @@ class AssetsController extends Controller
$this->authorize('delete', $asset);
- DB::table('assets')
- ->where('id', $asset->id)
- ->update(['assigned_to' => null]);
+ if ($asset->assignedTo) {
+
+ $target = $asset->assignedTo;
+ $checkin_at = date('Y-m-d H:i:s');
+ $originalValues = $asset->getRawOriginal();
+ 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]);
+ }
+
if ($asset->image) {
try {
@@ -435,6 +475,7 @@ class AssetsController extends Controller
}
}
+
$asset->delete();
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success'));
@@ -445,9 +486,8 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function getAssetBySerial(Request $request)
+ public function getAssetBySerial(Request $request) : RedirectResponse
{
$topsearch = ($request->get('topsearch')=="true");
@@ -465,14 +505,21 @@ class AssetsController extends Controller
* @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/
- public function getAssetByTag(Request $request, $tag=null)
+ public function getAssetByTag(Request $request, $tag=null) : RedirectResponse
{
$tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
- if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
+ // Search for an exact and unique asset tag match
+ $assets = Asset::where('asset_tag', '=', $tag);
+
+ // If not a unique result, redirect to the index view
+ if ($assets->count() != 1) {
+ return redirect()->route('hardware.index')
+ ->with('search', $tag)
+ ->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ]));
}
+ $asset = $assets->first();
$this->authorize('view', $asset);
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
@@ -485,16 +532,15 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return Response
*/
- public function getQrCode($assetId = null)
+ public function getQrCode(Asset $asset) : Response | BinaryFileResponse | string | bool
{
$settings = Setting::getSettings();
- if ($settings->qr_code == '1') {
- $asset = Asset::withTrashed()->find($assetId);
+ if ($settings->label2_2d_type !== 'none') {
+
if ($asset) {
- $size = Helper::barcodeDimensions($settings->barcode_type);
+ $size = Helper::barcodeDimensions($settings->label2_2d_type);
$qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png';
if (isset($asset->id, $asset->asset_tag)) {
@@ -504,7 +550,7 @@ class AssetsController extends Controller
return response()->file($qr_file, $header);
} else {
$barcode = new \Com\Tecnick\Barcode\Barcode();
- $barcode_obj = $barcode->getBarcodeObj($settings->barcode_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', [-2, -2, -2, -2]);
+ $barcode_obj = $barcode->getBarcodeObj($settings->label2_2d_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', [-2, -2, -2, -2]);
file_put_contents($qr_file, $barcode_obj->getPngData());
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
@@ -514,6 +560,7 @@ class AssetsController extends Controller
return 'That asset is invalid';
}
+ return false;
}
/**
@@ -528,7 +575,7 @@ class AssetsController extends Controller
{
$settings = Setting::getSettings();
if ($asset = Asset::withTrashed()->find($assetId)) {
- $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
+ $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->label2_1d_type).'-'.str_slug($asset->asset_tag).'.png';
if (isset($asset->id, $asset->asset_tag)) {
if (file_exists($barcode_file)) {
@@ -541,11 +588,11 @@ class AssetsController extends Controller
$barcode = new \Com\Tecnick\Barcode\Barcode();
try {
- $barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
+ $barcode_obj = $barcode->getBarcodeObj($settings->label2_1d_type, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
file_put_contents($barcode_file, $barcode_obj->getPngData());
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
- } catch (\Exception $e) {
+ } catch (\Exception|TypeError $e) {
Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
@@ -561,7 +608,7 @@ class AssetsController extends Controller
*
* @author [L. Swartzendruber] [
* @param int $assetId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getLabel($assetId = null)
{
@@ -585,28 +632,22 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function getClone($assetId = null)
+ public function getClone(Asset $asset)
{
- // Check if the asset exists
- if (is_null($asset_to_clone = Asset::find($assetId))) {
- // Redirect to the asset management page
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
- }
-
- $this->authorize('create', $asset_to_clone);
-
- $asset = clone $asset_to_clone;
- $asset->id = null;
- $asset->asset_tag = '';
- $asset->serial = '';
- $asset->assigned_to = '';
+ $this->authorize('create', $asset);
+ $cloned = clone $asset;
+ $cloned->id = null;
+ $cloned->asset_tag = '';
+ $cloned->serial = '';
+ $cloned->assigned_to = '';
+ $cloned->deleted_at = '';
return view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList())
- ->with('item', $asset);
+ ->with('item', $cloned);
}
/**
@@ -614,7 +655,7 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getImportHistory()
{
@@ -636,7 +677,7 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.3]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function postImportHistory(Request $request)
{
@@ -730,8 +771,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([
'item_id' => $asset->id,
'item_type' => Asset::class,
- 'user_id' => Auth::user()->id,
- 'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer',
+ 'created_by' => auth()->id(),
+ 'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer',
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
'target_type' => User::class,
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
@@ -758,8 +799,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
'item_type' => Asset::class,
- 'user_id' => Auth::user()->id,
- 'note' => 'Checkin imported by '.Auth::user()->present()->fullName().' from history importer',
+ 'created_by' => auth()->id(),
+ 'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer',
'target_id' => null,
'created_at' => $checkin_date,
'action_type' => 'checkin',
@@ -796,7 +837,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] []
* @param int $assetId
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function getRestore($assetId = null)
{
@@ -826,8 +867,8 @@ class AssetsController extends Controller
public function quickScan()
{
$this->authorize('audit', Asset::class);
- $dt = Carbon::now()->addMonths(12)->toDateString();
-
+ $settings = Setting::getSettings();
+ $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
return view('hardware/quickscan')->with('next_audit_date', $dt);
}
@@ -835,18 +876,9 @@ class AssetsController extends Controller
{
$this->authorize('checkin', Asset::class);
- return view('hardware/quickscan-checkin');
+ return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
- public function audit($id)
- {
- $settings = Setting::getSettings();
- $this->authorize('audit', Asset::class);
- $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
- $asset = Asset::findOrFail($id);
-
- return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
- }
public function dueForAudit()
{
@@ -863,22 +895,73 @@ class AssetsController extends Controller
}
- public function auditStore(UploadFileRequest $request, $id)
+ public function audit(Asset $asset): View | RedirectResponse
{
$this->authorize('audit', Asset::class);
+ $settings = Setting::getSettings();
- $rules = [
- 'location_id' => 'exists:locations,id|nullable|numeric',
- 'next_audit_date' => 'date|nullable',
- ];
- $validator = Validator::make($request->all(), $rules);
+ // Invoke the validation to see if the audit will complete successfully
+ $asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
- if ($validator->fails()) {
- return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
+ if ($asset->isInvalid()) {
+ return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
}
- $asset = Asset::findOrFail($id);
+ $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');
+
+
+ $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());
+ }
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
@@ -894,18 +977,11 @@ 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.
@@ -919,8 +995,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);
- return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
+ $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
+ return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.audit.success'));
}
return redirect()->back()->withInput()->withErrors($asset->getErrors());
diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php
index ffbf81944b..5ef1539392 100644
--- a/app/Http/Controllers/Assets/BulkAssetsController.php
+++ b/app/Http/Controllers/Assets/BulkAssetsController.php
@@ -2,7 +2,6 @@
namespace App\Http\Controllers\Assets;
-use App\Models\Actionlog;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
@@ -11,15 +10,17 @@ use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\Setting;
use App\View\Label;
+use Carbon\Carbon;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField;
+use Illuminate\Contracts\View\View;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
class BulkAssetsController extends Controller
{
@@ -36,12 +37,10 @@ class BulkAssetsController extends Controller
* action would make a lot more sense here and make things a lot more clear.
*
* @author [A. Gianotto] []
- * @return View
* @internal param int $assetId
* @since [v2.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit(Request $request)
+ public function edit(Request $request) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
@@ -54,6 +53,25 @@ 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]);
@@ -94,7 +112,9 @@ 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';
- $assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
+ $assets = Asset::with('assignedTo', 'location', 'model')
+ ->whereIn('assets.id', $asset_ids)
+ ->withTrashed();
$assets = $assets->get();
@@ -194,7 +214,7 @@ class BulkAssetsController extends Controller
* @internal param array $assets
* @since [v2.0]
*/
- public function update(Request $request)
+ public function update(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
$has_errors = 0;
@@ -209,6 +229,21 @@ 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'));
@@ -227,7 +262,8 @@ class BulkAssetsController extends Controller
* its checkout status.
*/
- if (($request->filled('purchase_date'))
+ if (($request->filled('name'))
+ || ($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
|| ($request->filled('supplier_id'))
@@ -239,10 +275,14 @@ class BulkAssetsController extends Controller
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('next_audit_date'))
+ || ($request->filled('asset_eol_date'))
+ || ($request->filled('null_name'))
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
+ || ($request->filled('null_asset_eol_date'))
|| ($request->anyFilled($custom_field_columns))
+ || ($request->anyFilled(array_keys($null_custom_fields_inputs)))
) {
// Let's loop through those assets and build an update array
@@ -251,28 +291,58 @@ class BulkAssetsController extends Controller
$this->update_array = [];
/**
- * Leave out model_id and status here because we do math on that later. We have to do some extra
- * validation and checks on those two.
+ * Leave out model_id and status here because we do math on that later. We have to do some
+ * extra validation and checks on those two.
*
* It's tempting to make these match the request check above, but some of these values require
* extra work to make sure the data makes sense.
*/
- $this->conditionallyAddItem('purchase_date')
+ $this->conditionallyAddItem('name')
+ ->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
- ->conditionallyAddItem('next_audit_date');
+ ->conditionallyAddItem('next_audit_date')
+ ->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')) {
+ $model = AssetModel::find($request->input('model_id'));
+ if ($model->eol > 0) {
+ if ($request->filled('purchase_date')) {
+ $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($model->eol)->format('Y-m-d');
+ } else {
+ $this->update_array['asset_eol_date'] = Carbon::parse($asset->purchase_date)->addMonths($model->eol)->format('Y-m-d');
+ }
+ } else {
+ $this->update_array['asset_eol_date'] = null;
+ }
+ } elseif (($request->filled('purchase_date')) && ($asset->model->eol > 0)) {
+ $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
+ }
+ }
+
/**
* Blank out fields that were requested to be blanked out via checkbox
*/
+ if ($request->input('null_name')=='1') {
+
+ $this->update_array['name'] = null;
+ }
+
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
+ if (!($asset->eol_explicit)) {
+ $this->update_array['asset_eol_date'] = null;
+ }
}
if ($request->input('null_expected_checkin_date')=='1') {
@@ -283,6 +353,17 @@ class BulkAssetsController extends Controller
$this->update_array['next_audit_date'] = null;
}
+ if ($request->input('null_asset_eol_date')=='1') {
+ $this->update_array['asset_eol_date'] = null;
+
+ // If they are nulling the EOL date to allow it to calculate, set eol explicit to 0
+ if ($request->input('calc_eol')=='1') {
+ $this->update_array['eol_explicit'] = 0;
+ }
+ }
+
+
+
if ($request->filled('purchase_cost')) {
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
}
@@ -311,16 +392,22 @@ class BulkAssetsController extends Controller
* to someone/something.
*/
if ($request->filled('status_id')) {
- $updated_status = Statuslabel::find($request->input('status_id'));
+ try {
+ $updated_status = Statuslabel::findOrFail($request->input('status_id'));
+ } catch (ModelNotFoundException $e) {
+ return redirect($bulk_back_url)->with('error', trans('admin/statuslabels/message.does_not_exist'));
+ }
// We cannot assign a non-deployable status type if the asset is already assigned.
// 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.
- if (
- ($asset->assigned_to == '')
- || ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
- ) {
+
+ $unassigned = $asset->assigned_to == '';
+ $deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1';
+ $pending = $updated_status->pending === 1;
+
+ if ($unassigned || $deployable || $pending) {
$this->update_array['status_id'] = $updated_status->id;
}
@@ -372,6 +459,7 @@ class BulkAssetsController extends Controller
}
/**
+ *
* Start all the custom fields shenanigans
*/
@@ -379,6 +467,15 @@ 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});
@@ -455,9 +552,8 @@ class BulkAssetsController extends Controller
/**
* Adds parameter to update array for an item if it exists in request
* @param string $field field name
- * @return BulkAssetsController Model for Chaining
*/
- protected function conditionallyAddItem($field)
+ protected function conditionallyAddItem($field) : BulkAssetsController
{
if (request()->filled($field)) {
$this->update_array[$field] = request()->input($field);
@@ -471,61 +567,62 @@ class BulkAssetsController extends Controller
*
* @author [A. Gianotto] []
* @param Request $request
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param array $assets
* @since [v2.0]
*/
- public function destroy(Request $request)
+ public function destroy(Request $request) : RedirectResponse
{
$this->authorize('delete', Asset::class);
$bulk_back_url = route('hardware.index');
+
if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
+ $assetIds = $request->get('ids');
- if ($request->filled('ids')) {
- $assets = Asset::find($request->get('ids'));
- foreach ($assets as $asset) {
- $update_array['deleted_at'] = date('Y-m-d H:i:s');
- $update_array['assigned_to'] = null;
-
- DB::table('assets')
- ->where('id', $asset->id)
- ->update($update_array);
- } // endforeach
-
- return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
- // no values given, nothing to update
+ if(empty($assetIds)) {
+ return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
}
- return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
+ $assignedAssets = Asset::whereIn('id', $assetIds)->whereNotNull('assigned_to')->get();
+ if($assignedAssets->isNotEmpty()) {
+
+ //if assets are checked out, return a list of asset tags that would need to be checked in first.
+ $assetTags = $assignedAssets->pluck('asset_tag')->implode(', ');
+ return redirect($bulk_back_url)->with('error', trans_choice('admin/hardware/message.delete.assigned_to_error', $assignedAssets->count(), ['asset_tag' => $assetTags] ));
+ }
+
+ foreach (Asset::wherein('id', $assetIds)->get() as $asset) {
+ $asset->delete();
+ }
+
+ return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
+ // no values given, nothing to update
+
}
/**
* Show Bulk Checkout Page
- * @return View View to checkout multiple assets
*/
- public function showCheckout()
+ public function showCheckout() : View
{
$this->authorize('checkout', Asset::class);
- // Filter out assets that are not deployable.
- return view('hardware/bulk-checkout');
+ $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);
}
/**
* Process Multiple Checkout Request
- * @return View
*/
- public function storeCheckout(AssetCheckoutRequest $request)
+ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
-
$this->authorize('checkout', Asset::class);
try {
- $admin = Auth::user();
+ $admin = auth()->user();
$target = $this->determineCheckoutTarget();
@@ -535,6 +632,8 @@ 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) {
@@ -544,57 +643,87 @@ 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 = e($request->get('checkout_at'));
+ $checkout_at = $request->get('checkout_at');
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
- $expected_checkin = e($request->get('expected_checkin'));
+ $expected_checkin = $request->get('expected_checkin');
}
$errors = [];
- DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) {
- foreach ($asset_ids as $asset_id) {
- $asset = Asset::findOrFail($asset_id);
+ DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
+ foreach ($assets as $asset) {
$this->authorize('checkout', $asset);
- $error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
-
- if ($target->location_id != '') {
- $asset->location_id = $target->location_id;
- $asset->unsetEventDispatcher();
- $asset->save();
+ // See if there is a status label passed
+ if ($request->filled('status_id')) {
+ $asset->status_id = $request->get('status_id');
}
- if ($error) {
- array_merge_recursive($errors, $asset->getErrors()->toArray());
+ $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?
+ if ($target->location_id != '') {
+ $asset->location_id = $target->location_id;
+ // TODO - I don't know why this is being saved without events
+ $asset::withoutEvents(function () use ($asset) {
+ $asset->save();
+ });
+ }
+
+ if (!$checkout_success) {
+ $errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
}
}
});
if (! $errors) {
// Redirect to the new asset page
- return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success'));
+ return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
}
// Redirect to the asset management page with error
- return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
+ 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')->with('error', $e->getErrors());
+ return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
}
}
- public function restore(Request $request) {
+ public function restore(Request $request) : RedirectResponse
+ {
$this->authorize('update', Asset::class);
- $assetIds = $request->get('ids');
- if (empty($assetIds)) {
- return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
+ $assetIds = $request->get('ids');
+
+ if (empty($assetIds)) {
+ return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
} else {
foreach ($assetIds as $key => $assetId) {
- $asset = Asset::withTrashed()->find($assetId);
- $asset->restore();
+ $asset = Asset::withTrashed()->find($assetId);
+ $asset->restore();
}
- return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
+ 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;
+ }
}
diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php
index a7322e7e59..f385c054c6 100644
--- a/app/Http/Controllers/Auth/ForgotPasswordController.php
+++ b/app/Http/Controllers/Auth/ForgotPasswordController.php
@@ -50,14 +50,14 @@ class ForgotPasswordController extends Controller
*/
public function sendResetLinkEmail(Request $request)
{
-
/**
* Let's set a max character count here to prevent potential
* buffer overflow issues with attackers sending very large
- * payloads through.
+ * payloads through. The addition of the string rule prevents attackers
+ * sending arrays through and causing 500s
*/
$request->validate([
- 'username' => ['required', 'max:255'],
+ 'username' => ['required', 'max:255', 'string'],
]);
/**
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index ac19a57a72..0b87865455 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -127,7 +127,7 @@ class LoginController extends Controller
$saml->clearData();
}
- if ($user = Auth::user()) {
+ if ($user = auth()->user()) {
$user->last_login = \Carbon::now();
$user->saveQuietly();
}
@@ -206,6 +206,7 @@ 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.
@@ -226,7 +227,7 @@ class LoginController extends Controller
$strip_prefixes = [
// IIS/AD
- // https://github.com/snipe/snipe-it/pull/5862
+ // https://github.com/grokability/snipe-it/pull/5862
'\\',
// Google Cloud IAP
@@ -283,8 +284,11 @@ class LoginController extends Controller
return redirect()->back()->withInput()->withErrors($validator);
}
- $this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
- $this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
+ // 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);
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
@@ -326,7 +330,7 @@ class LoginController extends Controller
}
}
- if ($user = Auth::user()) {
+ if ($user = auth()->user()) {
$user->last_login = \Carbon::now();
$user->activated = 1;
$user->saveQuietly();
@@ -350,11 +354,11 @@ class LoginController extends Controller
}
$settings = Setting::getSettings();
- $user = Auth::user();
+ $user = auth()->user();
// 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 check anyway in case there's a browser history or back button thing.
+ // but let's 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.
@@ -398,7 +402,7 @@ class LoginController extends Controller
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
- $user = Auth::user();
+ $user = auth()->user();
// Check whether there is a device enrolled.
// This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware
@@ -427,11 +431,12 @@ class LoginController extends Controller
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
}
- $user = Auth::user();
+ $user = auth()->user();
$secret = $request->input('two_factor_secret');
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);
@@ -479,6 +484,7 @@ 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());
@@ -508,8 +514,8 @@ class LoginController extends Controller
protected function validator(array $data)
{
return Validator::make($data, [
- 'username' => 'required',
- 'password' => 'required',
+ 'username' => 'required|not_array',
+ 'password' => 'required|not_array',
]);
}
@@ -519,45 +525,6 @@ 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()
{
diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php
index fe1b331289..805b829180 100644
--- a/app/Http/Controllers/Auth/ResetPasswordController.php
+++ b/app/Http/Controllers/Auth/ResetPasswordController.php
@@ -87,7 +87,7 @@ class ResetPasswordController extends Controller
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
];
- $request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
+ $request->validate($this->rules());
Log::debug('Checking if '.$request->input('username').' exists');
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing
@@ -103,22 +103,24 @@ class ResetPasswordController extends Controller
], $messages);
}
+ if ($user->ldap_import != '1') {
- // set the response
- $response = $broker->reset(
- $this->credentials($request), function ($user, $password) {
- $this->resetPassword($user, $password);
- });
+ // set the response
+ $response = $broker->reset(
+ $this->credentials($request), function ($user, $password) {
+ $this->resetPassword($user, $password);
+ });
- // Check if the password reset above actually worked
- if ($response == \Password::PASSWORD_RESET) {
- Log::debug('Password reset for '.$user->username.' worked');
- return redirect()->guest('login')->with('success', trans('passwords.reset'));
+ // Check if the password reset above actually worked
+ if ($response == \Password::PASSWORD_RESET) {
+ Log::debug('Password reset for ' . $user->username . ' worked');
+ return redirect()->guest('login')->with('success', trans('passwords.reset'));
+ }
+
+ Log::debug('Password reset for ' . $user->username . ' FAILED - this user exists but the token is not valid');
+ return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
}
- Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
- return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
-
}
diff --git a/app/Http/Controllers/Auth/SamlController.php b/app/Http/Controllers/Auth/SamlController.php
index 769f903494..6a4c1f65b0 100644
--- a/app/Http/Controllers/Auth/SamlController.php
+++ b/app/Http/Controllers/Auth/SamlController.php
@@ -99,12 +99,18 @@ class SamlController extends Controller
{
$saml = $this->saml;
$auth = $saml->getAuth();
- $auth->processResponse();
+ $saml_exception = false;
+ try {
+ $auth->processResponse();
+ } catch (\Exception $e) {
+ Log::warning("Exception caught in SAML login: " . $e->getMessage());
+ $saml_exception = true;
+ }
$errors = $auth->getErrors();
- if (! empty($errors)) {
- Log::error('There was an error with SAML ACS: '.implode(', ', $errors));
- Log::error('Reason: '.$auth->getLastErrorReason());
+ if (!empty($errors) || $saml_exception) {
+ Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors));
+ Log::warning('Reason: ' . $auth->getLastErrorReason());
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
}
@@ -132,12 +138,18 @@ class SamlController extends Controller
{
$auth = $this->saml->getAuth();
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
- $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
+ $saml_exception = false;
+ try {
+ $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
+ } catch (\Exception $e) {
+ Log::warning("Exception caught in SAML single-logout: " . $e->getMessage());
+ $saml_exception = true;
+ }
$errors = $auth->getErrors();
- if (! empty($errors)) {
- Log::error('There was an error with SAML SLS: '.implode(', ', $errors));
- Log::error('Reason: '.$auth->getLastErrorReason());
+ if (!empty($errors) || $saml_exception) {
+ Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors));
+ Log::warning('Reason: ' . $auth->getLastErrorReason());
return view('errors.403');
}
diff --git a/app/Http/Controllers/BulkAssetModelsController.php b/app/Http/Controllers/BulkAssetModelsController.php
index 5437791aa8..5f64ea0838 100644
--- a/app/Http/Controllers/BulkAssetModelsController.php
+++ b/app/Http/Controllers/BulkAssetModelsController.php
@@ -5,8 +5,8 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Input;
-use Illuminate\Support\Facades\Redirect;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
class BulkAssetModelsController extends Controller
{
@@ -16,9 +16,8 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.7]
* @param Request $request
- * @return \Illuminate\Contracts\View\View
*/
- public function edit(Request $request)
+ public function edit(Request $request) : View | RedirectResponse
{
$models_raw_array = $request->input('ids');
@@ -61,9 +60,8 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] []
* @since [v1.7]
* @param Request $request
- * @return \Illuminate\Contracts\View\View
*/
- public function update(Request $request)
+ public function update(Request $request): View | RedirectResponse
{
$this->authorize('update', AssetModel::class);
@@ -73,20 +71,28 @@ 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->filled('requestable') != '') {
+ if ($request->input('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);
@@ -105,9 +111,8 @@ class BulkAssetModelsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function destroy(Request $request)
+ public function destroy(Request $request) : RedirectResponse
{
$this->authorize('delete', AssetModel::class);
diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php
index d7e83b43d8..692e2745ac 100755
--- a/app/Http/Controllers/CategoriesController.php
+++ b/app/Http/Controllers/CategoriesController.php
@@ -4,10 +4,11 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
-use App\Models\Category as Category;
+use App\Models\Category;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
-use Str;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This class controls all actions related to Categories for
@@ -25,10 +26,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
// Show the page
$this->authorize('view', Category::class);
@@ -42,10 +41,8 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::store() method that stores the data
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
// Show the page
$this->authorize('create', Category::class);
@@ -61,10 +58,8 @@ class CategoriesController extends Controller
* @see CategoriesController::create() method that makes the form.
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Category::class);
$category = new Category();
@@ -74,7 +69,8 @@ class CategoriesController extends Controller
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->checkin_email = $request->input('checkin_email', '0');
- $category->user_id = Auth::id();
+ $category->notes = $request->input('notes');
+ $category->created_by = auth()->id();
$category = $request->handleImages($category);
if ($category->save()) {
@@ -91,17 +87,11 @@ class CategoriesController extends Controller
* @see CategoriesController::postEdit() method saves the data
* @param int $categoryId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($categoryId = null)
+ public function edit(Category $category) : RedirectResponse | View
{
$this->authorize('update', Category::class);
- if (is_null($item = Category::find($categoryId))) {
- return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
- }
-
- return view('categories/edit', compact('item'))
+ return view('categories/edit')->with('item', $category)
->with('category_types', Helper::categoryTypeList());
}
@@ -112,27 +102,27 @@ class CategoriesController extends Controller
* @see CategoriesController::getEdit() method that makes the form.
* @param ImageUploadRequest $request
* @param int $categoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $categoryId = null)
+ public function update(ImageUploadRequest $request, Category $category) : RedirectResponse
{
$this->authorize('update', Category::class);
- if (is_null($category = Category::find($categoryId))) {
- // Redirect to the categories management page
- return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist'));
- }
-
- // Update the category data
$category->name = $request->input('name');
- // If the item count is > 0, we disable the category type in the edit. Disabled items
- // don't POST, so if the category_type is blank we just set it to the default.
+
+ // Don't allow the user to change the category_type once it's been created
+ if (($request->filled('category_type') && ($category->itemCount() > 0))) {
+ $request->validate(['category_type' => 'in:'.$category->category_type]);
+ }
+
$category->category_type = $request->input('category_type', $category->category_type);
+
+ $category->fill($request->all());
+
$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->checkin_email = $request->input('checkin_email', '0');
+ $category->notes = $request->input('notes');
$category = $request->handleImages($category);
@@ -150,14 +140,12 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @since [v1.0]
* @param int $categoryId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($categoryId)
+ public function destroy($categoryId) : RedirectResponse
{
$this->authorize('delete', Category::class);
// Check if the category exists
- if (is_null($category = Category::findOrFail($categoryId))) {
+ 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))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
@@ -167,7 +155,6 @@ 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'));
}
@@ -178,14 +165,12 @@ class CategoriesController extends Controller
* @author [A. Gianotto] []
* @see CategoriesController::getDataView() method that generates the JSON response
* @param $id
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
- public function show($id)
+ public function show(Category $category) : View | RedirectResponse
{
$this->authorize('view', Category::class);
- if ($category = Category::find($id)) {
+
if ($category->category_type == 'asset') {
$category_type = 'hardware';
$category_type_route = 'assets';
@@ -200,8 +185,5 @@ class CategoriesController extends Controller
return view('categories/view', compact('category'))
->with('category_type', $category_type)
->with('category_type_route', $category_type_route);
- }
-
- return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
}
diff --git a/app/Http/Controllers/CheckInOutRequest.php b/app/Http/Controllers/CheckInOutRequest.php
index 6dd7e4aba3..6ab327fff8 100644
--- a/app/Http/Controllers/CheckInOutRequest.php
+++ b/app/Http/Controllers/CheckInOutRequest.php
@@ -11,9 +11,8 @@ trait CheckInOutRequest
{
/**
* Find target for checkout
- * @return SnipeModel Target asset is being checked out to.
*/
- protected function determineCheckoutTarget()
+ protected function determineCheckoutTarget() : ?SnipeModel
{
// This item is checked out to a location
switch (request('checkout_to_type')) {
@@ -21,7 +20,7 @@ trait CheckInOutRequest
return Location::findOrFail(request('assigned_location'));
case 'asset':
return Asset::findOrFail(request('assigned_asset'));
- case 'user':
+ default:
return User::findOrFail(request('assigned_user'));
}
@@ -34,7 +33,7 @@ trait CheckInOutRequest
* @param SnipeModel $target Target with location
* @return Asset Asset being updated
*/
- protected function updateAssetLocation($asset, $target)
+ protected function updateAssetLocation($asset, $target) : Asset
{
switch (request('checkout_to_type')) {
case 'location':
diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php
index 63205a598d..96a80e87e6 100644
--- a/app/Http/Controllers/CompaniesController.php
+++ b/app/Http/Controllers/CompaniesController.php
@@ -7,6 +7,8 @@ use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Companies for
@@ -21,10 +23,8 @@ final class CompaniesController extends Controller
*
* @author [Abdullah Alansari] []
* @since [v1.8]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', Company::class);
@@ -36,10 +36,8 @@ final class CompaniesController extends Controller
*
* @author [Abdullah Alansari] []
* @since [v1.8]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Company::class);
@@ -52,10 +50,8 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param Request $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Company::class);
@@ -64,6 +60,8 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
+ $company->notes = $request->input('notes');
+ $company->created_by = auth()->id();
$company = $request->handleImages($company);
@@ -81,19 +79,11 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param int $companyId
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($companyId)
+ public function edit(Company $company) : View | RedirectResponse
{
- if (is_null($item = Company::find($companyId))) {
- return redirect()->route('companies.index')
- ->with('error', trans('admin/companies/message.does_not_exist'));
- }
-
- $this->authorize('update', $item);
-
- return view('companies/edit')->with('item', $item);
+ $this->authorize('update', $company);
+ return view('companies/edit')->with('item', $company);
}
/**
@@ -103,21 +93,16 @@ final class CompaniesController extends Controller
* @since [v1.8]
* @param ImageUploadRequest $request
* @param int $companyId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $companyId)
+ public function update(ImageUploadRequest $request, Company $company) : RedirectResponse
{
- if (is_null($company = Company::find($companyId))) {
- return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist'));
- }
$this->authorize('update', $company);
-
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
+ $company->notes = $request->input('notes');
$company = $request->handleImages($company);
@@ -135,10 +120,8 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] []
* @since [v1.8]
* @param int $companyId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($companyId)
+ public function destroy($companyId) : RedirectResponse
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')
@@ -165,15 +148,9 @@ final class CompaniesController extends Controller
->with('success', trans('admin/companies/message.delete.success'));
}
- public function show($id)
+ public function show(Company $company) : View | RedirectResponse
{
$this->authorize('view', Company::class);
-
- if (is_null($company = Company::find($id))) {
- return redirect()->route('companies.index')
- ->with('error', trans('admin/companies/message.not_found'));
- }
-
return view('companies/view')->with('company', $company);
}
}
diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php
index 9f4724e353..379882c3c5 100644
--- a/app/Http/Controllers/Components/ComponentCheckinController.php
+++ b/app/Http/Controllers/Components/ComponentCheckinController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
@@ -95,13 +96,11 @@ class ComponentCheckinController extends Controller
$asset = Asset::find($component_assets->asset_id);
- event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
- if ($backto == 'asset'){
- return redirect()->route('hardware.show', $asset->id)->with('success',
- trans('admin/components/message.checkin.success'));
- }
+ event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
- return redirect()->route('components.index')->with('success',
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
trans('admin/components/message.checkin.success'));
}
diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php
index 79ff57e7df..b40d592369 100644
--- a/app/Http/Controllers/Components/ComponentCheckoutController.php
+++ b/app/Http/Controllers/Components/ComponentCheckoutController.php
@@ -4,9 +4,11 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
+use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@@ -93,22 +95,31 @@ class ComponentCheckoutController extends Controller
->withInput();
}
- // Check if the user exists
+ // Check if the asset exists
$asset = Asset::find($request->input('asset_id'));
+ if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
+ return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
+ }
+
// Update the component data
$component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
- 'user_id' => Auth::user(),
+ 'created_by' => auth()->user()->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'),
]);
- event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
+ event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note')));
- return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
+ $request->request->add(['checkout_to_type' => 'asset']);
+ $request->request->add(['assigned_asset' => $asset->id]);
+
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
+ return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
}
}
diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php
index 33ebde6456..74594d312b 100644
--- a/app/Http/Controllers/Components/ComponentsController.php
+++ b/app/Http/Controllers/Components/ComponentsController.php
@@ -73,6 +73,8 @@ class ComponentsController extends Controller
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
+ $component->manufacturer_id = $request->input('manufacturer_id');
+ $component->model_number = $request->input('model_number');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
@@ -81,13 +83,15 @@ class ComponentsController extends Controller
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty');
- $component->user_id = Auth::id();
+ $component->created_by = auth()->id();
$component->notes = $request->input('notes');
$component = $request->handleImages($component);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($component->save()) {
- return redirect()->route('components.index')->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());
@@ -103,15 +107,13 @@ class ComponentsController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($componentId = null)
+ public function edit(Component $component)
{
- if ($item = Component::find($componentId)) {
- $this->authorize('update', $item);
- return view('components/edit', compact('item'))->with('category_type', 'component');
- }
-
- return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
+ $this->authorize('update', $component);
+ return view('components/edit')
+ ->with('item', $component)
+ ->with('category_type', 'component');
}
@@ -126,11 +128,8 @@ class ComponentsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v3.0]
*/
- public function update(ImageUploadRequest $request, $componentId = null)
+ public function update(ImageUploadRequest $request, Component $component)
{
- if (is_null($component = Component::find($componentId))) {
- return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
- }
$min = $component->numCheckedOut();
$validator = Validator::make($request->all(), [
'qty' => "required|numeric|min:$min",
@@ -148,6 +147,8 @@ class ComponentsController extends Controller
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
+ $component->manufacturer_id = $request->input('manufacturer_id');
+ $component->model_number = $request->input('model_number');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number');
@@ -160,8 +161,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($component->save()) {
- return redirect()->route('components.index')->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());
@@ -185,7 +188,7 @@ class ComponentsController extends Controller
$this->authorize('delete', $component);
// Remove the image if one exists
- if (Storage::disk('public')->exists('components/'.$component->image)) {
+ if ($component->image && Storage::disk('public')->exists('components/' . $component->image)) {
try {
Storage::disk('public')->delete('components/'.$component->image);
} catch (\Exception $e) {
@@ -193,6 +196,10 @@ class ComponentsController extends Controller
}
}
+ if ($component->numCheckedOut() > 0) {
+ return redirect()->route('components.index')->with('error', trans('admin/components/message.delete.error_qty'));
+ }
+
$component->delete();
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
@@ -208,17 +215,9 @@ class ComponentsController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($componentId = null)
+ public function show(Component $component)
{
- $component = Component::find($componentId);
-
- if (isset($component->id)) {
$this->authorize('view', $component);
-
return view('components/view', compact('component'));
- }
- // Redirect to the user management page
- return redirect()->route('components.index')
- ->with('error', trans('admin/components/message.does_not_exist'));
}
}
diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php
index a7d42bb072..b5e30aa694 100644
--- a/app/Http/Controllers/Components/ComponentsFilesController.php
+++ b/app/Http/Controllers/Components/ComponentsFilesController.php
@@ -50,7 +50,7 @@ class ComponentsFilesController extends Controller
}
- return redirect()->route('components.show', $component->id)->with('success', trans('general.file_upload_success'));
+ return redirect()->route('components.show', $component->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
@@ -91,7 +91,7 @@ class ComponentsFilesController extends Controller
$log->delete();
- return redirect()->back()
+ return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
@@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller
public function show($componentId = null, $fileId = null)
{
Log::debug('Private filesystem is: '.config('filesystems.default'));
- $component = Component::find($componentId);
+
// the component is valid
- if (isset($component->id)) {
+ if ($component = Component::find($componentId)) {
$this->authorize('view', $component);
$this->authorize('components.files', $component);
- if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
- return response('No matching record for that asset/file', 500)
- ->header('Content-Type', 'text/plain');
- }
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
- $file = 'private_uploads/components/'.$log->filename;
+ $file = 'private_uploads/components/'.$log->filename;
- if (Storage::missing($file)) {
- Log::debug('FILE DOES NOT EXISTS for '.$file);
- Log::debug('URL should be '.Storage::url($file));
-
- return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
- ->header('Content-Type', 'text/plain');
- } else {
-
- // Display the file inline
- if (request('inline') == 'true') {
- $headers = [
- 'Content-Disposition' => 'inline',
- ];
- return Storage::download($file, $log->filename, $headers);
+ try {
+ return StorageHelper::showOrDownloadFile($file, $log->filename);
+ } catch (\Exception $e) {
+ return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
}
-
- if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
- return StorageHelper::downloader($file);
- }
}
+ return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found'));
+
}
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
index d838ee5696..e08da41229 100644
--- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
+++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php
@@ -3,13 +3,13 @@
namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
-use App\Models\Accessory;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
+use \Illuminate\Contracts\View\View;
+use \Illuminate\Http\RedirectResponse;
class ConsumableCheckoutController extends Controller
{
@@ -20,13 +20,11 @@ class ConsumableCheckoutController extends Controller
* @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0]
* @param int $id
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($id)
+ public function create($id) : View | RedirectResponse
{
- if ($consumable = Consumable::with('users')->find($id)) {
+ if ($consumable = Consumable::find($id)) {
$this->authorize('checkout', $consumable);
@@ -36,7 +34,7 @@ class ConsumableCheckoutController extends Controller
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index')
- ->with('error', trans('admin/consumables/message.checkout.unavailable'));
+ ->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
}
// Return the checkout view
@@ -72,17 +70,17 @@ class ConsumableCheckoutController extends Controller
$this->authorize('checkout', $consumable);
// If the quantity is not present in the request or is not a positive integer, set it to 1
- $quantity = $request->input('qty');
+ $quantity = $request->input('checkout_qty');
if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
$quantity = 1;
}
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) {
- return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
+ return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ]));
}
- $admin_user = Auth::user();
+ $admin_user = auth()->user();
$assigned_to = e($request->input('assigned_to'));
// Check if the user exists
@@ -94,17 +92,25 @@ class ConsumableCheckoutController extends Controller
// Update the consumable data
$consumable->assigned_to = e($request->input('assigned_to'));
- for($i = 0; $i < $quantity; $i++){
+ for ($i = 0; $i < $quantity; $i++){
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
- 'user_id' => $admin_user->id,
+ 'created_by' => $admin_user->id,
'assigned_to' => e($request->input('assigned_to')),
'note' => $request->input('note'),
]);
}
- event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
+
+ $consumable->checkout_qty = $quantity;
+ event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
+
+ $request->request->add(['checkout_to_type' => 'user']);
+ $request->request->add(['assigned_user' => $user->id]);
+
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
+
// Redirect to the new consumable page
- return redirect()->route('consumables.index')->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'));
}
}
diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php
index b33e6e07a9..c96c2db975 100644
--- a/app/Http/Controllers/Consumables/ConsumablesController.php
+++ b/app/Http/Controllers/Consumables/ConsumablesController.php
@@ -8,8 +8,10 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Consumable;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
+use App\Http\Requests\StoreConsumableRequest;
/**
* This controller handles all actions related to Consumables for
@@ -48,7 +50,7 @@ class ConsumablesController extends Controller
{
$this->authorize('create', Consumable::class);
- return view('consumables/edit')->with('category_type', 'consumable')
+ return view('consumables.edit')->with('category_type', 'consumable')
->with('item', new Consumable);
}
@@ -62,7 +64,7 @@ class ConsumablesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(StoreConsumableRequest $request)
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable();
@@ -79,14 +81,16 @@ class ConsumablesController extends Controller
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = $request->input('qty');
- $consumable->user_id = Auth::id();
+ $consumable->created_by = auth()->id();
$consumable->notes = $request->input('notes');
$consumable = $request->handleImages($consumable);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($consumable->save()) {
- return redirect()->route('consumables.index')->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());
@@ -99,18 +103,14 @@ class ConsumablesController extends Controller
* @param int $consumableId
* @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($consumableId = null)
+ public function edit(Consumable $consumable) : View | RedirectResponse
{
- if ($item = Consumable::find($consumableId)) {
- $this->authorize($item);
+ $this->authorize($consumable);
+ return view('consumables/edit')
+ ->with('item', $consumable)
+ ->with('category_type', 'consumable');
- return view('consumables/edit', compact('item'))->with('category_type', 'consumable');
- }
-
- return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
/**
@@ -124,11 +124,8 @@ class ConsumablesController extends Controller
* @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $consumableId = null)
+ public function update(StoreConsumableRequest $request, Consumable $consumable)
{
- if (is_null($consumable = Consumable::find($consumableId))) {
- return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
- }
$min = $consumable->numCheckedOut();
$validator = Validator::make($request->all(), [
@@ -160,8 +157,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($consumable->save()) {
- return redirect()->route('consumables.index')->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());
@@ -182,6 +181,7 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize($consumable);
+
$consumable->delete();
// Redirect to the locations management page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success'));
@@ -197,15 +197,22 @@ class ConsumablesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($consumableId = null)
+ public function show(Consumable $consumable)
{
- $consumable = Consumable::find($consumableId);
+ $consumable = Consumable::withCount('users as users_consumables')->find($consumable->id);
$this->authorize($consumable);
- if (isset($consumable->id)) {
- return view('consumables/view', compact('consumable'));
- }
+ return view('consumables/view', compact('consumable'));
+ }
- return redirect()->route('consumables.index')
- ->with('error', trans('admin/consumables/message.does_not_exist'));
+ public function clone(Consumable $consumable) : View
+ {
+ $this->authorize('create', $consumable);
+ $consumable_to_close = $consumable;
+ $consumable = clone $consumable_to_close;
+ $consumable->id = null;
+ $consumable->image = null;
+ $consumable->created_by = null;
+
+ return view('consumables/edit')->with('item', $consumable);
}
}
diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
index 35a4ae841e..545b008dc0 100644
--- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php
+++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php
@@ -48,7 +48,7 @@ class ConsumablesFilesController extends Controller
}
- return redirect()->route('consumables.show', $consumable->id)->with('success', trans('general.file_upload_success'));
+ return redirect()->route('consumables.show', $consumable->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
@@ -89,7 +89,7 @@ class ConsumablesFilesController extends Controller
$log->delete();
- return redirect()->back()
+ return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
@@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller
* @since [v1.4]
* @param int $consumableId
* @param int $fileId
- * @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null)
@@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
- if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
- return response('No matching record for that asset/file', 500)
- ->header('Content-Type', 'text/plain');
- }
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
+ $file = 'private_uploads/consumables/'.$log->filename;
- $file = 'private_uploads/consumables/'.$log->filename;
-
- if (Storage::missing($file)) {
- Log::debug('FILE DOES NOT EXISTS for '.$file);
- Log::debug('URL should be '.Storage::url($file));
-
- return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
- ->header('Content-Type', 'text/plain');
- } else {
-
- // Display the file inline
- if (request('inline') == 'true') {
- $headers = [
- 'Content-Disposition' => 'inline',
- ];
- return Storage::download($file, $log->filename, $headers);
- }
-
-
- // We have to override the URL stuff here, since local defaults in Laravel's Flysystem
- // won't work, as they're not accessible via the web
- if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
- return StorageHelper::downloader($file);
+ try {
+ return StorageHelper::showOrDownloadFile($file, $log->filename);
+ } catch (\Exception $e) {
+ return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
}
}
+ // The log record doesn't exist somehow
+ return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found'));
+
}
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index c716fff368..74fff19a37 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -35,6 +35,6 @@ abstract class Controller extends BaseController
public function __construct()
{
view()->share('signedIn', Auth::check());
- view()->share('user', Auth::user());
+ view()->share('user', auth()->user());
}
}
diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php
index 94b45552fd..7862719276 100644
--- a/app/Http/Controllers/CustomFieldsController.php
+++ b/app/Http/Controllers/CustomFieldsController.php
@@ -8,6 +8,8 @@ use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -25,10 +27,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
$this->authorize('view', CustomField::class);
@@ -45,10 +45,8 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField()
* @author [A. Gianotto] []
* @since [v5.1.5]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show()
+ public function show() : RedirectResponse
{
return redirect()->route('fields.index');
}
@@ -60,10 +58,8 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField()
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create(Request $request)
+ public function create(Request $request) : View
{
$this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get();
@@ -82,35 +78,36 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::createField()
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(CustomFieldRequest $request)
+ public function store(CustomFieldRequest $request) : RedirectResponse
{
$this->authorize('create', CustomField::class);
- $show_in_email = $request->get("show_in_email", 0);
- $display_in_user_view = $request->get("display_in_user_view", 0);
+ $show_in_email = $request->input("show_in_email", 0);
+ $display_in_user_view = $request->input("display_in_user_view", 0);
// Override the display settings if the field is encrypted
- if ($request->get("field_encrypted") == '1') {
+ if ($request->input("field_encrypted") == '1') {
$show_in_email = '0';
$display_in_user_view = '0';
}
-
+
$field = new CustomField([
- "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),
+ "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),
"show_in_email" => $show_in_email,
- "is_unique" => $request->get("is_unique", 0),
+ "is_unique" => $request->input("is_unique", 0),
"display_in_user_view" => $display_in_user_view,
- "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
- "show_in_listview" => $request->get("show_in_listview", 0),
- "show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
- "user_id" => Auth::id()
+ "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),
+ "created_by" => auth()->id()
]);
@@ -144,10 +141,8 @@ class CustomFieldsController extends Controller
*
* @author [A. Gianotto] []
* @since [v3.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function deleteFieldFromFieldset($field_id, $fieldset_id)
+ public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
{
$field = CustomField::find($field_id);
@@ -176,10 +171,8 @@ class CustomFieldsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($field_id)
+ public function destroy($field_id) : RedirectResponse
{
if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field);
@@ -202,13 +195,9 @@ class CustomFieldsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit(Request $request, $id)
+ public function edit(Request $request, CustomField $field) : View | RedirectResponse
{
- if ($field = CustomField::find($id)) {
-
$this->authorize('update', $field);
$fieldsets = CustomFieldset::get();
$customFormat = '';
@@ -222,11 +211,7 @@ class CustomFieldsController extends Controller
'fieldsets' => $fieldsets,
'predefinedFormats' => Helper::predefined_formats(),
]);
- }
- return redirect()->route("fields.index")
- ->with("error", trans('admin/custom_fields/message.field.invalid'));
-
}
@@ -241,13 +226,9 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(CustomFieldRequest $request, $id)
+ public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
{
- $field = CustomField::find($id);
-
$this->authorize('update', $field);
-
-
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
@@ -257,10 +238,10 @@ class CustomFieldsController extends Controller
$display_in_user_view = '0';
}
- $field->name = trim(e($request->get("name")));
- $field->element = e($request->get("element"));
+ $field->name = trim($request->get("name"));
+ $field->element = $request->get("element");
$field->field_values = $request->get("field_values");
- $field->user_id = Auth::id();
+ $field->created_by = auth()->id();
$field->help_text = $request->get("help_text");
$field->show_in_email = $show_in_email;
$field->is_unique = $request->get("is_unique", 0);
@@ -268,11 +249,14 @@ class CustomFieldsController extends Controller
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
+ $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 = e($request->get('custom_format'));
+ $field->format = $request->get('custom_format');
} else {
- $field->format = e($request->get('format'));
+ $field->format = $request->get('format');
}
if ($field->element == 'checkbox' || $field->element == 'radio'){
diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php
index e4382a31ea..fde585ba6f 100644
--- a/app/Http/Controllers/CustomFieldsetsController.php
+++ b/app/Http/Controllers/CustomFieldsetsController.php
@@ -6,10 +6,9 @@ use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
-use Redirect;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -23,7 +22,7 @@ use Redirect;
class CustomFieldsetsController extends Controller
{
- public function index()
+ public function index() : RedirectResponse
{
return redirect()->route("fields.index")
->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
@@ -34,14 +33,14 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @param int $id
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
- public function show($id)
+ public function show(CustomFieldset $fieldset) : View | RedirectResponse
{
$cfset = CustomFieldset::with('fields')
- ->where('id', '=', $id)->orderBy('id', 'ASC')->first();
+ ->where('id', '=', $fieldset->id)
+ ->orderBy('id', 'ASC')
+ ->first();
$this->authorize('view', $cfset);
@@ -70,10 +69,8 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', CustomField::class);
@@ -89,13 +86,13 @@ class CustomFieldsetsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(Request $request)
+ public function store(Request $request) : RedirectResponse
{
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset([
'name' => $request->get('name'),
- 'user_id' => Auth::user()->id,
+ 'created_by' => auth()->id(),
]);
$validator = Validator::make($request->all(), $fieldset->rules);
@@ -126,19 +123,11 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v6.0.14]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($id)
+ public function edit(CustomFieldset $fieldset) : View | RedirectResponse
{
$this->authorize('create', CustomField::class);
-
- if ($fieldset = CustomFieldset::find($id)) {
- return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
- }
-
- return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
-
+ return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
/**
@@ -147,26 +136,19 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v6.0.14]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(Request $request, $id)
+ public function update(Request $request, CustomFieldset $fieldset) : RedirectResponse
{
$this->authorize('create', CustomField::class);
- if ($fieldset = CustomFieldset::find($id)) {
-
- $fieldset->name = $request->input('name');
-
- if ($fieldset->save()) {
- return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
- }
-
- return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
+ $fieldset->name = $request->input('name');
+ if ($fieldset->save()) {
+ return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
- return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
+ return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
+
}
/**
@@ -175,10 +157,8 @@ class CustomFieldsetsController extends Controller
* @author [Brady Wetherington] []
* @param int $id
* @since [v1.8]
- * @return View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
$fieldset = CustomFieldset::find($id);
@@ -203,9 +183,8 @@ class CustomFieldsetsController extends Controller
*
* @author [Brady Wetherington] []
* @since [v1.8]
- * @return View
*/
- public function associate(Request $request, $id)
+ public function associate(Request $request, $id) : RedirectResponse
{
$set = CustomFieldset::find($id);
@@ -223,7 +202,7 @@ class CustomFieldsetsController extends Controller
return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success'));
}
- return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.');
+ return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected'));
}
/**
@@ -232,7 +211,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0]
*/
- public function makeFieldRequired($fieldset_id, $field_id)
+ public function makeFieldRequired($fieldset_id, $field_id) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
@@ -250,7 +229,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] []
* @since [v5.0]
*/
- public function makeFieldOptional($fieldset_id, $field_id)
+ public function makeFieldOptional($fieldset_id, $field_id) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id);
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index 89283a7c21..a2ccb62475 100755
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -2,8 +2,10 @@
namespace App\Http\Controllers;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Artisan;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Session;
/**
@@ -21,12 +23,11 @@ class DashboardController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function index()
+ public function index() : View | RedirectResponse
{
// Show the page
- if (Auth::user()->hasAccess('admin')) {
+ if (auth()->user()->hasAccess('admin')) {
$asset_stats = null;
$counts['asset'] = \App\Models\Asset::count();
@@ -34,16 +35,18 @@ class DashboardController extends Controller
$counts['license'] = \App\Models\License::assetcount();
$counts['consumable'] = \App\Models\Consumable::count();
$counts['component'] = \App\Models\Component::count();
- $counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count();
+ $counts['user'] = \App\Models\Company::scopeCompanyables(auth()->user())->count();
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('migrate', ['--force' => true]);
- \Artisan::call('passport:install');
+ Artisan::call('passport:install', ['--no-interaction' => true]);
}
return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts);
} else {
+ Session::reflash();
+
// Redirect to the profile page
return redirect()->intended('account/view-assets');
}
diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php
index 62823098ba..f49242127e 100644
--- a/app/Http/Controllers/DepartmentsController.php
+++ b/app/Http/Controllers/DepartmentsController.php
@@ -4,8 +4,10 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Department;
+use App\Models\Company;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
@@ -25,10 +27,8 @@ class DepartmentsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v4.0]
* @param Request $request
- * @return \Illuminate\Support\Facades\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index(Request $request)
+ public function index(Request $request) : View
{
$this->authorize('index', Department::class);
$company = null;
@@ -45,18 +45,17 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\Response
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
- $department->user_id = Auth::user()->id;
+ $department->created_by = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
+ $department->notes = $request->input('notes');
$department = $request->handleImages($department);
if ($department->save()) {
@@ -73,20 +72,11 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v4.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($id)
+ public function show(Department $department) : View | RedirectResponse
{
- $department = Department::find($id);
-
$this->authorize('view', $department);
-
- if (isset($department->id)) {
- return view('departments/view', compact('department'));
- }
-
- return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
+ return view('departments/view', compact('department'));
}
/**
@@ -95,10 +85,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @see DepartmentsController::postCreate() method that validates and stores the data
* @since [v4.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Department::class);
@@ -111,10 +99,8 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v4.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
if (is_null($department = Department::find($id))) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found'));
@@ -145,25 +131,23 @@ class DepartmentsController extends Controller
* @see LocationsController::postCreate() method that validates and stores
* @param int $departmentId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($departmentId = null)
+ public function edit(Department $department) : View | RedirectResponse
{
- if (is_null($item = Department::find($departmentId))) {
- return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
- }
-
- $this->authorize('update', $item);
-
- return view('departments/edit', compact('item'));
+ $this->authorize('update', $department);
+ return view('departments/edit')->with('item', $department);
}
- public function update(ImageUploadRequest $request, $id)
+ /**
+ * Save updated Department information.
+ *
+ * @author [A. Gianotto] []
+ * @see LocationsController::postCreate() method that validates and stores
+ * @param int $departmentId
+ * @since [v1.0]
+ */
+ public function update(ImageUploadRequest $request, Department $department) : RedirectResponse
{
- if (is_null($department = Department::find($id))) {
- return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
- }
$this->authorize('update', $department);
@@ -173,7 +157,7 @@ class DepartmentsController extends Controller
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->phone = $request->input('phone');
$department->fax = $request->input('fax');
-
+ $department->notes = $request->input('notes');
$department = $request->handleImages($department);
if ($department->save()) {
diff --git a/app/Http/Controllers/DepreciationsController.php b/app/Http/Controllers/DepreciationsController.php
index 70bfb78cb5..2c98e240b9 100755
--- a/app/Http/Controllers/DepreciationsController.php
+++ b/app/Http/Controllers/DepreciationsController.php
@@ -5,7 +5,8 @@ namespace App\Http\Controllers;
use App\Models\Depreciation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Depreciations for
* the Snipe-IT Asset Management application.
@@ -21,14 +22,10 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [authorize('view', Depreciation::class);
-
- // Show the page
return view('depreciations/index');
}
@@ -38,10 +35,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [authorize('create', Depreciation::class);
@@ -56,10 +51,8 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postCreate()
* @since [v1.0]
* @param Request $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(Request $request)
+ public function store(Request $request) : RedirectResponse
{
$this->authorize('create', Depreciation::class);
@@ -68,7 +61,21 @@ class DepreciationsController extends Controller
// Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
- $depreciation->user_id = Auth::id();
+ $depreciation->created_by = auth()->id();
+
+ $request->validate([
+ 'depreciation_min' => [
+ 'required',
+ 'numeric',
+ function ($attribute, $value, $fail) use ($request) {
+ if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
+ $fail(trans('validation.percent'));
+ }
+ },
+ ],
+ 'depreciation_type' => 'required|in:amount,percent',
+ ]);
+ $depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -87,20 +94,12 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($depreciationId = null)
+ public function edit(Depreciation $depreciation) : RedirectResponse | View
{
- // Check if the depreciation exists
- if (is_null($item = Depreciation::find($depreciationId))) {
- // Redirect to the blogs management page
- return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
- }
- $this->authorize('update', $item);
-
- return view('depreciations/edit', compact('item'));
+ $this->authorize('update', $depreciation);
+ return view('depreciations/edit')->with('item', $depreciation);
}
/**
@@ -110,23 +109,28 @@ class DepreciationsController extends Controller
* @see DepreciationsController::getEdit()
* @param Request $request
* @param int $depreciationId
- * @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(Request $request, $depreciationId = null)
+ public function update(Request $request, Depreciation $depreciation) : RedirectResponse
{
- // Check if the depreciation exists
- if (is_null($depreciation = Depreciation::find($depreciationId))) {
- // Redirect to the blogs management page
- return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
- }
$this->authorize('update', $depreciation);
-
- // Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
+
+ $request->validate([
+ 'depreciation_min' => [
+ 'required',
+ 'numeric',
+ function ($attribute, $value, $fail) use ($request) {
+ if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
+ $fail(trans('validation.percent'));
+ }
+ },
+ ],
+ 'depreciation_type' => 'required|in:amount,percent',
+ ]);
+ $depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created?
@@ -146,10 +150,8 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [find($depreciationId))) {
@@ -175,18 +177,23 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($id)
+ public function show(Depreciation $depreciation) : View | RedirectResponse
{
- if (is_null($depreciation = Depreciation::find($id))) {
- // Redirect to the blogs management page
- return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
- }
+ $depreciation = Depreciation::withCount('assets as assets_count')
+ ->withCount('models as models_count')
+ ->withCount('licenses as licenses_count')
+ ->find($depreciation->id);
$this->authorize('view', $depreciation);
- return view('depreciations/view', compact('depreciation'));
+ if ($depreciation) {
+ return view('depreciations/view', compact('depreciation'));
+
+ }
+
+ return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
+
+
}
}
diff --git a/app/Http/Controllers/GoogleAuthController.php b/app/Http/Controllers/GoogleAuthController.php
index 6f5d02fde9..d873491c5b 100644
--- a/app/Http/Controllers/GoogleAuthController.php
+++ b/app/Http/Controllers/GoogleAuthController.php
@@ -2,7 +2,7 @@
namespace App\Http\Controllers;
-use Illuminate\Http\Request;
+use Illuminate\Http\RedirectResponse;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
@@ -30,7 +30,7 @@ class GoogleAuthController extends Controller
return Socialite::driver('google')->redirect();
}
- public function handleGoogleCallback()
+ public function handleGoogleCallback() : RedirectResponse
{
try {
$socialUser = Socialite::driver('google')->user();
diff --git a/app/Http/Controllers/GroupsController.php b/app/Http/Controllers/GroupsController.php
index bfc8907524..577030390b 100755
--- a/app/Http/Controllers/GroupsController.php
+++ b/app/Http/Controllers/GroupsController.php
@@ -5,7 +5,8 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Group;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to User Groups for
@@ -22,11 +23,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
- $group->created_by = Auth::user()->id;
+ $group->created_by = auth()->id();
+ $group->notes = $request->input('notes');
if ($group->save()) {
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
@@ -80,21 +78,17 @@ class GroupsController extends Controller
* @see GroupsController::postEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function edit($id)
+ public function edit(Group $group) : View | RedirectResponse
{
- $group = Group::find($id);
+ $permissions = config('permissions');
+ $groupPermissions = $group->decodePermissions();
- if ($group) {
- $permissions = config('permissions');
- $groupPermissions = $group->decodePermissions();
- $selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
-
- return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
+ if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
+ $groupPermissions = [];
}
-
- return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
+ $selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
+ return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}
/**
@@ -104,15 +98,12 @@ class GroupsController extends Controller
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function update(Request $request, $id = null)
+ public function update(Request $request, Group $group) : RedirectResponse
{
- if (! $group = Group::find($id)) {
- return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
- }
$group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
+ $group->notes = $request->input('notes');
if (! config('app.lock_passwords')) {
if ($group->save()) {
@@ -132,10 +123,8 @@ class GroupsController extends Controller
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Exception
*/
- public function destroy($id)
+ public function destroy($id) : RedirectResponse
{
if (! config('app.lock_passwords')) {
if (! $group = Group::find($id)) {
@@ -154,17 +143,10 @@ class GroupsController extends Controller
*
* @author [A. Gianotto] []
* @param $id
- * @return \Illuminate\Contracts\View\View
* @since [v4.0.11]
*/
- public function show($id)
+ public function show(Group $group) : View | RedirectResponse
{
- $group = Group::find($id);
-
- if ($group) {
- return view('groups/view', compact('group'));
- }
-
- return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
+ return view('groups/view', compact('group'));
}
}
diff --git a/app/Http/Controllers/HealthController.php b/app/Http/Controllers/HealthController.php
index 456f6b6f94..dac1f17bf4 100644
--- a/app/Http/Controllers/HealthController.php
+++ b/app/Http/Controllers/HealthController.php
@@ -3,22 +3,48 @@
namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Support\Facades\DB;
/**
- * This controller provide the healthz route for
+ * This controller provide the health route for
* the Snipe-IT Asset Management application.
*
- * @version v1.0
+ * @version v1.0
+ *
+ * @return \Illuminate\Http\JsonResponse
+
*/
class HealthController extends BaseController
{
+
+ public function __construct()
+ {
+ $this->middleware('health');
+ }
+
+
/**
* Returns a fixed JSON content ({ "status": "ok"}) which indicate the app is up and running
*/
public function get()
{
- return response()->json([
- 'status' => 'ok',
- ]);
+ try {
+
+ if (DB::select('select 2 + 2')) {
+ return response()->json([
+ 'status' => 'ok',
+ ]);
+ }
+
+ } catch (\Exception $e) {
+ \Log::error('Could not connect to database');
+ return response()->json([
+ 'status' => 'database connection failed',
+ ], 500);
+
+ }
+
+
+
}
}
diff --git a/app/Http/Controllers/Kits/CheckoutKitController.php b/app/Http/Controllers/Kits/CheckoutKitController.php
index d806f7d170..f34dbdc2c0 100644
--- a/app/Http/Controllers/Kits/CheckoutKitController.php
+++ b/app/Http/Controllers/Kits/CheckoutKitController.php
@@ -4,10 +4,8 @@ namespace App\Http\Controllers\Kits;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
-use App\Models\PredefinedKit;
use App\Models\Asset;
-use App\Models\PredefinedLicence;
-use App\Models\PredefinedModel;
+use App\Models\PredefinedKit;
use App\Models\User;
use App\Services\PredefinedKitCheckoutService;
use Illuminate\Http\Request;
@@ -33,14 +31,11 @@ class CheckoutKitController extends Controller
* Show Bulk Checkout Page
*
* @author [D. Minaev.] []
- * @return View View to checkout
+ * @return \Illuminate\Contracts\View\View View to checkout
*/
- public function showCheckout($kit_id)
+ public function showCheckout(PredefinedKit $kit)
{
$this->authorize('checkout', Asset::class);
-
- $kit = PredefinedKit::findOrFail($kit_id);
-
return view('kits/checkout')->with('kit', $kit);
}
@@ -62,10 +57,10 @@ class CheckoutKitController extends Controller
$checkout_result = $this->kitService->checkout($request, $kit, $user);
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
- return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
+ return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']);
}
- return redirect()->back()->with('success', trans('general.checkout_success'))
+ return redirect()->back()->with('success', trans('admin/kits/general.checkout_success'))
->with('assets', Arr::get($checkout_result, 'assets', null))
->with('accessories', Arr::get($checkout_result, 'accessories', null))
->with('consumables', Arr::get($checkout_result, 'consumables', null));
diff --git a/app/Http/Controllers/Kits/PredefinedKitsController.php b/app/Http/Controllers/Kits/PredefinedKitsController.php
index 67d148b5ee..96e4ec4b0a 100644
--- a/app/Http/Controllers/Kits/PredefinedKitsController.php
+++ b/app/Http/Controllers/Kits/PredefinedKitsController.php
@@ -55,6 +55,7 @@ class PredefinedKitsController extends Controller
// Create a new Predefined Kit
$kit = new PredefinedKit;
$kit->name = $request->input('name');
+ $kit->created_by = auth()->id();
if (! $kit->save()) {
return redirect()->back()->withInput()->withErrors($kit->getErrors());
@@ -73,19 +74,17 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $kit_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function edit($kit_id = null)
+ public function edit(PredefinedKit $kit)
{
$this->authorize('update', PredefinedKit::class);
- if ($kit = PredefinedKit::find($kit_id)) {
+
return view('kits/edit')
->with('item', $kit)
->with('models', $kit->models)
->with('licenses', $kit->licenses);
- }
- return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
}
/**
@@ -97,15 +96,9 @@ class PredefinedKitsController extends Controller
* @param int $kit_id
* @return \Illuminate\Http\RedirectResponse
*/
- public function update(ImageUploadRequest $request, $kit_id = null)
+ public function update(ImageUploadRequest $request, PredefinedKit $kit)
{
$this->authorize('update', PredefinedKit::class);
- // Check if the kit exists
- if (is_null($kit = PredefinedKit::find($kit_id))) {
- // Redirect to the kits management page
- return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
- }
-
$kit->name = $request->input('name');
if ($kit->save()) {
@@ -150,11 +143,11 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @since [v1.0]
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function show($kit_id = null)
+ public function show(PredefinedKit $kit)
{
- return $this->edit($kit_id);
+ return $this->edit($kit);
}
/**
@@ -162,7 +155,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $kit_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editModel($kit_id, $model_id)
{
@@ -184,7 +177,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateModel(Request $request, $kit_id, $model_id)
{
@@ -214,7 +207,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $modelId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachModel($kit_id, $model_id)
{
@@ -237,7 +230,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editLicense($kit_id, $license_id)
{
@@ -262,7 +255,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateLicense(Request $request, $kit_id, $license_id)
{
@@ -293,7 +286,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $license_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachLicense($kit_id, $license_id)
{
@@ -316,7 +309,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $accessoryId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editAccessory($kit_id, $accessory_id)
{
@@ -341,7 +334,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $accessory_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateAccessory(Request $request, $kit_id, $accessory_id)
{
@@ -371,7 +364,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $accessory_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachAccessory($kit_id, $accessory_id)
{
@@ -394,7 +387,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $consumable_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function editConsumable($kit_id, $consumable_id)
{
@@ -419,7 +412,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] []
* @param int $kit_id
* @param int $consumableId
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function updateConsumable(Request $request, $kit_id, $consumable_id)
{
@@ -449,7 +442,7 @@ class PredefinedKitsController extends Controller
*
* @author [D. Minaev] []
* @param int $consumable_id
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
public function detachConsumable($kit_id, $consumable_id)
{
diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php
index 1c5715b35f..ce5bf7d249 100755
--- a/app/Http/Controllers/LabelsController.php
+++ b/app/Http/Controllers/LabelsController.php
@@ -14,8 +14,6 @@ use App\Models\Setting;
use App\Models\Supplier;
use App\Models\User;
use App\View\Label as LabelView;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Storage;
class LabelsController extends Controller
{
@@ -23,7 +21,6 @@ class LabelsController extends Controller
* Returns the Label view with test data
*
* @param string $labelName
- * @return \Illuminate\Contracts\View\View
* @author Grant Le Roux
*/
public function show(string $labelName)
@@ -41,6 +38,7 @@ 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'),
@@ -96,6 +94,5 @@ class LabelsController extends Controller
->with('bulkedit', false)
->with('count', 0);
- return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist'));
}
}
diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php
index be71dfc4d2..72a3d43d51 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckinController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\License;
use App\Models\LicenseSeat;
@@ -27,16 +28,11 @@ class LicenseCheckinController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($seatId = null, $backTo = null)
+ public function create(LicenseSeat $licenseSeat, $backTo = null)
{
// Check if the asset exists
- if (is_null($licenseSeat = LicenseSeat::find($seatId)) || is_null($license = License::find($licenseSeat->license_id))) {
- // Redirect to the asset management page with error
- return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
- }
-
+ $license = License::find($licenseSeat->license_id);
$this->authorize('checkout', $license);
-
return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo);
}
@@ -70,7 +66,7 @@ class LicenseCheckinController extends Controller
if (! $license->reassignable) {
// Not allowed to checkin
- Session::flash('error', 'License not reassignable.');
+ Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
return redirect()->back()->withInput();
}
@@ -91,6 +87,7 @@ class LicenseCheckinController extends Controller
if($licenseSeat->assigned_to != null){
$return_to = User::find($licenseSeat->assigned_to);
+ session()->put('checkedInFrom', $return_to->id);
} else {
$return_to = Asset::find($licenseSeat->asset_id);
}
@@ -100,15 +97,15 @@ class LicenseCheckinController extends Controller
$licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes');
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
+
// Was the asset updated?
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
+ event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
- if ($backTo == 'user') {
- return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
- }
- return redirect()->route('licenses.show', $licenseSeat->license_id)->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
diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
index d3887d365c..564ce97a89 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Accessory;
@@ -27,33 +28,24 @@ class LicenseCheckoutController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create($id)
+ public function create(License $license)
{
+ $this->authorize('checkout', $license);
- if ($license = License::find($id)) {
+ if ($license->category) {
- $this->authorize('checkout', $license);
-
- if ($license->category) {
-
- // Make sure there is at least one available to checkout
- if ($license->availCount()->count() < 1){
- return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
- }
-
- // Return the checkout view
- return view('licenses/checkout', compact('license'));
+ // Make sure there is at least one available to checkout
+ if ($license->availCount()->count() < 1) {
+ return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
}
- // Invalid category
- return redirect()->route('licenses.edit', ['license' => $license->id])
- ->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
-
+ // Return the checkout view
+ return view('licenses/checkout', compact('license'));
}
- // Not found
- return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
-
+ // Invalid category
+ return redirect()->route('licenses.edit', ['license' => $license->id])
+ ->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
}
@@ -76,15 +68,32 @@ class LicenseCheckoutController extends Controller
$this->authorize('checkout', $license);
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
- $licenseSeat->user_id = Auth::id();
+ $licenseSeat->created_by = auth()->id();
$licenseSeat->notes = $request->input('notes');
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
- if ($this->$checkoutMethod($licenseSeat)) {
- return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success'));
+
+ if ($request->filled('asset_id')) {
+
+ $checkoutTarget = $this->checkoutToAsset($licenseSeat);
+ $request->request->add(['assigned_asset' => $checkoutTarget->id]);
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
+
+ } elseif ($request->filled('assigned_to')) {
+ $checkoutTarget = $this->checkoutToUser($licenseSeat);
+ $request->request->add(['assigned_user' => $checkoutTarget->id]);
+ session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
}
+
+
+ if ($checkoutTarget) {
+ return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
+ }
+
+
+
return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.'));
}
@@ -94,14 +103,14 @@ class LicenseCheckoutController extends Controller
if (! $licenseSeat) {
if ($seatId) {
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable')));
}
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
}
if (! $licenseSeat->license->is($license)) {
- throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
+ throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
}
return $licenseSeat;
@@ -119,9 +128,8 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
-
- return true;
+ event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
+ return $target;
}
return false;
@@ -136,9 +144,8 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
-
- return true;
+ event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes')));
+ return $target;
}
return false;
diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php
index fa18e8cf48..6ab3cb7703 100644
--- a/app/Http/Controllers/Licenses/LicenseFilesController.php
+++ b/app/Http/Controllers/Licenses/LicenseFilesController.php
@@ -112,37 +112,19 @@ class LicenseFilesController extends Controller
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
- if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
- return response('No matching record for that asset/file', 500)
- ->header('Content-Type', 'text/plain');
- }
-
- $file = 'private_uploads/licenses/'.$log->filename;
-
- if (Storage::missing($file)) {
- Log::debug('NOT EXISTS for '.$file);
- Log::debug('NOT EXISTS URL should be '.Storage::url($file));
-
- return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
- ->header('Content-Type', 'text/plain');
- } else {
-
- if (request('inline') == 'true') {
-
- $headers = [
- 'Content-Disposition' => 'inline',
- ];
-
- return Storage::download($file, $log->filename, $headers);
- }
-
- // We have to override the URL stuff here, since local defaults in Laravel's Flysystem
- // won't work, as they're not accessible via the web
- if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
- return StorageHelper::downloader($file);
+ if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
+ $file = 'private_uploads/licenses/'.$log->filename;
+ try {
+ return StorageHelper::showOrDownloadFile($file, $log->filename);
+ } catch (\Exception $e) {
+ return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
}
}
+
+ // The log record doesn't exist somehow
+ return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found'));
+
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php
index 01de4b4d46..ab1e3868d2 100755
--- a/app/Http/Controllers/Licenses/LicensesController.php
+++ b/app/Http/Controllers/Licenses/LicensesController.php
@@ -99,11 +99,13 @@ class LicensesController extends Controller
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
- $license->user_id = Auth::id();
+ $license->created_by = auth()->id();
$license->min_amt = $request->input('min_amt');
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($license->save()) {
- return redirect()->route('licenses.index')->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());
@@ -119,13 +121,10 @@ class LicensesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($licenseId = null)
+ public function edit(License $license)
{
- if (is_null($item = License::find($licenseId))) {
- return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
- }
- $this->authorize('update', $item);
+ $this->authorize('update', $license);
$maintained_list = [
'' => 'Maintained',
@@ -133,7 +132,8 @@ class LicensesController extends Controller
'0' => 'No',
];
- return view('licenses/edit', compact('item'))
+ return view('licenses/edit')
+ ->with('item', $license)
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list);
}
@@ -151,11 +151,9 @@ class LicensesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(Request $request, $licenseId = null)
+ public function update(Request $request, License $license)
{
- if (is_null($license = License::find($licenseId))) {
- return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
- }
+
$this->authorize('update', $license);
@@ -180,8 +178,10 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id');
$license->min_amt = $request->input('min_amt');
+ session()->put(['redirect_option' => $request->get('redirect_option')]);
+
if ($license->save()) {
- return redirect()->route('licenses.show', ['license' => $licenseId])->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());
@@ -197,10 +197,10 @@ class LicensesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($licenseId)
+ public function destroy(License $license)
{
// Check if the license exists
- if (is_null($license = License::find($licenseId))) {
+ if (is_null($license = License::find($license->id))) {
// Redirect to the license management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
@@ -234,14 +234,9 @@ class LicensesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($licenseId = null)
+ public function show(License $license)
{
- $license = License::with('assignedusers')->find($licenseId);
-
- if (!$license) {
- return redirect()->route('licenses.index')
- ->with('error', trans('admin/licenses/message.does_not_exist'));
- }
+ $license = License::with('assignedusers')->find($license->id);
$users_count = User::where('autoassign_licenses', '1')->count();
$total_seats_count = $license->totalSeatsByLicenseID();
@@ -263,10 +258,10 @@ class LicensesController extends Controller
*
* @author [A. Gianotto] []
* @param int $licenseId
- * @return \Illuminate\Http\RedirectResponse
+ * @return \Illuminate\Http\RedirectResponse | \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function getClone($licenseId = null)
+ public function getClone($licenseId = null) : \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
{
if (is_null($license_to_clone = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php
index 938c76fe12..da4e6a7e44 100755
--- a/app/Http/Controllers/LocationsController.php
+++ b/app/Http/Controllers/LocationsController.php
@@ -2,15 +2,19 @@
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\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
-
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Locations for
* the Snipe-IT Asset Management application.
@@ -26,10 +30,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @see LocationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ public function index() : View
{
// Grab all the locations
$this->authorize('view', Location::class);
@@ -43,10 +45,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @see LocationsController::postCreate() method that validates and stores the data
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Location::class);
@@ -62,12 +62,11 @@ class LocationsController extends Controller
* @see LocationsController::getCreate() method that makes the form
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ 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);
@@ -80,9 +79,22 @@ class LocationsController extends Controller
$location->zip = $request->input('zip');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
- $location->user_id = Auth::id();
+ $location->created_by = auth()->id();
$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);
@@ -100,18 +112,11 @@ class LocationsController extends Controller
* @see LocationsController::postCreate() method that validates and stores
* @param int $locationId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($locationId = null)
+ public function edit(Location $location) : View | RedirectResponse
{
$this->authorize('update', Location::class);
- // Check if the location exists
- if (is_null($item = Location::find($locationId))) {
- return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
- }
-
- return view('locations/edit', compact('item'));
+ return view('locations/edit')->with('item', $location);
}
/**
@@ -121,19 +126,12 @@ class LocationsController extends Controller
* @see LocationsController::getEdit() method that makes the form view
* @param ImageUploadRequest $request
* @param int $locationId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
- public function update(ImageUploadRequest $request, $locationId = null)
+ public function update(ImageUploadRequest $request, Location $location) : RedirectResponse
{
$this->authorize('update', Location::class);
- // Check if the location exists
- if (is_null($location = Location::find($locationId))) {
- return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
- }
- // Update the location data
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
$location->currency = $request->input('currency', '$');
@@ -147,6 +145,18 @@ class LocationsController extends Controller
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$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);
@@ -163,12 +173,11 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($locationId)
+ 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'));
}
@@ -202,11 +211,18 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $id
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function show($id = null)
+ public function show(Location $location) : View | RedirectResponse
{
- $location = Location::find($id);
+ $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')
+ ->withCount('children as children_count')
+ ->withCount('users as users_count')
+ ->withTrashed()
+ ->find($location->id);
if (isset($location->id)) {
return view('locations/view', compact('location'));
@@ -215,22 +231,26 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
- public function print_assigned($id)
+ 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);
-
+ return view('locations/print')
+ ->with('assets', $assets)
+ ->with('users',$users)
+ ->with('location', $location)
+ ->with('parent', $parent)
+ ->with('manager', $manager)
+ ->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
-
-
-
}
@@ -240,9 +260,8 @@ class LocationsController extends Controller
* @author [A. Gianotto] []
* @param int $locationId
* @since [v6.0.14]
- * @return \Illuminate\Contracts\View\View
*/
- public function getClone($locationId = null)
+ public function getClone($locationId = null) : View | RedirectResponse
{
$this->authorize('create', Location::class);
@@ -263,15 +282,57 @@ class LocationsController extends Controller
}
- public function print_all_assigned($id)
+ /**
+ * Restore a given Asset Model (mark as un-deleted)
+ *
+ * @author [A. Gianotto] []
+ * @since [v1.0]
+ * @param int $id
+ */
+ public function postRestore($id) : RedirectResponse
{
+ $this->authorize('create', Location::class);
+
+ if ($location = Location::withTrashed()->find($id)) {
+
+ if ($location->deleted_at == '') {
+ return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
+ }
+
+ if ($location->restore()) {
+ $logaction = new Actionlog();
+ $logaction->item_type = Location::class;
+ $logaction->item_id = $location->id;
+ $logaction->created_at = date('Y-m-d H:i:s');
+ $logaction->created_by = auth()->id();
+ $logaction->logaction('restore');
+
+ return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
+ }
+
+ // Check validation
+ return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
+ }
+
+ return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
+
+ }
+ 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);
-
+ return view('locations/print')
+ ->with('assets', $assets)
+ ->with('users',$users)
+ ->with('location', $location)
+ ->with('parent', $parent)
+ ->with('manager', $manager)
+ ->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -282,10 +343,11 @@ class LocationsController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.3.1]
- * @return \Illuminate\Contracts\View\View
*/
- public function postBulkDelete(Request $request)
+ 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
@@ -315,9 +377,12 @@ class LocationsController extends Controller
*
* @author [A. Gianotto] []
* @since [v6.3.1]
- * @return \Illuminate\Http\RedirectResponse
+
*/
- public function postBulkDeleteStore(Request $request) {
+ 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)) {
diff --git a/app/Http/Controllers/LocationsFilesController.php b/app/Http/Controllers/LocationsFilesController.php
new file mode 100644
index 0000000000..3aaec0e089
--- /dev/null
+++ b/app/Http/Controllers/LocationsFilesController.php
@@ -0,0 +1,111 @@
+]
+ */
+ 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] []
+ * @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] []
+ * @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'));
+
+ }
+}
diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php
index 833c5b6977..cdc5fc1bb8 100755
--- a/app/Http/Controllers/ManufacturersController.php
+++ b/app/Http/Controllers/ManufacturersController.php
@@ -2,17 +2,16 @@
namespace App\Http\Controllers;
-use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
-use App\Models\Asset;
use App\Models\Manufacturer;
-use App\Models\User;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
-use Redirect;
use Illuminate\Support\Facades\Log;
+use Illuminate\Http\RedirectResponse;
+use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Manufacturers for
@@ -29,14 +28,34 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function index()
+ 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] []
+ * @see Api\ManufacturersController::index() method that generates the JSON response
+ * @since [v1.0]
+ */
+ public function seed() : RedirectResponse
{
$this->authorize('index', Manufacturer::class);
- return view('manufacturers/index');
+ $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]));
}
/**
@@ -45,10 +64,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @see ManufacturersController::store()
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function create()
+ public function create() : View
{
$this->authorize('create', Manufacturer::class);
@@ -62,20 +79,19 @@ class ManufacturersController extends Controller
* @see ManufacturersController::create()
* @since [v1.0]
* @param ImageUploadRequest $request
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function store(ImageUploadRequest $request)
+ public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
$manufacturer->name = $request->input('name');
- $manufacturer->user_id = Auth::id();
+ $manufacturer->created_by = auth()->id();
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
+ $manufacturer->notes = $request->input('notes');
$manufacturer = $request->handleImages($manufacturer);
if ($manufacturer->save()) {
@@ -92,21 +108,11 @@ class ManufacturersController extends Controller
* @see ManufacturersController::update()
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function edit($manufacturerId = null)
+ public function edit(Manufacturer $manufacturer) : View | RedirectResponse
{
- // Handles manufacturer checks and permissions.
$this->authorize('update', Manufacturer::class);
-
- // Check if the manufacturer exists
- if (! $item = Manufacturer::find($manufacturerId)) {
- return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
- }
-
- // Show the page
- return view('manufacturers/edit', compact('item'));
+ return view('manufacturers/edit')->with('item', $manufacturer);
}
/**
@@ -116,26 +122,19 @@ class ManufacturersController extends Controller
* @see ManufacturersController::getEdit()
* @param Request $request
* @param int $manufacturerId
- * @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(ImageUploadRequest $request, $manufacturerId = null)
+ public function update(ImageUploadRequest $request, Manufacturer $manufacturer) : RedirectResponse
{
$this->authorize('update', Manufacturer::class);
- // Check if the manufacturer exists
- if (is_null($manufacturer = Manufacturer::find($manufacturerId))) {
- // Redirect to the manufacturer page
- return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
- }
- // Save the data
$manufacturer->name = $request->input('name');
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
+ $manufacturer->notes = $request->input('notes');
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
@@ -157,10 +156,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function destroy($manufacturerId)
+ public function destroy($manufacturerId) : RedirectResponse
{
$this->authorize('delete', Manufacturer::class);
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
@@ -197,21 +194,11 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @param int $manufacturerId
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function show($manufacturerId = null)
+ public function show(Manufacturer $manufacturer) : View | RedirectResponse
{
$this->authorize('view', Manufacturer::class);
- $manufacturer = Manufacturer::find($manufacturerId);
-
- if (isset($manufacturer->id)) {
- return view('manufacturers/view', compact('manufacturer'));
- }
-
- $error = trans('admin/manufacturers/message.does_not_exist');
- // Redirect to the user management page
- return redirect()->route('manufacturers.index')->with('error', $error);
+ return view('manufacturers/view', compact('manufacturer'));
}
/**
@@ -220,10 +207,8 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] []
* @since [v4.1.15]
* @param int $manufacturers_id
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function restore($id)
+ public function restore($id) : RedirectResponse
{
$this->authorize('delete', Manufacturer::class);
@@ -238,7 +223,7 @@ class ManufacturersController extends Controller
$logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s');
- $logaction->user_id = Auth::user()->id;
+ $logaction->created_by = auth()->id();
$logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
diff --git a/app/Http/Controllers/ModalController.php b/app/Http/Controllers/ModalController.php
index 6f6b39dd12..10b17ec8e2 100644
--- a/app/Http/Controllers/ModalController.php
+++ b/app/Http/Controllers/ModalController.php
@@ -15,7 +15,7 @@ class ModalController extends Controller
* @version v5.3.7-pre
* @author [Brady Wetherington] []
* @author [A. Gianotto] [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;
}
diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php
new file mode 100644
index 0000000000..c5de66526e
--- /dev/null
+++ b/app/Http/Controllers/NotesController.php
@@ -0,0 +1,42 @@
+authorize('update', Asset::class);
+
+ $validated = $request->validate([
+ 'id' => 'required',
+ 'note' => 'required|string|max:50000',
+ 'type' => [
+ 'required',
+ Rule::in(['asset']),
+ ],
+ ]);
+
+ $item = Asset::findOrFail($validated['id']);
+
+ $this->authorize('update', $item);
+
+ $logaction = new Actionlog;
+ $logaction->item_id = $item->id;
+ $logaction->item_type = get_class($item);
+ $logaction->note = $validated['note'];
+ $logaction->created_by = Auth::id();
+ $logaction->logaction('note added');
+
+ return redirect()
+ ->route('hardware.show', $validated['id'])
+ ->withFragment('history')
+ ->with('success', trans('general.note_added'));
+ }
+}
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index 72179161f1..bbbc46da6f 100755
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -10,7 +10,8 @@ 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;
/**
* This controller handles all actions related to User Profiles for
* the Snipe-IT Asset Management application.
@@ -24,12 +25,11 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Contracts\View\View
*/
- public function getIndex()
+ public function getIndex() : View
{
$this->authorize('self.profile');
- $user = Auth::user();
+ $user = auth()->user();
return view('account/profile', compact('user'));
}
@@ -38,21 +38,22 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function postIndex(ImageUploadRequest $request)
+ public function postIndex(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('self.profile');
- $user = Auth::user();
+ $user = auth()->user();
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->skin = $request->input('skin');
$user->phone = $request->input('phone');
+ $user->enable_sounds = $request->input('enable_sounds', false);
+ $user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) {
- $user->locale = $request->input('locale', 'en-US');
+ $user->locale = $request->input('locale');
}
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
@@ -84,7 +85,7 @@ class ProfileController extends Controller
* @author [A. Gianotto] []
* @since [v4.0]
*/
- public function api(): \Illuminate\Contracts\View\View
+ public function api(): View
{
// Make sure the self.api permission has been granted
if (!Gate::allows('self.api')) {
@@ -97,29 +98,29 @@ class ProfileController extends Controller
/**
* User change email page.
*
- * @return View
*/
- public function password()
+ public function password() : View | RedirectResponse
{
- $user = Auth::user();
-
+
+ $user = auth()->user();
+ if ($user->ldap_import=='1') {
+ return redirect()->route('account')->with('error', trans('admin/users/message.error.password_ldap'));
+ }
return view('account/change-password', compact('user'));
}
/**
* Users change password form processing page.
- *
- * @return \Illuminate\Http\RedirectResponse
*/
- public function passwordSave(Request $request)
+ public function passwordSave(Request $request) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords'));
}
- $user = Auth::user();
+ $user = auth()->user();
if ($user->ldap_import == '1') {
- return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap'));
+ return redirect()->route('account')->with('error', trans('admin/users/message.error.password_ldap'));
}
$rules = [
@@ -135,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/snipe/snipe-it/issues/8661)
+ // email address, first name or last name (see https://github.com/grokability/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.
@@ -178,9 +179,8 @@ class ProfileController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function getMenuState(Request $request)
+ public function getMenuState(Request $request) : void
{
if ($request->input('state') == 'open') {
$request->session()->put('menu_state', 'open');
@@ -195,18 +195,17 @@ class ProfileController extends Controller
*
* @author A. Gianotto
* @since [v6.0.12]
- * @return Illuminate\View\View
*/
- public function printInventory()
+ public function printInventory() : View
{
- $show_user = Auth::user();
+ $show_users = User::where('id',auth()->user()->id)->get();
return view('users/print')
- ->with('assets', Auth::user()->assets)
- ->with('licenses', $show_user->licenses()->get())
- ->with('accessories', $show_user->accessories()->get())
- ->with('consumables', $show_user->consumables()->get())
- ->with('show_user', $show_user)
+ ->with('assets', auth()->user()->assets())
+ ->with('licenses', auth()->user()->licenses()->get())
+ ->with('accessories', auth()->user()->accessories()->get())
+ ->with('consumables', auth()->user()->consumables()->get())
+ ->with('users', $show_users)
->with('settings', Setting::getSettings());
}
@@ -215,12 +214,11 @@ class ProfileController extends Controller
*
* @author A. Gianotto
* @since [v6.0.12]
- * @return \Illuminate\Http\RedirectResponse
*/
- public function emailAssetList()
+ public function emailAssetList() : RedirectResponse
{
- if (!$user = User::find(Auth::user()->id)) {
+ if (!$user = User::find(auth()->id())) {
return redirect()->back()
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
}
@@ -228,7 +226,12 @@ class ProfileController extends Controller
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
}
- $user->notify((new CurrentInventory($user)));
+ try {
+ $user->notify((new CurrentInventory($user)));
+ } catch (\Exception $e) {
+ \Log::error($e);
+ }
+
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
}
}
diff --git a/app/Http/Controllers/ReportTemplatesController.php b/app/Http/Controllers/ReportTemplatesController.php
new file mode 100644
index 0000000000..2be1da99e0
--- /dev/null
+++ b/app/Http/Controllers/ReportTemplatesController.php
@@ -0,0 +1,80 @@
+authorize('reports.view');
+
+ // Ignore "options" rules since data does not come in under that key...
+ $validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));
+
+ $report = $request->user()->reportTemplates()->create([
+ 'name' => $validated['name'],
+ 'options' => $request->except(['_token', 'name']),
+ ]);
+
+ session()->flash('success', trans('admin/reports/message.create.success'));
+
+ return redirect()->route('report-templates.show', $report->id);
+ }
+
+ public function show(ReportTemplate $reportTemplate)
+ {
+ $this->authorize('reports.view');
+
+ $customfields = CustomField::get();
+ $report_templates = ReportTemplate::orderBy('name')->get();
+
+ return view('reports/custom', [
+ 'customfields' => $customfields,
+ 'report_templates' => $report_templates,
+ 'template' => $reportTemplate,
+ ]);
+ }
+
+ public function edit(ReportTemplate $reportTemplate)
+ {
+ $this->authorize('reports.view');
+
+ return view('reports/custom', [
+ 'customfields' => CustomField::get(),
+ 'template' => $reportTemplate,
+ ]);
+ }
+
+ public function update(Request $request, ReportTemplate $reportTemplate): RedirectResponse
+ {
+ $this->authorize('reports.view');
+
+ // Ignore "options" rules since data does not come in under that key...
+ $validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));
+
+ $reportTemplate->update([
+ 'name' => $validated['name'],
+ 'options' => $request->except(['_token', 'name']),
+ ]);
+
+ session()->flash('success', trans('admin/reports/message.update.success'));
+
+ return redirect()->route('report-templates.show', $reportTemplate->id);
+ }
+
+ public function destroy(ReportTemplate $reportTemplate): RedirectResponse
+ {
+ $this->authorize('reports.view');
+
+ $reportTemplate->delete();
+
+ return redirect()->route('reports/custom')
+ ->with('success', trans('admin/reports/message.delete.success'));
+ }
+}
diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php
index 67a73d0dc0..031e0bbdb5 100644
--- a/app/Http/Controllers/ReportsController.php
+++ b/app/Http/Controllers/ReportsController.php
@@ -3,28 +3,34 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
+use App\Mail\CheckoutAssetMail;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
+use App\Models\AssetModel;
+use App\Models\Category;
use App\Models\AssetMaintenance;
use App\Models\CheckoutAcceptance;
+use App\Models\Company;
use App\Models\CustomField;
use App\Models\Depreciation;
use App\Models\License;
+use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
-use Illuminate\Auth\Access\AuthorizationException;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
-use Illuminate\Support\Facades\Response;
-use Illuminate\Support\Facades\View;
+use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
use Illuminate\Support\Facades\Log;
-
+use Illuminate\Http\RedirectResponse;
/**
* This controller handles all actions related to Reports for
@@ -47,9 +53,9 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
+ * @return \Illuminate\Contracts\View\View
*/
- public function getAccessoryReport()
+ public function getAccessoryReport() : View
{
$this->authorize('reports.view');
@@ -65,7 +71,7 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
- public function exportAccessoryReport()
+ public function exportAccessoryReport() : Response
{
$this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->get();
@@ -92,7 +98,7 @@ class ReportsController extends Controller
}
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -104,9 +110,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getDeprecationReport()
+ public function getDeprecationReport() : View
{
$this->authorize('reports.view');
$depreciations = Depreciation::get();
@@ -119,9 +124,8 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function exportDeprecationReport()
+ public function exportDeprecationReport() : Response
{
$this->authorize('reports.view');
// Grab all the assets
@@ -180,7 +184,7 @@ class ReportsController extends Controller
$currency = e(Setting::getSettings()->default_currency);
}
- $row[] = $asset->purchase_date;
+ $row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false);
$row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost);
$row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue());
$row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
@@ -197,12 +201,10 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v4.0]
- * @return View
*/
- public function audit()
+ public function audit() : View
{
$this->authorize('reports.view');
-
return view('reports/audit');
}
@@ -212,9 +214,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getActivityReport()
+ public function getActivityReport() : View
{
$this->authorize('reports.view');
@@ -226,9 +227,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v5.0.7]
- * @return \Illuminate\Http\Response
*/
- public function postActivityReport(Request $request)
+ public function postActivityReport(Request $request) : StreamedResponse
{
ini_set('max_execution_time', 12000);
$this->authorize('reports.view');
@@ -243,7 +243,7 @@ class ReportsController extends Controller
$header = [
trans('general.date'),
- trans('general.admin'),
+ trans('general.created_by'),
trans('general.action'),
trans('general.type'),
trans('general.item'),
@@ -264,9 +264,9 @@ class ReportsController extends Controller
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Added headers: '.$executionTime);
- $actionlogs = Actionlog::with('item', 'user', 'target', 'location')
+ $actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser')
->orderBy('created_at', 'DESC')
- ->chunk(20, function ($actionlogs) use ($handle) {
+ ->chunk(500, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
$count = 0;
@@ -291,7 +291,7 @@ class ReportsController extends Controller
$row = [
$actionlog->created_at,
- ($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '',
+ ($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
@@ -328,9 +328,8 @@ class ReportsController extends Controller
*
* @author [A. Gianotto] []
* @since [v1.0]
- * @return View
*/
- public function getLicenseReport()
+ public function getLicenseReport() : View
{
$this->authorize('reports.view');
$licenses = License::with('depreciation')->orderBy('created_at', 'DESC')
@@ -346,9 +345,8 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] []
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function exportLicenseReport()
+ public function exportLicenseReport() : Response
{
$this->authorize('reports.view');
$licenses = License::orderBy('created_at', 'DESC')->get();
@@ -385,7 +383,7 @@ class ReportsController extends Controller
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -398,14 +396,28 @@ class ReportsController extends Controller
* @author [A. Gianotto] []
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function getCustomReport()
+ public function getCustomReport(Request $request) : View
{
$this->authorize('reports.view');
$customfields = CustomField::get();
+ $report_templates = ReportTemplate::orderBy('name')->get();
- return view('reports/custom')->with('customfields', $customfields);
+ // The view needs a template to render correctly, even if it is empty...
+ $template = new ReportTemplate;
+
+ // Set the report's input values in the cases we were redirected back
+ // with validation errors so the report is populated as expected.
+ if ($request->old()) {
+ $template->name = $request->old('name');
+ $template->options = $request->old();
+ }
+
+ return view('reports/custom', [
+ 'customfields' => $customfields,
+ 'report_templates' => $report_templates,
+ 'template' => $template,
+ ]);
}
/**
@@ -414,9 +426,8 @@ class ReportsController extends Controller
* @author [A. Gianotto] []
* @see ReportsController::getCustomReport() method that generates form view
* @since [v1.0]
- * @return \Illuminate\Http\Response
*/
- public function postCustom(CustomAssetReportRequest $request)
+ public function postCustom(CustomAssetReportRequest $request) : StreamedResponse
{
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
@@ -474,7 +485,7 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.purchase_date');
}
- if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) {
+ if ($request->filled('purchase_cost')) {
$header[] = trans('admin/hardware/table.purchase_cost');
}
@@ -482,6 +493,17 @@ 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');
}
@@ -568,17 +590,6 @@ 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');
}
@@ -681,15 +692,15 @@ class ReportsController extends Controller
}
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
- $created_start = \Carbon::parse($request->input('created_start'))->startOfDay();
- $created_end = \Carbon::parse($request->input('created_end'))->endOfDay();
+ $created_start = Carbon::parse($request->input('created_start'))->startOfDay();
+ $created_end = Carbon::parse($request->input('created_end'))->endOfDay();
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
}
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
- $checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
- $checkout_end = \Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
+ $checkout_start = Carbon::parse($request->input('checkout_date_start'))->startOfDay();
+ $checkout_end = Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
$actionlogassets = Actionlog::where('action_type','=', 'checkout')
->where('item_type', 'LIKE', '%Asset%',)
@@ -700,9 +711,9 @@ class ReportsController extends Controller
}
if (($request->filled('checkin_date_start'))) {
- $checkin_start = \Carbon::parse($request->input('checkin_date_start'))->startOfDay();
+ $checkin_start = Carbon::parse($request->input('checkin_date_start'))->startOfDay();
// use today's date is `checkin_date_end` is not provided
- $checkin_end = \Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
+ $checkin_end = Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
$assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]);
}
@@ -712,9 +723,13 @@ class ReportsController extends Controller
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
+ if (($request->filled('asset_eol_date_start')) && ($request->filled('asset_eol_date_end'))) {
+ $assets->whereBetween('assets.asset_eol_date', [$request->input('asset_eol_date_start'), $request->input('asset_eol_date_end')]);
+ }
+
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
- $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
- $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
+ $last_audit_start = Carbon::parse($request->input('last_audit_start'))->startOfDay();
+ $last_audit_end = Carbon::parse($request->input('last_audit_end'))->endOfDay();
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
}
@@ -722,6 +737,11 @@ 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();
}
@@ -733,7 +753,7 @@ class ReportsController extends Controller
}
Log::debug($assets->toSql());
- $assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
+ $assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
@@ -787,7 +807,20 @@ class ReportsController extends Controller
}
if ($request->filled('eol')) {
- $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
+ $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')) {
@@ -923,19 +956,6 @@ 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 : '';
}
@@ -1015,11 +1035,10 @@ class ReportsController extends Controller
/**
* getImprovementsReport
*
- * @return View
* @author Vincent Sposato
* @version v1.0
*/
- public function getAssetMaintenancesReport()
+ public function getAssetMaintenancesReport() : View
{
$this->authorize('reports.view');
@@ -1029,11 +1048,10 @@ class ReportsController extends Controller
/**
* exportImprovementsReport
*
- * @return \Illuminate\Http\Response
* @author Vincent Sposato
* @version v1.0
*/
- public function exportAssetMaintenancesReport()
+ public function exportAssetMaintenancesReport() : Response
{
$this->authorize('reports.view');
// Grab all the improvements
@@ -1068,10 +1086,10 @@ class ReportsController extends Controller
$row[] = e($assetMaintenance->start_date);
$row[] = e($assetMaintenance->completion_date);
if (is_null($assetMaintenance->asset_maintenance_time)) {
- $improvementTime = intval(Carbon::now()
- ->diffInDays(Carbon::parse($assetMaintenance->start_date)));
+ $improvementTime = (int) Carbon::now()
+ ->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
} else {
- $improvementTime = intval($assetMaintenance->asset_maintenance_time);
+ $improvementTime = (int) $assetMaintenance->asset_maintenance_time;
}
$row[] = $improvementTime;
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
@@ -1080,7 +1098,7 @@ class ReportsController extends Controller
// spit out a csv
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -1090,39 +1108,39 @@ class ReportsController extends Controller
/**
* getAssetAcceptanceReport
*
- * @return mixed
- * @throws \Illuminate\Auth\Access\AuthorizationException
-
* @author Vincent Sposato
* @version v1.0
*/
- public function getAssetAcceptanceReport($deleted = false)
+ public function getAssetAcceptanceReport($deleted = false) : View
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
- /**
- * Get all assets with pending checkout acceptances
- */
- if($showDeleted) {
- $acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo' , 'checkoutable.assignedTo', 'checkoutable.model'])->get();
- } else {
- $acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo' => function ($query) {
- $query->withTrashed();
- }, 'checkoutable.assignedTo', 'checkoutable.model'])->get();
+ $query = CheckoutAcceptance::pending()
+ ->where('checkoutable_type', 'App\Models\Asset')
+ ->with([
+ 'checkoutable' => function (MorphTo $query) {
+ $query->morphWith([
+ AssetModel::class => ['model'],
+ Company::class => ['company'],
+ Asset::class => ['assignedTo'],
+ ])->with('model.category');
+ },
+ 'assignedTo' => function($query){
+ $query->withTrashed();
+ }
+ ]);
+
+ if ($showDeleted) {
+ $query->withTrashed();
}
- $assetsForReport = $acceptances
- ->filter(function ($acceptance) {
- $acceptance_checkoutable_flag = false;
- if ($acceptance->checkoutable){
- $acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser();
- }
-
- return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag;
- })
- ->map(function($acceptance) {
- return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
+ $assetsForReport = $query->get()
+ ->map(function ($acceptance) {
+ return [
+ 'assetItem' => $acceptance->checkoutable,
+ 'acceptance' => $acceptance,
+ ];
});
return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
@@ -1132,11 +1150,9 @@ class ReportsController extends Controller
* sentAssetAcceptanceReminder
*
* @param integer|null $acceptanceId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
- public function sentAssetAcceptanceReminder(Request $request)
+ public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse
{
$this->authorize('reports.view');
@@ -1162,27 +1178,15 @@ class ReportsController extends Controller
}
$logItem = $logItem_res[0];
}
+ $email = $assetItem->assignedTo?->email;
+ $locale = $assetItem->assignedTo?->locale;
- // Only send notification if assigned
- if ($assetItem->assignedTo) {
-
- if (!$assetItem->assignedTo->locale) {
- Notification::locale(Setting::getSettings()->locale)->send(
- $assetItem->assignedTo,
- new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
- );
- } else {
- Notification::send(
- $assetItem->assignedTo,
- new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
- );
- }
- }
-
- if ($assetItem->assignedTo->email == ''){
+ if (is_null($email) || $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'));
}
@@ -1190,11 +1194,9 @@ class ReportsController extends Controller
* sentAssetAcceptanceReminder
*
* @param integer|null $acceptanceId
- * @return \Illuminate\Http\RedirectResponse
- * @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
- public function deleteAssetAcceptance($acceptanceId = null)
+ public function deleteAssetAcceptance($acceptanceId = null) : RedirectResponse
{
$this->authorize('reports.view');
@@ -1213,11 +1215,10 @@ class ReportsController extends Controller
/**
* Exports the AssetAcceptance report to CSV
*
- * @return \Illuminate\Http\Response
* @author Vincent Sposato
* @version v1.0
*/
- public function postAssetAcceptanceReport($deleted = false)
+ public function postAssetAcceptanceReport($deleted = false) : Response
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
@@ -1268,7 +1269,7 @@ class ReportsController extends Controller
// spit out a csv
$csv = implode("\n", $rows);
- $response = Response::make($csv, 200);
+ $response = response()->make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -1284,7 +1285,7 @@ class ReportsController extends Controller
* @author Vincent Sposato
* @version v1.0
*/
- protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance)
+ protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance) : View
{
$this->authorize('reports.view');
$assets = Asset::deployed()
@@ -1304,11 +1305,11 @@ class ReportsController extends Controller
* @author Vincent Sposato
* @version v1.0
*/
- protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance)
+ protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance) : array
{
$this->authorize('reports.view');
- return array_pluck(Model::inCategory($assetCategoriesRequiringAcceptance)
+ return array_pluck(AssetModel::inCategory($assetCategoriesRequiringAcceptance)
->select('id')
->get()
->toArray(), 'id');
@@ -1321,7 +1322,7 @@ class ReportsController extends Controller
* @author Vincent Sposato
* @version v1.0
*/
- protected function getCategoriesThatRequireAcceptance()
+ protected function getCategoriesThatRequireAcceptance() : array
{
$this->authorize('reports.view');
@@ -1334,11 +1335,10 @@ class ReportsController extends Controller
/**
* getAssetsCheckedOutRequiringAcceptance
*
- * @return array
* @author Vincent Sposato
* @version v1.0
*/
- protected function getAssetsCheckedOutRequiringAcceptance()
+ protected function getAssetsCheckedOutRequiringAcceptance() : array
{
$this->authorize('reports.view');
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
old mode 100755
new mode 100644
index a3c3cab8f7..b652455a57
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -7,19 +7,26 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest;
+use App\Http\Requests\StoreLdapSettings;
+use App\Http\Requests\StoreLocalizationSettings;
+use App\Http\Requests\StoreNotificationSettings;
+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;
use App\Notifications\FirstAdminNotification;
use App\Notifications\MailTest;
-use Illuminate\Http\Client\HttpClientException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
-use Redirect;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\JsonResponse;
+use \Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
@@ -30,6 +37,7 @@ use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Validator;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This controller handles all actions related to Settings for
@@ -47,9 +55,9 @@ class SettingsController extends Controller
*
* @since [v3.0]
*
- * @return View
+ * @return \Illuminate\Contracts\View\View | \Illuminate\Http\Response
*/
- public function getSetupIndex()
+ public function getSetupIndex() : View
{
$start_settings['php_version_min'] = false;
@@ -123,14 +131,14 @@ class SettingsController extends Controller
* @return bool This method will return true when exceptions (such as curl exception) is thrown.
* Check the log files to see more details about the exception.
*/
- protected function dotEnvFileIsExposed()
+ protected function dotEnvFileIsExposed() : bool
{
try {
- return Http::timeout(10)
+ return Http::withoutVerifying()->timeout(10)
->accept('*/*')
->get(URL::to('.env'))
->successful();
- } catch (HttpClientException $e) {
+ } catch (\Exception $e) {
Log::debug($e->getMessage());
return true;
}
@@ -153,13 +161,12 @@ class SettingsController extends Controller
* Save the first admin user from Setup.
*
* @author [A. Gianotto] [