Compare commits
11 Commits
move_faker
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20a84c66a4 | ||
|
|
0fda3e2961 | ||
|
|
d6d467d1bc | ||
|
|
6e24c4294a | ||
|
|
25043283d7 | ||
|
|
a3081fdbaf | ||
|
|
218674b4cf | ||
|
|
6033485755 | ||
|
|
0a9bf97ae0 | ||
|
|
b05ab19cde | ||
|
|
4c7c48e38b |
@@ -2590,10 +2590,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "qveensi",
|
||||
"login": "QveenSi",
|
||||
"name": "Yevhenii Huzii",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4",
|
||||
"profile": "https://github.com/qveensi",
|
||||
"profile": "https://github.com/QveenSi",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
@@ -2607,6 +2607,15 @@
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "QveenSi",
|
||||
"name": "Yevhenii Huzii",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4",
|
||||
"profile": "https://github.com/QveenSi",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chrisweirich",
|
||||
"name": "Christian Weirich",
|
||||
@@ -3262,906 +3271,6 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ APP_DEBUG=true
|
||||
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en-US
|
||||
APP_LOCALE=en
|
||||
MAX_RESULTS=500
|
||||
|
||||
# --------------------------------------------
|
||||
@@ -35,7 +35,6 @@ DB_USERNAME=snipeit
|
||||
DB_PASSWORD=changeme1234
|
||||
DB_PREFIX=null
|
||||
DB_DUMP_PATH='/usr/bin'
|
||||
DB_DUMP_SKIP_SSL=true
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
|
||||
@@ -79,13 +78,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
|
||||
BACKUP_ENV=true
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
|
||||
# --------------------------------------------
|
||||
#PHP_UPLOAD_LIMIT=10
|
||||
#PHP_POST_MAX_SIZE=10
|
||||
#PHP_UPLOAD_MAX_FILESIZE=10
|
||||
#PHP_MEMORY_LIMIT=10
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
|
||||
10
.env.docker
10
.env.docker
@@ -35,7 +35,6 @@ DB_PASSWORD=changeme1234
|
||||
MYSQL_ROOT_PASSWORD=changeme1234
|
||||
DB_PREFIX=null
|
||||
DB_DUMP_PATH='/usr/bin'
|
||||
DB_DUMP_SKIP_SSL=true
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
|
||||
@@ -84,15 +83,6 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
|
||||
BACKUP_ENV=true
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
|
||||
# --------------------------------------------
|
||||
#PHP_UPLOAD_LIMIT=10
|
||||
#PHP_POST_MAX_SIZE=10
|
||||
#PHP_UPLOAD_MAX_FILESIZE=10
|
||||
#PHP_MEMORY_LIMIT=10
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
# --------------------------------------------
|
||||
|
||||
@@ -30,7 +30,6 @@ DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
DB_PREFIX=null
|
||||
DB_DUMP_PATH='/usr/bin'
|
||||
DB_DUMP_SKIP_SSL=false
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
DB_SANITIZE_BY_DEFAULT=false
|
||||
@@ -100,7 +99,7 @@ PASSPORT_COOKIE_NAME='snipeit_passport_token'
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=15
|
||||
BS_TABLE_STORAGE=localStorage
|
||||
BS_TABLE_STORAGE=cookieStorage
|
||||
BS_TABLE_DEEPLINK=true
|
||||
|
||||
# --------------------------------------------
|
||||
@@ -179,7 +178,6 @@ PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
LOG_CHANNEL=single
|
||||
LOG_DEPRECATIONS=false
|
||||
LOG_MAX_DAYS=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
|
||||
8
.github/workflows/codacy-analysis.yml
vendored
8
.github/workflows/codacy-analysis.yml
vendored
@@ -10,10 +10,10 @@ name: Codacy Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ develop ]
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '36 23 * * 3'
|
||||
|
||||
@@ -22,11 +22,11 @@ permissions:
|
||||
|
||||
jobs:
|
||||
codacy-security-scan:
|
||||
# Ensure schedule job never runs on forked repos. It's only executed for 'grokability/snipe-it'
|
||||
# Ensure schedule job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule'))
|
||||
if: (github.repository == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule'))
|
||||
name: Codacy Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
6
.github/workflows/docker-alpine.yml
vendored
6
.github/workflows/docker-alpine.yml
vendored
@@ -20,8 +20,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
|
||||
if: github.repository == 'grokability/snipe-it'
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
if: github.repository == 'snipe/snipe-it'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||
type=ref,event=tag,suffix=-alpine
|
||||
type=semver,pattern=v{{major}}-latest-alpine
|
||||
type=semver,pattern=v{{major}}-latest-alpine
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We turn off 'latest' tag by default.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Snipe-IT Docker image build for hub.docker.com
|
||||
name: Docker images (Ubuntu)
|
||||
name: Docker images
|
||||
|
||||
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
|
||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||
@@ -20,8 +20,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
|
||||
if: github.repository == 'grokability/snipe-it'
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
if: github.repository == 'snipe/snipe-it'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=tag
|
||||
type=semver,pattern=v{{major}}-latest
|
||||
type=semver,pattern=v{{major}}-latest
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We turn off 'latest' tag by default.
|
||||
4
.github/workflows/tests-mysql.yml
vendored
4
.github/workflows/tests-mysql.yml
vendored
@@ -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 --no-interaction
|
||||
php artisan passport:install
|
||||
chmod -R 777 storage bootstrap/cache
|
||||
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
|
||||
5
.github/workflows/tests-postgres.yml
vendored
5
.github/workflows/tests-postgres.yml
vendored
@@ -21,10 +21,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
|
||||
|
||||
name: PHP ${{ matrix.php-version }}
|
||||
|
||||
@@ -65,7 +64,7 @@ jobs:
|
||||
run: |
|
||||
php artisan key:generate
|
||||
php artisan migrate --force
|
||||
php artisan passport:install --no-interaction
|
||||
php artisan passport:install
|
||||
chmod -R 777 storage bootstrap/cache
|
||||
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
|
||||
2
.github/workflows/tests-sqlite.yml
vendored
2
.github/workflows/tests-sqlite.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.3"
|
||||
- "8.1.1"
|
||||
|
||||
name: PHP ${{ matrix.php-version }}
|
||||
|
||||
|
||||
240
.pa11yci.json
240
.pa11yci.json
@@ -1,240 +0,0 @@
|
||||
{
|
||||
"standard": "WCAG2AA",
|
||||
"level": "error",
|
||||
"defaults": {
|
||||
"useIncognitoBrowserContext": false,
|
||||
"timeout": 500000,
|
||||
"wait": 5000,
|
||||
"ignore" : [
|
||||
"WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail",
|
||||
"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail"
|
||||
],
|
||||
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 1024
|
||||
}
|
||||
},
|
||||
"urls": [
|
||||
{
|
||||
"__NOTE" : "this should always be FIRST (if browser context is preserved)",
|
||||
"url": "https://snipe-it.test/login",
|
||||
"actions": [
|
||||
"navigate to https://snipe-it.test/login",
|
||||
"screen capture tests/pa11y/login.png",
|
||||
"set field input[name='username'] to admin",
|
||||
"set field input[name='password'] to password",
|
||||
"click element button[type=submit]",
|
||||
"wait for url to be https://snipe-it.test/",
|
||||
"screen capture tests/pa11y/dashboard.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/admin",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/admin",
|
||||
"screen capture tests/pa11y/admin-settings.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/admin/branding",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/admin/branding",
|
||||
"screen capture tests/pa11y/admin-branding.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/admin/general",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/admin/general",
|
||||
"screen capture tests/pa11y/admin-general.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/hardware/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/hardware/create",
|
||||
"screen capture tests/pa11y/asset-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/hardware",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/hardware",
|
||||
"screen capture tests/pa11y/asset-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/hardware/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/hardware/1",
|
||||
"screen capture tests/pa11y/asset-detail.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/account/view-assets",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/account/view-assets",
|
||||
"screen capture tests/pa11y/profile.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/licences",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/licenses",
|
||||
"screen capture tests/pa11y/license-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/licences/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/licenses/create",
|
||||
"screen capture tests/pa11y/license-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/licences/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/licenses/1",
|
||||
"screen capture tests/pa11y/license-view.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/consumables",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/consumables",
|
||||
"screen capture tests/pa11y/consumable-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/consumables/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/consumables/create",
|
||||
"screen capture tests/pa11y/consumable-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/consumables/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/consumables/1",
|
||||
"screen capture tests/pa11y/consumable-view.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/accessories",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/accessories",
|
||||
"screen capture tests/pa11y/accessory-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/accessories/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/accessories/create",
|
||||
"screen capture tests/pa11y/accessory-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/accessories/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/accessories/1",
|
||||
"screen capture tests/pa11y/accessory-view.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/locations",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/locations",
|
||||
"screen capture tests/pa11y/location-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/locations/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/locations/create",
|
||||
"screen capture tests/pa11y/location-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/locations/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/locations/1",
|
||||
"screen capture tests/pa11y/location-view.png"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url" : "https://snipe-it.test/models",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/models",
|
||||
"screen capture tests/pa11y/model-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/models/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/models/create",
|
||||
"screen capture tests/pa11y/model-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/models/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/models/1",
|
||||
"screen capture tests/pa11y/model-view.png"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url" : "https://snipe-it.test/companies",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/companies",
|
||||
"screen capture tests/pa11y/company-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/companies/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/companies/create",
|
||||
"screen capture tests/pa11y/company-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/companies/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/companies/1",
|
||||
"screen capture tests/pa11y/company-view.png"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url" : "https://snipe-it.test/departments",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/departments",
|
||||
"screen capture tests/pa11y/department-list.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/departments/create",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/departments/create",
|
||||
"screen capture tests/pa11y/department-create.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url" : "https://snipe-it.test/departments/1",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/departments/1",
|
||||
"screen capture tests/pa11y/department-view.png"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url" : "https://snipe-it.test/invalid-url",
|
||||
"actions" : [
|
||||
"navigate to https://snipe-it.test/invalid-url",
|
||||
"screen capture tests/pa11y/404.png"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.2.0",
|
||||
"php_max_major_minor": "8.4",
|
||||
"php_max_wontwork": "8.5.0",
|
||||
"current_snipeit_version": "8.0"
|
||||
"php_min_version": "8.1.0",
|
||||
"php_max_major_minor": "8.3",
|
||||
"php_max_wontwork": "8.4.0",
|
||||
"current_snipeit_version": "7.0"
|
||||
}
|
||||
|
||||
@@ -42,32 +42,18 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/qveensi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/3302372?v=4" width="110px;"/><br /><sub>Jared Busch</sub>](https://github.com/sorvani)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [<img src="https://avatars.githubusercontent.com/u/111956991?v=4" width="110px;"/><br /><sub>seanborg-codethink</sub>](https://github.com/seanborg-codethink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [<img src="https://avatars.githubusercontent.com/u/160669961?v=4" width="110px;"/><br /><sub>dkaatz</sub>](https://github.com/dkaatz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [<img src="https://avatars.githubusercontent.com/u/827205?v=4" width="110px;"/><br /><sub>Daniel Ruf</sub>](https://threema.id/74SF7MW6?text=)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [<img src="https://avatars.githubusercontent.com/u/38883201?v=4" width="110px;"/><br /><sub>ahpaleus</sub>](https://github.com/ahpaleus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [<img src="https://avatars.githubusercontent.com/u/22906055?v=4" width="110px;"/><br /><sub>Anh DAO-DUY</sub>](https://github.com/mink-adao-duy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [<img src="https://avatars.githubusercontent.com/u/4723453?v=4" width="110px;"/><br /><sub>Andres Gutierrez</sub>](https://github.com/Serdnad)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/111083379?v=4" width="110px;"/><br /><sub>Warren White</sub>](https://github.com/wewhite)<br />[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [<img src="https://avatars.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://robintemme.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [<img src="https://avatars.githubusercontent.com/u/47008367?v=4" width="110px;"/><br /><sub>herroworrd</sub>](https://github.com/herroworrd)<br />[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [<img src="https://avatars.githubusercontent.com/u/28558609?v=4" width="110px;"/><br /><sub>vicleos</sub>](https://mubiu.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [<img src="https://avatars.githubusercontent.com/u/1016780?v=4" width="110px;"/><br /><sub>Bob Clough</sub>](http://thinkl33t.co.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [<img src="https://avatars.githubusercontent.com/u/10648463?v=4" width="110px;"/><br /><sub>Brandon Daniel Bailey</sub>](https://github.com/brandon-bailey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [<img src="https://avatars.githubusercontent.com/u/23556080?v=4" width="110px;"/><br /><sub>Marc Bartelt</sub>](https://github.com/marcquark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/18286893?v=4" width="110px;"/><br /><sub>manu-crealytics</sub>](https://github.com/manu-crealytics)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [<img src="https://avatars.githubusercontent.com/u/18245993?v=4" width="110px;"/><br /><sub>Konstantin Köhring</sub>](https://www.galaxy102.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [<img src="https://avatars.githubusercontent.com/u/685167?v=4" width="110px;"/><br /><sub>Deloz</sub>](https://deloz.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [<img src="https://avatars.githubusercontent.com/u/2682426?v=4" width="110px;"/><br /><sub>Martin Berg</sub>](https://github.com/mbrrg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [<img src="https://avatars.githubusercontent.com/u/3694534?v=4" width="110px;"/><br /><sub>Richard Schwab</sub>](https://github.com/Nothing4You)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [<img src="https://avatars.githubusercontent.com/u/8959676?v=4" width="110px;"/><br /><sub>Rick Heil</sub>](https://rickheil.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [<img src="https://avatars.githubusercontent.com/u/397106?v=4" width="110px;"/><br /><sub>Ross Crawford-d'Heureuse</sub>](https://github.com/rosscdh)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1621107?v=4" width="110px;"/><br /><sub>Ryan McGuire</sub>](https://github.com/McG800)<br />[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [<img src="https://avatars.githubusercontent.com/u/77835667?v=4" width="110px;"/><br /><sub>SBrown2021</sub>](https://github.com/SBrown2021)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [<img src="https://avatars.githubusercontent.com/u/8780913?v=4" width="110px;"/><br /><sub>Serkan</sub>](https://github.com/serkanerip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [<img src="https://avatars.githubusercontent.com/u/63188620?v=4" width="110px;"/><br /><sub>Shanks</sub>](https://www.yudelei.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [<img src="https://avatars.githubusercontent.com/u/198525698?v=4" width="110px;"/><br /><sub>cendai-mis</sub>](https://github.com/cendai-mis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [<img src="https://avatars.githubusercontent.com/u/8724583?v=4" width="110px;"/><br /><sub>Shaun McPeck</sub>](https://smcpeck.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [<img src="https://avatars.githubusercontent.com/u/1378836?v=4" width="110px;"/><br /><sub>Stephen</sub>](https://github.com/snazy2000)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/4462739?v=4" width="110px;"/><br /><sub>Steven</sub>](http://nevets82.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [<img src="https://avatars.githubusercontent.com/u/29017267?v=4" width="110px;"/><br /><sub>Mateus Villar</sub>](https://mateusvillar.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [<img src="https://avatars.githubusercontent.com/u/12749393?v=4" width="110px;"/><br /><sub>Matthew Zackschewski</sub>](https://github.com/mzack5020)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [<img src="https://avatars.githubusercontent.com/u/12660103?v=4" width="110px;"/><br /><sub>Matthias Frei</sub>](https://www.frei.media/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [<img src="https://avatars.githubusercontent.com/u/824840?v=4" width="110px;"/><br /><sub>Nenad Ticaric</sub>](https://github.com/nticaric)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [<img src="https://avatars.githubusercontent.com/u/706439?v=4" width="110px;"/><br /><sub>Nikolay Didenko</sub>](https://github.com/Scorcher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [<img src="https://avatars.githubusercontent.com/u/5457236?v=4" width="110px;"/><br /><sub>Nuno Maduro</sub>](https://nunomaduro.com/sponsorships)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/8883074?v=4" width="110px;"/><br /><sub>Oliver Walerys</sub>](https://tektikhq.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [<img src="https://avatars.githubusercontent.com/u/3102039?v=4" width="110px;"/><br /><sub>R. Christian McDonald</sub>](https://keybase.io/rcmcdonald91)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [<img src="https://avatars.githubusercontent.com/u/1525581?v=4" width="110px;"/><br /><sub>nix</sub>](https://nnix.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [<img src="https://avatars.githubusercontent.com/u/55462380?v=4" width="110px;"/><br /><sub>octobunny</sub>](https://github.com/octobunny)<br />[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [<img src="https://avatars.githubusercontent.com/u/8558670?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/sreyemnayr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>p3nj</sub>](https://benji.ltd/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [<img src="https://avatars.githubusercontent.com/u/6201617?v=4" width="110px;"/><br /><sub>Tim White</sub>](https://github.com/timwsuqld)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>yannikp</sub>](https://github.com/yannikp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [<img src="https://avatars.githubusercontent.com/u/20525448?v=4" width="110px;"/><br /><sub>victoria</sub>](https://github.com/viclou)<br />[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [<img src="https://avatars.githubusercontent.com/u/40685314?v=4" width="110px;"/><br /><sub>Valentyn Tulub</sub>](https://github.com/valentyntu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [<img src="https://avatars.githubusercontent.com/u/864520?v=4" width="110px;"/><br /><sub>Wouter van Os</sub>](http://wouter0100.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [<img src="https://avatars.githubusercontent.com/u/3946540?v=4" width="110px;"/><br /><sub>Wyatt Teeter</sub>](https://www.linkedin.com/in/wyatt-teeter)<br />[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [<img src="https://avatars.githubusercontent.com/u/1596124?v=4" width="110px;"/><br /><sub>Yorick Terweijden</sub>](https://github.com/terwey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [<img src="https://avatars.githubusercontent.com/u/69298836?v=4" width="110px;"/><br /><sub>bmkalle</sub>](https://github.com/bmkalle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/28403467?v=4" width="110px;"/><br /><sub>bricelabelle</sub>](https://github.com/bricelabelle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [<img src="https://avatars.githubusercontent.com/u/97770090?v=4" width="110px;"/><br /><sub>corydlamb</sub>](https://github.com/corydlamb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [<img src="https://avatars.githubusercontent.com/u/1154133?v=4" width="110px;"/><br /><sub>Diogenes S. Jesus</sub>](http://twitter.com/splash)<br />[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [<img src="https://avatars.githubusercontent.com/u/5826629?v=4" width="110px;"/><br /><sub>D M</sub>](https://github.com/dkmansion)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [<img src="https://avatars.githubusercontent.com/u/14837699?v=4" width="110px;"/><br /><sub>Dustin B</sub>](https://github.com/Jarli01)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [<img src="https://avatars.githubusercontent.com/u/348344?v=4" width="110px;"/><br /><sub>Fabian Grutschus</sub>](https://github.com/fabiang)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [<img src="https://avatars.githubusercontent.com/u/1491053?v=4" width="110px;"/><br /><sub>MelonSmasher</sub>](https://github.com/MelonSmasher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/80526133?v=4" width="110px;"/><br /><sub>AlexanderWPapyrus</sub>](https://github.com/AlexanderWPapyrus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [<img src="https://avatars.githubusercontent.com/u/306231?v=4" width="110px;"/><br /><sub>Alexandr Hacicheant</sub>](https://github.com/disc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [<img src="https://avatars.githubusercontent.com/u/3032891?v=4" width="110px;"/><br /><sub>Hex</sub>](https://hex128.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [<img src="https://avatars.githubusercontent.com/u/8697942?v=4" width="110px;"/><br /><sub>Arunas Skirius</sub>](https://github.com/arukompas)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [<img src="https://avatars.githubusercontent.com/u/104396?v=4" width="110px;"/><br /><sub>Ben Periton</sub>](https://github.com/benperiton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [<img src="https://avatars.githubusercontent.com/u/11906832?v=4" width="110px;"/><br /><sub>Byron Wolfman</sub>](https://wolfman.dev/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [<img src="https://avatars.githubusercontent.com/u/56485508?v=4" width="110px;"/><br /><sub>Calvin</sub>](https://github.com/CalvinSchwartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
40
Dockerfile
40
Dockerfile
@@ -1,8 +1,8 @@
|
||||
FROM ubuntu:24.04
|
||||
FROM ubuntu:22.04
|
||||
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
|
||||
|
||||
# No need to add `apt-get clean` here, reference:
|
||||
# - https://github.com/grokability/snipe-it/pull/9201
|
||||
# - https://github.com/snipe/snipe-it/pull/9201
|
||||
# - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
|
||||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive; \
|
||||
@@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
|
||||
apt-utils \
|
||||
apache2 \
|
||||
apache2-bin \
|
||||
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 \
|
||||
libapache2-mod-php8.1 \
|
||||
php8.1-curl \
|
||||
php8.1-ldap \
|
||||
php8.1-mysql \
|
||||
php8.1-gd \
|
||||
php8.1-xml \
|
||||
php8.1-mbstring \
|
||||
php8.1-zip \
|
||||
php8.1-bcmath \
|
||||
php8.1-redis \
|
||||
php-memcached \
|
||||
patch \
|
||||
curl \
|
||||
@@ -40,7 +40,8 @@ autoconf \
|
||||
libc-dev \
|
||||
libldap-common \
|
||||
pkg-config \
|
||||
php8.3-dev \
|
||||
libmcrypt-dev \
|
||||
php8.1-dev \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
dnsutils \
|
||||
@@ -50,13 +51,18 @@ 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.3/apache2/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/cli/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/apache2/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/cli/php.ini
|
||||
|
||||
RUN useradd -m --uid 10000 --gid 50 docker
|
||||
RUN useradd -m --uid 1000 --gid 50 docker
|
||||
|
||||
RUN echo export APACHE_RUN_USER=docker >> /etc/apache2/envvars
|
||||
RUN echo export APACHE_RUN_GROUP=staff >> /etc/apache2/envvars
|
||||
@@ -110,7 +116,7 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Get dependencies
|
||||
USER docker
|
||||
RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html && rm -rf /var/www/html/vendor/*/*/.git
|
||||
RUN composer install --no-dev --working-dir=/var/www/html
|
||||
USER root
|
||||
|
||||
############### APPLICATION INSTALL/INIT #################
|
||||
|
||||
@@ -70,7 +70,7 @@ COPY --from=composer /usr/bin/composer /usr/local/bin
|
||||
ARG COMPOSER_ALLOW_SUPERUSER=1
|
||||
RUN set -eux; \
|
||||
# Download and extract snipeit tarball
|
||||
curl -o snipeit.tar.gz -fL "https://github.com/grokability/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
|
||||
curl -o snipeit.tar.gz -fL "https://github.com/snipe/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
|
||||
tar -xzf snipeit.tar.gz --strip-components=1 -C /var/www/html/; \
|
||||
rm snipeit.tar.gz; \
|
||||
# Install composer php dependencies
|
||||
|
||||
41
README.md
41
README.md
@@ -1,15 +1,15 @@
|
||||

|
||||

|
||||
|
||||
[](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)
|
||||
[](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](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)
|
||||
[](#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 11](http://laravel.com).
|
||||
It is built on [Laravel 10](http://laravel.com).
|
||||
|
||||
Snipe-IT is actively developed and we [release quite frequently](https://github.com/grokability/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
|
||||
> [!TIP]
|
||||
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||
@@ -44,7 +44,7 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
|
||||
-----
|
||||
### Bug Reports & Feature Requests
|
||||
|
||||
Feel free to check out the [GitHub Issues for this project](https://github.com/grokability/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
|
||||
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
|
||||
@@ -76,36 +76,23 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
> [!NOTE]
|
||||
> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
#### Libraries & Modules
|
||||
|
||||
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
|
||||
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
|
||||
- [UniFi to Snipe-IT](https://www.edtechirl.com/p/snipe-it-and-azure-asset-management) originally by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
|
||||
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
|
||||
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
|
||||
- [InQRy (archived)](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [Marksman (archived)](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Python Module (archived)](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
|
||||
We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks.
|
||||
|
||||
#### Mobile Apps
|
||||
|
||||
We're currently working on our own mobile app, but in the meantime, check out these third-party apps that work with Snipe-IT:
|
||||
|
||||
- [SnipeMate](https://snipemate.app/) (iOS, Google Play, Huawei AppGallery) by Mars Technology
|
||||
- [Snipe-Scan](https://apps.apple.com/do/app/snipe-scan/id6744179400?uo=2) (iOS) by Nicolas Maton
|
||||
- [Snipe-IT Assets Management](https://play.google.com/store/apps/details?id=com.diegogarciadev.assetsmanager.snipeit&hl=en&pli=1) (Google Play) by DiegoGarciaDEV
|
||||
- [AssetX](https://apps.apple.com/my/app/assetx-for-snipe-it/id6741996196?uo=2) (iOS) for Snipe-IT by Rishi Gupta
|
||||
|
||||
-----
|
||||
|
||||
@@ -133,15 +120,9 @@ The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||
|
||||
Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years!
|
||||
|
||||
-----
|
||||
|
||||
### Star History
|
||||
|
||||
[](https://www.star-history.com/#grokability/snipe-it&Date)
|
||||
|
||||
------
|
||||
### Announcement List
|
||||
|
||||
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
|
||||
To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important.
|
||||
|
||||
|
||||
We also usually make smaller announcements on our social accounts, our Discord, and our blog, so be sure to subscribe to those if you're looking for more granular announcements.
|
||||
|
||||
@@ -11,7 +11,6 @@ make it impossible to backport security fixes on older versions.
|
||||
|
||||
| Version | Supported |
|
||||
|---------| ------------------ |
|
||||
| 8.x | :white_check_mark: |
|
||||
| 7.x | :white_check_mark: |
|
||||
| 6.x | :x: |
|
||||
| 5.1.x | :x: |
|
||||
|
||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -1,7 +1,7 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh"
|
||||
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
|
||||
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
|
||||
2
app.json
2
app.json
@@ -6,7 +6,7 @@
|
||||
"it asset"
|
||||
],
|
||||
"website": "https://snipeitapp.com/",
|
||||
"repository": "https://github.com/grokability/snipe-it",
|
||||
"repository": "https://github.com/snipe/snipe-it",
|
||||
"logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg",
|
||||
"success_url": "/setup",
|
||||
"env": {
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CheckoutRequests;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\RequestAssetCancelation;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
|
||||
class CancelCheckoutRequestAction
|
||||
{
|
||||
public static function run(Asset $asset, User $user)
|
||||
{
|
||||
if (!Company::isCurrentUserHasAccess($asset)) {
|
||||
throw new AuthorizationException();
|
||||
}
|
||||
|
||||
$asset->cancelRequest();
|
||||
|
||||
$asset->decrement('requests_counter', 1);
|
||||
|
||||
$data['item'] = $asset;
|
||||
$data['target'] = $user;
|
||||
$data['item_quantity'] = 1;
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_id = $data['asset_id'] = $asset->id;
|
||||
$logaction->item_type = $data['item_type'] = Asset::class;
|
||||
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
|
||||
$logaction->target_id = $data['user_id'] = auth()->id();
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->location_id = $user->location_id ?? null;
|
||||
$logaction->logaction('request canceled');
|
||||
|
||||
try {
|
||||
$settings->notify(new RequestAssetCancelation($data));
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning($e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CheckoutRequests;
|
||||
|
||||
use App\Exceptions\AssetNotRequestable;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\RequestAssetNotification;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Log;
|
||||
|
||||
class CreateCheckoutRequestAction
|
||||
{
|
||||
/**
|
||||
* @throws AssetNotRequestable
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public static function run(Asset $asset, User $user): string
|
||||
{
|
||||
if (is_null(Asset::RequestableAssets()->find($asset->id))) {
|
||||
throw new AssetNotRequestable($asset);
|
||||
}
|
||||
if (!Company::isCurrentUserHasAccess($asset)) {
|
||||
throw new AuthorizationException();
|
||||
}
|
||||
|
||||
$data['item'] = $asset;
|
||||
$data['target'] = $user;
|
||||
$data['item_quantity'] = 1;
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_id = $data['asset_id'] = $asset->id;
|
||||
$logaction->item_type = $data['item_type'] = Asset::class;
|
||||
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
|
||||
$logaction->target_id = $data['user_id'] = auth()->id();
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->location_id = $user->location_id ?? null;
|
||||
$logaction->logaction('requested');
|
||||
|
||||
$asset->request();
|
||||
$asset->increment('requests_counter', 1);
|
||||
try {
|
||||
$settings->notify(new RequestAssetNotification($data));
|
||||
} catch (\Exception $e) {
|
||||
Log::warning($e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DisableSAML extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:saml-disable';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This is a rescue command that can be used to turn off SAML settings in the event that you managed to lock yourself out using bad SAML settings.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->confirm("\n****************************************************\nThis will disable SAML support. You will not be able \nto login with an account that does not exist \nlocally in the Snipe-IT local database. \n****************************************************\n\nDo you wish to continue? [y|N]")) {
|
||||
$setting = Setting::getSettings();
|
||||
$setting->saml_enabled = 0;
|
||||
if ($setting->save()) {
|
||||
$this->info('SAML has been set to disabled.');
|
||||
} else {
|
||||
$this->info('Unable to disable SAML.');
|
||||
}
|
||||
} else {
|
||||
$this->info('Canceled. No actions taken.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Actionlog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FixBulkAccessoryCheckinActionLogEntries extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:fix-bulk-accessory-action-log-entries {--dry-run : Run the sync process but don\'t update the database} {--skip-backup : Skip pre-execution backup}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This script attempts to fix timestamps and missing created_by values for bulk checkin entries in the log table';
|
||||
|
||||
private bool $dryrun = false;
|
||||
private bool $skipBackup = false;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->skipBackup = $this->option('skip-backup');
|
||||
$this->dryrun = $this->option('dry-run');
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('This is a DRY RUN - no changes will be saved.');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$logs = Actionlog::query()
|
||||
// only look for accessory checkin logs
|
||||
->where('item_type', Accessory::class)
|
||||
// that were part of a bulk checkin
|
||||
->where('note', 'Bulk checkin items')
|
||||
// logs that were improperly timestamped should have created_at in the 1970s
|
||||
->whereYear('created_at', '1970')
|
||||
->get();
|
||||
|
||||
if ($logs->isEmpty()) {
|
||||
$this->info('No logs found with incorrect timestamps.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:');
|
||||
|
||||
$this->table(
|
||||
['ID', 'Created By', 'Created At', 'Updated At'],
|
||||
$logs->map(function ($log) {
|
||||
return [
|
||||
$log->id,
|
||||
$log->created_by,
|
||||
$log->created_at,
|
||||
$log->updated_at,
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
if (!$this->dryrun && !$this->confirm('Update these logs?')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$this->dryrun && !$this->skipBackup) {
|
||||
$this->info('Backing up the database before making changes...');
|
||||
$this->call('snipeit:backup');
|
||||
}
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->newLine();
|
||||
$this->info('DRY RUN. NOT ACTUALLY UPDATING LOGS.');
|
||||
}
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$this->newLine();
|
||||
$this->info('Processing log id:' . $log->id);
|
||||
|
||||
// created_by was not being set for accessory bulk checkins
|
||||
// so let's see if there was another bulk checkin log
|
||||
// with the same timestamp and a created_by value we can use.
|
||||
if (is_null($log->created_by)) {
|
||||
$createdByFromSimilarLog = $this->getCreatedByAttributeFromSimilarLog($log);
|
||||
|
||||
if ($createdByFromSimilarLog) {
|
||||
$this->line(vsprintf('Updating log id:%s created_by to %s', [$log->id, $createdByFromSimilarLog]));
|
||||
$log->created_by = $createdByFromSimilarLog;
|
||||
} else {
|
||||
$this->warn(vsprintf('No created_by found for log id:%s', [$log->id]));
|
||||
$this->warn('Skipping updating this log since no similar log was found to update created_by from.');
|
||||
|
||||
// If we can't find a similar log then let's skip updating it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at]));
|
||||
$log->created_at = $log->updated_at;
|
||||
|
||||
if (!$this->dryrun) {
|
||||
Model::withoutTimestamps(function () use ($log) {
|
||||
$log->saveQuietly();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('DRY RUN. NO CHANGES WERE ACTUALLY MADE.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hopefully the bulk checkin included other items like assets or licenses
|
||||
* so we can use one of those logs to get the correct created_by value.
|
||||
*
|
||||
* This method attempts to find a bulk check in log that was
|
||||
* created at the same time as the log passed in.
|
||||
*/
|
||||
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int
|
||||
{
|
||||
$similarLog = Actionlog::query()
|
||||
->whereNotNull('created_by')
|
||||
->where([
|
||||
'action_type' => 'checkin from',
|
||||
'note' => 'Bulk checkin items',
|
||||
'target_id' => $log->target_id,
|
||||
'target_type' => $log->target_type,
|
||||
'created_at' => $log->updated_at,
|
||||
])
|
||||
->first();
|
||||
|
||||
if ($similarLog) {
|
||||
return $similarLog->created_by;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use Laravel\Passport\TokenRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class GeneratePersonalAccessToken extends Command
|
||||
@@ -41,8 +43,9 @@ class GeneratePersonalAccessToken extends Command
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TokenRepository $tokenRepository)
|
||||
public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
|
||||
{
|
||||
$this->validation = $validation;
|
||||
$this->tokenRepository = $tokenRepository;
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -73,7 +76,7 @@ class GeneratePersonalAccessToken extends Command
|
||||
|
||||
} else {
|
||||
|
||||
$this->warn('Your API Token has been created. Be sure to copy this token now, as it WILL NOT be accessible again.');
|
||||
$this->warn('Your API Token has been created. Be sure to copy this token now, as it will not be accessible again.');
|
||||
|
||||
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) {
|
||||
$this->info('API Token ID: '.$token->id);
|
||||
|
||||
@@ -125,10 +125,6 @@ class LdapSync extends Command
|
||||
*/
|
||||
$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) {
|
||||
@@ -361,15 +357,9 @@ class LdapSync extends Command
|
||||
// (Specifically, we don't handle a value of '0.0' correctly)
|
||||
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
|
||||
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
$boolean_cast = (bool) $raw_value;
|
||||
|
||||
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
|
||||
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
|
||||
$user->activated = !($filter_var ?? $boolean_cast);
|
||||
}else{
|
||||
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
|
||||
}
|
||||
|
||||
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
|
||||
|
||||
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
|
||||
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
|
||||
@@ -434,12 +424,8 @@ class LdapSync extends Command
|
||||
$item['note'] = $item['createorupdate'];
|
||||
$item['status'] = 'success';
|
||||
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
|
||||
// Check if the relationship already exists
|
||||
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
|
||||
$user->groups()->attach($ldap_default_group);
|
||||
}
|
||||
$user->groups()->attach($ldap_default_group);
|
||||
}
|
||||
|
||||
//updates assets location based on user's location
|
||||
if ($user->wasChanged('location_id')) {
|
||||
foreach ($user->assets as $asset) {
|
||||
|
||||
@@ -51,7 +51,8 @@ class PaveIt extends Command
|
||||
}
|
||||
|
||||
// List all the tables in the database so we don't have to worry about missing some as the app grows
|
||||
$tables = Schema::getTables();
|
||||
$tables = DB::connection()->getDoctrineSchemaManager()->listTableNames();
|
||||
|
||||
$except_tables = [
|
||||
'oauth_access_tokens',
|
||||
'oauth_clients',
|
||||
@@ -73,8 +74,7 @@ class PaveIt extends Command
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tables as $table_obj) {
|
||||
$table = $table_obj['name'];
|
||||
foreach ($tables as $table) {
|
||||
if (in_array($table, $except_tables)) {
|
||||
$this->info($table. ' is SKIPPED.');
|
||||
} else {
|
||||
|
||||
157
app/Console/Commands/RecryptFromMcrypt.php
Normal file
157
app/Console/Commands/RecryptFromMcrypt.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\LegacyEncrypter\McryptEncrypter;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class RecryptFromMcrypt extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:legacy-recrypt
|
||||
{--force : Force a re-crypt of encrypted data from MCRYPT.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command allows upgrading users to de-encrypt their deprecated mcrypt encrypted fields and re-encrypt them using the current OpenSSL encryption.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
// Check and see if they have a legacy app key listed in their .env
|
||||
// If not, we can try to use the current APP_KEY if looks like it's old
|
||||
$legacy_key = env('LEGACY_APP_KEY');
|
||||
$key_parts = explode(':', $legacy_key);
|
||||
$legacy_cipher = env('LEGACY_CIPHER', 'rijndael-256');
|
||||
$errors = [];
|
||||
|
||||
if (! $legacy_key) {
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,6 @@ class RestoreFromBackup extends Command
|
||||
'storage/private_uploads/consumables',
|
||||
'storage/private_uploads/eula-pdfs',
|
||||
'storage/private_uploads/imports',
|
||||
'storage/private_uploads/locations',
|
||||
'storage/private_uploads/licenses',
|
||||
'storage/private_uploads/signatures',
|
||||
'storage/private_uploads/users',
|
||||
@@ -290,7 +289,6 @@ class RestoreFromBackup extends Command
|
||||
|
||||
$interesting_files = [];
|
||||
$boring_files = [];
|
||||
$unsafe_files = [];
|
||||
|
||||
for ($i = 0; $i < $za->numFiles; $i++) {
|
||||
$stat_results = $za->statIndex($i);
|
||||
@@ -330,8 +328,7 @@ 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', 'avif'
|
||||
];
|
||||
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
|
||||
foreach (array_merge($private_files, $public_files) as $file) {
|
||||
$has_wildcard = (strpos($file, '*') !== false);
|
||||
if ($has_wildcard) {
|
||||
@@ -341,9 +338,7 @@ class RestoreFromBackup extends Command
|
||||
if ($last_pos !== false) {
|
||||
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
||||
if (!in_array($extension, $good_extensions)) {
|
||||
// gathering potentially unsafe files here to return at exit
|
||||
$unsafe_files[] = $raw_path;
|
||||
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
|
||||
$this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
|
||||
$boring_files[] = $raw_path;
|
||||
continue 2;
|
||||
}
|
||||
@@ -377,7 +372,6 @@ class RestoreFromBackup extends Command
|
||||
if ($this->option('sanitize-guess-prefix')) {
|
||||
$prefix = SQLStreamer::guess_prefix($sql_contents);
|
||||
$this->line($prefix);
|
||||
|
||||
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitize your SQL.");
|
||||
}
|
||||
|
||||
@@ -511,11 +505,6 @@ class RestoreFromBackup extends Command
|
||||
} else {
|
||||
$this->info(count($interesting_files).' files were succesfully transferred');
|
||||
}
|
||||
if (count($unsafe_files) > 0) {
|
||||
foreach ($unsafe_files as $unsafe_file) {
|
||||
$this->warn('Potentially unsafe file '.$unsafe_file.' was skipped');
|
||||
}
|
||||
}
|
||||
foreach ($boring_files as $boring_file) {
|
||||
$this->warn($boring_file.' was skipped.');
|
||||
}
|
||||
|
||||
@@ -64,43 +64,28 @@ class SendAcceptanceReminder extends Command
|
||||
->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++;
|
||||
$this->info($acceptance->assignedTo?->present()->fullName().' has no email address.');
|
||||
}
|
||||
$item_count = $unacceptedAssetGroup->count();
|
||||
|
||||
if ($locale && $email) {
|
||||
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale));
|
||||
|
||||
} elseif ($email) {
|
||||
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count)));
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->info($count.' users notified.');
|
||||
$headers = ['ID', 'Name'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($no_email_list as $user) {
|
||||
$rows[] = [$user['id'], $user['name']];
|
||||
}
|
||||
$this->info("The following users do not have an email address:");
|
||||
$this->table($headers, $rows);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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,28 +42,27 @@ class SendExpirationAlerts extends Command
|
||||
public function handle()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$alert_interval = $settings->alert_interval;
|
||||
$threshold = $settings->alert_interval;
|
||||
|
||||
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
|
||||
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))
|
||||
->map(fn($item) => trim($item)) // Trim each email
|
||||
->filter(fn($item) => !empty($item))
|
||||
->all();
|
||||
// Expiring Assets
|
||||
$assets = Asset::getExpiringWarrantee($alert_interval);
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
// Expiring Assets
|
||||
$assets = Asset::getExpiringWarrantee($threshold);
|
||||
if ($assets->count() > 0) {
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
||||
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
|
||||
\Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
|
||||
}
|
||||
|
||||
// Expiring licenses
|
||||
$licenses = License::getExpiringLicenses($alert_interval);
|
||||
$licenses = License::getExpiringLicenses($threshold);
|
||||
if ($licenses->count() > 0) {
|
||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
||||
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
|
||||
\Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
|
||||
}
|
||||
} else {
|
||||
if ($settings->alert_email == '') {
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Mail\SendUpcomingAuditMail;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\SendUpcomingAuditNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendUpcomingAuditReport extends Command
|
||||
{
|
||||
@@ -47,20 +48,19 @@ class SendUpcomingAuditReport extends Command
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval);
|
||||
|
||||
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
|
||||
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
|
||||
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
|
||||
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
|
||||
|
||||
|
||||
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
||||
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))
|
||||
->map(fn($item) => trim($item))
|
||||
->filter(fn($item) => !empty($item))
|
||||
->all();
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
|
||||
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
|
||||
|
||||
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
|
||||
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class TestLocationsFMCS extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:test-locations-fmcs {--location_id=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Test for company ID inconsistencies if FullMultipleCompanySupport with scoped locations will be used.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('This script checks for company ID inconsistencies if Full Multiple Company Support with scoped locations will be used.');
|
||||
$this->info('This could take a few moments if have a very large dataset.');
|
||||
$this->newLine();
|
||||
|
||||
// if parameter location_id is set, only test this location
|
||||
$location_id = null;
|
||||
if ($this->option('location_id')) {
|
||||
$location_id = $this->option('location_id');
|
||||
}
|
||||
|
||||
$mismatched = Helper::test_locations_fmcs(true, $location_id);
|
||||
$this->warn(trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)));
|
||||
$this->newLine();
|
||||
$this->info('Edit your locations to associate them with the correct company.');
|
||||
|
||||
$header = ['Type', 'ID', 'Name', 'Checkout Type', 'Company ID', 'Item Company', 'Item Location', 'Location Company', 'Location Company ID'];
|
||||
sort($mismatched);
|
||||
|
||||
$this->table($header, $mismatched);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace App\Console;
|
||||
use App\Console\Commands\ImportLocations;
|
||||
use App\Console\Commands\ReEncodeCustomFieldNames;
|
||||
use App\Console\Commands\RestoreDeletedUsers;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@@ -19,14 +18,12 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
if(Setting::getSettings()?->alerts_enabled === 1) {
|
||||
$schedule->command('snipeit:inventory-alerts')->daily();
|
||||
$schedule->command('snipeit:expiring-alerts')->daily();
|
||||
$schedule->command('snipeit:expected-checkin')->daily();
|
||||
$schedule->command('snipeit:upcoming-audits')->daily();
|
||||
}
|
||||
$schedule->command('snipeit:inventory-alerts')->daily();
|
||||
$schedule->command('snipeit:expiring-alerts')->daily();
|
||||
$schedule->command('snipeit:expected-checkin')->daily();
|
||||
$schedule->command('snipeit:backup')->weekly();
|
||||
$schedule->command('backup:clean')->daily();
|
||||
$schedule->command('snipeit:upcoming-audits')->daily();
|
||||
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
|
||||
$schedule->command('saml:clear_expired_nonces')->weekly();
|
||||
}
|
||||
|
||||
28
app/Events/NoteAdded.php
Normal file
28
app/Events/NoteAdded.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class NoteAdded
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
public $itemNoteAddedOn;
|
||||
public $note;
|
||||
public $noteAddedBy;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($itemNoteAddedOn, User $noteAddedBy, $note)
|
||||
{
|
||||
$this->itemNoteAddedOn = $itemNoteAddedOn;
|
||||
$this->note = $note;
|
||||
$this->noteAddedBy = $noteAddedBy;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AssetNotRequestable extends Exception
|
||||
{
|
||||
}
|
||||
@@ -122,33 +122,6 @@ 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';
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route($route)
|
||||
->withError(trans('general.generic_model_not_found', ['model' => $model_name]));
|
||||
}
|
||||
|
||||
|
||||
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {
|
||||
@@ -205,4 +178,4 @@ class Handler extends ExceptionHandler
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UserDoestExistException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use App\Models\Depreciation;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Carbon\Carbon;
|
||||
@@ -722,8 +721,8 @@ class Helper
|
||||
// The check and message that the user is still using the deprecated version
|
||||
$deprecations = [
|
||||
'ms_teams_deprecated' => array(
|
||||
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'),
|
||||
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
|
||||
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'),
|
||||
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
|
||||
];
|
||||
|
||||
// if item of concern is being used and its being used with the deprecated values return the notification array.
|
||||
@@ -869,7 +868,7 @@ class Helper
|
||||
$filetype = @finfo_file($finfo, $file);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif') || ($filetype == 'image/webp')) {
|
||||
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
@@ -896,12 +895,6 @@ class Helper
|
||||
public static function selectedPermissionsArray($permissions, $selected_arr = [])
|
||||
{
|
||||
$permissions_arr = [];
|
||||
if (is_array($permissions)) {
|
||||
$permissions = json_encode($permissions);
|
||||
}
|
||||
|
||||
// Set default to empty JSON if the value is null
|
||||
$permissions = json_decode($permissions ?? '{}', JSON_OBJECT_AS_ARRAY);
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
for ($x = 0; $x < count($permission); $x++) {
|
||||
@@ -912,13 +905,13 @@ class Helper
|
||||
if (is_array($selected_arr)) {
|
||||
|
||||
if (array_key_exists($permission_name, $selected_arr)) {
|
||||
$permissions_arr[$permission_name] = (int) $selected_arr[$permission_name];
|
||||
$permissions_arr[$permission_name] = $selected_arr[$permission_name];
|
||||
} else {
|
||||
$permissions_arr[$permission_name] = 0;
|
||||
$permissions_arr[$permission_name] = '0';
|
||||
}
|
||||
|
||||
} else {
|
||||
$permissions_arr[$permission_name] = 0;
|
||||
$permissions_arr[$permission_name] = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1486,8 +1479,6 @@ class Helper
|
||||
|
||||
$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 == 'index') {
|
||||
@@ -1529,129 +1520,13 @@ class Helper
|
||||
if ($redirect_option == 'target') {
|
||||
switch ($checkout_to_type) {
|
||||
case 'user':
|
||||
return route('users.show', $request->assigned_user ?? $checkedInFrom);
|
||||
return route('users.show', ['user' => $request->assigned_user]);
|
||||
case 'location':
|
||||
return route('locations.show', $request->assigned_location ?? $checkedInFrom);
|
||||
return route('locations.show', ['location' => $request->assigned_location]);
|
||||
case 'asset':
|
||||
return route('hardware.show', $request->assigned_asset ?? $checkedInFrom);
|
||||
return route('hardware.show', ['hardware' => $request->assigned_asset]);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 <tobias.regnery@gmail.com>
|
||||
* @since 7.0
|
||||
*
|
||||
* @param $artisan when false, bail out on first inconsistent entry
|
||||
* @param $location_id when set, only test this specific location
|
||||
* @param $new_company_id in case of updating a location, this is the newly requested company_id
|
||||
* @return string []
|
||||
*/
|
||||
static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) {
|
||||
$mismatched = [];
|
||||
|
||||
if ($location_id) {
|
||||
$location = Location::find($location_id);
|
||||
if ($location) {
|
||||
$locations = collect([])->push(Location::find($location_id));
|
||||
}
|
||||
} else {
|
||||
$locations = Location::all();
|
||||
}
|
||||
|
||||
// Bail out early if there are no locations
|
||||
if ($locations->count() == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach($locations as $location) {
|
||||
// in case of an update of a single location, use the newly requested company_id
|
||||
if ($new_company_id) {
|
||||
$location_company = $new_company_id;
|
||||
} else {
|
||||
$location_company = $location->company_id;
|
||||
}
|
||||
|
||||
// Depending on the relationship, we must use different operations to retrieve the objects
|
||||
$keywords_relation = [
|
||||
'many' => [
|
||||
'accessories',
|
||||
'assets',
|
||||
'assignedAccessories',
|
||||
'assignedAssets',
|
||||
'components',
|
||||
'consumables',
|
||||
'rtd_assets',
|
||||
'users',
|
||||
],
|
||||
'one' => [
|
||||
'manager',
|
||||
'parent',
|
||||
]];
|
||||
|
||||
// In case of a single location, the children must be checked as well, because we don't walk every location
|
||||
if ($location_id) {
|
||||
$keywords_relation['many'][] = 'children';
|
||||
}
|
||||
|
||||
foreach ($keywords_relation as $relation => $keywords) {
|
||||
foreach($keywords as $keyword) {
|
||||
if ($relation == 'many') {
|
||||
$items = $location->{$keyword}->all();
|
||||
} else {
|
||||
$items = collect([])->push($location->$keyword);
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ($items as $item) {
|
||||
|
||||
|
||||
if ($item && $item->company_id != $location_company) {
|
||||
|
||||
$mismatched[] = [
|
||||
class_basename(get_class($item)),
|
||||
$item->id,
|
||||
$item->name ?? $item->asset_tag ?? $item->serial ?? $item->username,
|
||||
$item->assigned_type ? str_replace('App\\Models\\', '', $item->assigned_type) : null,
|
||||
$item->company_id ?? null,
|
||||
$item->company->name ?? null,
|
||||
// $item->defaultLoc->id ?? null,
|
||||
// $item->defaultLoc->name ?? null,
|
||||
// $item->defaultLoc->company->id ?? null,
|
||||
// $item->defaultLoc->company->name ?? null,
|
||||
$item->location->name ?? null,
|
||||
$item->location->company->name ?? null,
|
||||
$location_company ?? null,
|
||||
];
|
||||
|
||||
$count++;
|
||||
|
||||
// Bail early if this is not being run via artisan
|
||||
if ((!$artisan) && ($count > 0)) {
|
||||
return $mismatched;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $mismatched;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ class IconHelper
|
||||
return 'fas fa-cog';
|
||||
case 'angle-left':
|
||||
return 'fas fa-angle-left';
|
||||
case 'angle-right':
|
||||
return 'fas fa-angle-right';
|
||||
case 'warning':
|
||||
return 'fas fa-exclamation-triangle';
|
||||
case 'kits':
|
||||
@@ -186,8 +184,6 @@ class IconHelper
|
||||
return 'fa-regular fa-id-card';
|
||||
case 'department' :
|
||||
return 'fa-solid fa-building-user';
|
||||
case 'home' :
|
||||
return 'fa-solid fa-house';
|
||||
case 'note':
|
||||
case 'notes':
|
||||
return 'fas fa-sticky-note';
|
||||
|
||||
@@ -95,10 +95,16 @@ class AccessoriesController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
*/
|
||||
public function edit(Accessory $accessory) : View | RedirectResponse
|
||||
public function edit($accessoryId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Accessory::class);
|
||||
return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
|
||||
|
||||
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'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,17 +114,24 @@ class AccessoriesController extends Controller
|
||||
* @param int $accessoryId
|
||||
* @since [v6.0]
|
||||
*/
|
||||
public function getClone(Accessory $accessory) : View | RedirectResponse
|
||||
public function getClone($accessoryId = null) : View | RedirectResponse
|
||||
{
|
||||
|
||||
$this->authorize('create', Accessory::class);
|
||||
$cloned = clone $accessory;
|
||||
$cloned->id = null;
|
||||
$cloned->deleted_at = '';
|
||||
$cloned->location_id = null;
|
||||
|
||||
// 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;
|
||||
|
||||
return view('accessories/edit')
|
||||
->with('item', $cloned);
|
||||
->with('item', $accessory);
|
||||
|
||||
}
|
||||
|
||||
@@ -129,9 +142,9 @@ class AccessoriesController extends Controller
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $accessoryId
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
|
||||
{
|
||||
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) {
|
||||
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) {
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
@@ -184,15 +197,15 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function destroy($accessoryId) : RedirectResponse
|
||||
{
|
||||
if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) {
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
|
||||
if ($accessory->checkouts_count > 0) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
|
||||
if ($accessory->hasUsers() > 0) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()]));
|
||||
}
|
||||
|
||||
if ($accessory->image) {
|
||||
@@ -218,13 +231,14 @@ class AccessoriesController extends Controller
|
||||
* @see AccessoriesController::getDataView() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Accessory $accessory) : View | RedirectResponse
|
||||
public function show($accessoryID = null) : View | RedirectResponse
|
||||
{
|
||||
$accessory->loadCount('checkouts as checkouts_count');
|
||||
|
||||
$accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
|
||||
|
||||
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
return view('accessories.view', compact('accessory'));
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories.view', compact('accessory'));
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ class AccessoriesFilesController extends Controller
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded'));
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded'));
|
||||
}
|
||||
// Prepare the error message
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
|
||||
return redirect()->route('accessories.index')
|
||||
->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,27 +72,29 @@ class AccessoriesFilesController extends Controller
|
||||
*/
|
||||
public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
|
||||
{
|
||||
if ($accessory = Accessory::find($accessoryId)) {
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
// the asset is valid
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('update', $accessory);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
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'));
|
||||
}
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('accessories/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('accessories/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
Log::debug($e);
|
||||
}
|
||||
|
||||
}
|
||||
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,11 +124,10 @@ class AccessoriesFilesController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
|
||||
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.log_record_not_found'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('general.file_not_found'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\AccessoryCheckout;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
@@ -29,17 +30,9 @@ class AccessoryCheckinController extends Controller
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
|
||||
//based on what the accessory is checked out to the target redirect option will be displayed accordingly.
|
||||
$target_option = match ($accessory_user->assigned_type) {
|
||||
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]),
|
||||
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
|
||||
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
|
||||
};
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
|
||||
|
||||
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,14 +51,8 @@ class AccessoryCheckinController extends Controller
|
||||
|
||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||
|
||||
session()->put('checkedInFrom', $accessory_checkout->assigned_to);
|
||||
session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) {
|
||||
'App\Models\User' => 'user',
|
||||
'App\Models\Location' => 'location',
|
||||
'App\Models\Asset' => 'asset',
|
||||
});
|
||||
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
$checkin_hours = date('H:i:s');
|
||||
$checkin_at = date('Y-m-d H:i:s');
|
||||
if ($request->filled('checkin_at')) {
|
||||
|
||||
@@ -208,12 +208,9 @@ class AcceptanceController extends Controller
|
||||
*/
|
||||
$branding_settings = SettingsController::getPDFBranding();
|
||||
|
||||
$path_logo = "";
|
||||
|
||||
// Check for the PDF logo path and use that, otherwise use the regular logo path
|
||||
if (!is_null($branding_settings->acceptance_pdf_logo)) {
|
||||
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
|
||||
} elseif (!is_null($branding_settings->logo)) {
|
||||
if (is_null($branding_settings->logo)){
|
||||
$path_logo = "";
|
||||
} else {
|
||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class AccessoriesController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
|
||||
$accessories->where('company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('category_id')) {
|
||||
@@ -249,11 +249,11 @@ class AccessoriesController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Accessory::class);
|
||||
$accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id);
|
||||
$accessory = Accessory::findOrFail($id);
|
||||
$this->authorize($accessory);
|
||||
|
||||
if ($accessory->checkouts_count > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/general.delete_disabled')));
|
||||
if ($accessory->hasUsers() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()])));
|
||||
}
|
||||
|
||||
$accessory->delete();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Transformers\UploadedFilesTransformer;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -14,7 +13,6 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
/**
|
||||
@@ -74,37 +72,33 @@ class AssetFilesController extends Controller
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function list(Asset $asset, Request $request) : JsonResponse | array
|
||||
public function list($assetId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
$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')));
|
||||
}
|
||||
|
||||
$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'));
|
||||
// There are no files.
|
||||
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,8 +111,12 @@ class AssetFilesController extends Controller
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
@@ -166,8 +164,12 @@ class AssetFilesController extends Controller
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function destroy(Asset $asset, $fileId = null) : JsonResponse
|
||||
public function destroy($assetId = null, $fileId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
$rel_path = 'private_uploads/assets';
|
||||
|
||||
@@ -177,14 +179,12 @@ 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
|
||||
|
||||
@@ -75,11 +75,6 @@ class AssetModelFilesController extends Controller
|
||||
*/
|
||||
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());
|
||||
|
||||
@@ -77,18 +77,6 @@ class AssetModelsController extends Controller
|
||||
$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->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'));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\View\Label;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
||||
/**
|
||||
@@ -298,15 +297,9 @@ class AssetsController extends Controller
|
||||
if ($request->input('requestable') == 'true') {
|
||||
$assets->where('assets.requestable', '=', '1');
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('model_id')) {
|
||||
// If model_id is already an array, just use it as-is
|
||||
if (is_array($request->input('model_id'))) {
|
||||
$assets->InModelList($request->input('model_id'));
|
||||
} else {
|
||||
// Otherwise, turn it into an array
|
||||
$assets->InModelList([$request->input('model_id')]);
|
||||
}
|
||||
$assets->InModelList([$request->input('model_id')]);
|
||||
}
|
||||
|
||||
if ($request->filled('category_id')) {
|
||||
@@ -442,6 +435,12 @@ class AssetsController extends Controller
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Here we're just determining which Transformer (via $transformer) to use based on the
|
||||
* variables we set earlier on in this method - we default to AssetsTransformer.
|
||||
*/
|
||||
return (new $transformer)->transformAssets($assets, $total, $request);
|
||||
}
|
||||
|
||||
@@ -492,32 +491,15 @@ class AssetsController extends Controller
|
||||
public function showBySerial(Request $request, $serial): JsonResponse | array
|
||||
{
|
||||
$this->authorize('index', Asset::class);
|
||||
$assets = Asset::where('serial', $serial)->with([
|
||||
'assetstatus',
|
||||
'assignedTo',
|
||||
'company',
|
||||
'defaultLoc',
|
||||
'location',
|
||||
'model.category',
|
||||
'model.depreciation',
|
||||
'model.fieldset',
|
||||
'model.manufacturer',
|
||||
'supplier',
|
||||
]);
|
||||
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
|
||||
|
||||
// Check if they've passed ?deleted=true
|
||||
if ($request->input('deleted', 'false') == 'true') {
|
||||
$assets = $assets->withTrashed();
|
||||
}
|
||||
|
||||
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
$total = $assets->count();
|
||||
$assets = $assets->skip($offset)->take($limit)->get();
|
||||
|
||||
if (($assets) && ($assets->count()) > 0) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||
if (($assets = $assets->get()) && ($assets->count()) > 0) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
|
||||
// If there are 0 results, return the "no such asset" response
|
||||
@@ -574,12 +556,7 @@ class AssetsController extends Controller
|
||||
'assets.assigned_to',
|
||||
'assets.assigned_type',
|
||||
'assets.status_id',
|
||||
])->with('model', 'assetstatus', 'assignedTo')
|
||||
->NotArchived();
|
||||
|
||||
if ((Setting::getSettings()->full_multiple_companies_support=='1') && ($request->filled('companyId'))) {
|
||||
$assets->where('assets.company_id', $request->input('companyId'));
|
||||
}
|
||||
])->with('model', 'assetstatus', 'assignedTo')->NotArchived();
|
||||
|
||||
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
|
||||
$assets = $assets->RTD();
|
||||
@@ -589,6 +566,7 @@ class AssetsController extends Controller
|
||||
$assets = $assets->AssignedSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
$assets = $assets->paginate(50);
|
||||
|
||||
// Loop through and set some custom properties for the transformer to use.
|
||||
@@ -701,9 +679,7 @@ class AssetsController extends Controller
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
|
||||
|
||||
// below is what we want the _eventual_ return to look like - in a more standardized format.
|
||||
// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
@@ -789,13 +765,9 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
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')));
|
||||
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('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
@@ -1071,7 +1043,7 @@ class AssetsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function audit(Request $request, Asset $asset): JsonResponse
|
||||
public function audit(Request $request): JsonResponse
|
||||
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
@@ -1079,15 +1051,36 @@ class AssetsController extends Controller
|
||||
$settings = Setting::getSettings();
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
|
||||
// Allow the asset tag to be passed in the payload (legacy method)
|
||||
if ($request->filled('asset_tag')) {
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
// No tag passed - return an error
|
||||
if (!$request->filled('asset_tag')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => '',
|
||||
'error' => trans('admin/hardware/message.no_tag'),
|
||||
], trans('admin/hardware/message.no_tag')), 200);
|
||||
}
|
||||
|
||||
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
|
||||
|
||||
if ($asset) {
|
||||
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
/**
|
||||
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
|
||||
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
|
||||
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
|
||||
* the audit log entry we're creating through this controller.
|
||||
*
|
||||
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
|
||||
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
|
||||
* will bypass normal model-level validation that's usually handled at the observer )
|
||||
*
|
||||
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
|
||||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
*/
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->next_audit_date = $dt;
|
||||
|
||||
if ($request->filled('next_audit_date')) {
|
||||
@@ -1102,89 +1095,33 @@ class AssetsController extends Controller
|
||||
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// Set up the payload for re-display in the API response
|
||||
$payload = [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => $request->input('note'),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Update custom fields in the database.
|
||||
* Validation for these fields is handled through the AssetRequest form request
|
||||
* $model = AssetModel::find($request->get('model_id'));
|
||||
*/
|
||||
if (($asset->model) && ($asset->model->fieldset)) {
|
||||
$payload['custom_fields'] = [];
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the validation to see if the audit will complete successfully
|
||||
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
|
||||
|
||||
// Validate the rest of the data before we turn off the event dispatcher
|
||||
if ($asset->isInvalid()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
|
||||
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
|
||||
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
|
||||
* the audit log entry we're creating through this controller.
|
||||
*
|
||||
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
|
||||
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
|
||||
* will bypass normal model-level validation that's usually handled at the observer)
|
||||
*
|
||||
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
|
||||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
|
||||
/**
|
||||
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
|
||||
* We have to invoke this manually because of the unsetEventDispatcher() above.)
|
||||
*/
|
||||
if ($asset->isValid() && $asset->save()) {
|
||||
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
|
||||
$asset->logAudit(request('note'), request('location_id'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||
'asset_tag' => e($asset->asset_tag),
|
||||
'note' => e($request->input('note')),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
], trans('admin/hardware/message.audit.success')));
|
||||
}
|
||||
|
||||
// Asset failed validation or was not able to be saved
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => e($asset->asset_tag),
|
||||
'error' => $asset->getErrors()->first(),
|
||||
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
|
||||
}
|
||||
|
||||
|
||||
// No matching asset for the asset tag that was passed.
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => e($request->input('asset_tag')),
|
||||
'error' => trans('admin/hardware/message.audit.error'),
|
||||
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
|
||||
}
|
||||
|
||||
|
||||
@@ -1289,10 +1226,7 @@ class AssetsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$this->authorize('view', $asset);
|
||||
$accessory_checkouts = AccessoryCheckout::AssetsAssigned()
|
||||
->where('assigned_to', $asset->id)
|
||||
->with('adminuser')
|
||||
->with('accessories');
|
||||
$accessory_checkouts = AccessoryCheckout::AssetsAssigned()->with('adminuser')->with('accessories');
|
||||
|
||||
$offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
@@ -1301,8 +1235,6 @@ class AssetsController extends Controller
|
||||
$accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
|
||||
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate asset labels by tag
|
||||
*
|
||||
|
||||
@@ -39,7 +39,6 @@ class CategoriesController extends Controller
|
||||
'components_count',
|
||||
'licenses_count',
|
||||
'image',
|
||||
'notes',
|
||||
];
|
||||
|
||||
$categories = Category::select([
|
||||
@@ -53,10 +52,9 @@ class CategoriesController extends Controller
|
||||
'require_acceptance',
|
||||
'checkin_email',
|
||||
'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');
|
||||
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
|
||||
|
||||
|
||||
/*
|
||||
@@ -212,7 +210,7 @@ class CategoriesController extends Controller
|
||||
public function destroy($id) : JsonResponse
|
||||
{
|
||||
$this->authorize('delete', Category::class);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
|
||||
if (! $category->isDeletable()) {
|
||||
return response()->json(
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
|
||||
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
|
||||
use App\Exceptions\AssetNotRequestable;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Exception;
|
||||
|
||||
class CheckoutRequest extends Controller
|
||||
{
|
||||
public function store(Asset $asset): JsonResponse
|
||||
{
|
||||
try {
|
||||
CreateCheckoutRequestAction::run($asset, auth()->user());
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success')));
|
||||
} catch (AssetNotRequestable $e) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable'));
|
||||
} catch (AuthorizationException $e) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
|
||||
} catch (Exception $e) {
|
||||
report($e);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Asset $asset): JsonResponse
|
||||
{
|
||||
try {
|
||||
CancelCheckoutRequestAction::run($asset, auth()->user());
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled')));
|
||||
} catch (AuthorizationException $e) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
|
||||
} catch (Exception $e) {
|
||||
report($e);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ class CompaniesController extends Controller
|
||||
'accessories_count',
|
||||
'consumables_count',
|
||||
'components_count',
|
||||
'notes',
|
||||
];
|
||||
|
||||
$companies = Company::withCount(['assets as assets_count' => function ($query) {
|
||||
@@ -63,10 +62,11 @@ class CompaniesController extends Controller
|
||||
|
||||
|
||||
// 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');
|
||||
$offset = ($request->input('offset') > $companies->count()) ? 0 : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
$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) {
|
||||
|
||||
@@ -48,8 +48,7 @@ class ComponentsController extends Controller
|
||||
];
|
||||
|
||||
$components = Component::select('components.*')
|
||||
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
|
||||
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
|
||||
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$components = $components->TextSearch($request->input('search'));
|
||||
@@ -60,7 +59,7 @@ class ComponentsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$components->where('components.company_id', '=', $request->input('company_id'));
|
||||
$components->where('company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('category_id')) {
|
||||
@@ -198,11 +197,6 @@ class ComponentsController extends Controller
|
||||
$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')));
|
||||
|
||||
@@ -23,7 +23,7 @@ class DepartmentsController extends Controller
|
||||
public function index(Request $request) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Department::class);
|
||||
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes'];
|
||||
$allowed_columns = ['id', 'name', 'image', 'users_count'];
|
||||
|
||||
$departments = Department::select(
|
||||
'departments.id',
|
||||
@@ -35,8 +35,7 @@ class DepartmentsController extends Controller
|
||||
'departments.manager_id',
|
||||
'departments.created_at',
|
||||
'departments.updated_at',
|
||||
'departments.image',
|
||||
'departments.notes',
|
||||
'departments.image'
|
||||
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
|
||||
@@ -24,7 +24,7 @@ class GroupsController extends Controller
|
||||
|
||||
$this->authorize('view', Group::class);
|
||||
|
||||
$groups = Group::select('id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count');
|
||||
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$groups = $groups->TextSearch($request->input('search'));
|
||||
@@ -76,12 +76,11 @@ class GroupsController extends Controller
|
||||
$this->authorize('superadmin');
|
||||
$group = new Group;
|
||||
// Get all the available permissions
|
||||
$permissions = json_encode(config('permissions'));
|
||||
$permissions = config('permissions');
|
||||
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
|
||||
|
||||
$group->name = $request->input('name');
|
||||
$group->created_by = auth()->id();
|
||||
$group->notes = $request->input('notes');
|
||||
$group->permissions = json_encode($request->input('permissions', $groupPermissions));
|
||||
|
||||
if ($group->save()) {
|
||||
@@ -119,7 +118,6 @@ class GroupsController extends Controller
|
||||
$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()) {
|
||||
|
||||
@@ -66,41 +66,25 @@ class ImportController extends Controller
|
||||
if (! ini_get('auto_detect_line_endings')) {
|
||||
ini_set('auto_detect_line_endings', '1');
|
||||
}
|
||||
if (function_exists('iconv')) {
|
||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||
$encoding = $detector->getEncoding($file_contents);
|
||||
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
|
||||
$reader = null;
|
||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||
$transliterated = false;
|
||||
try {
|
||||
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
|
||||
} catch (\Exception $e) {
|
||||
$transliterated = false; //blank out the partially-decoded string
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
if ($transliterated !== false) {
|
||||
$tmpname = tempnam(sys_get_temp_dir(), '');
|
||||
$tmpresults = file_put_contents($tmpname, $transliterated);
|
||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||
$encoding = $detector->getEncoding($file_contents);
|
||||
$reader = null;
|
||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||
$transliterated = iconv($encoding, 'UTF-8', $file_contents);
|
||||
if ($transliterated !== false) {
|
||||
$tmpname = tempnam(sys_get_temp_dir(), '');
|
||||
$tmpresults = file_put_contents($tmpname, $transliterated);
|
||||
if ($tmpresults !== false) {
|
||||
$transliterated = null; //save on memory?
|
||||
if ($tmpresults !== false) {
|
||||
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
|
||||
if ($newfile->isValid()) {
|
||||
$file = $newfile;
|
||||
}
|
||||
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
|
||||
if ($newfile->isValid()) {
|
||||
$file = $newfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
$file_contents = null; //try to save on memory, I guess?
|
||||
}
|
||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||
$file_contents = null; //try to save on memory, I guess?
|
||||
|
||||
try {
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
|
||||
@@ -34,7 +34,7 @@ class LicenseSeatsController extends Controller
|
||||
if ($request->input('sort') == 'department') {
|
||||
$seats->OrderDepartments($order);
|
||||
} else {
|
||||
$seats->orderBy('updated_at', $order);
|
||||
$seats->orderBy('id', $order);
|
||||
}
|
||||
|
||||
$total = $seats->count();
|
||||
@@ -136,13 +136,13 @@ class LicenseSeatsController extends Controller
|
||||
if ($licenseSeat->save()) {
|
||||
|
||||
if ($is_checkin) {
|
||||
$licenseSeat->logCheckin($target, $request->input('notes'));
|
||||
$licenseSeat->logCheckin($target, $request->input('note'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
||||
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
|
||||
$licenseSeat->logCheckout($request->input('notes'), $target);
|
||||
$licenseSeat->logCheckout($request->input('note'), $target);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\AccessoryCheckout;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@@ -48,7 +46,6 @@ class LocationsController extends Controller
|
||||
'id',
|
||||
'image',
|
||||
'ldap_ou',
|
||||
'company_id',
|
||||
'manager_id',
|
||||
'name',
|
||||
'rtd_assets_count',
|
||||
@@ -56,7 +53,6 @@ class LocationsController extends Controller
|
||||
'updated_at',
|
||||
'users_count',
|
||||
'zip',
|
||||
'notes',
|
||||
];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
@@ -77,10 +73,7 @@ class LocationsController extends Controller
|
||||
'locations.image',
|
||||
'locations.ldap_ou',
|
||||
'locations.currency',
|
||||
'locations.company_id',
|
||||
'locations.notes',
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('assignedAccessories as assigned_accessories_count')
|
||||
@@ -89,11 +82,6 @@ class LocationsController extends Controller
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
// 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'));
|
||||
}
|
||||
@@ -126,10 +114,6 @@ class LocationsController extends Controller
|
||||
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
@@ -146,9 +130,6 @@ class LocationsController extends Controller
|
||||
case 'manager':
|
||||
$locations->OrderManager($order);
|
||||
break;
|
||||
case 'company':
|
||||
$locations->OrderCompany($order);
|
||||
break;
|
||||
default:
|
||||
$locations->orderBy($sort, $order);
|
||||
break;
|
||||
@@ -176,15 +157,6 @@ class LocationsController extends Controller
|
||||
$location->fill($request->all());
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
// Only scope location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
|
||||
// check if parent is set and has a different company
|
||||
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
|
||||
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($location->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success')));
|
||||
}
|
||||
@@ -202,7 +174,7 @@ class LocationsController extends Controller
|
||||
public function show($id) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$location = Location::with('parent', 'manager', 'children', 'company')
|
||||
$location = Location::with('parent', 'manager', 'children')
|
||||
->select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
@@ -218,7 +190,6 @@ class LocationsController extends Controller
|
||||
'locations.updated_at',
|
||||
'locations.image',
|
||||
'locations.currency',
|
||||
'locations.notes',
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
@@ -246,19 +217,6 @@ class LocationsController extends Controller
|
||||
$location->fill($request->all());
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
// Only scope location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
|
||||
// check if there are related objects with different company
|
||||
if (Helper::test_locations_fmcs(false, $id, $location->company_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations'));
|
||||
}
|
||||
} else {
|
||||
$location->company_id = $request->get('company_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($location->isValid()) {
|
||||
|
||||
$location->save();
|
||||
@@ -297,7 +255,7 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Accessory::class);
|
||||
$this->authorize('view', $location);
|
||||
$accessory_checkouts = AccessoryCheckout::LocationAssigned()->where('assigned_to', $location->id)->with('adminuser')->with('accessories');
|
||||
$accessory_checkouts = AccessoryCheckout::LocationAssigned()->with('adminuser')->with('accessories');
|
||||
|
||||
$offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
@@ -379,11 +337,6 @@ class LocationsController extends Controller
|
||||
'locations.image',
|
||||
]);
|
||||
|
||||
// Only scope locations if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$locations = Company::scopeCompanyables($locations);
|
||||
}
|
||||
|
||||
$page = 1;
|
||||
if ($request->filled('page')) {
|
||||
$page = $request->input('page');
|
||||
|
||||
@@ -39,8 +39,7 @@ class ManufacturersController extends Controller
|
||||
'assets_count',
|
||||
'consumables_count',
|
||||
'components_count',
|
||||
'licenses_count',
|
||||
'notes',
|
||||
'licenses_count'
|
||||
];
|
||||
|
||||
$manufacturers = Manufacturer::select([
|
||||
@@ -56,7 +55,6 @@ class ManufacturersController extends Controller
|
||||
'updated_at',
|
||||
'image',
|
||||
'deleted_at',
|
||||
'notes',
|
||||
])
|
||||
->with('adminuser')
|
||||
->withCount('assets as assets_count')
|
||||
|
||||
43
app/Http/Controllers/Api/NotesController.php
Normal file
43
app/Http/Controllers/Api/NotesController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\NoteAdded;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class NotesController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'note' => 'required|string|max:500',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['asset']),
|
||||
],
|
||||
]);
|
||||
|
||||
// This can be made dynamic by using $request->input('type') to determine which model type to add the note to.
|
||||
// For now, we are only placing this on Assets
|
||||
$item = Asset::findOrFail($request->input("id"));
|
||||
$this->authorize('update', $item);
|
||||
|
||||
event(new NoteAdded($item, Auth::user(), $validated['note']));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success'));
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
|
||||
}
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ActionlogsTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -20,11 +18,10 @@ class ReportsController extends Controller
|
||||
*/
|
||||
public function index(Request $request) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('activity.view');
|
||||
$this->authorize('reports.view');
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location');
|
||||
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
|
||||
}
|
||||
|
||||
@@ -290,12 +290,10 @@ class StatuslabelsController extends Controller
|
||||
|
||||
/**
|
||||
* Returns a boolean response based on whether the status label
|
||||
* is one that is deployable or pending.
|
||||
* is one that is deployable.
|
||||
*
|
||||
* 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,
|
||||
* 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
|
||||
* we should provide a dropdown of users for them to check the asset out to.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
@@ -303,7 +301,7 @@ class StatuslabelsController extends Controller
|
||||
public function checkIfDeployable($id) : string
|
||||
{
|
||||
$statuslabel = Statuslabel::findOrFail($id);
|
||||
if (($statuslabel->getStatuslabelType() == 'pending') || ($statuslabel->getStatuslabelType() == 'deployable')) {
|
||||
if ($statuslabel->getStatuslabelType() == 'deployable') {
|
||||
return '1';
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ class UsersController extends Controller
|
||||
'users.address',
|
||||
'users.avatar',
|
||||
'users.city',
|
||||
'users.company_id',
|
||||
'users.country',
|
||||
'users.created_by',
|
||||
'users.created_at',
|
||||
@@ -80,7 +79,7 @@ class UsersController extends Controller
|
||||
'users.autoassign_licenses',
|
||||
'users.website',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
|
||||
])->with('groups', 'userloc', 'companies', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'manager')
|
||||
->withCount([
|
||||
'assets as assets_count' => function(Builder $query) {
|
||||
$query->withoutTrashed();
|
||||
@@ -102,7 +101,7 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$users = $users->where('users.company_id', '=', $request->input('company_id'));
|
||||
$users = $users->ByCompany($request->get('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
@@ -243,9 +242,9 @@ class UsersController extends Controller
|
||||
case 'created_by':
|
||||
$users = $users->OrderByCreatedBy($order);
|
||||
break;
|
||||
case 'company':
|
||||
$users = $users->OrderCompany($order);
|
||||
break;
|
||||
// case 'company':
|
||||
// $users = $users->OrderCompany($order);
|
||||
// break;
|
||||
case 'first_name':
|
||||
$users->orderBy('first_name', $order);
|
||||
$users->orderBy('last_name', $order);
|
||||
@@ -339,7 +338,6 @@ class UsersController extends Controller
|
||||
$users = $users->where(function ($query) use ($request) {
|
||||
$query->SimpleNameSearch($request->get('search'))
|
||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||
});
|
||||
}
|
||||
@@ -413,6 +411,8 @@ class UsersController extends Controller
|
||||
$user->groups()->sync([]);
|
||||
}
|
||||
|
||||
$user->companies()->sync($request->input('companies'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.create')));
|
||||
}
|
||||
|
||||
@@ -513,6 +513,7 @@ class UsersController extends Controller
|
||||
|
||||
// Sync the groups since the user is a superuser and the groups pass validation
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
$user->companies()->sync($request->input('companies'));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class AssetMaintenancesController extends Controller
|
||||
) {
|
||||
$startDate = Carbon::parse($assetMaintenance->start_date);
|
||||
$completionDate = Carbon::parse($assetMaintenance->completion_date);
|
||||
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
|
||||
}
|
||||
|
||||
// Was the asset maintenance created?
|
||||
@@ -139,12 +139,19 @@ class AssetMaintenancesController extends Controller
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
public function edit($assetMaintenanceId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
|
||||
// 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($maintenance->asset)) {
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
@@ -154,7 +161,7 @@ class AssetMaintenancesController extends Controller
|
||||
return view('asset_maintenances/edit')
|
||||
->with('selectedAsset', null)
|
||||
->with('assetMaintenanceType', $assetMaintenanceType)
|
||||
->with('item', $maintenance);
|
||||
->with('item', $assetMaintenance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,20 +174,24 @@ class AssetMaintenancesController extends Controller
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
public function update(Request $request, $assetMaintenanceId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
|
||||
// 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($maintenance->asset)) {
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
$maintenance->supplier_id = $request->input('supplier_id');
|
||||
$maintenance->is_warranty = $request->input('is_warranty');
|
||||
$maintenance->cost = $request->input('cost');
|
||||
$maintenance->notes = $request->input('notes');
|
||||
$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('asset_id'));
|
||||
|
||||
@@ -189,39 +200,39 @@ class AssetMaintenancesController extends Controller
|
||||
}
|
||||
|
||||
// Save the asset maintenance data
|
||||
$maintenance->asset_id = $request->input('asset_id');
|
||||
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||
$maintenance->title = $request->input('title');
|
||||
$maintenance->start_date = $request->input('start_date');
|
||||
$maintenance->completion_date = $request->input('completion_date');
|
||||
$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 (($maintenance->completion_date == null)
|
||||
if (($assetMaintenance->completion_date == null)
|
||||
) {
|
||||
if (($maintenance->asset_maintenance_time !== 0)
|
||||
|| (! is_null($maintenance->asset_maintenance_time))
|
||||
if (($assetMaintenance->asset_maintenance_time !== 0)
|
||||
|| (! is_null($assetMaintenance->asset_maintenance_time))
|
||||
) {
|
||||
$maintenance->asset_maintenance_time = null;
|
||||
$assetMaintenance->asset_maintenance_time = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (($maintenance->completion_date !== null)
|
||||
&& ($maintenance->start_date !== '')
|
||||
&& ($maintenance->start_date !== '0000-00-00')
|
||||
if (($assetMaintenance->completion_date !== null)
|
||||
&& ($assetMaintenance->start_date !== '')
|
||||
&& ($assetMaintenance->start_date !== '0000-00-00')
|
||||
) {
|
||||
$startDate = Carbon::parse($maintenance->start_date);
|
||||
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
$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 ($maintenance->save()) {
|
||||
if ($assetMaintenance->save()) {
|
||||
|
||||
// Redirect to the new asset maintenance page
|
||||
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($maintenance->getErrors());
|
||||
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,13 +271,19 @@ class AssetMaintenancesController extends Controller
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
public function show($assetMaintenanceId) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
|
||||
return view('asset_maintenances/view')->with('assetMaintenance', $assetMaintenance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +109,16 @@ class AssetModelsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
*/
|
||||
public function edit(AssetModel $model) : View | RedirectResponse
|
||||
public function edit($modelId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
$category_type = 'asset';
|
||||
return view('models/edit', compact('category_type'))->with('item', $model)->with('depreciation_list', Helper::depreciationList());
|
||||
if ($item = AssetModel::find($modelId)) {
|
||||
$category_type = 'asset';
|
||||
return view('models/edit', compact('item', 'category_type'))->with('depreciation_list', Helper::depreciationList());
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
@@ -128,11 +133,16 @@ class AssetModelsController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(StoreAssetModelRequest $request, AssetModel $model) : RedirectResponse
|
||||
public function update(StoreAssetModelRequest $request, $modelId) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
|
||||
if (is_null($model = AssetModel::find($modelId))) {
|
||||
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');
|
||||
@@ -178,16 +188,28 @@ class AssetModelsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
*/
|
||||
public function destroy(AssetModel $model) : RedirectResponse
|
||||
public function destroy($modelId) : 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);
|
||||
$model->update(['image' => null]);
|
||||
} catch (\Exception $e) {
|
||||
Log::info($e);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the model
|
||||
$model->delete();
|
||||
|
||||
@@ -245,10 +267,16 @@ class AssetModelsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
*/
|
||||
public function show(AssetModel $model) : View | RedirectResponse
|
||||
public function show($modelId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', AssetModel::class);
|
||||
return view('models/view', compact('model'));
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,20 +286,23 @@ class AssetModelsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
*/
|
||||
public function getClone(AssetModel $model) : View | RedirectResponse
|
||||
public function getClone($modelId = null) : 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'));
|
||||
}
|
||||
|
||||
$cloned_model = clone $model;
|
||||
$model = clone $model_to_clone;
|
||||
$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->id)
|
||||
->with('clone_model', $cloned_model);
|
||||
->with('model_id', $model_to_clone->id)
|
||||
->with('clone_model', $model_to_clone);
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +321,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that allows the user to bulk edit model attributes
|
||||
* Returns a view that allows the user to bulk edit model attrbutes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.7]
|
||||
|
||||
@@ -58,9 +58,11 @@ class AssetModelsFilesController extends Controller
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
|
||||
public function show($modelId = null, $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)) {
|
||||
@@ -85,6 +87,12 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,21 +103,29 @@ class AssetModelsFilesController extends Controller
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function destroy(AssetModel $model, $fileId = null) : RedirectResponse
|
||||
public function destroy($modelId = null, $fileId = null) : RedirectResponse
|
||||
{
|
||||
$rel_path = 'private_uploads/assetmodels';
|
||||
$model = AssetModel::find($modelId);
|
||||
$this->authorize('update', $model);
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
$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()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
$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'));
|
||||
|
||||
// Redirect to the hardware management page
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AssetCheckinController extends Controller
|
||||
{
|
||||
@@ -28,12 +27,18 @@ class AssetCheckinController extends Controller
|
||||
* @param string $backto
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function create(Asset $asset, $backto = null) : View | RedirectResponse
|
||||
public function create($assetId, $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'));
|
||||
}
|
||||
@@ -42,23 +47,7 @@ class AssetCheckinController extends Controller
|
||||
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
|
||||
}
|
||||
|
||||
// Invoke the validation to see if the audit will complete successfully
|
||||
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
|
||||
|
||||
if ($asset->isInvalid()) {
|
||||
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
$target_option = match ($asset->assigned_type) {
|
||||
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset_previous')]),
|
||||
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
|
||||
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
|
||||
};
|
||||
return view('hardware/checkin', compact('asset', 'target_option'))
|
||||
->with('item', $asset)
|
||||
->with('statusLabel_list', Helper::statusLabelList())
|
||||
->with('backto', $backto)
|
||||
->with('table_name', 'Assets');
|
||||
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,12 +77,9 @@ class AssetCheckinController extends Controller
|
||||
|
||||
$this->authorize('checkin', $asset);
|
||||
|
||||
session()->put('checkedInFrom', $asset->assignedTo->id);
|
||||
session()->put('checkout_to_type', match ($asset->assigned_type) {
|
||||
'App\Models\User' => 'user',
|
||||
'App\Models\Location' => 'location',
|
||||
'App\Models\Asset' => 'asset',
|
||||
});
|
||||
if ($asset->assignedType() == Asset::USER) {
|
||||
$user = $asset->assignedTo;
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkin = now();
|
||||
@@ -105,9 +91,6 @@ 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;
|
||||
@@ -145,9 +128,6 @@ class AssetCheckinController extends Controller
|
||||
|
||||
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));
|
||||
|
||||
@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AssetCheckoutController extends Controller
|
||||
{
|
||||
@@ -27,33 +26,27 @@ class AssetCheckoutController extends Controller
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(Asset $asset) : View | RedirectResponse
|
||||
public function create($assetId) : 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)
|
||||
->with('error', trans('admin/hardware/general.model_invalid_fix'));
|
||||
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
|
||||
}
|
||||
|
||||
// Invoke the validation to see if the audit will complete successfully
|
||||
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
|
||||
|
||||
if ($asset->isInvalid()) {
|
||||
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
|
||||
if ($asset->availableForCheckout()) {
|
||||
return view('hardware/checkout', compact('asset'))
|
||||
->with('statusLabel_list', Helper::deployableStatusLabelList())
|
||||
->with('table_name', 'Assets')
|
||||
->with('item', $asset);
|
||||
->with('table_name', 'Assets');
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +68,7 @@ class AssetCheckoutController extends Controller
|
||||
$this->authorize('checkout', $asset);
|
||||
|
||||
if (!$asset->model) {
|
||||
return redirect()->route('hardware.show', $asset)->with('error', trans('admin/hardware/general.model_invalid_fix'));
|
||||
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
|
||||
}
|
||||
|
||||
$admin = auth()->user();
|
||||
@@ -98,7 +91,6 @@ 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){
|
||||
@@ -108,26 +100,23 @@ 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()->route('hardware.checkout.create', $asset)->with('error', trans('general.error_user_company'));
|
||||
return redirect()->to("hardware/$assetId/checkout")->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 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()->route("hardware.checkout.create", $asset)->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
|
||||
return redirect()->to("hardware/$assetId/checkout")->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) {
|
||||
|
||||
@@ -26,8 +26,11 @@ class AssetFilesController extends Controller
|
||||
*@since [v1.0]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse
|
||||
public function store(UploadFileRequest $request, $assetId = null) : RedirectResponse
|
||||
{
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
@@ -56,28 +59,31 @@ class AssetFilesController extends Controller
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
|
||||
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
|
||||
{
|
||||
if ($asset = Asset::find($assetId)) {
|
||||
|
||||
$this->authorize('view', $asset);
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
|
||||
$file = 'private_uploads/assets/'.$log->filename;
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found'));
|
||||
}
|
||||
|
||||
if ($log->action_type == 'audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
try {
|
||||
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found'));
|
||||
}
|
||||
|
||||
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
|
||||
}
|
||||
|
||||
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
|
||||
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
|
||||
}
|
||||
|
||||
@@ -89,20 +95,23 @@ class AssetFilesController extends Controller
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function destroy(Asset $asset, $fileId = null) : RedirectResponse
|
||||
public function destroy($assetId = null, $fileId = null) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $asset);
|
||||
$rel_path = 'private_uploads/assets';
|
||||
if ($asset = Asset::find($assetId)) {
|
||||
$this->authorize('update', $asset);
|
||||
$rel_path = 'private_uploads/assets';
|
||||
|
||||
if ($log = Actionlog::find($fileId)) {
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
if ($log = Actionlog::find($fileId)) {
|
||||
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'));
|
||||
}
|
||||
$log->delete();
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
|
||||
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
|
||||
}
|
||||
|
||||
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Events\CheckoutableCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Requests\UpdateAssetRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -31,7 +30,6 @@ 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
|
||||
@@ -203,7 +201,7 @@ class AssetsController extends Controller
|
||||
$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);
|
||||
}
|
||||
|
||||
$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
|
||||
$successes[] = "<a href='" . route('hardware.show', ['hardware' => $asset->id]) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
|
||||
|
||||
} else {
|
||||
$failures[] = join(",", $asset->getErrors()->all());
|
||||
@@ -224,7 +222,7 @@ class AssetsController extends Controller
|
||||
//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)]));
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
|
||||
} else {
|
||||
//multi-success
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
@@ -242,14 +240,20 @@ class AssetsController extends Controller
|
||||
* Returns a view that presents a form to edit an existing asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Asset $asset) : View | RedirectResponse
|
||||
public function edit($assetId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize($asset);
|
||||
return view('hardware/edit')
|
||||
->with('item', $asset)
|
||||
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'))
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('statuslabel_types', Helper::statusTypeList());
|
||||
}
|
||||
@@ -263,14 +267,15 @@ class AssetsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show(Asset $asset) : View | RedirectResponse
|
||||
public function show($assetId = null) : 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', '=', $asset->id)
|
||||
->where('item_id', '=', $assetId)
|
||||
->where('item_type', '=', Asset::class)
|
||||
->orderBy('created_at', 'DESC')->first();
|
||||
|
||||
@@ -286,7 +291,7 @@ class AssetsController extends Controller
|
||||
|
||||
$qr_code = (object) [
|
||||
'display' => $settings->qr_code == '1',
|
||||
'url' => route('qr_code/hardware', $asset),
|
||||
'url' => route('qr_code/hardware', $asset->id),
|
||||
];
|
||||
|
||||
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
|
||||
@@ -303,9 +308,14 @@ class AssetsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Asset $asset) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $assetId = null) : 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);
|
||||
@@ -319,7 +329,7 @@ class AssetsController extends Controller
|
||||
$asset->eol_explicit = false;
|
||||
} elseif ($request->filled('asset_eol_date')) {
|
||||
$asset->asset_eol_date = $request->input('asset_eol_date', null);
|
||||
$months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true);
|
||||
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
|
||||
if($asset->model->eol) {
|
||||
if($months != $asset->model->eol > 0) {
|
||||
$asset->eol_explicit = true;
|
||||
@@ -341,16 +351,22 @@ class AssetsController extends Controller
|
||||
|
||||
$status = Statuslabel::find($request->input('status_id'));
|
||||
|
||||
// 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)) {
|
||||
// This is a non-deployable status label - we should check the asset back in.
|
||||
if (($status && $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));
|
||||
|
||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update', 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);
|
||||
@@ -386,27 +402,27 @@ class AssetsController extends Controller
|
||||
$asset = $request->handleImages($asset);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handlded through the AssetRequest form request
|
||||
// FIXME: No idea why this is returning a Builder error on db_column_name.
|
||||
// Need to investigate and fix. Using static method for now.
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,7 +430,7 @@ class AssetsController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
if ($asset->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets'))
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
|
||||
@@ -515,12 +531,12 @@ class AssetsController extends Controller
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function getQrCode(Asset $asset) : Response | BinaryFileResponse | string | bool
|
||||
public function getQrCode($assetId = null) : Response | BinaryFileResponse | string | bool
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if ($settings->label2_2d_type !== 'none') {
|
||||
|
||||
if ($settings->qr_code == '1') {
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
if ($asset) {
|
||||
$size = Helper::barcodeDimensions($settings->label2_2d_type);
|
||||
$qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png';
|
||||
@@ -574,7 +590,7 @@ class AssetsController extends Controller
|
||||
file_put_contents($barcode_file, $barcode_obj->getPngData());
|
||||
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
} catch (\Exception|TypeError $e) {
|
||||
} catch (\Exception $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');
|
||||
@@ -861,6 +877,14 @@ class AssetsController extends Controller
|
||||
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()
|
||||
{
|
||||
@@ -877,73 +901,22 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
public function audit(Asset $asset): View | RedirectResponse
|
||||
public function auditStore(UploadFileRequest $request, $id)
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$rules = [
|
||||
'location_id' => 'exists:locations,id|nullable|numeric',
|
||||
'next_audit_date' => 'date|nullable',
|
||||
];
|
||||
|
||||
// Invoke the validation to see if the audit will complete successfully
|
||||
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
|
||||
if ($asset->isInvalid()) {
|
||||
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||
}
|
||||
|
||||
$dt = Carbon::now()->addMonths($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());
|
||||
}
|
||||
$asset = Asset::findOrFail($id);
|
||||
|
||||
/**
|
||||
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
|
||||
@@ -959,11 +932,18 @@ class AssetsController extends Controller
|
||||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// Check to see if they checked the box to update the physical location,
|
||||
// not just note it in the audit notes
|
||||
if ($request->input('update_location') == '1') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
|
||||
@@ -977,8 +957,8 @@ class AssetsController extends Controller
|
||||
$file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
|
||||
}
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.audit.success'));
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
|
||||
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
|
||||
@@ -358,11 +358,7 @@ class BulkAssetsController extends Controller
|
||||
* to someone/something.
|
||||
*/
|
||||
if ($request->filled('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'));
|
||||
}
|
||||
$updated_status = Statuslabel::find($request->input('status_id'));
|
||||
|
||||
// We cannot assign a non-deployable status type if the asset is already assigned.
|
||||
// This could probably be added to a form request.
|
||||
@@ -370,7 +366,7 @@ class BulkAssetsController extends Controller
|
||||
// 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')
|
||||
|| ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
|
||||
) {
|
||||
$this->update_array['status_id'] = $updated_status->id;
|
||||
}
|
||||
@@ -529,31 +525,21 @@ class BulkAssetsController extends Controller
|
||||
$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(empty($assetIds)) {
|
||||
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) {
|
||||
if ($request->filled('ids')) {
|
||||
$assets = Asset::find($request->get('ids'));
|
||||
foreach ($assets as $asset) {
|
||||
$asset->delete();
|
||||
}
|
||||
} // endforeach
|
||||
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
|
||||
// no values given, nothing to update
|
||||
}
|
||||
|
||||
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,6 +556,7 @@ class BulkAssetsController extends Controller
|
||||
*/
|
||||
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
|
||||
{
|
||||
|
||||
$this->authorize('checkout', Asset::class);
|
||||
|
||||
try {
|
||||
@@ -583,8 +570,6 @@ class BulkAssetsController extends Controller
|
||||
|
||||
$asset_ids = array_filter($request->get('selected_assets'));
|
||||
|
||||
$assets = Asset::findOrFail($asset_ids);
|
||||
|
||||
if (request('checkout_to_type') == 'asset') {
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
if ($target->id == $asset_id) {
|
||||
@@ -604,8 +589,9 @@ class BulkAssetsController extends Controller
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
|
||||
foreach ($assets as $asset) {
|
||||
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
$asset = Asset::findOrFail($asset_id);
|
||||
$this->authorize('checkout', $asset);
|
||||
|
||||
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
|
||||
@@ -632,7 +618,7 @@ class BulkAssetsController extends Controller
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
|
||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -206,7 +206,6 @@ class LoginController extends Controller
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->email = $ldap_attr['email'];
|
||||
$user->first_name = $ldap_attr['firstname'];
|
||||
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
||||
@@ -227,7 +226,7 @@ class LoginController extends Controller
|
||||
|
||||
$strip_prefixes = [
|
||||
// IIS/AD
|
||||
// https://github.com/grokability/snipe-it/pull/5862
|
||||
// https://github.com/snipe/snipe-it/pull/5862
|
||||
'\\',
|
||||
|
||||
// Google Cloud IAP
|
||||
@@ -284,11 +283,8 @@ class LoginController extends Controller
|
||||
return redirect()->back()->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
// Set the custom lockout attempts from the env and sett the custom lockout throttle from the env.
|
||||
// We divide decayMinutes by 60 here to get minutes, since Laravel changed the default from minutes
|
||||
// to seconds, and we don't want to break limits on existing systems
|
||||
$this->maxAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
$this->decayMinutes = (config('auth.passwords.users.throttle.lockout_duration') / 60);
|
||||
$this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
$this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
|
||||
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
@@ -358,7 +354,7 @@ class LoginController extends Controller
|
||||
|
||||
// We wouldn't normally see this page if 2FA isn't enforced via the
|
||||
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
|
||||
// but let's check anyway in case there's a browser history or back button thing.
|
||||
// but let's check check anyway in case there's a browser history or back button thing.
|
||||
// While you can access this page directly, enrolling a device when 2FA isn't enforced
|
||||
// won't cause any harm.
|
||||
|
||||
@@ -436,7 +432,6 @@ class LoginController extends Controller
|
||||
|
||||
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
||||
$user->two_factor_enrolled = 1;
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->saveQuietly();
|
||||
$request->session()->put('2fa_authed', $user->id);
|
||||
|
||||
@@ -484,7 +479,6 @@ class LoginController extends Controller
|
||||
}
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
$request->session()->forget('2fa_authed');
|
||||
|
||||
if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){
|
||||
$request->session()->remove('password_hash_'.Auth::getDefaultDriver());
|
||||
@@ -525,6 +519,45 @@ class LoginController extends Controller
|
||||
return 'username';
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user after determining they are locked out.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function sendLockoutResponse(Request $request)
|
||||
{
|
||||
$seconds = $this->limiter()->availableIn(
|
||||
$this->throttleKey($request)
|
||||
);
|
||||
|
||||
$minutes = round($seconds / 60);
|
||||
|
||||
$message = trans('auth/message.throttle', ['minutes' => $minutes]);
|
||||
|
||||
return redirect()->back()
|
||||
->withInput($request->only($this->username(), 'remember'))
|
||||
->withErrors([$this->username() => $message]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override the lockout time and duration
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasTooManyLoginAttempts(Request $request)
|
||||
{
|
||||
$lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
$maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
|
||||
return $this->limiter()->tooManyAttempts(
|
||||
$this->throttleKey($request),
|
||||
$maxLoginAttempts,
|
||||
$lockoutTime
|
||||
);
|
||||
}
|
||||
|
||||
public function legacyAuthRedirect()
|
||||
{
|
||||
|
||||
@@ -71,28 +71,20 @@ class BulkAssetModelsController extends Controller
|
||||
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) {
|
||||
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
|
||||
}
|
||||
|
||||
if (($request->filled('category_id') && ($request->input('category_id') != 'NC'))) {
|
||||
$update_array['category_id'] = $request->input('category_id');
|
||||
}
|
||||
|
||||
if ($request->input('fieldset_id') != 'NC') {
|
||||
$update_array['fieldset_id'] = $request->input('fieldset_id');
|
||||
}
|
||||
|
||||
if ($request->input('depreciation_id') != 'NC') {
|
||||
$update_array['depreciation_id'] = $request->input('depreciation_id');
|
||||
}
|
||||
|
||||
if ($request->input('requestable') != '') {
|
||||
if ($request->filled('requestable') != '') {
|
||||
$update_array['requestable'] = $request->input('requestable');
|
||||
}
|
||||
|
||||
if ($request->filled('min_amt')) {
|
||||
$update_array['min_amt'] = $request->input('min_amt');
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (count($update_array) > 0) {
|
||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||
|
||||
@@ -69,7 +69,6 @@ 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->notes = $request->input('notes');
|
||||
$category->created_by = auth()->id();
|
||||
|
||||
$category = $request->handleImages($category);
|
||||
@@ -88,10 +87,14 @@ class CategoriesController extends Controller
|
||||
* @param int $categoryId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Category $category) : RedirectResponse | View
|
||||
public function edit($categoryId = null) : RedirectResponse | View
|
||||
{
|
||||
$this->authorize('update', Category::class);
|
||||
return view('categories/edit')->with('item', $category)
|
||||
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'))
|
||||
->with('category_types', Helper::categoryTypeList());
|
||||
}
|
||||
|
||||
@@ -104,10 +107,19 @@ class CategoriesController extends Controller
|
||||
* @param int $categoryId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Category $category) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $categoryId = null) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Category::class);
|
||||
if (is_null($category = Category::find($categoryId))) {
|
||||
// Redirect to the categories management page
|
||||
return redirect()->route('categories.index')->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))) {
|
||||
@@ -122,7 +134,6 @@ 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->notes = $request->input('notes');
|
||||
|
||||
$category = $request->handleImages($category);
|
||||
|
||||
@@ -145,7 +156,7 @@ class CategoriesController extends Controller
|
||||
{
|
||||
$this->authorize('delete', Category::class);
|
||||
// Check if the category exists
|
||||
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
|
||||
if (is_null($category = Category::findOrFail($categoryId))) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
|
||||
}
|
||||
|
||||
@@ -155,6 +166,7 @@ class CategoriesController extends Controller
|
||||
|
||||
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
||||
$category->delete();
|
||||
// Redirect to the locations management page
|
||||
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
||||
}
|
||||
|
||||
@@ -167,10 +179,10 @@ class CategoriesController extends Controller
|
||||
* @param $id
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show(Category $category) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Category::class);
|
||||
|
||||
if ($category = Category::find($id)) {
|
||||
if ($category->category_type == 'asset') {
|
||||
$category_type = 'hardware';
|
||||
$category_type_route = 'assets';
|
||||
@@ -185,5 +197,8 @@ 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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ 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);
|
||||
@@ -80,10 +79,16 @@ final class CompaniesController extends Controller
|
||||
* @since [v1.8]
|
||||
* @param int $companyId
|
||||
*/
|
||||
public function edit(Company $company) : View | RedirectResponse
|
||||
public function edit($companyId) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $company);
|
||||
return view('companies/edit')->with('item', $company);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,15 +99,18 @@ final class CompaniesController extends Controller
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $companyId
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Company $company) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $companyId) : 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);
|
||||
|
||||
@@ -148,9 +156,15 @@ final class CompaniesController extends Controller
|
||||
->with('success', trans('admin/companies/message.delete.success'));
|
||||
}
|
||||
|
||||
public function show(Company $company) : View | RedirectResponse
|
||||
public function show($id) : 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +107,15 @@ class ComponentsController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(Component $component)
|
||||
public function edit($componentId = null)
|
||||
{
|
||||
if ($item = Component::find($componentId)) {
|
||||
$this->authorize('update', $item);
|
||||
|
||||
$this->authorize('update', $component);
|
||||
return view('components/edit')
|
||||
->with('item', $component)
|
||||
->with('category_type', 'component');
|
||||
return view('components/edit', compact('item'))->with('category_type', 'component');
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
@@ -128,8 +130,11 @@ class ComponentsController extends Controller
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v3.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Component $component)
|
||||
public function update(ImageUploadRequest $request, $componentId = null)
|
||||
{
|
||||
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",
|
||||
@@ -196,10 +201,6 @@ 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'));
|
||||
@@ -215,9 +216,17 @@ class ComponentsController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show(Component $component)
|
||||
public function show($componentId = null)
|
||||
{
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,13 +104,15 @@ class ConsumablesController extends Controller
|
||||
* @see ConsumablesController::postEdit() method that stores the form data.
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Consumable $consumable) : View | RedirectResponse
|
||||
public function edit($consumableId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize($consumable);
|
||||
return view('consumables/edit')
|
||||
->with('item', $consumable)
|
||||
->with('category_type', 'consumable');
|
||||
if ($item = Consumable::find($consumableId)) {
|
||||
$this->authorize($item);
|
||||
|
||||
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,8 +126,11 @@ class ConsumablesController extends Controller
|
||||
* @see ConsumablesController::getEdit() method that stores the form data.
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(StoreConsumableRequest $request, Consumable $consumable)
|
||||
public function update(StoreConsumableRequest $request, $consumableId = null)
|
||||
{
|
||||
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(), [
|
||||
@@ -197,11 +202,16 @@ class ConsumablesController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show(Consumable $consumable)
|
||||
public function show($consumableId = null)
|
||||
{
|
||||
$consumable = Consumable::withCount('users as users_consumables')->find($consumable->id);
|
||||
$consumable = Consumable::withCount('users as users_consumables')->find($consumableId);
|
||||
$this->authorize($consumable);
|
||||
return view('consumables/view', compact('consumable'));
|
||||
if (isset($consumable->id)) {
|
||||
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
|
||||
|
||||
@@ -83,30 +83,27 @@ class CustomFieldsController extends Controller
|
||||
{
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$show_in_email = $request->input("show_in_email", 0);
|
||||
$display_in_user_view = $request->input("display_in_user_view", 0);
|
||||
$show_in_email = $request->get("show_in_email", 0);
|
||||
$display_in_user_view = $request->get("display_in_user_view", 0);
|
||||
|
||||
// Override the display settings if the field is encrypted
|
||||
if ($request->input("field_encrypted") == '1') {
|
||||
if ($request->get("field_encrypted") == '1') {
|
||||
$show_in_email = '0';
|
||||
$display_in_user_view = '0';
|
||||
}
|
||||
|
||||
|
||||
$field = new CustomField([
|
||||
"name" => trim($request->input("name")),
|
||||
"element" => $request->input("element"),
|
||||
"help_text" => $request->input("help_text"),
|
||||
"field_values" => $request->input("field_values"),
|
||||
"field_encrypted" => $request->input("field_encrypted", 0),
|
||||
"name" => trim($request->get("name")),
|
||||
"element" => $request->get("element"),
|
||||
"help_text" => $request->get("help_text"),
|
||||
"field_values" => $request->get("field_values"),
|
||||
"field_encrypted" => $request->get("field_encrypted", 0),
|
||||
"show_in_email" => $show_in_email,
|
||||
"is_unique" => $request->input("is_unique", 0),
|
||||
"is_unique" => $request->get("is_unique", 0),
|
||||
"display_in_user_view" => $display_in_user_view,
|
||||
"auto_add_to_fieldsets" => $request->input("auto_add_to_fieldsets", 0),
|
||||
"show_in_listview" => $request->input("show_in_listview", 0),
|
||||
"show_in_requestable_list" => $request->input("show_in_requestable_list", 0),
|
||||
"display_checkin" => $request->input("display_checkin", 0),
|
||||
"display_checkout" => $request->input("display_checkout", 0),
|
||||
"display_audit" => $request->input("display_audit", 0),
|
||||
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
|
||||
"show_in_listview" => $request->get("show_in_listview", 0),
|
||||
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
|
||||
"created_by" => auth()->id()
|
||||
]);
|
||||
|
||||
@@ -196,8 +193,10 @@ class CustomFieldsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function edit(Request $request, CustomField $field) : View | RedirectResponse
|
||||
public function edit(Request $request, $id) : View | RedirectResponse
|
||||
{
|
||||
if ($field = CustomField::find($id)) {
|
||||
|
||||
$this->authorize('update', $field);
|
||||
$fieldsets = CustomFieldset::get();
|
||||
$customFormat = '';
|
||||
@@ -211,7 +210,11 @@ class CustomFieldsController extends Controller
|
||||
'fieldsets' => $fieldsets,
|
||||
'predefinedFormats' => Helper::predefined_formats(),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route("fields.index")
|
||||
->with("error", trans('admin/custom_fields/message.field.invalid'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -226,9 +229,13 @@ class CustomFieldsController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
|
||||
public function update(CustomFieldRequest $request, $id) : 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);
|
||||
|
||||
@@ -238,8 +245,8 @@ class CustomFieldsController extends Controller
|
||||
$display_in_user_view = '0';
|
||||
}
|
||||
|
||||
$field->name = trim($request->get("name"));
|
||||
$field->element = $request->get("element");
|
||||
$field->name = trim(e($request->get("name")));
|
||||
$field->element = e($request->get("element"));
|
||||
$field->field_values = $request->get("field_values");
|
||||
$field->created_by = auth()->id();
|
||||
$field->help_text = $request->get("help_text");
|
||||
@@ -249,14 +256,11 @@ 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 = $request->get('custom_format');
|
||||
$field->format = e($request->get('custom_format'));
|
||||
} else {
|
||||
$field->format = $request->get('format');
|
||||
$field->format = e($request->get('format'));
|
||||
}
|
||||
|
||||
if ($field->element == 'checkbox' || $field->element == 'radio'){
|
||||
|
||||
@@ -35,12 +35,10 @@ class CustomFieldsetsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show(CustomFieldset $fieldset) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
$cfset = CustomFieldset::with('fields')
|
||||
->where('id', '=', $fieldset->id)
|
||||
->orderBy('id', 'ASC')
|
||||
->first();
|
||||
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
|
||||
|
||||
$this->authorize('view', $cfset);
|
||||
|
||||
@@ -124,10 +122,16 @@ class CustomFieldsetsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v6.0.14]
|
||||
*/
|
||||
public function edit(CustomFieldset $fieldset) : View | RedirectResponse
|
||||
public function edit($id) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('create', CustomField::class);
|
||||
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
|
||||
|
||||
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]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,18 +141,23 @@ class CustomFieldsetsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v6.0.14]
|
||||
*/
|
||||
public function update(Request $request, CustomFieldset $fieldset) : RedirectResponse
|
||||
public function update(Request $request, $id) : RedirectResponse
|
||||
{
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$fieldset->name = $request->input('name');
|
||||
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());
|
||||
|
||||
if ($fieldset->save()) {
|
||||
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
|
||||
|
||||
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@ class DashboardController extends Controller
|
||||
|
||||
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
Artisan::call('passport:install', ['--no-interaction' => true]);
|
||||
\Artisan::call('passport:install');
|
||||
}
|
||||
|
||||
return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts);
|
||||
|
||||
@@ -55,7 +55,6 @@ class DepartmentsController extends Controller
|
||||
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
|
||||
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
|
||||
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
|
||||
$department->notes = $request->input('notes');
|
||||
$department = $request->handleImages($department);
|
||||
|
||||
if ($department->save()) {
|
||||
@@ -73,10 +72,17 @@ class DepartmentsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function show(Department $department) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
$department = Department::find($id);
|
||||
|
||||
$this->authorize('view', $department);
|
||||
return view('departments/view', compact('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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,10 +138,15 @@ class DepartmentsController extends Controller
|
||||
* @param int $departmentId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Department $department) : View | RedirectResponse
|
||||
public function edit($departmentId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $department);
|
||||
return view('departments/edit')->with('item', $department);
|
||||
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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,8 +157,11 @@ class DepartmentsController extends Controller
|
||||
* @param int $departmentId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Department $department) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $id) : 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);
|
||||
|
||||
@@ -157,7 +171,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()) {
|
||||
|
||||
@@ -95,11 +95,17 @@ class DepreciationsController extends Controller
|
||||
* @param int $depreciationId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Depreciation $depreciation) : RedirectResponse | View
|
||||
public function edit($depreciationId = null) : 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', $depreciation);
|
||||
return view('depreciations/edit')->with('item', $depreciation);
|
||||
$this->authorize('update', $item);
|
||||
|
||||
return view('depreciations/edit', compact('item'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,10 +117,17 @@ class DepreciationsController extends Controller
|
||||
* @param int $depreciationId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(Request $request, Depreciation $depreciation) : RedirectResponse
|
||||
public function update(Request $request, $depreciationId = null) : 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');
|
||||
|
||||
@@ -178,12 +191,12 @@ class DepreciationsController extends Controller
|
||||
* @param int $depreciationId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Depreciation $depreciation) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
$depreciation = Depreciation::withCount('assets as assets_count')
|
||||
->withCount('models as models_count')
|
||||
->withCount('licenses as licenses_count')
|
||||
->find($depreciation->id);
|
||||
->find($id);
|
||||
|
||||
$this->authorize('view', $depreciation);
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ class GroupsController extends Controller
|
||||
$group->name = $request->input('name');
|
||||
$group->permissions = json_encode($request->input('permission'));
|
||||
$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'));
|
||||
@@ -79,16 +78,19 @@ class GroupsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Group $group) : View | RedirectResponse
|
||||
public function edit($id) : View | RedirectResponse
|
||||
{
|
||||
$permissions = config('permissions');
|
||||
$groupPermissions = $group->decodePermissions();
|
||||
$group = Group::find($id);
|
||||
|
||||
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
|
||||
$groupPermissions = [];
|
||||
if ($group) {
|
||||
$permissions = config('permissions');
|
||||
$groupPermissions = $group->decodePermissions();
|
||||
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
|
||||
|
||||
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
|
||||
}
|
||||
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
|
||||
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
|
||||
|
||||
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,11 +101,13 @@ class GroupsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(Request $request, Group $group) : RedirectResponse
|
||||
public function update(Request $request, $id = null) : 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()) {
|
||||
@@ -145,8 +149,14 @@ class GroupsController extends Controller
|
||||
* @param $id
|
||||
* @since [v4.0.11]
|
||||
*/
|
||||
public function show(Group $group) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
return view('groups/view', compact('group'));
|
||||
$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]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace App\Http\Controllers\Kits;
|
||||
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\PredefinedKit;
|
||||
use App\Models\Asset;
|
||||
use App\Models\PredefinedLicence;
|
||||
use App\Models\PredefinedModel;
|
||||
use App\Models\User;
|
||||
use App\Services\PredefinedKitCheckoutService;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -33,9 +35,12 @@ class CheckoutKitController extends Controller
|
||||
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
|
||||
* @return \Illuminate\Contracts\View\View View to checkout
|
||||
*/
|
||||
public function showCheckout(PredefinedKit $kit)
|
||||
public function showCheckout($kit_id)
|
||||
{
|
||||
$this->authorize('checkout', Asset::class);
|
||||
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
return view('kits/checkout')->with('kit', $kit);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,15 +76,17 @@ class PredefinedKitsController extends Controller
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(PredefinedKit $kit)
|
||||
public function edit($kit_id = null)
|
||||
{
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,9 +98,15 @@ class PredefinedKitsController extends Controller
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, PredefinedKit $kit)
|
||||
public function update(ImageUploadRequest $request, $kit_id = null)
|
||||
{
|
||||
$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()) {
|
||||
@@ -145,9 +153,9 @@ class PredefinedKitsController extends Controller
|
||||
* @param int $modelId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show(PredefinedKit $kit)
|
||||
public function show($kit_id = null)
|
||||
{
|
||||
return $this->edit($kit);
|
||||
return $this->edit($kit_id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,6 @@ class LabelsController extends Controller
|
||||
$exampleAsset->order_number = '12345';
|
||||
$exampleAsset->purchase_date = '2023-01-01';
|
||||
$exampleAsset->status_id = 1;
|
||||
$exampleAsset->location_id = 1;
|
||||
|
||||
$exampleAsset->company = new Company([
|
||||
'name' => trans('admin/labels/table.example_company'),
|
||||
|
||||
@@ -28,11 +28,16 @@ class LicenseCheckinController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create(LicenseSeat $licenseSeat, $backTo = null)
|
||||
public function create($seatId = null, $backTo = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
$license = License::find($licenseSeat->license_id);
|
||||
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'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $license);
|
||||
|
||||
return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo);
|
||||
}
|
||||
|
||||
@@ -87,7 +92,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -28,24 +28,33 @@ class LicenseCheckoutController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create(License $license)
|
||||
public function create($id)
|
||||
{
|
||||
$this->authorize('checkout', $license);
|
||||
|
||||
if ($license->category) {
|
||||
if ($license = License::find($id)) {
|
||||
|
||||
// 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'));
|
||||
$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'));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('licenses/checkout', compact('license'));
|
||||
// Invalid category
|
||||
return redirect()->route('licenses.edit', ['license' => $license->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
|
||||
|
||||
}
|
||||
|
||||
// Invalid category
|
||||
return redirect()->route('licenses.edit', ['license' => $license->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
|
||||
// Not found
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -121,10 +121,13 @@ class LicensesController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(License $license)
|
||||
public function edit($licenseId = null)
|
||||
{
|
||||
if (is_null($item = License::find($licenseId))) {
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('update', $license);
|
||||
$this->authorize('update', $item);
|
||||
|
||||
$maintained_list = [
|
||||
'' => 'Maintained',
|
||||
@@ -132,8 +135,7 @@ class LicensesController extends Controller
|
||||
'0' => 'No',
|
||||
];
|
||||
|
||||
return view('licenses/edit')
|
||||
->with('item', $license)
|
||||
return view('licenses/edit', compact('item'))
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('maintained_list', $maintained_list);
|
||||
}
|
||||
@@ -151,9 +153,11 @@ class LicensesController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(Request $request, License $license)
|
||||
public function update(Request $request, $licenseId = null)
|
||||
{
|
||||
|
||||
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);
|
||||
|
||||
@@ -197,10 +201,10 @@ class LicensesController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy(License $license)
|
||||
public function destroy($licenseId)
|
||||
{
|
||||
// Check if the license exists
|
||||
if (is_null($license = License::find($license->id))) {
|
||||
if (is_null($license = License::find($licenseId))) {
|
||||
// Redirect to the license management page
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||
}
|
||||
@@ -234,9 +238,14 @@ class LicensesController extends Controller
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show(License $license)
|
||||
public function show($licenseId = null)
|
||||
{
|
||||
$license = License::with('assignedusers')->find($license->id);
|
||||
$license = License::with('assignedusers')->find($licenseId);
|
||||
|
||||
if (!$license) {
|
||||
return redirect()->route('licenses.index')
|
||||
->with('error', trans('admin/licenses/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$users_count = User::where('autoassign_licenses', '1')->count();
|
||||
$total_seats_count = $license->totalSeatsByLicenseID();
|
||||
@@ -258,10 +267,10 @@ class LicensesController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $licenseId
|
||||
* @return \Illuminate\Http\RedirectResponse | \Illuminate\Contracts\View\View
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getClone($licenseId = null) : \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
|
||||
public function getClone($licenseId = null)
|
||||
{
|
||||
if (is_null($license_to_clone = License::find($licenseId))) {
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -66,7 +63,6 @@ class LocationsController extends Controller
|
||||
public function store(ImageUploadRequest $request) : RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Location::class);
|
||||
|
||||
$location = new Location();
|
||||
$location->name = $request->input('name');
|
||||
$location->parent_id = $request->input('parent_id', null);
|
||||
@@ -82,19 +78,6 @@ class LocationsController extends Controller
|
||||
$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);
|
||||
|
||||
@@ -113,10 +96,15 @@ class LocationsController extends Controller
|
||||
* @param int $locationId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Location $location) : View | RedirectResponse
|
||||
public function edit($locationId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Location::class);
|
||||
return view('locations/edit')->with('item', $location);
|
||||
// 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,10 +116,15 @@ class LocationsController extends Controller
|
||||
* @param int $locationId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Location $location) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $locationId = null) : 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', '$');
|
||||
@@ -145,18 +138,6 @@ 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);
|
||||
|
||||
@@ -177,7 +158,6 @@ class LocationsController extends Controller
|
||||
public function destroy($locationId) : RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', Location::class);
|
||||
|
||||
if (is_null($location = Location::find($locationId))) {
|
||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
@@ -212,17 +192,15 @@ class LocationsController extends Controller
|
||||
* @param int $id
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Location $location) : View | RedirectResponse
|
||||
public function show($id = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
|
||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')
|
||||
->withTrashed()
|
||||
->find($location->id);
|
||||
->find($id);
|
||||
|
||||
if (isset($location->id)) {
|
||||
return view('locations/view', compact('location'));
|
||||
@@ -233,24 +211,20 @@ class LocationsController extends Controller
|
||||
|
||||
public function print_assigned($id) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$company = Company::where('id', $location->company_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
||||
return view('locations/print')
|
||||
->with('assets', $assets)
|
||||
->with('users',$users)
|
||||
->with('location', $location)
|
||||
->with('parent', $parent)
|
||||
->with('manager', $manager)
|
||||
->with('company', $company);
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -319,20 +293,13 @@ class LocationsController extends Controller
|
||||
}
|
||||
public function print_all_assigned($id) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$company = Company::where('id', $location->company_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
||||
return view('locations/print')
|
||||
->with('assets', $assets)
|
||||
->with('users',$users)
|
||||
->with('location', $location)
|
||||
->with('parent', $parent)
|
||||
->with('manager', $manager)
|
||||
->with('company', $company);
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
}
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
@@ -346,8 +313,6 @@ class LocationsController extends Controller
|
||||
*/
|
||||
public function postBulkDelete(Request $request) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Location::class);
|
||||
|
||||
$locations_raw_array = $request->input('ids');
|
||||
|
||||
// Make sure some IDs have been selected
|
||||
@@ -381,8 +346,6 @@ class LocationsController extends Controller
|
||||
*/
|
||||
public function postBulkDeleteStore(Request $request) : RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', Location::class);
|
||||
|
||||
$locations_raw_array = $request->input('ids');
|
||||
|
||||
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use \Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class LocationsFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Upload a file to the server.
|
||||
*
|
||||
* @param UploadFileRequest $request
|
||||
* @param int $modelId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@since [v1.0]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, Location $location) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $location);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
|
||||
if (! Storage::exists('private_uploads/locations')) {
|
||||
Storage::makeDirectory('private_uploads/locations', 775);
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file);
|
||||
$location->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $modelId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
|
||||
{
|
||||
|
||||
$this->authorize('view', $location);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return redirect()->back()->withFragment('files')->with('error', 'No matching file record');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/locations/'.$log->filename;
|
||||
|
||||
if (! Storage::exists($file)) {
|
||||
return redirect()->back()->withFragment('files')->with('error', 'No matching file on server');
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $modelId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function destroy(Location $location, $fileId = null) : RedirectResponse
|
||||
{
|
||||
$rel_path = 'private_uploads/locations';
|
||||
$this->authorize('update', $location);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
if ($log) {
|
||||
|
||||
// This should be moved to purge
|
||||
// if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
// Storage::delete($rel_path.'/'.$log->filename);
|
||||
// }
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Manufacturer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -32,30 +31,7 @@ class ManufacturersController extends Controller
|
||||
public function index() : View
|
||||
{
|
||||
$this->authorize('index', Manufacturer::class);
|
||||
$manufacturer_count = Manufacturer::withTrashed()->count();
|
||||
return view('manufacturers/index')->with('manufacturer_count', $manufacturer_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the manufacturers listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see Api\ManufacturersController::index() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function seed() : RedirectResponse
|
||||
{
|
||||
$this->authorize('index', Manufacturer::class);
|
||||
|
||||
$manufacturers_count = Manufacturer::withTrashed()->count();
|
||||
|
||||
if ($manufacturers_count == 0) {
|
||||
Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']);
|
||||
return redirect()->route('manufacturers.index')->with('success', trans('general.seeding.manufacturers.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('manufacturers.index')->with('error', trans_choice('general.seeding.manufacturers.error', ['count' => $manufacturers_count]));
|
||||
return view('manufacturers/index');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +67,6 @@ class ManufacturersController extends Controller
|
||||
$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()) {
|
||||
@@ -109,10 +84,18 @@ class ManufacturersController extends Controller
|
||||
* @param int $manufacturerId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function edit(Manufacturer $manufacturer) : View | RedirectResponse
|
||||
public function edit($manufacturerId = null) : View | RedirectResponse
|
||||
{
|
||||
// Handles manufacturer checks and permissions.
|
||||
$this->authorize('update', Manufacturer::class);
|
||||
return view('manufacturers/edit')->with('item', $manufacturer);
|
||||
|
||||
// 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'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,17 +107,22 @@ class ManufacturersController extends Controller
|
||||
* @param int $manufacturerId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Manufacturer $manufacturer) : RedirectResponse
|
||||
public function update(ImageUploadRequest $request, $manufacturerId = null) : 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) {
|
||||
@@ -195,10 +183,18 @@ class ManufacturersController extends Controller
|
||||
* @param int $manufacturerId
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function show(Manufacturer $manufacturer) : View | RedirectResponse
|
||||
public function show($manufacturerId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Manufacturer::class);
|
||||
return view('manufacturers/view', compact('manufacturer'));
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,11 +41,10 @@ class ModalController extends Controller
|
||||
$view = view("modals.${type}");
|
||||
|
||||
if ($type == "statuslabel") {
|
||||
$view->with('statuslabel_types', Helper::statusTypeList());
|
||||
}
|
||||
|
||||
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
|
||||
$view->with('kitId', $itemId);
|
||||
$view->with('statuslabel_types', Helper::statusTypeList());
|
||||
}
|
||||
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
|
||||
$view->with('kitId', $itemId);
|
||||
}
|
||||
return $view;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class NotesController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->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'));
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class ProfileController extends Controller
|
||||
$user->enable_confetti = $request->input('enable_confetti', false);
|
||||
|
||||
if (! config('app.lock_passwords')) {
|
||||
$user->locale = $request->input('locale');
|
||||
$user->locale = $request->input('locale', 'en-US');
|
||||
}
|
||||
|
||||
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
|
||||
@@ -136,7 +136,7 @@ class ProfileController extends Controller
|
||||
}
|
||||
|
||||
// This checks to make sure that the user's password isn't the same as their username,
|
||||
// email address, first name or last name (see https://github.com/grokability/snipe-it/issues/8661)
|
||||
// email address, first name or last name (see https://github.com/snipe/snipe-it/issues/8661)
|
||||
// While this is handled via SaveUserRequest form request in other places, we have to do this manually
|
||||
// here because we don't have the username, etc form fields available in the profile password change
|
||||
// form.
|
||||
|
||||
@@ -243,7 +243,7 @@ class ReportsController extends Controller
|
||||
|
||||
$header = [
|
||||
trans('general.date'),
|
||||
trans('general.created_by'),
|
||||
trans('general.admin'),
|
||||
trans('general.action'),
|
||||
trans('general.type'),
|
||||
trans('general.item'),
|
||||
@@ -266,7 +266,7 @@ class ReportsController extends Controller
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->chunk(500, function ($actionlogs) use ($handle) {
|
||||
->chunk(20, function ($actionlogs) use ($handle) {
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
Log::debug('Walking results: '.$executionTime);
|
||||
$count = 0;
|
||||
@@ -485,7 +485,7 @@ class ReportsController extends Controller
|
||||
$header[] = trans('admin/hardware/table.purchase_date');
|
||||
}
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) {
|
||||
$header[] = trans('admin/hardware/table.purchase_cost');
|
||||
}
|
||||
|
||||
@@ -493,17 +493,6 @@ class ReportsController extends Controller
|
||||
$header[] = trans('admin/hardware/table.eol');
|
||||
}
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$header[] = trans('admin/hardware/form.warranty');
|
||||
$header[] = trans('admin/hardware/form.warranty_expires');
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
$header[] = trans('admin/hardware/table.book_value');
|
||||
$header[] = trans('admin/hardware/table.diff');
|
||||
$header[] = trans('admin/hardware/form.fully_depreciated');
|
||||
}
|
||||
|
||||
if ($request->filled('order')) {
|
||||
$header[] = trans('admin/hardware/form.order');
|
||||
}
|
||||
@@ -590,6 +579,17 @@ class ReportsController extends Controller
|
||||
$header[] = trans('general.status');
|
||||
}
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$header[] = trans('admin/hardware/form.warranty');
|
||||
$header[] = trans('admin/hardware/form.warranty_expires');
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
$header[] = trans('admin/hardware/table.book_value');
|
||||
$header[] = trans('admin/hardware/table.diff');
|
||||
$header[] = trans('admin/hardware/form.fully_depreciated');
|
||||
}
|
||||
|
||||
if ($request->filled('checkout_date')) {
|
||||
$header[] = trans('admin/hardware/table.checkout_date');
|
||||
}
|
||||
@@ -737,11 +737,6 @@ class ReportsController extends Controller
|
||||
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
||||
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
|
||||
}
|
||||
|
||||
if (($request->filled('last_updated_start')) && ($request->filled('last_updated_end'))) {
|
||||
$assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]);
|
||||
}
|
||||
|
||||
if ($request->filled('exclude_archived')) {
|
||||
$assets->notArchived();
|
||||
}
|
||||
@@ -753,7 +748,7 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
Log::debug($assets->toSql());
|
||||
$assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) {
|
||||
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
Log::debug('Walking results: '.$executionTime);
|
||||
@@ -810,19 +805,6 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : '';
|
||||
}
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
|
||||
$row[] = $asset->present()->warranty_expires();
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
$depreciation = $asset->getDepreciatedValue();
|
||||
$diff = ($asset->purchase_cost - $depreciation);
|
||||
$row[] = Helper::formatCurrencyOutput($depreciation);
|
||||
$row[] = Helper::formatCurrencyOutput($diff);
|
||||
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
|
||||
}
|
||||
|
||||
if ($request->filled('order')) {
|
||||
$row[] = ($asset->order_number) ? $asset->order_number : '';
|
||||
}
|
||||
@@ -956,6 +938,19 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
|
||||
}
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
|
||||
$row[] = $asset->present()->warranty_expires();
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
$depreciation = $asset->getDepreciatedValue();
|
||||
$diff = ($asset->purchase_cost - $depreciation);
|
||||
$row[] = Helper::formatCurrencyOutput($depreciation);
|
||||
$row[] = Helper::formatCurrencyOutput($diff);
|
||||
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
|
||||
}
|
||||
|
||||
if ($request->filled('checkout_date')) {
|
||||
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
|
||||
}
|
||||
@@ -1086,10 +1081,10 @@ class ReportsController extends Controller
|
||||
$row[] = e($assetMaintenance->start_date);
|
||||
$row[] = e($assetMaintenance->completion_date);
|
||||
if (is_null($assetMaintenance->asset_maintenance_time)) {
|
||||
$improvementTime = (int) Carbon::now()
|
||||
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
|
||||
$improvementTime = intval(Carbon::now()
|
||||
->diffInDays(Carbon::parse($assetMaintenance->start_date)));
|
||||
} else {
|
||||
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
|
||||
$improvementTime = intval($assetMaintenance->asset_maintenance_time);
|
||||
}
|
||||
$row[] = $improvementTime;
|
||||
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
|
||||
@@ -1180,13 +1175,18 @@ class ReportsController extends Controller
|
||||
}
|
||||
$email = $assetItem->assignedTo?->email;
|
||||
$locale = $assetItem->assignedTo?->locale;
|
||||
// Only send notification if assigned
|
||||
if ($locale && $email) {
|
||||
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note))->locale($locale));
|
||||
|
||||
if (is_null($email) || $email === '') {
|
||||
} elseif ($email) {
|
||||
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)));
|
||||
}
|
||||
|
||||
if ($email == ''){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
|
||||
}
|
||||
|
||||
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
|
||||
|
||||
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
|
||||
}
|
||||
|
||||
|
||||
34
app/Http/Controllers/SettingsController.php
Normal file → Executable file
34
app/Http/Controllers/SettingsController.php
Normal file → Executable file
@@ -14,7 +14,6 @@ use App\Http\Requests\StoreLabelSettings;
|
||||
use App\Http\Requests\StoreSecuritySettings;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Group;
|
||||
use App\Models\Labels\Label as LabelModel;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
@@ -257,7 +256,7 @@ class SettingsController extends Controller
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
|
||||
Artisan::call('migrate', ['--path' => 'vendor/laravel/passport/database/migrations', '--force' => true]);
|
||||
Artisan::call('passport:install', ['--no-interaction' => true]);
|
||||
Artisan::call('passport:install');
|
||||
}
|
||||
|
||||
return view('setup/migrate')
|
||||
@@ -291,6 +290,7 @@ class SettingsController extends Controller
|
||||
public function getSettings() : View
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
return view('settings/general', compact('setting'));
|
||||
}
|
||||
|
||||
@@ -314,23 +314,7 @@ class SettingsController extends Controller
|
||||
$setting->modellist_displays = implode(',', $request->input('show_in_model_list'));
|
||||
}
|
||||
|
||||
$old_locations_fmcs = $setting->scope_locations_fmcs;
|
||||
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
|
||||
$setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0');
|
||||
|
||||
// Backward compatibility for locations makes no sense without FullMultipleCompanySupport
|
||||
if (!$setting->full_multiple_companies_support) {
|
||||
$setting->scope_locations_fmcs = '0';
|
||||
}
|
||||
|
||||
// check for inconsistencies when activating scoped locations
|
||||
if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') {
|
||||
$mismatched = Helper::test_locations_fmcs(false);
|
||||
if (count($mismatched) != 0) {
|
||||
return redirect()->back()->withInput()->with('error', trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)).' '.trans('admin/settings/message.location_scoping.not_saved'));
|
||||
}
|
||||
}
|
||||
|
||||
$setting->unique_serial = $request->input('unique_serial', '0');
|
||||
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
|
||||
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
|
||||
@@ -444,13 +428,6 @@ class SettingsController extends Controller
|
||||
$setting->label_logo = null;
|
||||
}
|
||||
|
||||
// Acceptance PDF upload
|
||||
$setting = $request->handleImages($setting, 600, 'acceptance_pdf_logo', '', 'acceptance_pdf_logo');
|
||||
if ('1' == $request->input('clear_acceptance_pdf_logo')) {
|
||||
$setting = $request->deleteExistingImage($setting, '', 'acceptance_pdf_logo');
|
||||
$setting->acceptance_pdf_logo = null;
|
||||
}
|
||||
|
||||
// Favicon upload
|
||||
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
|
||||
if ('1' == $request->input('clear_favicon')) {
|
||||
@@ -458,7 +435,6 @@ class SettingsController extends Controller
|
||||
$setting->favicon = null;
|
||||
}
|
||||
|
||||
|
||||
// Default avatar upload
|
||||
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
|
||||
if ($request->input('clear_default_avatar') == '1') {
|
||||
@@ -772,7 +748,6 @@ class SettingsController extends Controller
|
||||
$setting->label2_2d_type = $request->input('label2_2d_type');
|
||||
$setting->label2_2d_target = $request->input('label2_2d_target');
|
||||
$setting->label2_fields = $request->input('label2_fields');
|
||||
$setting->label2_empty_row_count = $request->input('label2_empty_row_count');
|
||||
$setting->labels_per_page = $request->input('labels_per_page');
|
||||
$setting->labels_width = $request->input('labels_width');
|
||||
$setting->labels_height = $request->input('labels_height');
|
||||
@@ -825,7 +800,6 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
if ($setting->save()) {
|
||||
|
||||
return redirect()->route('settings.labels.index')
|
||||
->with('success', trans('admin/settings/message.update.success'));
|
||||
}
|
||||
@@ -875,8 +849,7 @@ class SettingsController extends Controller
|
||||
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
||||
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
||||
$setting->ldap_version = $request->input('ldap_version', 3);
|
||||
$setting->ldap_active_flag = $request->input('ldap_active_flag', 0);
|
||||
$setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag', 0);
|
||||
$setting->ldap_active_flag = $request->input('ldap_active_flag');
|
||||
$setting->ldap_emp_num = $request->input('ldap_emp_num');
|
||||
$setting->ldap_email = $request->input('ldap_email');
|
||||
$setting->ldap_manager = $request->input('ldap_manager');
|
||||
@@ -896,6 +869,7 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
if ($setting->save()) {
|
||||
$setting->update_client_side_cert_files();
|
||||
return redirect()->route('settings.ldap.index')
|
||||
->with('success', trans('admin/settings/message.update.success'));
|
||||
}
|
||||
|
||||
@@ -26,10 +26,14 @@ class StatuslabelsController extends Controller
|
||||
return view('statuslabels.index');
|
||||
}
|
||||
|
||||
public function show(Statuslabel $statuslabel) : View | RedirectResponse
|
||||
public function show($id) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
return view('statuslabels.view')->with('statuslabel', $statuslabel);
|
||||
if ($statuslabel = Statuslabel::find($id)) {
|
||||
return view('statuslabels.view')->with('statuslabel', $statuslabel);
|
||||
}
|
||||
|
||||
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,15 +91,20 @@ class StatuslabelsController extends Controller
|
||||
*
|
||||
* @param int $statuslabelId
|
||||
*/
|
||||
public function edit(Statuslabel $statuslabel) : View | RedirectResponse
|
||||
public function edit($statuslabelId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Statuslabel::class);
|
||||
// Check if the Statuslabel exists
|
||||
if (is_null($item = Statuslabel::find($statuslabelId))) {
|
||||
// Redirect to the blogs management page
|
||||
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$use_statuslabel_type = $item->getStatuslabelType();
|
||||
|
||||
$statuslabel_types = ['' => trans('admin/hardware/form.select_statustype')] + ['undeployable' => trans('admin/hardware/general.undeployable')] + ['pending' => trans('admin/hardware/general.pending')] + ['archived' => trans('admin/hardware/general.archived')] + ['deployable' => trans('admin/hardware/general.deployable')];
|
||||
|
||||
return view('statuslabels/edit', compact('statuslabel_types'))
|
||||
->with('item', $statuslabel)
|
||||
->with('use_statuslabel_type', $statuslabel);
|
||||
return view('statuslabels/edit', compact('item', 'statuslabel_types'))->with('use_statuslabel_type', $use_statuslabel_type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,9 +112,14 @@ class StatuslabelsController extends Controller
|
||||
*
|
||||
* @param int $statuslabelId
|
||||
*/
|
||||
public function update(Request $request, Statuslabel $statuslabel) : RedirectResponse
|
||||
public function update(Request $request, $statuslabelId = null) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Statuslabel::class);
|
||||
// Check if the Statuslabel exists
|
||||
if (is_null($statuslabel = Statuslabel::find($statuslabelId))) {
|
||||
// Redirect to the blogs management page
|
||||
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if (! $request->filled('statuslabel_types')) {
|
||||
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);
|
||||
|
||||
@@ -77,10 +77,17 @@ class SuppliersController extends Controller
|
||||
*
|
||||
* @param int $supplierId
|
||||
*/
|
||||
public function edit(Supplier $supplier) : View | RedirectResponse
|
||||
public function edit($supplierId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Supplier::class);
|
||||
return view('suppliers/edit')->with('item', $supplier);
|
||||
// Check if the supplier exists
|
||||
if (is_null($item = Supplier::find($supplierId))) {
|
||||
// Redirect to the supplier page
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
|
||||
}
|
||||
|
||||
// Show the page
|
||||
return view('suppliers/edit', compact('item'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,9 +95,14 @@ class SuppliersController extends Controller
|
||||
*
|
||||
* @param int $supplierId
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Supplier $supplier) : RedirectResponse
|
||||
public function update($supplierId, ImageUploadRequest $request) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Supplier::class);
|
||||
|
||||
if (is_null($supplier = Supplier::find($supplierId))) {
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
|
||||
}
|
||||
|
||||
// Save the data
|
||||
$supplier->name = request('name');
|
||||
$supplier->address = request('address');
|
||||
@@ -151,10 +163,15 @@ class SuppliersController extends Controller
|
||||
* @param null $supplierId
|
||||
* @internal param int $assetId
|
||||
*/
|
||||
public function show(Supplier $supplier) : View | RedirectResponse
|
||||
public function show($supplierId = null) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Supplier::class);
|
||||
return view('suppliers/view', compact('supplier'));
|
||||
$supplier = Supplier::find($supplierId);
|
||||
|
||||
if (isset($supplier->id)) {
|
||||
return view('suppliers/view', compact('supplier'));
|
||||
}
|
||||
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ use App\Models\ConsumableAssignment;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -53,28 +52,6 @@ class BulkUsersController extends Controller
|
||||
return view('users/bulk-edit', compact('users'))
|
||||
->with('groups', Group::pluck('name', 'id'));
|
||||
|
||||
// bulk send assigned inventory
|
||||
} elseif ($request->input('bulk_actions') == 'send_assigned') {
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
$users_without_email = 0;
|
||||
foreach ($users as $user) {
|
||||
if (empty($user->email)) {
|
||||
$users_without_email++;
|
||||
} else {
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
}
|
||||
}
|
||||
|
||||
if ($users_without_email == 0) {
|
||||
return redirect()->back()->with('success', trans_choice('admin/users/general.users_notified', $users->count()));
|
||||
} else {
|
||||
return redirect()->back()->with('warning', trans_choice('admin/users/general.users_notified_warning', $users->count(), ['no_email' => $users_without_email]));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// bulk delete, display the bulk delete confirmation form
|
||||
} elseif ($request->input('bulk_actions') == 'delete') {
|
||||
$this->authorize('delete', User::class);
|
||||
@@ -298,6 +275,7 @@ class BulkUsersController extends Controller
|
||||
$this->logItemCheckinAndDelete($assets, Asset::class);
|
||||
$this->logAccessoriesCheckin($accessoryUserRows);
|
||||
$this->logItemCheckinAndDelete($licenses, License::class);
|
||||
$this->logConsumablesCheckin($consumableUserRows);
|
||||
|
||||
Asset::whereIn('id', $assets->pluck('id'))->update([
|
||||
'status_id' => e(request('status_id')),
|
||||
@@ -359,7 +337,21 @@ class BulkUsersController extends Controller
|
||||
$logAction->item_type = Accessory::class;
|
||||
$logAction->target_id = $accessoryUserRow->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->created_by = auth()->id();
|
||||
$logAction->created_at = auth()->id();
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
private function logConsumablesCheckin(Collection $consumableUserRows): void
|
||||
{
|
||||
foreach ($consumableUserRows as $consumableUserRow) {
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $consumableUserRow->consumable_id;
|
||||
$logAction->item_type = Consumable::class;
|
||||
$logAction->target_id = $consumableUserRow->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->created_at = auth()->id();
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
|
||||
@@ -22,34 +22,43 @@ class UserFilesController extends Controller
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.6]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, User $user)
|
||||
public function store(UploadFileRequest $request, $userId = null)
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
$files = $request->file('file');
|
||||
$user = User::find($userId);
|
||||
$destinationPath = config('app.private_uploads').'/users';
|
||||
|
||||
if (is_null($files)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
|
||||
if (isset($user->id)) {
|
||||
$this->authorize('update', $user);
|
||||
|
||||
//Log the uploaded file to the log
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->created_by = auth()->id();
|
||||
$logAction->note = $request->input('notes');
|
||||
$logAction->target_id = null;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->filename = $file_name;
|
||||
$logAction->action_type = 'uploaded';
|
||||
$logActions = [];
|
||||
$files = $request->file('file');
|
||||
|
||||
if (! $logAction->save()) {
|
||||
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
|
||||
if (is_null($files)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
|
||||
//Log the uploaded file to the log
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->created_by = auth()->id();
|
||||
$logAction->note = $request->input('notes');
|
||||
$logAction->target_id = null;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->filename = $file_name;
|
||||
$logAction->action_type = 'uploaded';
|
||||
|
||||
if (! $logAction->save()) {
|
||||
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
|
||||
}
|
||||
$logActions[] = $logAction;
|
||||
}
|
||||
// dd($logActions);
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
|
||||
}
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
|
||||
|
||||
}
|
||||
@@ -101,7 +110,7 @@ class UserFilesController extends Controller
|
||||
* @return mixed
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show(User $user, $fileId = null)
|
||||
public function show($userId = null, $fileId = null)
|
||||
{
|
||||
|
||||
|
||||
@@ -109,21 +118,29 @@ class UserFilesController extends Controller
|
||||
return redirect()->route('users.show')->with('error', 'Invalid file request');
|
||||
}
|
||||
|
||||
if ($user = User::find($userId)) {
|
||||
|
||||
$this->authorize('view', $user);
|
||||
|
||||
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
|
||||
$file = 'private_uploads/users/'.$log->filename;
|
||||
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
|
||||
$file = 'private_uploads/users/'.$log->filename;
|
||||
|
||||
try {
|
||||
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
|
||||
try {
|
||||
return StorageHelper::showOrDownloadFile($file, $log->filename);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
|
||||
}
|
||||
}
|
||||
|
||||
// The log record doesn't exist somehow
|
||||
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
|
||||
|
||||
|
||||
return redirect()->back()->with('error', trans('general.file_not_found'));
|
||||
}
|
||||
|
||||
// The log record doesn't exist somehow
|
||||
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
|
||||
|
||||
// Redirect to the user management page if the user doesn't exist
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user