diff --git a/.all-contributorsrc b/.all-contributorsrc index 22cecb3852..088a20081e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -2590,10 +2590,10 @@ ] }, { - "login": "QveenSi", + "login": "qveensi", "name": "Yevhenii Huzii", "avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4", - "profile": "https://github.com/QveenSi", + "profile": "https://github.com/qveensi", "contributions": [ "code" ] @@ -2607,15 +2607,6 @@ "code" ] }, - { - "login": "QveenSi", - "name": "Yevhenii Huzii", - "avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4", - "profile": "https://github.com/QveenSi", - "contributions": [ - "code" - ] - }, { "login": "chrisweirich", "name": "Christian Weirich", @@ -3328,7 +3319,7 @@ }, { "login": "36864", - "name": 36864, + "name": "36864", "avatar_url": "https://avatars.githubusercontent.com/u/109086466?v=4", "profile": "https://github.com/36864", "contributions": [ @@ -3361,6 +3352,888 @@ "contributions": [ "code" ] + }, + { + "login": "chfsx", + "name": "Fabian Schmid", + "avatar_url": "https://avatars.githubusercontent.com/u/6661332?v=4", + "profile": "http://sr.solutions", + "contributions": [ + "code" + ] + }, + { + "login": "realchrisolin", + "name": "Chris Olin", + "avatar_url": "https://avatars.githubusercontent.com/u/1288116?v=4", + "profile": "https://www.chrisolin.com", + "contributions": [ + "code" + ] + }, + { + "login": "mnemonicly", + "name": "Dan", + "avatar_url": "https://avatars.githubusercontent.com/u/3803132?v=4", + "profile": "https://github.com/mnemonicly", + "contributions": [ + "code" + ] + }, + { + "login": "NebelKreis", + "name": "Nebel", + "avatar_url": "https://avatars.githubusercontent.com/u/43917728?v=4", + "profile": "https://github.com/NebelKreis", + "contributions": [ + "code" + ] + }, + { + "login": "test1337ahp", + "name": "test1337ahp", + "avatar_url": "https://avatars.githubusercontent.com/u/132433803?v=4", + "profile": "https://github.com/test1337ahp", + "contributions": [ + "code" + ] + }, + { + "login": "JonathonReinhart", + "name": "Jonathon Reinhart", + "avatar_url": "https://avatars.githubusercontent.com/u/1916566?v=4", + "profile": "https://github.com/JonathonReinhart", + "contributions": [ + "code" + ] + }, + { + "login": "aranar-pro", + "name": "aranar-pro", + "avatar_url": "https://avatars.githubusercontent.com/u/484742?v=4", + "profile": "https://github.com/aranar-pro", + "contributions": [ + "code" + ] + }, + { + "login": "phil-flip", + "name": "Phil", + "avatar_url": "https://avatars.githubusercontent.com/u/27019397?v=4", + "profile": "https://github.com/phil-flip", + "contributions": [ + "code" + ] + }, + { + "login": "fe80", + "name": "Steffy Fort", + "avatar_url": "https://avatars.githubusercontent.com/u/6473460?v=4", + "profile": "https://fe80.fr/", + "contributions": [ + "code" + ] + }, + { + "login": "sorvani", + "name": "Jared Busch", + "avatar_url": "https://avatars.githubusercontent.com/u/3302372?v=4", + "profile": "https://github.com/sorvani", + "contributions": [ + "code" + ] + }, + { + "login": "seanborg-codethink", + "name": "seanborg-codethink", + "avatar_url": "https://avatars.githubusercontent.com/u/111956991?v=4", + "profile": "https://github.com/seanborg-codethink", + "contributions": [ + "code" + ] + }, + { + "login": "dkaatz", + "name": "dkaatz", + "avatar_url": "https://avatars.githubusercontent.com/u/160669961?v=4", + "profile": "https://github.com/dkaatz", + "contributions": [ + "code" + ] + }, + { + "login": "DanielRuf", + "name": "Daniel Ruf", + "avatar_url": "https://avatars.githubusercontent.com/u/827205?v=4", + "profile": "https://threema.id/74SF7MW6?text=", + "contributions": [ + "code" + ] + }, + { + "login": "ahpaleus", + "name": "ahpaleus", + "avatar_url": "https://avatars.githubusercontent.com/u/38883201?v=4", + "profile": "https://github.com/ahpaleus", + "contributions": [ + "code" + ] + }, + { + "login": "mink-adao-duy", + "name": "Anh DAO-DUY", + "avatar_url": "https://avatars.githubusercontent.com/u/22906055?v=4", + "profile": "https://github.com/mink-adao-duy", + "contributions": [ + "code" + ] + }, + { + "login": "Serdnad", + "name": "Andres Gutierrez", + "avatar_url": "https://avatars.githubusercontent.com/u/4723453?v=4", + "profile": "https://github.com/Serdnad", + "contributions": [ + "code" + ] + }, + { + "login": "wewhite", + "name": "Warren White", + "avatar_url": "https://avatars.githubusercontent.com/u/111083379?v=4", + "profile": "https://github.com/wewhite", + "contributions": [ + "code" + ] + }, + { + "login": "robintemme", + "name": "Robin Temme", + "avatar_url": "https://avatars.githubusercontent.com/u/2809241?v=4", + "profile": "https://robintemme.de/", + "contributions": [ + "code" + ] + }, + { + "login": "herroworrd", + "name": "herroworrd", + "avatar_url": "https://avatars.githubusercontent.com/u/47008367?v=4", + "profile": "https://github.com/herroworrd", + "contributions": [ + "code" + ] + }, + { + "login": "vicleos", + "name": "vicleos", + "avatar_url": "https://avatars.githubusercontent.com/u/28558609?v=4", + "profile": "https://mubiu.com/", + "contributions": [ + "code" + ] + }, + { + "login": "thinkl33t", + "name": "Bob Clough", + "avatar_url": "https://avatars.githubusercontent.com/u/1016780?v=4", + "profile": "http://thinkl33t.co.uk/", + "contributions": [ + "code" + ] + }, + { + "login": "brandon-bailey", + "name": "Brandon Daniel Bailey", + "avatar_url": "https://avatars.githubusercontent.com/u/10648463?v=4", + "profile": "https://github.com/brandon-bailey", + "contributions": [ + "code" + ] + }, + { + "login": "marcquark", + "name": "Marc Bartelt", + "avatar_url": "https://avatars.githubusercontent.com/u/23556080?v=4", + "profile": "https://github.com/marcquark", + "contributions": [ + "code" + ] + }, + { + "login": "manu-crealytics", + "name": "manu-crealytics", + "avatar_url": "https://avatars.githubusercontent.com/u/18286893?v=4", + "profile": "https://github.com/manu-crealytics", + "contributions": [ + "code" + ] + }, + { + "login": "Galaxy102", + "name": "Konstantin Köhring", + "avatar_url": "https://avatars.githubusercontent.com/u/18245993?v=4", + "profile": "https://www.galaxy102.de/", + "contributions": [ + "code" + ] + }, + { + "login": "deloz", + "name": "Deloz", + "avatar_url": "https://avatars.githubusercontent.com/u/685167?v=4", + "profile": "https://deloz.net/", + "contributions": [ + "code" + ] + }, + { + "login": "mbrrg", + "name": "Martin Berg", + "avatar_url": "https://avatars.githubusercontent.com/u/2682426?v=4", + "profile": "https://github.com/mbrrg", + "contributions": [ + "code" + ] + }, + { + "login": "Nothing4You", + "name": "Richard Schwab", + "avatar_url": "https://avatars.githubusercontent.com/u/3694534?v=4", + "profile": "https://github.com/Nothing4You", + "contributions": [ + "code" + ] + }, + { + "login": "rickheil", + "name": "Rick Heil", + "avatar_url": "https://avatars.githubusercontent.com/u/8959676?v=4", + "profile": "https://rickheil.com/", + "contributions": [ + "code" + ] + }, + { + "login": "rosscdh", + "name": "Ross Crawford-d'Heureuse", + "avatar_url": "https://avatars.githubusercontent.com/u/397106?v=4", + "profile": "https://github.com/rosscdh", + "contributions": [ + "code" + ] + }, + { + "login": "McG800", + "name": "Ryan McGuire", + "avatar_url": "https://avatars.githubusercontent.com/u/1621107?v=4", + "profile": "https://github.com/McG800", + "contributions": [ + "code" + ] + }, + { + "login": "SBrown2021", + "name": "SBrown2021", + "avatar_url": "https://avatars.githubusercontent.com/u/77835667?v=4", + "profile": "https://github.com/SBrown2021", + "contributions": [ + "code" + ] + }, + { + "login": "serkanerip", + "name": "Serkan", + "avatar_url": "https://avatars.githubusercontent.com/u/8780913?v=4", + "profile": "https://github.com/serkanerip", + "contributions": [ + "code" + ] + }, + { + "login": "Shankschn", + "name": "Shanks", + "avatar_url": "https://avatars.githubusercontent.com/u/63188620?v=4", + "profile": "https://www.yudelei.com/", + "contributions": [ + "code" + ] + }, + { + "login": "cendai-mis", + "name": "cendai-mis", + "avatar_url": "https://avatars.githubusercontent.com/u/198525698?v=4", + "profile": "https://github.com/cendai-mis", + "contributions": [ + "code" + ] + }, + { + "login": "smcpeck", + "name": "Shaun McPeck", + "avatar_url": "https://avatars.githubusercontent.com/u/8724583?v=4", + "profile": "https://smcpeck.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "snazy2000", + "name": "Stephen", + "avatar_url": "https://avatars.githubusercontent.com/u/1378836?v=4", + "profile": "https://github.com/snazy2000", + "contributions": [ + "code" + ] + }, + { + "login": "Nevets82", + "name": "Steven", + "avatar_url": "https://avatars.githubusercontent.com/u/4462739?v=4", + "profile": "http://nevets82.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "Mateus-Romera", + "name": "Mateus Villar", + "avatar_url": "https://avatars.githubusercontent.com/u/29017267?v=4", + "profile": "https://mateusvillar.com/", + "contributions": [ + "code" + ] + }, + { + "login": "mzack5020", + "name": "Matthew Zackschewski", + "avatar_url": "https://avatars.githubusercontent.com/u/12749393?v=4", + "profile": "https://github.com/mzack5020", + "contributions": [ + "code" + ] + }, + { + "login": "firefrei", + "name": "Matthias Frei", + "avatar_url": "https://avatars.githubusercontent.com/u/12660103?v=4", + "profile": "https://www.frei.media/", + "contributions": [ + "code" + ] + }, + { + "login": "nticaric", + "name": "Nenad Ticaric", + "avatar_url": "https://avatars.githubusercontent.com/u/824840?v=4", + "profile": "https://github.com/nticaric", + "contributions": [ + "code" + ] + }, + { + "login": "Scorcher", + "name": "Nikolay Didenko", + "avatar_url": "https://avatars.githubusercontent.com/u/706439?v=4", + "profile": "https://github.com/Scorcher", + "contributions": [ + "code" + ] + }, + { + "login": "nunomaduro", + "name": "Nuno Maduro", + "avatar_url": "https://avatars.githubusercontent.com/u/5457236?v=4", + "profile": "https://nunomaduro.com/sponsorships", + "contributions": [ + "code" + ] + }, + { + "login": "owalerys", + "name": "Oliver Walerys", + "avatar_url": "https://avatars.githubusercontent.com/u/8883074?v=4", + "profile": "https://tektikhq.com/", + "contributions": [ + "code" + ] + }, + { + "login": "rcmcdonald91", + "name": "R. Christian McDonald", + "avatar_url": "https://avatars.githubusercontent.com/u/3102039?v=4", + "profile": "https://keybase.io/rcmcdonald91", + "contributions": [ + "code" + ] + }, + { + "login": "nixn", + "name": "nix", + "avatar_url": "https://avatars.githubusercontent.com/u/1525581?v=4", + "profile": "https://nnix.net/", + "contributions": [ + "code" + ] + }, + { + "login": "octobunny", + "name": "octobunny", + "avatar_url": "https://avatars.githubusercontent.com/u/55462380?v=4", + "profile": "https://github.com/octobunny", + "contributions": [ + "code" + ] + }, + { + "login": "sreyemnayr", + "name": "Ryan", + "avatar_url": "https://avatars.githubusercontent.com/u/8558670?v=4", + "profile": "https://github.com/sreyemnayr", + "contributions": [ + "code" + ] + }, + { + "login": "p3nj", + "name": "p3nj", + "avatar_url": "https://avatars.githubusercontent.com/u/1501022?v=4", + "profile": "https://benji.ltd/", + "contributions": [ + "code" + ] + }, + { + "login": "timwsuqld", + "name": "Tim White", + "avatar_url": "https://avatars.githubusercontent.com/u/6201617?v=4", + "profile": "https://github.com/timwsuqld", + "contributions": [ + "code" + ] + }, + { + "login": "yannikp", + "name": "yannikp", + "avatar_url": "https://avatars.githubusercontent.com/u/22473767?v=4", + "profile": "https://github.com/yannikp", + "contributions": [ + "code" + ] + }, + { + "login": "viclou", + "name": "victoria", + "avatar_url": "https://avatars.githubusercontent.com/u/20525448?v=4", + "profile": "https://github.com/viclou", + "contributions": [ + "code" + ] + }, + { + "login": "valentyntu", + "name": "Valentyn Tulub", + "avatar_url": "https://avatars.githubusercontent.com/u/40685314?v=4", + "profile": "https://github.com/valentyntu", + "contributions": [ + "code" + ] + }, + { + "login": "Wouter0100", + "name": "Wouter van Os", + "avatar_url": "https://avatars.githubusercontent.com/u/864520?v=4", + "profile": "http://wouter0100.nl/", + "contributions": [ + "code" + ] + }, + { + "login": "xWyatt", + "name": "Wyatt Teeter", + "avatar_url": "https://avatars.githubusercontent.com/u/3946540?v=4", + "profile": "https://www.linkedin.com/in/wyatt-teeter", + "contributions": [ + "code" + ] + }, + { + "login": "terwey", + "name": "Yorick Terweijden", + "avatar_url": "https://avatars.githubusercontent.com/u/1596124?v=4", + "profile": "https://github.com/terwey", + "contributions": [ + "code" + ] + }, + { + "login": "bmkalle", + "name": "bmkalle", + "avatar_url": "https://avatars.githubusercontent.com/u/69298836?v=4", + "profile": "https://github.com/bmkalle", + "contributions": [ + "code" + ] + }, + { + "login": "bricelabelle", + "name": "bricelabelle", + "avatar_url": "https://avatars.githubusercontent.com/u/28403467?v=4", + "profile": "https://github.com/bricelabelle", + "contributions": [ + "code" + ] + }, + { + "login": "corydlamb", + "name": "corydlamb", + "avatar_url": "https://avatars.githubusercontent.com/u/97770090?v=4", + "profile": "https://github.com/corydlamb", + "contributions": [ + "code" + ] + }, + { + "login": "splashx", + "name": "Diogenes S. Jesus", + "avatar_url": "https://avatars.githubusercontent.com/u/1154133?v=4", + "profile": "http://twitter.com/splash", + "contributions": [ + "code" + ] + }, + { + "login": "dkmansion", + "name": "D M", + "avatar_url": "https://avatars.githubusercontent.com/u/5826629?v=4", + "profile": "https://github.com/dkmansion", + "contributions": [ + "code" + ] + }, + { + "login": "Jarli01", + "name": "Dustin B", + "avatar_url": "https://avatars.githubusercontent.com/u/14837699?v=4", + "profile": "https://github.com/Jarli01", + "contributions": [ + "code" + ] + }, + { + "login": "fabiang", + "name": "Fabian Grutschus", + "avatar_url": "https://avatars.githubusercontent.com/u/348344?v=4", + "profile": "https://github.com/fabiang", + "contributions": [ + "code" + ] + }, + { + "login": "MelonSmasher", + "name": "MelonSmasher", + "avatar_url": "https://avatars.githubusercontent.com/u/1491053?v=4", + "profile": "https://github.com/MelonSmasher", + "contributions": [ + "code" + ] + }, + { + "login": "AlexanderWPapyrus", + "name": "AlexanderWPapyrus", + "avatar_url": "https://avatars.githubusercontent.com/u/80526133?v=4", + "profile": "https://github.com/AlexanderWPapyrus", + "contributions": [ + "code" + ] + }, + { + "login": "disc", + "name": "Alexandr Hacicheant", + "avatar_url": "https://avatars.githubusercontent.com/u/306231?v=4", + "profile": "https://github.com/disc", + "contributions": [ + "code" + ] + }, + { + "login": "hex128", + "name": "Hex", + "avatar_url": "https://avatars.githubusercontent.com/u/3032891?v=4", + "profile": "https://hex128.io/", + "contributions": [ + "code" + ] + }, + { + "login": "arukompas", + "name": "Arunas Skirius", + "avatar_url": "https://avatars.githubusercontent.com/u/8697942?v=4", + "profile": "https://github.com/arukompas", + "contributions": [ + "code" + ] + }, + { + "login": "benperiton", + "name": "Ben Periton", + "avatar_url": "https://avatars.githubusercontent.com/u/104396?v=4", + "profile": "https://github.com/benperiton", + "contributions": [ + "code" + ] + }, + { + "login": "byronwolfman", + "name": "Byron Wolfman", + "avatar_url": "https://avatars.githubusercontent.com/u/11906832?v=4", + "profile": "https://wolfman.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "CalvinSchwartz", + "name": "Calvin", + "avatar_url": "https://avatars.githubusercontent.com/u/56485508?v=4", + "profile": "https://github.com/CalvinSchwartz", + "contributions": [ + "code" + ] + }, + { + "login": "juanfont", + "name": "Juan Font", + "avatar_url": "https://avatars.githubusercontent.com/u/181059?v=4", + "profile": "https://github.com/juanfont", + "contributions": [ + "code" + ] + }, + { + "login": "juhotaipale", + "name": "Juho Taipale", + "avatar_url": "https://avatars.githubusercontent.com/u/13137708?v=4", + "profile": "https://github.com/juhotaipale", + "contributions": [ + "code" + ] + }, + { + "login": "KorvinSzanto", + "name": "Korvin Szanto", + "avatar_url": "https://avatars.githubusercontent.com/u/1007419?v=4", + "profile": "https://github.com/KorvinSzanto", + "contributions": [ + "code" + ] + }, + { + "login": "sniff122", + "name": "Lewis Foster", + "avatar_url": "https://avatars.githubusercontent.com/u/8513053?v=4", + "profile": "https://lewisfoster.foo/", + "contributions": [ + "code" + ] + }, + { + "login": "loganswartz", + "name": "Logan Swartzendruber", + "avatar_url": "https://avatars.githubusercontent.com/u/33877541?v=4", + "profile": "https://github.com/loganswartz", + "contributions": [ + "code" + ] + }, + { + "login": "lopezio", + "name": "Lorenzo P.", + "avatar_url": "https://avatars.githubusercontent.com/u/1156208?v=4", + "profile": "https://github.com/lopezio", + "contributions": [ + "code" + ] + }, + { + "login": "m4us1ne", + "name": "Lukas Jung", + "avatar_url": "https://avatars.githubusercontent.com/u/33946590?v=4", + "profile": "https://github.com/m4us1ne", + "contributions": [ + "code" + ] + }, + { + "login": "LeafedFox", + "name": "Ellie", + "avatar_url": "https://avatars.githubusercontent.com/u/10965027?v=4", + "profile": "https://leafedfox.xyz/", + "contributions": [ + "code" + ] + }, + { + "login": "gastamper", + "name": "GA Stamper", + "avatar_url": "https://avatars.githubusercontent.com/u/20960555?v=4", + "profile": "https://github.com/gastamper", + "contributions": [ + "code" + ] + }, + { + "login": "gl-pup", + "name": "Guillaume Lefranc", + "avatar_url": "https://avatars.githubusercontent.com/u/206553556?v=4", + "profile": "https://github.com/gl-pup", + "contributions": [ + "code" + ] + }, + { + "login": "dasjoe", + "name": "Hajo Möller", + "avatar_url": "https://avatars.githubusercontent.com/u/733892?v=4", + "profile": "https://github.com/dasjoe", + "contributions": [ + "code" + ] + }, + { + "login": "pottom", + "name": "Istvan Basa", + "avatar_url": "https://avatars.githubusercontent.com/u/3420063?v=4", + "profile": "https://github.com/pottom", + "contributions": [ + "code" + ] + }, + { + "login": "jjasghar", + "name": "JJ Asghar", + "avatar_url": "https://avatars.githubusercontent.com/u/810824?v=4", + "profile": "https://jjasghar.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "JemCdo", + "name": "James E. Msenga", + "avatar_url": "https://avatars.githubusercontent.com/u/40404495?v=4", + "profile": "https://github.com/JemCdo", + "contributions": [ + "code" + ] + }, + { + "login": "jfwiebe", + "name": "Jan Felix Wiebe", + "avatar_url": "https://avatars.githubusercontent.com/u/6865786?v=4", + "profile": "https://github.com/jfwiebe", + "contributions": [ + "code" + ] + }, + { + "login": "drexljo", + "name": "Jo Drexl", + "avatar_url": "https://avatars.githubusercontent.com/u/43412008?v=4", + "profile": "https://www.nfon.com/", + "contributions": [ + "code" + ] + }, + { + "login": "austinsasko", + "name": "Austin Sasko", + "avatar_url": "https://avatars.githubusercontent.com/u/4807843?v=4", + "profile": "https://github.com/austinsasko", + "contributions": [ + "code" + ] + }, + { + "login": "JassonCordones", + "name": "Jasson", + "avatar_url": "https://avatars.githubusercontent.com/u/4875039?v=4", + "profile": "http://jassoncordones.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "Tinyblargon", + "name": "Okean", + "avatar_url": "https://avatars.githubusercontent.com/u/76069640?v=4", + "profile": "https://github.com/Tinyblargon", + "contributions": [ + "code" + ] + }, + { + "login": "amedranogil", + "name": "Alejandro Medrano", + "avatar_url": "https://avatars.githubusercontent.com/u/6515064?v=4", + "profile": "https://www.lst.tfo.upm.es/alejandro-medrano/", + "contributions": [ + "code" + ] + }, + { + "login": "lukaskraic", + "name": "Lukas Kraic", + "avatar_url": "https://avatars.githubusercontent.com/u/58696401?v=4", + "profile": "https://github.com/lukaskraic", + "contributions": [ + "code" + ] + }, + { + "login": "mckaygerhard", + "name": "Герхард PICCORO Lenz McKAY ", + "avatar_url": "https://avatars.githubusercontent.com/u/1571724?v=4", + "profile": "https://github-readme-stats.vercel.app/api?username=mckaygerhard", + "contributions": [ + "code" + ] + }, + { + "login": "FlorestanII", + "name": "Johannes Pollitt", + "avatar_url": "https://avatars.githubusercontent.com/u/15015119?v=4", + "profile": "https://github.com/FlorestanII", + "contributions": [ + "code" + ] + }, + { + "login": "strobelm", + "name": "Michael Strobel", + "avatar_url": "https://avatars.githubusercontent.com/u/14185442?v=4", + "profile": "https://strobelm.de", + "contributions": [ + "code" + ] + }, + { + "login": "nickwest", + "name": "Nicky West", + "avatar_url": "https://avatars.githubusercontent.com/u/634790?v=4", + "profile": "http://nickwest.me", + "contributions": [ + "code" + ] + }, + { + "login": "akaspeh1", + "name": "akaspeh1", + "avatar_url": "https://avatars.githubusercontent.com/u/1347327?v=4", + "profile": "https://github.com/akaspeh1", + "contributions": [ + "code" + ] } ] } diff --git a/.env.docker b/.env.docker index 88b65b8f79..9eae34385e 100644 --- a/.env.docker +++ b/.env.docker @@ -28,6 +28,7 @@ PUBLIC_FILESYSTEM_DISK=local_public # -------------------------------------------- DB_CONNECTION=mysql DB_HOST=db +DB_SOCKET=null DB_PORT='3306' DB_DATABASE=snipeit DB_USERNAME=snipeit @@ -168,6 +169,7 @@ AWS_DEFAULT_REGION=null LOGIN_MAX_ATTEMPTS=5 LOGIN_LOCKOUT_DURATION=60 RESET_PASSWORD_LINK_EXPIRES=900 +INVITE_PASSWORD_LINK_EXPIRES=1500 # -------------------------------------------- # OPTIONAL: MISC diff --git a/.env.example b/.env.example index 81de24ead1..6e423d4fe2 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public # -------------------------------------------- DB_CONNECTION=mysql DB_HOST=127.0.0.1 +DB_SOCKET=null DB_PORT=3306 DB_DATABASE=null DB_USERNAME=null @@ -174,6 +175,7 @@ LOGIN_AUTOCOMPLETE=false RESET_PASSWORD_LINK_EXPIRES=15 PASSWORD_CONFIRM_TIMEOUT=10800 PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50 +INVITE_PASSWORD_LINK_EXPIRES=1500 # -------------------------------------------- # OPTIONAL: MISC @@ -191,11 +193,17 @@ LDAP_TIME_LIM=600 IMPORT_TIME_LIMIT=600 IMPORT_MEMORY_LIMIT=500M REPORT_TIME_LIMIT=12000 -REQUIRE_SAML=false API_THROTTLE_PER_MINUTE=120 CSV_ESCAPE_FORMULAS=true LIVEWIRE_URL_PREFIX=null + +# -------------------------------------------- +# OPTIONAL: SAML SETTINGS +# -------------------------------------------- +REQUIRE_SAML=false +SAML_KEY_SIZE=2048 + # -------------------------------------------- # OPTIONAL: HASHING # -------------------------------------------- diff --git a/.github/workflows/SA-codeql.yml b/.github/workflows/SA-codeql.yml index 29f3e1b1f1..007d07d37a 100644 --- a/.github/workflows/SA-codeql.yml +++ b/.github/workflows/SA-codeql.yml @@ -26,7 +26,7 @@ jobs: language: [ 'javascript' ] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml index 0788d6eaa2..fee3bab64c 100644 --- a/.github/workflows/codacy-analysis.yml +++ b/.github/workflows/codacy-analysis.yml @@ -10,10 +10,10 @@ name: Codacy Security Scan on: push: - branches: [ master ] + branches: [ develop ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ develop ] schedule: - cron: '36 23 * * 3' @@ -32,11 +32,11 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@v4.4.5 + uses: codacy/codacy-analysis-cli-action@v4.4.7 with: # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # You can also omit the token and run the tools that support default configurations diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 7b9331c97d..b2e798d562 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Crowdin push uses: crowdin/github-action@v2 diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml new file mode 100644 index 0000000000..a65d48d2e2 --- /dev/null +++ b/.github/workflows/docker-alpine.yml @@ -0,0 +1,86 @@ +# Snipe-IT (Alpine) Docker image build for hub.docker.com +name: Docker images (Alpine) + +# 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 +on: + push: + branches: + - master + - develop + tags: + - 'v**' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + pull_request: + +permissions: + contents: read + +jobs: + docker: + # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' + if: github.repository == 'grokability/snipe-it' + runs-on: ubuntu-latest + env: + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + 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 + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We turn off 'latest' tag by default. + TAGS_FLAVOR: | + latest=false + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v5 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + ############################################### + # Build/Push the 'snipe/snipe-it' image + ############################################### + # https://github.com/docker/metadata-action + # Get Metadata for docker_build step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image + id: meta_build + uses: docker/metadata-action@v5 + with: + images: snipe/snipe-it + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + # https://github.com/docker/build-push-action + - name: Build and push 'snipe-it' image + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile.alpine + platforms: linux/amd64,linux/arm64 + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} diff --git a/.github/workflows/docker-arm.yml b/.github/workflows/docker-arm.yml deleted file mode 100644 index 9d883b54bf..0000000000 --- a/.github/workflows/docker-arm.yml +++ /dev/null @@ -1,151 +0,0 @@ -# Snipe-IT Docker image build for hub.docker.com -name: Docker ARM64 images (Ubuntu) - -# Run this Build for all pushes to 'master' or develop branch, or tagged releases. -# Also run for PRs to ensure PR doesn't break Docker build process -on: - push: - branches: - - master - - develop - tags: - - 'v**' - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - pull_request: - -permissions: - contents: read - -jobs: - docker-ubuntu-arm: - # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' - if: github.repository == 'grokability/snipe-it' - runs-on: ubuntu-24.04-arm - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - 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 - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - ############################################### - # Build/Push the 'grokability/snipe-it' image - ############################################### - # https://github.com/docker/metadata-action - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: grokability/snipe-it - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'snipe-it' image - id: docker_build - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/arm64 - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} - docker-alpine-arm: - # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' - if: github.repository == 'grokability/snipe-it' - runs-on: ubuntu-24.04-arm - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - 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 - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - ############################################### - # Build/Push the 'grokability/snipe-it' image - ############################################### - # https://github.com/docker/metadata-action - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: grokability/snipe-it - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'snipe-it' image - id: docker_build - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile.alpine - platforms: linux/arm64 - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} diff --git a/.github/workflows/docker-intel.yml b/.github/workflows/docker-intel.yml deleted file mode 100644 index d025c2a18f..0000000000 --- a/.github/workflows/docker-intel.yml +++ /dev/null @@ -1,151 +0,0 @@ -# Snipe-IT Docker image build for hub.docker.com -name: Docker Intel/amd64 images (Ubuntu) - -# Run this Build for all pushes to 'master' or develop branch, or tagged releases. -# Also run for PRs to ensure PR doesn't break Docker build process -on: - push: - branches: - - master - - develop - tags: - - 'v**' - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - pull_request: - -permissions: - contents: read - -jobs: - docker-ubuntu-intel: - # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' - if: github.repository == 'grokability/snipe-it' - runs-on: ubuntu-latest - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - 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 - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - ############################################### - # Build/Push the 'grokability/snipe-it' image - ############################################### - # https://github.com/docker/metadata-action - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: grokability/snipe-it - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'snipe-it' image - id: docker_build - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64 - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} - docker-alpine-intel: - # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' - if: github.repository == 'grokability/snipe-it' - runs-on: ubuntu-latest - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - 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 - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - ############################################### - # Build/Push the 'grokability/snipe-it' image - ############################################### - # https://github.com/docker/metadata-action - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: grokability/snipe-it - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'snipe-it' image - id: docker_build - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile.alpine - platforms: linux/amd64 - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} diff --git a/.github/workflows/docker-ubuntu.yml b/.github/workflows/docker-ubuntu.yml new file mode 100644 index 0000000000..c345105b7e --- /dev/null +++ b/.github/workflows/docker-ubuntu.yml @@ -0,0 +1,86 @@ +# Snipe-IT Docker image build for hub.docker.com +name: Docker images (Ubuntu) + +# Run this Build for all pushes to 'master' or develop branch, or tagged releases. +# Also run for PRs to ensure PR doesn't break Docker build process +on: + push: + branches: + - master + - develop + tags: + - 'v**' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + pull_request: + +permissions: + contents: read + +jobs: + docker: + # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' + if: github.repository == 'grokability/snipe-it' + runs-on: ubuntu-latest + env: + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (master), use the literal tag 'latest' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + 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 + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We turn off 'latest' tag by default. + TAGS_FLAVOR: | + latest=false + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v5 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + ############################################### + # Build/Push the 'snipe/snipe-it' image + ############################################### + # https://github.com/docker/metadata-action + # Get Metadata for docker_build step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image + id: meta_build + uses: docker/metadata-action@v5 + with: + images: snipe/snipe-it + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + # https://github.com/docker/build-push-action + - name: Build and push 'snipe-it' image + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index f9064dec95..8b5782339b 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -11,7 +11,7 @@ jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Docker Hub Description uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4 diff --git a/.github/workflows/tests-mysql.yml b/.github/workflows/tests-mysql.yml index 16190900b0..237220b337 100644 --- a/.github/workflows/tests-mysql.yml +++ b/.github/workflows/tests-mysql.yml @@ -37,7 +37,7 @@ jobs: php-version: "${{ matrix.php-version }}" coverage: none - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get Composer Cache Directory id: composer-cache diff --git a/.github/workflows/tests-postgres.yml b/.github/workflows/tests-postgres.yml index d111fc87a5..97379ec2bc 100644 --- a/.github/workflows/tests-postgres.yml +++ b/.github/workflows/tests-postgres.yml @@ -34,7 +34,7 @@ jobs: php-version: "${{ matrix.php-version }}" coverage: none - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get Composer Cache Directory id: composer-cache diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml index cd06805e01..fdf4ea2ce9 100644 --- a/.github/workflows/tests-sqlite.yml +++ b/.github/workflows/tests-sqlite.yml @@ -25,7 +25,7 @@ jobs: php-version: "${{ matrix.php-version }}" coverage: none - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get Composer Cache Directory id: composer-cache diff --git a/.pa11yci.json b/.pa11yci.json new file mode 100644 index 0000000000..73b84a8140 --- /dev/null +++ b/.pa11yci.json @@ -0,0 +1,240 @@ +{ + "standard": "WCAG2AA", + "level": "error", + "defaults": { + "useIncognitoBrowserContext": false, + "timeout": 500000, + "wait": 5000, + "ignore" : [ + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail", + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail" + ], + + "viewport": { + "width": 1280, + "height": 1024 + } + }, + "urls": [ + { + "__NOTE" : "this should always be FIRST (if browser context is preserved)", + "url": "https://snipe-it.test/login", + "actions": [ + "navigate to https://snipe-it.test/login", + "screen capture tests/pa11y/login.png", + "set field input[name='username'] to admin", + "set field input[name='password'] to password", + "click element button[type=submit]", + "wait for url to be https://snipe-it.test/", + "screen capture tests/pa11y/dashboard.png" + ] + }, + { + "url" : "https://snipe-it.test/admin", + "actions" : [ + "navigate to https://snipe-it.test/admin", + "screen capture tests/pa11y/admin-settings.png" + ] + }, + { + "url" : "https://snipe-it.test/admin/branding", + "actions" : [ + "navigate to https://snipe-it.test/admin/branding", + "screen capture tests/pa11y/admin-branding.png" + ] + }, + { + "url" : "https://snipe-it.test/admin/general", + "actions" : [ + "navigate to https://snipe-it.test/admin/general", + "screen capture tests/pa11y/admin-general.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware/create", + "actions" : [ + "navigate to https://snipe-it.test/hardware/create", + "screen capture tests/pa11y/asset-create.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware", + "actions" : [ + "navigate to https://snipe-it.test/hardware", + "screen capture tests/pa11y/asset-list.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware/1", + "actions" : [ + "navigate to https://snipe-it.test/hardware/1", + "screen capture tests/pa11y/asset-detail.png" + ] + }, + { + "url" : "https://snipe-it.test/account/view-assets", + "actions" : [ + "navigate to https://snipe-it.test/account/view-assets", + "screen capture tests/pa11y/profile.png" + ] + }, + { + "url" : "https://snipe-it.test/licences", + "actions" : [ + "navigate to https://snipe-it.test/licenses", + "screen capture tests/pa11y/license-list.png" + ] + }, + { + "url" : "https://snipe-it.test/licences/create", + "actions" : [ + "navigate to https://snipe-it.test/licenses/create", + "screen capture tests/pa11y/license-create.png" + ] + }, + { + "url" : "https://snipe-it.test/licences/1", + "actions" : [ + "navigate to https://snipe-it.test/licenses/1", + "screen capture tests/pa11y/license-view.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables", + "actions" : [ + "navigate to https://snipe-it.test/consumables", + "screen capture tests/pa11y/consumable-list.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables/create", + "actions" : [ + "navigate to https://snipe-it.test/consumables/create", + "screen capture tests/pa11y/consumable-create.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables/1", + "actions" : [ + "navigate to https://snipe-it.test/consumables/1", + "screen capture tests/pa11y/consumable-view.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories", + "actions" : [ + "navigate to https://snipe-it.test/accessories", + "screen capture tests/pa11y/accessory-list.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories/create", + "actions" : [ + "navigate to https://snipe-it.test/accessories/create", + "screen capture tests/pa11y/accessory-create.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories/1", + "actions" : [ + "navigate to https://snipe-it.test/accessories/1", + "screen capture tests/pa11y/accessory-view.png" + ] + }, + { + "url" : "https://snipe-it.test/locations", + "actions" : [ + "navigate to https://snipe-it.test/locations", + "screen capture tests/pa11y/location-list.png" + ] + }, + { + "url" : "https://snipe-it.test/locations/create", + "actions" : [ + "navigate to https://snipe-it.test/locations/create", + "screen capture tests/pa11y/location-create.png" + ] + }, + { + "url" : "https://snipe-it.test/locations/1", + "actions" : [ + "navigate to https://snipe-it.test/locations/1", + "screen capture tests/pa11y/location-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/models", + "actions" : [ + "navigate to https://snipe-it.test/models", + "screen capture tests/pa11y/model-list.png" + ] + }, + { + "url" : "https://snipe-it.test/models/create", + "actions" : [ + "navigate to https://snipe-it.test/models/create", + "screen capture tests/pa11y/model-create.png" + ] + }, + { + "url" : "https://snipe-it.test/models/1", + "actions" : [ + "navigate to https://snipe-it.test/models/1", + "screen capture tests/pa11y/model-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/companies", + "actions" : [ + "navigate to https://snipe-it.test/companies", + "screen capture tests/pa11y/company-list.png" + ] + }, + { + "url" : "https://snipe-it.test/companies/create", + "actions" : [ + "navigate to https://snipe-it.test/companies/create", + "screen capture tests/pa11y/company-create.png" + ] + }, + { + "url" : "https://snipe-it.test/companies/1", + "actions" : [ + "navigate to https://snipe-it.test/companies/1", + "screen capture tests/pa11y/company-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/departments", + "actions" : [ + "navigate to https://snipe-it.test/departments", + "screen capture tests/pa11y/department-list.png" + ] + }, + { + "url" : "https://snipe-it.test/departments/create", + "actions" : [ + "navigate to https://snipe-it.test/departments/create", + "screen capture tests/pa11y/department-create.png" + ] + }, + { + "url" : "https://snipe-it.test/departments/1", + "actions" : [ + "navigate to https://snipe-it.test/departments/1", + "screen capture tests/pa11y/department-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/invalid-url", + "actions" : [ + "navigate to https://snipe-it.test/invalid-url", + "screen capture tests/pa11y/404.png" + ] + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 122382894e..c2011a5423 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,60 +1,74 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far: -| [
snipe](http://www.snipe.net)
[💻](https://github.com/grokability/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/grokability/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/grokability/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](https://github.com/grokability/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/grokability/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Documentation") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/grokability/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/grokability/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/grokability/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/grokability/snipe-it/commits?author=vjandrea "Code") | -| :---: | :---: | :---: |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| :---: | -| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/grokability/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/grokability/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/grokability/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/grokability/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") | -| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/grokability/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/grokability/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/grokability/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/grokability/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/grokability/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/grokability/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/grokability/snipe-it/commits?author=rassie "Code") | -| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/grokability/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/grokability/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/grokability/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/grokability/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/grokability/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/grokability/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/grokability/snipe-it/commits?author=adamdunson "Code") | -| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/grokability/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/grokability/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/grokability/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/grokability/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/grokability/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/grokability/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/grokability/snipe-it/commits?author=hawk554 "Code") | -| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/grokability/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/grokability/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/grokability/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](https://github.com/grokability/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/grokability/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/grokability/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/grokability/snipe-it/commits?author=mdaniels5757 "Code") | -| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/grokability/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/grokability/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/grokability/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/grokability/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/grokability/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/grokability/snipe-it/commits?author=splaer "Code") | -| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/grokability/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/grokability/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/grokability/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/grokability/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/grokability/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/grokability/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/grokability/snipe-it/commits?author=burlito "Documentation") | -| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/grokability/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/grokability/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/grokability/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/grokability/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/grokability/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/grokability/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/grokability/snipe-it/commits?author=feeva "Code") | -| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/grokability/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/grokability/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/grokability/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/grokability/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/grokability/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/grokability/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/grokability/snipe-it/commits?author=BrettFagerlund "Tests") | -| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/grokability/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/grokability/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/grokability/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/grokability/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/grokability/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/grokability/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/grokability/snipe-it/commits?author=gibsonjoshua55 "Code") | -| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/grokability/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/grokability/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/grokability/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/grokability/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/grokability/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/grokability/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/grokability/snipe-it/commits?author=BlueHatbRit "Documentation") | -| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/grokability/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/grokability/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/grokability/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/grokability/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/grokability/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/grokability/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/grokability/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/grokability/snipe-it/commits?author=vcordes79 "Code") | -| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/grokability/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/grokability/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/grokability/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/grokability/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/grokability/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/grokability/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/grokability/snipe-it/commits?author=hannahtinkler "Code") | -| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/grokability/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/grokability/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/grokability/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/grokability/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/grokability/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/grokability/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/grokability/snipe-it/commits?author=plexorama "Code") | -| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/grokability/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/grokability/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/grokability/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/grokability/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/grokability/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") | -| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") | -| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") | -| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") | -| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") | -| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") | -| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") | -| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") | -| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") | -| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") | -| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/grokability/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/grokability/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/grokability/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/grokability/snipe-it/commits?author=patrict "Code") | -| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/grokability/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/grokability/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/grokability/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/grokability/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/grokability/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/grokability/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/grokability/snipe-it/commits?author=smb "Code") | -| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/grokability/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/grokability/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/grokability/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/grokability/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/grokability/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/grokability/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/grokability/snipe-it/commits?author=omyno "Code") | -| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/grokability/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/grokability/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/grokability/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/grokability/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/grokability/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/grokability/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/grokability/snipe-it/commits?author=Godmartinz "Code") | -| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/grokability/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/grokability/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/grokability/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/grokability/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/grokability/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/grokability/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/grokability/snipe-it/commits?author=sanjay900 "Code") | -| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/grokability/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/grokability/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/grokability/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/grokability/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/grokability/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/grokability/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/grokability/snipe-it/commits?author=sigmoidal "Code") | -| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/grokability/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/grokability/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/grokability/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/grokability/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/grokability/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/grokability/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/grokability/snipe-it/commits?author=winstan "Code") | -| [
Folke](https://github.com/fashberg)
[💻](https://github.com/grokability/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/grokability/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/grokability/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/grokability/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/grokability/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/grokability/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/grokability/snipe-it/commits?author=Kurokat "Code") | -| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/grokability/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/grokability/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/grokability/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/grokability/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/grokability/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/grokability/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/grokability/snipe-it/commits?author=JuustoMestari "Code") | -| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/grokability/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/grokability/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/grokability/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/grokability/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/grokability/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/grokability/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/grokability/snipe-it/commits?author=limeless "Code") | -| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/grokability/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/grokability/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/grokability/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/grokability/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/grokability/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/grokability/snipe-it/commits?author=psarossy "Code") | -| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/grokability/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/grokability/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/grokability/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/grokability/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/grokability/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/grokability/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/grokability/snipe-it/commits?author=travismiller "Code") | -| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/grokability/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/grokability/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/grokability/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/grokability/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/grokability/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/grokability/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/grokability/snipe-it/commits?author=jerm "Code") | -| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/grokability/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/grokability/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/grokability/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/grokability/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/grokability/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/grokability/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/grokability/snipe-it/commits?author=leitwerk-ag "Code") | -| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/grokability/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/grokability/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/grokability/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/grokability/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/grokability/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/grokability/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/grokability/snipe-it/commits?author=UniversalSuperBox "Code") | -| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/grokability/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/grokability/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/grokability/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/grokability/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/grokability/snipe-it/commits?author=QveenSi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/grokability/snipe-it/commits?author=veenone "Code") | [
Yevhenii Huzii](https://github.com/QveenSi)
[💻](https://github.com/grokability/snipe-it/commits?author=QveenSi "Code") | -| [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/grokability/snipe-it/commits?author=chrisweirich "Code") | [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/grokability/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/grokability/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/grokability/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/grokability/snipe-it/commits?author=vickyjaura183 "Code") | -| [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/grokability/snipe-it/commits?author=julian-piehl "Code") | [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/grokability/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/grokability/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/grokability/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/grokability/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/grokability/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/grokability/snipe-it/commits?author=Vautia "Code") | -| [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/grokability/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/grokability/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/grokability/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/grokability/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/grokability/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/grokability/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/grokability/snipe-it/commits?author=fernando-almeida "Code") | -| [
akemidx](https://github.com/akemidx)
[💻](https://github.com/grokability/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/grokability/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/grokability/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/grokability/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/grokability/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/grokability/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/grokability/snipe-it/commits?author=floschoepfer "Code") | -| [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/grokability/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/grokability/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/grokability/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/grokability/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/grokability/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/grokability/snipe-it/commits?author=ak-piracha "Code") | -| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/grokability/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/grokability/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/grokability/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/grokability/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/grokability/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/grokability/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/grokability/snipe-it/commits?author=Azooz2014 "Code") | -| [
bilias](https://github.com/bilias)
[💻](https://github.com/grokability/snipe-it/commits?author=bilias "Code") | [
coach1988](https://github.com/coach1988)
[💻](https://github.com/grokability/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/grokability/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/grokability/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/grokability/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/grokability/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/grokability/snipe-it/commits?author=Q4kK "Code") | -| [
squintfox](https://github.com/squintfox)
[💻](https://github.com/grokability/snipe-it/commits?author=squintfox "Code") | [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/grokability/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/grokability/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/grokability/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/grokability/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/grokability/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/grokability/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/grokability/snipe-it/commits?author=bryanlopezinc "Tests") | -| [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/grokability/snipe-it/commits?author=U-H-T "Code") | [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/grokability/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/grokability/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/grokability/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/grokability/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/grokability/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/grokability/snipe-it/commits?author=arne-kroeger "Code") | -| [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/grokability/snipe-it/commits?author=Glukose1 "Code") | [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/grokability/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/grokability/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [
Darren Rainey](https://darrenraineys.co.uk)
[💻](https://github.com/grokability/snipe-it/commits?author=DarrenRainey "Code") | [
maciej-poleszczyk](https://github.com/maciej-poleszczyk)
[💻](https://github.com/grokability/snipe-it/commits?author=maciej-poleszczyk "Code") | [
Sebastian Groß](https://github.com/sgross-emlix)
[💻](https://github.com/grokability/snipe-it/commits?author=sgross-emlix "Code") | -| [
Anouar Touati](https://github.com/AnouarTouati)
[💻](https://github.com/grokability/snipe-it/commits?author=AnouarTouati "Code") | [
aHVzY2g](https://github.com/aHVzY2g)
[💻](https://github.com/grokability/snipe-it/commits?author=aHVzY2g "Code") | [
林博仁 Buo-ren Lin](https://brlin.me)
[💻](https://github.com/grokability/snipe-it/commits?author=brlin-tw "Code") | [
Adugna Gizaw](https://orbalia.pythonanywhere.com/)
[🌍](#translation-addex12 "Translation") | [
Jesse Ostrander](https://github.com/jostrander)
[💻](https://github.com/grokability/snipe-it/commits?author=jostrander "Code") | [
James M](https://github.com/azmcnutt)
[💻](https://github.com/grokability/snipe-it/commits?author=azmcnutt "Code") | [
Fiala06](https://github.com/Fiala06)
[💻](https://github.com/grokability/snipe-it/commits?author=Fiala06 "Code") | -| [
Nathan Taylor](https://github.com/ntaylor-86)
[💻](https://github.com/grokability/snipe-it/commits?author=ntaylor-86 "Code") | [
fvollmer](https://github.com/fvollmer)
[💻](https://github.com/grokability/snipe-it/commits?author=fvollmer "Code") |[
36864](https://github.com/36864)
[💻](https://github.com/grokability/snipe-it/commits?author=36864 "Code") |[
CloCkWeRX](https://github.com/CloCkWeRX)
[💻](https://github.com/grokability/snipe-it/commits?author=CloCkWeRX "Code")|[
BeatSpark](https://github.com/BeatSpark)
[💻](https://github.com/grokability/snipe-it/commits?author=BeatSpark "Code") | [
mrdahbi](https://github.com/mrdahbi)
[💻](https://github.com/grokability/snipe-it/commits?author=mrdahbi "Code") | +| [
snipe](http://www.snipe.net)
[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") | +| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") | +| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") | +| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") | +| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") | +| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") | +| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") | +| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") | +| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") | +| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") | +| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") | +| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") | +| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") | +| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") | +| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") | +| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") | +| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") | +| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") | +| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") | +| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") | +| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") | +| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") | +| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") | +| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") | +| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") | +| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") | +| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") | +| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | +| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") | +| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") | +| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") | +| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") | +| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") | +| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") | +| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") | +| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") | +| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") | +| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") | +| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") | +| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/qveensi)
[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | +| [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | +| [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | +| [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | +| [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | +| [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | +| [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | +| [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | +| [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | +| [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | +| [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [
Darren Rainey](https://darrenraineys.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [
maciej-poleszczyk](https://github.com/maciej-poleszczyk)
[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [
Sebastian Groß](https://github.com/sgross-emlix)
[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [
Anouar Touati](https://github.com/AnouarTouati)
[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | +| [
aHVzY2g](https://github.com/aHVzY2g)
[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [
林博仁 Buo-ren Lin](https://brlin.me)
[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [
Adugna Gizaw](https://orbalia.pythonanywhere.com/)
[🌍](#translation-addex12 "Translation") | [
Jesse Ostrander](https://github.com/jostrander)
[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [
James M](https://github.com/azmcnutt)
[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [
Fiala06](https://github.com/Fiala06)
[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [
Nathan Taylor](https://github.com/ntaylor-86)
[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") | +| [
fvollmer](https://github.com/fvollmer)
[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [
36864](https://github.com/36864)
[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [
Daniel O'Connor](http://clockwerx.blogspot.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [
BeatSpark](https://github.com/BeatSpark)
[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [
mrdahbi](https://github.com/mrdahbi)
[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [
Fabian Schmid](http://sr.solutions)
[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [
Chris Olin](https://www.chrisolin.com)
[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") | +| [
Dan](https://github.com/mnemonicly)
[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [
Nebel](https://github.com/NebelKreis)
[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [
test1337ahp](https://github.com/test1337ahp)
[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [
Jonathon Reinhart](https://github.com/JonathonReinhart)
[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [
aranar-pro](https://github.com/aranar-pro)
[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [
Phil](https://github.com/phil-flip)
[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [
Steffy Fort](https://fe80.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") | +| [
Jared Busch](https://github.com/sorvani)
[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [
seanborg-codethink](https://github.com/seanborg-codethink)
[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [
dkaatz](https://github.com/dkaatz)
[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [
Daniel Ruf](https://threema.id/74SF7MW6?text=)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [
ahpaleus](https://github.com/ahpaleus)
[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [
Anh DAO-DUY](https://github.com/mink-adao-duy)
[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [
Andres Gutierrez](https://github.com/Serdnad)
[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") | +| [
Warren White](https://github.com/wewhite)
[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [
Robin Temme](https://robintemme.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [
herroworrd](https://github.com/herroworrd)
[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [
vicleos](https://mubiu.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [
Bob Clough](http://thinkl33t.co.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [
Brandon Daniel Bailey](https://github.com/brandon-bailey)
[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [
Marc Bartelt](https://github.com/marcquark)
[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") | +| [
manu-crealytics](https://github.com/manu-crealytics)
[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [
Konstantin Köhring](https://www.galaxy102.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [
Deloz](https://deloz.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [
Martin Berg](https://github.com/mbrrg)
[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [
Richard Schwab](https://github.com/Nothing4You)
[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [
Rick Heil](https://rickheil.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [
Ross Crawford-d'Heureuse](https://github.com/rosscdh)
[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") | +| [
Ryan McGuire](https://github.com/McG800)
[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [
SBrown2021](https://github.com/SBrown2021)
[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [
Serkan](https://github.com/serkanerip)
[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [
Shanks](https://www.yudelei.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [
cendai-mis](https://github.com/cendai-mis)
[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [
Shaun McPeck](https://smcpeck.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [
Stephen](https://github.com/snazy2000)
[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") | +| [
Steven](http://nevets82.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [
Mateus Villar](https://mateusvillar.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [
Matthew Zackschewski](https://github.com/mzack5020)
[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [
Matthias Frei](https://www.frei.media/)
[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [
Nenad Ticaric](https://github.com/nticaric)
[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [
Nikolay Didenko](https://github.com/Scorcher)
[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [
Nuno Maduro](https://nunomaduro.com/sponsorships)
[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") | +| [
Oliver Walerys](https://tektikhq.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [
R. Christian McDonald](https://keybase.io/rcmcdonald91)
[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [
nix](https://nnix.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [
octobunny](https://github.com/octobunny)
[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [
Ryan](https://github.com/sreyemnayr)
[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [
p3nj](https://benji.ltd/)
[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [
Tim White](https://github.com/timwsuqld)
[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") | +| [
yannikp](https://github.com/yannikp)
[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [
victoria](https://github.com/viclou)
[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [
Valentyn Tulub](https://github.com/valentyntu)
[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [
Wouter van Os](http://wouter0100.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [
Wyatt Teeter](https://www.linkedin.com/in/wyatt-teeter)
[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [
Yorick Terweijden](https://github.com/terwey)
[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [
bmkalle](https://github.com/bmkalle)
[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") | +| [
bricelabelle](https://github.com/bricelabelle)
[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [
corydlamb](https://github.com/corydlamb)
[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [
Diogenes S. Jesus](http://twitter.com/splash)
[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [
D M](https://github.com/dkmansion)
[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [
Dustin B](https://github.com/Jarli01)
[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [
Fabian Grutschus](https://github.com/fabiang)
[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [
MelonSmasher](https://github.com/MelonSmasher)
[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") | +| [
AlexanderWPapyrus](https://github.com/AlexanderWPapyrus)
[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [
Alexandr Hacicheant](https://github.com/disc)
[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [
Hex](https://hex128.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [
Arunas Skirius](https://github.com/arukompas)
[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [
Ben Periton](https://github.com/benperiton)
[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [
Byron Wolfman](https://wolfman.dev/)
[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [
Calvin](https://github.com/CalvinSchwartz)
[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") | +| [
Juan Font](https://github.com/juanfont)
[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [
Juho Taipale](https://github.com/juhotaipale)
[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [
Korvin Szanto](https://github.com/KorvinSzanto)
[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [
Lewis Foster](https://lewisfoster.foo/)
[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [
Logan Swartzendruber](https://github.com/loganswartz)
[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [
Lorenzo P.](https://github.com/lopezio)
[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [
Lukas Jung](https://github.com/m4us1ne)
[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") | +| [
Ellie](https://leafedfox.xyz/)
[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo Möller](https://github.com/dasjoe)
[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") | +| [
Jan Felix Wiebe](https://github.com/jfwiebe)
[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [
Lukas Kraic](https://github.com/lukaskraic)
[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") | +| [
Герхард PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/README.md b/README.md index 34a5ee405c..062be60453 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml) [![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) ## Snipe-IT - Open Source Asset Management System @@ -133,9 +133,15 @@ The ERD is available [online here](https://drawsql.app/templates/snipe-it). Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years! +----- + +### Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=grokability/snipe-it&type=Date)](https://www.star-history.com/#grokability/snipe-it&Date) + ------ ### Announcement List -To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. - +To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. +We also usually make smaller announcements on our social accounts, our Discord, and our blog, so be sure to subscribe to those if you're looking for more granular announcements. diff --git a/app/Console/Commands/CleanIncorrectCheckoutAcceptances.php b/app/Console/Commands/CleanIncorrectCheckoutAcceptances.php new file mode 100644 index 0000000000..30dca964c7 --- /dev/null +++ b/app/Console/Commands/CleanIncorrectCheckoutAcceptances.php @@ -0,0 +1,68 @@ +withProgressBar(CheckoutAcceptance::all(), function ($checkoutAcceptance) use (&$deletions, &$skips) { + $item = $checkoutAcceptance->checkoutable; + $checkout_to_id = $checkoutAcceptance->assigned_to_id; + if(is_null($item)) { + $this->info("'Checkoutable' Item is null, going to next record"); + return; //'false' allegedly breaks execution entirely, so 'true' maybe doesn't? hrm. just straight return maybe? + } + if(get_class($item) == LicenseSeat::class) { + $item = $item->license; + } + foreach($item->assetlog()->where('action_type','checkout')->get() as $assetlog) { + if ($assetlog->target_id == $checkout_to_id && $assetlog->target_type != User::class) { + //We have a checkout-to an ID for a non-User, which matches to an ID in the checkout_acceptances table + + //now, let's compare the _times_ - are they close? + //I'm picking `created_at` over `action_date` because I'm more interested in when the actionlogs + //were _created_, not when they were alleged to have happened - those created_at times need to be within 'X' seconds of + //each other (currently 5) + if ($assetlog->created_at->diffInSeconds($checkoutAcceptance->created_at, true) <= 5) { //we're allowing for five _ish_ seconds of slop + $deletions++; + $checkoutAcceptance->forceDelete(); // HARD delete this record; it should have never been + return; + } else { + //$this->info("The two records are too far apart"); + } + } else { + //$this->info("No match! checkout to id: " . $checkout_to_id." target_id: ".$assetlog->target_id." target_type: ".$assetlog->target_type); + } + } + $skips++; + }); + $this->error("Final deletion count: $deletions, and skip count: $skips"); + } +} diff --git a/app/Console/Commands/CleanOldCheckoutRequests.php b/app/Console/Commands/CleanOldCheckoutRequests.php new file mode 100644 index 0000000000..a96a58a349 --- /dev/null +++ b/app/Console/Commands/CleanOldCheckoutRequests.php @@ -0,0 +1,74 @@ + function ($query) { + $query->withTrashed(); + }, + 'requestedItem' => function ($query) { + $query->withTrashed(); + }, + ])->get(); + + $this->info("Processing {$requests->count()} checkout requests"); + + $this->withProgressBar($requests, function ($request) { + if ($this->shouldForceDelete($request)) { + $request->forceDelete(); + $this->deletions++; + return; + } + + if ($this->shouldSoftDelete($request)) { + $request->delete(); + $this->deletions++; + return; + } + + $this->skips++; + }); + + $this->info("Final deletion count: $this->deletions, and skip count: $this->skips"); + + return 0; + } + + private function shouldForceDelete(CheckoutRequest $request) + { + // check if the requestable or user relationship is null + return !$request->requestable || !$request->user; + } + + private function shouldSoftDelete(CheckoutRequest $request) + { + return $request->requestable->trashed() || $request->user->trashed(); + } +} diff --git a/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php new file mode 100644 index 0000000000..f368929f16 --- /dev/null +++ b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php @@ -0,0 +1,32 @@ +whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]); + $this->info("Assets with an assigned_type but no assigned_to are fixed"); + } +} diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php index f90aeafd8d..381ae900d9 100644 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -55,6 +55,8 @@ class LdapSync extends Command ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); + + // Map the LDAP attributes to the Snipe-IT user fields. $ldap_map = [ "username" => Setting::getSettings()->ldap_username_field, "last_name" => Setting::getSettings()->ldap_lname_field, @@ -63,11 +65,17 @@ class LdapSync extends Command "emp_num" => Setting::getSettings()->ldap_emp_num, "email" => Setting::getSettings()->ldap_email, "phone" => Setting::getSettings()->ldap_phone_field, + "mobile" => Setting::getSettings()->ldap_mobile, "jobtitle" => Setting::getSettings()->ldap_jobtitle, + "address" => Setting::getSettings()->ldap_address, + "city" => Setting::getSettings()->ldap_city, + "state" => Setting::getSettings()->ldap_state, + "zip" => Setting::getSettings()->ldap_zip, "country" => Setting::getSettings()->ldap_country, "location" => Setting::getSettings()->ldap_location, "dept" => Setting::getSettings()->ldap_dept, "manager" => Setting::getSettings()->ldap_manager, + "display_name" => Setting::getSettings()->ldap_display_name, ]; $ldap_default_group = Setting::getSettings()->ldap_default_group; @@ -182,7 +190,7 @@ class LdapSync extends Command // Inject location information fields for ($i = 0; $i < $results['count']; $i++) { $results[$i]['ldap_location_override'] = false; - $results[$i]['location_id'] = 0; + $results[$i]['location_id'] = null; } // Grab subsets based on location-specific DNs, and overwrite location for these users. @@ -234,9 +242,11 @@ class LdapSync extends Command } + // Assign the mapped LDAP attributes for each user to the Snipe-IT user fields for ($i = 0; $i < $results['count']; $i++) { $item = []; $item['username'] = $results[$i][$ldap_map["username"]][0] ?? ''; + $item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? ''; $item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? ''; $item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? ''; $item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? ''; @@ -244,8 +254,13 @@ class LdapSync extends Command $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; $item['location_id'] = $results[$i]['location_id'] ?? ''; $item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? ''; + $item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? ''; $item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? ''; + $item['address'] = $results[$i][$ldap_map["address"]][0] ?? ''; + $item['city'] = $results[$i][$ldap_map["city"]][0] ?? ''; + $item['state'] = $results[$i][$ldap_map["state"]][0] ?? ''; $item['country'] = $results[$i][$ldap_map["country"]][0] ?? ''; + $item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? ''; $item['department'] = $results[$i][$ldap_map["dept"]][0] ?? ''; $item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? ''; $item['location'] = $results[$i][$ldap_map["location"]][0] ?? ''; @@ -278,6 +293,9 @@ class LdapSync extends Command if($ldap_map["username"] != null){ $user->username = $item['username']; } + if($ldap_map["display_name"] != null){ + $user->display_name = $item['display_name']; + } if($ldap_map["last_name"] != null){ $user->last_name = $item['lastname']; } @@ -293,6 +311,9 @@ class LdapSync extends Command if($ldap_map["phone"] != null){ $user->phone = $item['telephone']; } + if($ldap_map["mobile"] != null){ + $user->mobile = $item['mobile']; + } if($ldap_map["jobtitle"] != null){ $user->jobtitle = $item['jobtitle']; } diff --git a/app/Console/Commands/LdapTroubleshooter.php b/app/Console/Commands/LdapTroubleshooter.php index 5bb3cdd366..cb19ff8c53 100644 --- a/app/Console/Commands/LdapTroubleshooter.php +++ b/app/Console/Commands/LdapTroubleshooter.php @@ -6,6 +6,7 @@ use Illuminate\Console\Command; use App\Models\Setting; use Exception; use Illuminate\Support\Facades\Crypt; +use App\Models\Ldap; /** * Check if a given ip is in a network @@ -160,7 +161,15 @@ class LdapTroubleshooter extends Command $output[] = "-x"; $output[] = "-b ".escapeshellarg($settings->ldap_basedn); $output[] = "-D ".escapeshellarg($settings->ldap_uname); - $output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword)); + + try { + $w = Crypt::Decrypt($settings->ldap_pword); + } catch (\Exception $e) { + $this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting."); + exit(0); + } + + $output[] = "-w ". escapeshellarg($w); $output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter)); if($settings->ldap_tls) { $this->line("# adding STARTTLS option"); @@ -171,6 +180,23 @@ class LdapTroubleshooter extends Command $this->line(implode(" \\\n",$output)); exit(0); } + + //PHP Version check for warning + $php_version = phpversion(); + list($major, $minor, $patch) = explode('.', $php_version); + if ( + $major < 8 || + ($major == 8 && $minor < 3) || + ($major == 8 && $minor == 3 && $patch < 21) || + ($major == 8 && $minor == 4 && $patch < 7) + ) { + $this->warn("PHP Version: $php_version WARNING - Versions before 8.3.21 or 8.4.7 will return INCONSISTENT results!"); + if (!$this->confirm("Are you sure you wish to continue?")) { + $this->warn("ABORTING"); + exit(-1); + } + } + if(!$this->option('force')) { $confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?'); if(!$confirmation) { @@ -179,7 +205,7 @@ class LdapTroubleshooter extends Command } } //$this->line(print_r($settings,true)); - $this->info("STAGE 1: Checking settings"); + $this->line("STAGE 1: Checking settings"); if(!$settings->ldap_enabled) { $this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)"); } @@ -210,32 +236,40 @@ class LdapTroubleshooter extends Command $this->info("Determined LDAP hostname to be: ".$parsed['host']); } - $this->info("Performing DNS lookup of: ".$parsed['host']); - $ips = dns_get_record($parsed['host']); $raw_ips = []; - //$this->info("Host IP is: ".print_r($ips,true)); + if (inet_pton($parsed['host']) !== false) { + $this->line($parsed['host'] . " already looks like an address; skipping DNS lookup"); + $raw_ips[] = $parsed['host']; + } else { + $this->line("Performing DNS lookup of: " . $parsed['host']); + $ips = dns_get_record($parsed['host']); - if(!$ips || count($ips) == 0) { - $this->error("ERROR: DNS lookup of host: ".$parsed['host']." has failed. ABORTING."); - exit(-1); - } - $this->debugout("IP's? ".print_r($ips,true)); - foreach($ips as $ip) { - if(!isset($ip['ip'])) { - continue; + //$this->info("Host IP is: ".print_r($ips,true)); + + if (!$ips || count($ips) == 0) { + $this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING."); + exit(-1); } - $raw_ips[]=$ip['ip']; - if($ip['ip'] == "127.0.0.1") { + $this->debugout("IP's? " . print_r($ips, true)); + foreach ($ips as $ip) { + if (!isset($ip['ip'])) { + continue; + } + $raw_ips[] = $ip['ip']; + } + } + foreach ($raw_ips as $ip) { + if ($ip == "127.0.0.1") { $this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong"); } - if(ip_in_range($ip['ip'],'10.0.0.0/8') || ip_in_range($ip['ip'],'192.168.0.0/16') || ip_in_range($ip['ip'], '172.16.0.0/12')) { + if (ip_in_range($ip, '10.0.0.0/8') || ip_in_range($ip, '192.168.0.0/16') || ip_in_range($ip, '172.16.0.0/12')) { $this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network"); } } - $this->info("STAGE 2: Checking basic network connectivity"); - $ports = [389,636]; + $this->line("STAGE 2: Checking basic network connectivity"); + $ports = [636, 389]; if(@$parsed['port'] && !in_array($parsed['port'],$ports)) { $ports[] = $parsed['port']; } @@ -246,7 +280,7 @@ class LdapTroubleshooter extends Command $errstr = ''; $timeout = 30.0; $result = ''; - $this->info("Attempting to connect to port: ".$port." - may take up to $timeout seconds"); + $this->line("Attempting to connect to port: " . $port . " - may take up to $timeout seconds"); try { $result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0); } catch(Exception $e) { @@ -265,9 +299,9 @@ class LdapTroubleshooter extends Command exit(-1); } - $this->info("STAGE 3: Determine encryption algorithm, if any"); + $this->line("STAGE 3: Determine encryption algorithm, if any"); - $ldap_urls = []; + $ldap_urls = []; // [url, cert-check?, start_tls?] $pretty_ldap_urls = []; foreach($open_ports as $port) { $this->line("Trying TLS first for port $port"); @@ -275,35 +309,46 @@ class LdapTroubleshooter extends Command if($this->test_anonymous_bind($ldap_url)) { $this->info("Anonymous bind succesful to $ldap_url!"); $ldap_urls[] = [ $ldap_url, true, false ]; - $pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ]; + $pretty_ldap_urls[] = [$ldap_url, "enabled", "n/a (no)"]; continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines... } else { $this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks."); } if($this->test_anonymous_bind($ldap_url, false)) { - $this->info("Anonymous bind succesful to $ldap_url with certifcate-checks disabled"); - $ldap_urls[] = [ $ldap_url, false, false ]; - $pretty_ldap_urls[] = [ $ldap_url, "no", "no" ]; + $this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled"); + $ldap_urls[] = [$ldap_url, false, false]; + $pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"]; continue; } else { $this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS"); } + // now switching to ldap:// URL's from ldaps:// $ldap_url = "ldap://".$parsed['host'].":$port"; + if($this->test_anonymous_bind($ldap_url, true, true)) { $this->info("Plain connection to $ldap_url with STARTTLS succesful!"); $ldap_urls[] = [ $ldap_url, true, true ]; - $pretty_ldap_urls[] = [ $ldap_url, "YES", "YES" ]; + $pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"]; continue; } else { - $this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without STARTTLS"); + $this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without certificate checks."); + } + + if ($this->test_anonymous_bind($ldap_url, false, true)) { + $this->info("Plain connection to $ldap_url with STARTTLS and cert checks *disabled* successful!"); + $ldap_urls[] = [$ldap_url, false, true]; + $pretty_ldap_urls[] = [$ldap_url, "DISABLED", "STARTTLS ENABLED"]; + continue; + } else { + $this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled, and cert checks disabled. Trying without STARTTLS"); } if($this->test_anonymous_bind($ldap_url)) { $this->info("Plain connection to $ldap_url succesful!"); $ldap_urls[] = [ $ldap_url, true, false ]; - $pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ]; + $pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"]; continue; } else { $this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port"); @@ -313,23 +358,29 @@ class LdapTroubleshooter extends Command $this->debugout(print_r($ldap_urls,true)); if(count($ldap_urls) > 0 ) { - $this->info("Found working LDAP URL's: "); + $this->debugout("Found working LDAP URL's: "); foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead? - $this->info("LDAP URL: ".$ldap_url[0]); - $this->info($ldap_url[0]. ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled"). ($ldap_url[2] ? " STARTTLS Enabled ": " STARTTLS Disabled")); + $this->debugout("LDAP URL: " . $ldap_url[0]); + $this->debugout($ldap_url[0] . ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled") . ($ldap_url[2] ? " STARTTLS Enabled " : " STARTTLS Disabled")); } - $this->table(["URL", "Cert Checks Enabled?", "STARTTLS Enabled?"],$pretty_ldap_urls); + $this->table(["URL", "Cert Checks?", "STARTTLS?"], $pretty_ldap_urls); } else { $this->error("ERROR - no valid LDAP URL's available - ABORTING"); exit(1); } - $this->info("STAGE 4: Test Administrative Bind for LDAP Sync"); + $this->line("STAGE 4: Test Administrative Bind for LDAP Sync"); foreach($ldap_urls AS $ldap_url) { - $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, Crypt::decrypt($settings->ldap_pword)); + try { + $w = Crypt::Decrypt($settings->ldap_pword); + } catch (\Exception $e) { + $this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting."); + exit(0); + } + $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w); } - $this->info("STAGE 5: Test BaseDN"); + $this->line("STAGE 5: Test BaseDN"); //grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name) $all_defined_constants = get_defined_constants(); $ldap_constants = []; @@ -341,16 +392,23 @@ class LdapTroubleshooter extends Command $this->debugout("LDAP constants are: ".print_r($ldap_constants,true)); foreach($ldap_urls AS $ldap_url) { - if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword),$settings)) { + try { + $w = Crypt::Decrypt($settings->ldap_pword); + } catch (\Exception $e) { + $this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting."); + exit(0); + } + + if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,$w,$settings)) { $this->info("Success getting informational bind!"); } else { $this->error("Unable to get information from bind."); } } - $this->info("STAGE 6: Test LDAP Login to Snipe-IT"); + $this->line("STAGE 6: Test LDAP Login to Snipe-IT"); foreach($ldap_urls AS $ldap_url) { - $this->info("Starting auth to ".$ldap_url[0]); + $this->line("Starting auth to " . $ldap_url[0]); while(true) { $with_tls = $ldap_url[1] ? "with": "without"; $with_startssl = $ldap_url[2] ? "using": "not using"; @@ -359,7 +417,12 @@ class LdapTroubleshooter extends Command } $username = $this->ask("Username"); $password = $this->secret("Password"); - $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results? + $results = $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results? + if ($results) { + $this->info("Success authenticating with " . $username); + } else { + $this->error("Unable to authenticate with " . $username); + } } } @@ -368,14 +431,17 @@ class LdapTroubleshooter extends Command public function connect_to_ldap($ldap_url, $check_cert, $start_tls) { + if ($check_cert) { + $this->line("we *ARE* checking certs"); + Ldap::ignoreCertificates(false); + + } else { + $this->line("we are IGNORING certs"); + Ldap::ignoreCertificates(true); + } $lconn = ldap_connect($ldap_url); ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3? // no - it's formally deprecated: https://tools.ietf.org/html/rfc3494 - if(!$check_cert) { - putenv('LDAPTLS_REQCERT=never'); // This is horrible; is this *really* the only way to do it? - } else { - putenv('LDAPTLS_REQCERT'); // have to very explicitly and manually *UN* set the env var here to ensure it works - } if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) { // client-side TLS certificate support for LDAP (Google Secure LDAP) putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert'); @@ -404,9 +470,10 @@ class LdapTroubleshooter extends Command return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) { try { $lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls); - $this->info("gonna try to bind now, this can take a while if we mess it up"); + $this->line("Attempting to bind now, this can take a while if we mess it up"); $bind_results = ldap_bind($lconn); - $this->info("Bind results are: ".$bind_results." which translate into boolean: ".(bool)$bind_results); + $this->line("Bind results are: " . $bind_results . " which translate into boolean: " . (bool)$bind_results); + ldap_close($lconn); return (bool)$bind_results; } catch (Exception $e) { $this->error("WARNING: Exception caught during bind - ".$e->getMessage()); @@ -421,6 +488,7 @@ class LdapTroubleshooter extends Command try { $lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls); $bind_results = ldap_bind($lconn, $username, $password); + ldap_close($lconn); if(!$bind_results) { $this->error("WARNING: Failed to bind to $ldap_url as $username"); return false; @@ -446,22 +514,62 @@ class LdapTroubleshooter extends Command return false; } $this->info("SUCCESS - Able to bind to $ldap_url as $username"); - $result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/); - $results = ldap_get_entries($conn, $result); - $cleaned_results = $this->ldap_results_cleaner($results); - $this->line(print_r($cleaned_results,true)); - //okay, great - now how do we display those results? I have no idea. + $cleaned_results = []; + try { + // This _may_ only work for Active Directory? + $result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/); + $results = ldap_get_entries($conn, $result); + $cleaned_results = $this->ldap_results_cleaner($results); + //$this->line(print_r($cleaned_results,true)); + $default_naming_contexts = $cleaned_results[0]['namingcontexts']; + $this->info("Default Naming Contexts:"); + $this->info(implode(", ", $default_naming_contexts)); + //okay, great - now how do we display those results? I have no idea. + } catch (\Exception $e) { + $this->error("Unable to get base naming contexts - here's what we *did* get:"); + $this->line(print_r($cleaned_results, true)); + } // I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it? - $this->comment("I guess we're trying to do the ldap search here, but sometimes it takes too long?"); + $this->debugout("I guess we're trying to do the ldap search here, but sometimes it takes too long?"); $this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter)); $search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter)); + $entries = ldap_get_entries($conn, $search_results); $this->info("Printing first 10 results: "); - for($i=0;$i<10;$i++) { - $this->info($search_results[$i]); + $pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10); + //print_r($data); + $headers = []; + foreach ($pretty_data as $row) { + //populate headers + foreach ($row as $key => $value) { + //skip objectsid and objectguid because it junks up output + if ($key == "objectsid" || $key == "objectguid") { + continue; + } + if (!in_array($key, $headers)) { + $headers[] = $key; + } + } } + $table = []; + //repeat again to populate table + foreach ($pretty_data as $row) { + $newrow = []; + foreach ($headers as $header) { + if (is_array(@$row[$header])) { + $newrow[] = "[" . implode(", ", $row[$header]) . "]"; + } else { + $newrow[] = @$row[$header]; + } + } + $table[] = $newrow; + } + + $this->table($headers, $table); } catch (\Exception $e) { $this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage()); return false; + } finally { + ldap_close($conn); } }); } @@ -477,7 +585,7 @@ class LdapTroubleshooter extends Command { if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) { // POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout) - $this->info('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected'); + $this->line('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected'); return $function(); } else { $parent_pid = posix_getpid(); @@ -514,4 +622,6 @@ class LdapTroubleshooter extends Command } } + + } diff --git a/app/Console/Commands/MoveUploadsToNewDisk.php b/app/Console/Commands/MoveUploadsToNewDisk.php index a27b06feed..615c12a545 100644 --- a/app/Console/Commands/MoveUploadsToNewDisk.php +++ b/app/Console/Commands/MoveUploadsToNewDisk.php @@ -96,7 +96,7 @@ class MoveUploadsToNewDisk extends Command $private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*"); $private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*"); $private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*"); - $private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*"); + $private_uploads['assetmodels'] = glob('storage/private_uploads/models'."/*.*"); $private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*"); $private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*"); $private_uploads['users'] = glob('storage/private_uploads/users'."/*.*"); diff --git a/app/Console/Commands/PaveIt.php b/app/Console/Commands/PaveIt.php index ef69d25a5b..8c6bc89a51 100644 --- a/app/Console/Commands/PaveIt.php +++ b/app/Console/Commands/PaveIt.php @@ -4,7 +4,7 @@ namespace App\Console\Commands; use App\Models\Asset; use App\Models\CustomField; -use Schema; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\DB; use Illuminate\Console\Command; @@ -59,6 +59,9 @@ class PaveIt extends Command 'migrations', 'settings', 'users', + 'telescope_entries', + 'telescope_entries_tags', + 'telescope_monitoring', ]; // We only need to find out what these are so we can nuke these columns on the assets table. @@ -66,8 +69,8 @@ class PaveIt extends Command foreach ($custom_fields as $custom_field) { $this->info('DROP the '.$custom_field->db_column.' column from assets as well.'); - if (\Schema::hasColumn('assets', $custom_field->db_column)) { - \Schema::table('assets', function ($table) use ($custom_field) { + if (Schema::hasColumn('assets', $custom_field->db_column)) { + Schema::table('assets', function ($table) use ($custom_field) { $table->dropColumn($custom_field->db_column); }); } @@ -84,8 +87,8 @@ class PaveIt extends Command } // Leave in the demo oauth keys so we don't have to reset them every day in the demos - \DB::statement('delete from oauth_clients WHERE id > 2'); - \DB::statement('delete from oauth_access_tokens WHERE id > 2'); + DB::statement('delete from oauth_clients WHERE id > 2'); + DB::statement('delete from oauth_access_tokens WHERE user_id > 2'); } } \ No newline at end of file diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php index 1dd2aaa51d..4db7bac147 100644 --- a/app/Console/Commands/Purge.php +++ b/app/Console/Commands/Purge.php @@ -62,19 +62,19 @@ class Purge extends Command $assetcount = $assets->count(); $this->info($assets->count().' assets purged.'); $asset_assoc = 0; - $asset_maintenances = 0; + $maintenances = 0; foreach ($assets as $asset) { - $this->info('- Asset "'.$asset->present()->name().'" deleted.'); + $this->info('- Asset "'.$asset->display_name.'" deleted.'); $asset_assoc += $asset->assetlog()->count(); $asset->assetlog()->forceDelete(); - $asset_maintenances += $asset->assetmaintenances()->count(); - $asset->assetmaintenances()->forceDelete(); + $maintenances += $asset->maintenances()->count(); + $asset->maintenances()->forceDelete(); $asset->forceDelete(); } $this->info($asset_assoc.' corresponding log records purged.'); - $this->info($asset_maintenances.' corresponding maintenance records purged.'); + $this->info($maintenances.' corresponding maintenance records purged.'); $locations = Location::whereNotNull('deleted_at')->withTrashed()->get(); $this->info($locations->count().' locations purged.'); diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 0bee8dcdcf..b5109c25ec 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -243,12 +243,15 @@ class RestoreFromBackup extends Command $private_dirs = [ 'storage/private_uploads/accessories', 'storage/private_uploads/assetmodels', + 'storage/private_uploads/maintenances', + 'storage/private_uploads/models', 'storage/private_uploads/assets', // these are asset _files_, not the pictures. 'storage/private_uploads/audits', 'storage/private_uploads/components', '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', @@ -259,9 +262,10 @@ class RestoreFromBackup extends Command ]; $public_dirs = [ 'public/uploads/accessories', + 'public/uploads/assetmodels', + 'public/uploads/maintenances', 'public/uploads/assets', // these are asset _pictures_, not asset files 'public/uploads/avatars', - //'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated 'public/uploads/categories', 'public/uploads/companies', 'public/uploads/components', @@ -328,9 +332,9 @@ 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' - ]; + + $good_extensions = config('filesystems.allowed_upload_extensions_array'); + foreach (array_merge($private_files, $public_files) as $file) { $has_wildcard = (strpos($file, '*') !== false); if ($has_wildcard) { diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php index 250b08abf9..67efecbb34 100644 --- a/app/Console/Commands/SendAcceptanceReminder.php +++ b/app/Console/Commands/SendAcceptanceReminder.php @@ -77,7 +77,7 @@ class SendAcceptanceReminder extends Command if(!$email){ $no_email_list[] = [ 'id' => $acceptance->assignedTo?->id, - 'name' => $acceptance->assignedTo?->present()->fullName(), + 'name' => $acceptance->assignedTo?->display_name, ]; } else { $count++; @@ -99,8 +99,11 @@ class SendAcceptanceReminder extends Command 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); + + if (!empty($rows)) { + $this->info("The following users do not have an email address:"); + $this->table($headers, $rows); + } return 0; } diff --git a/app/Console/Commands/TestLocationsFMCS.php b/app/Console/Commands/TestLocationsFMCS.php index f14c78063a..70eface2de 100644 --- a/app/Console/Commands/TestLocationsFMCS.php +++ b/app/Console/Commands/TestLocationsFMCS.php @@ -27,7 +27,7 @@ class TestLocationsFMCS extends 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 few moments if have a very large dataset.'); + $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 diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 664c8edc62..ca85459b67 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -19,7 +19,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - if(Setting::getSettings()->alerts_enabled === 1) { + if(Setting::getSettings()?->alerts_enabled === 1) { $schedule->command('snipeit:inventory-alerts')->daily(); $schedule->command('snipeit:expiring-alerts')->daily(); $schedule->command('snipeit:expected-checkin')->daily(); diff --git a/app/Events/CheckoutableCheckedIn.php b/app/Events/CheckoutableCheckedIn.php index 48aed2a64d..fedbd49fbe 100644 --- a/app/Events/CheckoutableCheckedIn.php +++ b/app/Events/CheckoutableCheckedIn.php @@ -28,7 +28,7 @@ class CheckoutableCheckedIn $this->checkedOutTo = $checkedOutTo; $this->checkedInBy = $checkedInBy; $this->note = $note; - $this->action_date = $action_date ?? date('Y-m-d'); + $this->action_date = $action_date ?? date('Y-m-d H:i:s'); $this->originalValues = $originalValues; } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index e340d70b09..d68418ce59 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Log; use Throwable; use JsonException; use Carbon\Exceptions\InvalidFormatException; +use Illuminate\Http\Exceptions\ThrottleRequestsException; class Handler extends ExceptionHandler { @@ -107,27 +108,43 @@ class Handler extends ExceptionHandler $statusCode = $e->getStatusCode(); + // API throttle requests are handled in the RouteServiceProvider configureRateLimiting() method, so we don't need to handle them here switch ($e->getStatusCode()) { case '404': return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404); - case '429': - return response()->json(Helper::formatStandardApiResponse('error', null, 'Too many requests'), 429); case '405': return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405); default: return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode); - } + } + + // This handles API validation exceptions that happen at the Form Request level, so they + // never even get to the controller where we normally nicely format JSON responses + if ($e instanceof ValidationException) { + $response = $this->invalidJson($request, $e); + return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200); + } + } // This is traaaaash but it handles models that are not found while using route model binding :( // The only alternative is to set that at *each* route, which is crazypants if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { + $ids = method_exists($e, 'getIds') ? $e->getIds() : []; + + if (in_array('bulkedit', $ids, true)) { + $error_array = session()->get('bulk_asset_errors'); + return redirect() + ->route('hardware.index') + ->withErrors($error_array, 'bulk_asset_errors') + ->withInput(); + } // This gets the MVC model name from the exception and formats in a way that's less fugly - $model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))); + $model_name = trim(strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))))); $route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index'; // Sigh. @@ -143,6 +160,8 @@ class Handler extends ExceptionHandler $route = 'maintenances.index'; } elseif ($route === 'licenseseats.index') { $route = 'licenses.index'; + } elseif (($route === 'customfieldsets.index') || ($route === 'customfields.index')) { + $route = 'fields.index'; } return redirect() @@ -201,6 +220,7 @@ class Handler extends ExceptionHandler */ public function register() { + $this->reportable(function (Throwable $e) { // }); diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 3352f0a6a0..5e954a24c5 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -13,6 +13,7 @@ use App\Models\Setting; use App\Models\Statuslabel; use App\Models\License; use App\Models\Location; +use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Crypt; use Illuminate\Contracts\Encryption\DecryptException; use Carbon\Carbon; @@ -722,8 +723,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'), - 'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. Change webhook endpoint'), + 'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'), + 'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. Change webhook endpoint'), ]; // if item of concern is being used and its being used with the deprecated values return the notification array. @@ -876,6 +877,48 @@ class Helper return false; } + /** + * Check if the file is a video, so we can show a preview + * + * @param File $file + * @return string | Boolean + * @author [B. Wetherington] [] + * @since [v8.1.18] + */ + public static function checkUploadIsVideo($file) + { + $finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension + $filetype = @finfo_file($finfo, $file); + finfo_close($finfo); + + if (($filetype == 'video/mp4') || ($filetype == 'video/quicktime') || ($filetype == 'video/mpeg') || ($filetype == 'video/ogg') || ($filetype == 'video/webm') || ($filetype == 'video/x-msvide')) { + return $filetype; + } + + return false; + } + + /** + * Check if the file is audio, so we can show a preview + * + * @param File $file + * @return string | Boolean + * @author [A. Gianotto] [] + * @since [v3.0] + */ + public static function checkUploadIsAudio($file) + { + $finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension + $filetype = @finfo_file($finfo, $file); + finfo_close($finfo); + + if (($filetype == 'audio/mpeg') || ($filetype == 'audio/ogg')) { + return $filetype; + } + + return false; + } + /** * Walks through the permissions in the permissions config file and determines if * permissions are granted based on a $selected_arr array. @@ -1154,22 +1197,42 @@ class Helper 'webp' => 'far fa-image', 'avif' => 'far fa-image', 'svg' => 'fas fa-vector-square', + // word 'doc' => 'far fa-file-word', 'docx' => 'far fa-file-word', + // Excel 'xls' => 'far fa-file-excel', 'xlsx' => 'far fa-file-excel', + 'ods' => 'far fa-file-excel', + + // Presentation + 'ppt' => 'far fa-file-powerpoint', + 'odp' => 'far fa-file-powerpoint', + // archive 'zip' => 'fas fa-file-archive', 'rar' => 'fas fa-file-archive', + //Text + 'odt' => 'far fa-file-alt', 'txt' => 'far fa-file-alt', 'rtf' => 'far fa-file-alt', 'xml' => 'fas fa-code', + // Misc 'pdf' => 'far fa-file-pdf', 'lic' => 'far fa-save', + + // video + 'mov' => 'fa-solid fa-video', + 'mp4' => 'fa-solid fa-video', + + // audio + 'ogg' => 'fa-solid fa-file-audio', + 'mp3' => 'fa-solid fa-file-audio', + 'wav' => 'fa-solid fa-file-audio', ]; if ($extension && array_key_exists($extension, $allowedExtensionMap)) { @@ -1313,25 +1376,24 @@ class Helper switch ($item) { case 'asset': return 'fas fa-barcode'; - break; case 'accessory': return 'fas fa-keyboard'; - break; case 'component': return 'fas fa-hdd'; - break; case 'consumable': return 'fas fa-tint'; - break; case 'license': return 'far fa-save'; - break; case 'location': return 'fas fa-map-marker-alt'; - break; case 'user': return 'fas fa-user'; - break; + case 'supplier': + return 'fa-solid fa-store'; + case 'manufacturer': + return 'fa-solid fa-building'; + case 'category': + return 'fa-solid fa-table-columns'; } } @@ -1481,60 +1543,62 @@ class Helper } - static public function getRedirectOption($request, $id, $table, $item_id = null) + static public function getRedirectOption($request, $id, $table, $item_id = null) : RedirectResponse { - $redirect_option = Session::get('redirect_option'); - $checkout_to_type = Session::get('checkout_to_type'); + $redirect_option = Session::get('redirect_option') ?? $request->redirect_option; + $checkout_to_type = Session::get('checkout_to_type') ?? null; $checkedInFrom = Session::get('checkedInFrom'); + $other_redirect = Session::get('other_redirect'); + $backUrl = Session::pull('back_url', route('home')); + + // return to previous page + if ($redirect_option === 'back') { + return redirect()->to($backUrl); + } // return to index if ($redirect_option == 'index') { - switch ($table) { - case "Assets": - return route('hardware.index'); - case "Users": - return route('users.index'); - case "Licenses": - return route('licenses.index'); - case "Accessories": - return route('accessories.index'); - case "Components": - return route('components.index'); - case "Consumables": - return route('consumables.index'); - } + return match ($table) { + 'Assets' => redirect()->route('hardware.index'), + 'Users' => redirect()->route('users.index'), + 'Licenses' => redirect()->route('licenses.index'), + 'Accessories' => redirect()->route('accessories.index'), + 'Components' => redirect()->route('components.index'), + 'Consumables' => redirect()->route('consumables.index'), + }; } // return to thing being assigned if ($redirect_option == 'item') { - switch ($table) { - case "Assets": - return route('hardware.show', $id ?? $item_id); - case "Users": - return route('users.show', $id ?? $item_id); - case "Licenses": - return route('licenses.show', $id ?? $item_id); - case "Accessories": - return route('accessories.show', $id ?? $item_id); - case "Components": - return route('components.show', $id ?? $item_id); - case "Consumables": - return route('consumables.show', $id ?? $item_id); - } + return match ($table) { + 'Assets' => redirect()->route('hardware.show', $id ?? $item_id), + 'Users' => redirect()->route('users.show', $id ?? $item_id), + 'Licenses' => redirect()->route('licenses.show', $id ?? $item_id), + 'Accessories' => redirect()->route('accessories.show', $id ?? $item_id), + 'Components' => redirect()->route('components.show', $id ?? $item_id), + 'Consumables' => redirect()->route('consumables.show', $id ?? $item_id), + }; } // return to assignment target if ($redirect_option == 'target') { - switch ($checkout_to_type) { - case 'user': - return route('users.show', $request->assigned_user ?? $checkedInFrom); - case 'location': - return route('locations.show', $request->assigned_location ?? $checkedInFrom); - case 'asset': - return route('hardware.show', $request->assigned_asset ?? $checkedInFrom); - } + return match ($checkout_to_type) { + 'user' => redirect()->route('users.show', $request->assigned_user ?? $checkedInFrom), + 'location' => redirect()->route('locations.show', $request->assigned_location ?? $checkedInFrom), + 'asset' => redirect()->route('hardware.show', $request->assigned_asset ?? $checkedInFrom), + }; } + + // return to somewhere else + if ($redirect_option == 'other_redirect') { + return match ($other_redirect) { + 'audit' => redirect()->route('assets.audit.due'), + 'model' => redirect()->route('models.show', $request->model_id), + }; + + } + return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error')); } @@ -1562,6 +1626,11 @@ class Helper $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) { @@ -1600,14 +1669,17 @@ class Helper $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, - str_replace('App\\Models\\', '', $item->assigned_type) ?? null, + $item->assigned_type ? str_replace('App\\Models\\', '', $item->assigned_type) : null, $item->company_id ?? null, $item->company->name ?? null, // $item->defaultLoc->id ?? null, @@ -1619,6 +1691,15 @@ class Helper $location_company ?? null, ]; + $count++; + + // Bail early if this is not being run via artisan + if ((!$artisan) && ($count > 0)) { + return $mismatched; + } + + + } } } diff --git a/app/Helpers/IconHelper.php b/app/Helpers/IconHelper.php index 7c8e2a7456..8172f2bbbb 100644 --- a/app/Helpers/IconHelper.php +++ b/app/Helpers/IconHelper.php @@ -16,6 +16,7 @@ class IconHelper case 'clone': return 'far fa-clone'; case 'delete': + case 'upload deleted': return 'fas fa-trash'; case 'create': return 'fa-solid fa-plus'; @@ -43,6 +44,8 @@ class IconHelper return 'fa-regular fa-envelope'; case 'phone': return 'fa-solid fa-phone'; + case 'mobile': + return 'fas fa-mobile-screen-button'; case 'long-arrow-right': return 'fas fa-long-arrow-alt-right'; case 'download': @@ -151,6 +154,7 @@ class IconHelper case 'location': return 'fas fa-map-marker-alt'; case 'superadmin': + case 'admin': return 'fas fa-crown'; case 'print': return 'fa-solid fa-print'; diff --git a/app/Helpers/StorageHelper.php b/app/Helpers/StorageHelper.php index 47700f913a..cbd801d302 100644 --- a/app/Helpers/StorageHelper.php +++ b/app/Helpers/StorageHelper.php @@ -16,38 +16,84 @@ class StorageHelper $disk = config('filesystems.default'); } switch (config("filesystems.disks.$disk.driver")) { - case 'local': - return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?! + case 'local': + return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?! - case 's3': - return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess? + case 's3': + return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess? - default: - return Storage::disk($disk)->download($filename); + default: + return Storage::disk($disk)->download($filename); } } + public static function getMediaType($file_with_path) { + + // Get the file extension and determine the media type + if (Storage::exists($file_with_path)) { + $fileinfo = pathinfo($file_with_path); + $extension = strtolower($fileinfo['extension']); + switch ($extension) { + case 'avif': + case 'jpg': + case 'png': + case 'gif': + case 'svg': + case 'webp': + return 'image'; + case 'pdf': + return 'pdf'; + case 'mp3': + case 'wav': + case 'ogg': + return 'audio'; + case 'mp4': + case 'webm': + case 'mov': + return 'video'; + case 'doc': + case 'docx': + return 'document'; + case 'txt': + return 'text'; + case 'xls': + case 'xlsx': + case 'ods': + return 'spreadsheet'; + default: + return $extension; // Default for unknown types + } + } + return null; + } /** * This determines the file types that should be allowed inline and checks their fileinfo extension * to determine that they are safe to display inline. * * @author [ - * @since v7.0.14 - * @param $file_with_path + * @since v7.0.14 + * @param $file_with_path * @return bool */ - public static function allowSafeInline($file_with_path) { + public static function allowSafeInline($file_with_path) + { $allowed_inline = [ - 'pdf', - 'svg', - 'jpg', - 'gif', - 'svg', 'avif', - 'webp', + 'gif', + 'gif', + 'jpg', + 'mov', + 'mp3', + 'mp4', + 'ogg', + 'pdf', 'png', + 'svg', + 'wav', + 'webm', + 'webp', ]; @@ -59,10 +105,24 @@ class StorageHelper } + public static function getFiletype($file_with_path) + { + + // The file exists and is allowed to be displayed inline + if (Storage::exists($file_with_path)) { + return pathinfo($file_with_path, PATHINFO_EXTENSION); + } + + return null; + + } + + /** * Decide whether to show the file inline or download it. */ - public static function showOrDownloadFile($file, $filename) { + public static function showOrDownloadFile($file, $filename) + { $headers = []; diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index bc1ac56fc9..00cfce0516 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -77,13 +77,30 @@ class AccessoriesController extends Controller $accessory->supplier_id = request('supplier_id'); $accessory->notes = request('notes'); - $accessory = $request->handleImages($accessory); + if ($request->has('use_cloned_image')) { + $cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; + $new_image = 'accessories/'.$new_image_name; + Storage::disk('public')->copy('accessories/'.$cloned_model_img->image, $new_image); + $accessory->image = $new_image_name; + } + + } else { + $accessory = $request->handleImages($accessory); + } + + if($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } - session()->put(['redirect_option' => $request->get('redirect_option')]); // Was the accessory created? if ($accessory->save()) { // Redirect to the new accessory page - return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success')); + return Helper::getRedirectOption($request, $accessory->id, 'Accessories') + ->with('success', trans('admin/accessories/message.create.success')); } return redirect()->back()->withInput()->withErrors($accessory->getErrors()); @@ -113,11 +130,12 @@ class AccessoriesController extends Controller $this->authorize('create', Accessory::class); $cloned = clone $accessory; + $accessory_to_clone = $accessory; $cloned->id = null; $cloned->deleted_at = ''; - $cloned->location_id = null; return view('accessories/edit') + ->with('cloned_model', $accessory_to_clone) ->with('item', $cloned); } @@ -167,7 +185,8 @@ class AccessoriesController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); if ($accessory->save()) { - return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success')); + return Helper::getRedirectOption($request, $accessory->id, 'Accessories') + ->with('success', trans('admin/accessories/message.update.success')); } } else { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); @@ -220,7 +239,10 @@ class AccessoriesController extends Controller */ public function show(Accessory $accessory) : View | RedirectResponse { - $accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id); + $accessory->loadCount('checkouts as checkouts_count'); + + $accessory->load(['adminuser' => fn($query) => $query->withTrashed()]); + $this->authorize('view', $accessory); return view('accessories.view', compact('accessory')); } diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php deleted file mode 100644 index 9dbb16d83a..0000000000 --- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php +++ /dev/null @@ -1,132 +0,0 @@ -] - * @since [v1.0] - * @todo Switch to using the AssetFileRequest form request validator. - */ - public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse - { - - if (config('app.lock_passwords')) { - return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled')); - } - - $accessory = Accessory::find($accessoryId); - - if (isset($accessory->id)) { - $this->authorize('accessories.files', $accessory); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/accessories')) { - Storage::makeDirectory('private_uploads/accessories', 775); - } - - foreach ($request->file('file') as $file) { - - $file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file); - //Log the upload to the log - $accessory->logUpload($file_name, e($request->input('notes'))); - } - - - return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('success', trans('general.file_upload_success')); - - } - - return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded')); - } - // Prepare the error message - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); - - } - - /** - * Deletes the selected accessory file. - * - * @author [A. Gianotto] [] - * @since [v1.0] - * @param int $accessoryId - * @param int $fileId - */ - public function destroy($accessoryId = null, $fileId = null) : RedirectResponse - { - if ($accessory = Accessory::find($accessoryId)) { - $this->authorize('update', $accessory); - - 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')); - } - } - - } - return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found')); - } - - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); - } - - /** - * Allows the selected file to be viewed. - * - * @author [A. Gianotto] [] - * @since [v1.4] - * @param int $accessoryId - * @param int $fileId - */ - public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse - { - - - // the accessory is valid - if ($accessory = Accessory::find($accessoryId)) { - $this->authorize('view', $accessory); - $this->authorize('accessories.files', $accessory); - - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { - $file = 'private_uploads/accessories/'.$log->filename; - - try { - return StorageHelper::showOrDownloadFile($file, $log->filename); - } catch (\Exception $e) { - return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found')); - } - } - - return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found')); - - } - - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); - - } -} diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php index ab24200d78..a7655a278b 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php @@ -78,7 +78,8 @@ class AccessoryCheckinController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); - return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success')); + return Helper::getRedirectOption($request, $accessory->id, 'Accessories') + ->with('success', trans('admin/accessories/message.checkin.success')); } // Redirect to the accessory management page with error return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error')); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 58ce787245..9ed8c0fe45 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -71,6 +71,7 @@ class AccessoryCheckoutController extends Controller $this->authorize('checkout', $accessory); $target = $this->determineCheckoutTarget(); + session()->put(['checkout_to_type' => $target]); $accessory->checkout_qty = $request->input('checkout_qty', 1); @@ -97,7 +98,7 @@ class AccessoryCheckoutController extends Controller // Redirect to the new accessory page - return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories')) + return Helper::getRedirectOption($request, $accessory->id, 'Accessories') ->with('success', trans('admin/accessories/message.checkout.success')); } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index b20f7d97e8..f79ec1842f 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -7,6 +7,7 @@ use App\Events\CheckoutDeclined; use App\Events\ItemAccepted; use App\Events\ItemDeclined; use App\Http\Controllers\Controller; +use App\Mail\CheckoutAcceptanceResponseMail; use App\Models\Actionlog; use App\Models\Asset; use App\Models\CheckoutAcceptance; @@ -20,9 +21,12 @@ use App\Models\License; use App\Models\Component; use App\Models\Consumable; use App\Notifications\AcceptanceAssetAcceptedNotification; +use App\Notifications\AcceptanceAssetAcceptedToUserNotification; use App\Notifications\AcceptanceAssetDeclinedNotification; +use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use App\Http\Controllers\SettingsController; @@ -148,6 +152,8 @@ class AcceptanceController extends Controller } } + + $assigned_user = User::find($acceptance->assigned_to_id); // this is horrible switch($acceptance->checkoutable_type){ case 'App\Models\Asset': @@ -157,35 +163,30 @@ class AcceptanceController extends Controller return redirect()->back()->with('error', trans('admin/models/message.does_not_exist')); } $display_model = $asset_model->name; - $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\Accessory': $pdf_view_route ='account.accept.accept-accessory-eula'; $accessory = Accessory::find($item->id); $display_model = $accessory->name; - $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\LicenseSeat': $pdf_view_route ='account.accept.accept-license-eula'; $license = License::find($item->license_id); $display_model = $license->name; - $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\Component': $pdf_view_route ='account.accept.accept-component-eula'; $component = Component::find($item->id); $display_model = $component->name; - $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\Consumable': $pdf_view_route ='account.accept.accept-consumable-eula'; $consumable = Consumable::find($item->id); $display_model = $consumable->name; - $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; } // if ($acceptance->checkoutable_type == 'App\Models\Asset') { @@ -226,11 +227,12 @@ class AcceptanceController extends Controller 'note' => $request->input('note'), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'), 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'), - 'assigned_to' => $assigned_to, + 'assigned_to' => $assigned_user->present()->fullName, 'company_name' => $branding_settings->site_name, 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'logo' => $path_logo, 'date_settings' => $branding_settings->date_display_format, + 'admin' => auth()->user()->present()?->fullName, ]; if ($pdf_view_route!='') { @@ -240,8 +242,21 @@ class AcceptanceController extends Controller } $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); + + // Send the PDF to the signing user + if (($request->input('send_copy') == '1') && ($assigned_user->email !='')) { + + // Add the attachment for the signing user into the $data array + $data['file'] = $pdf_filename; + $locale = $assigned_user->locale; + try { + $assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($locale)); + } catch (\Exception $e) { + Log::warning($e); + } + } try { - $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); + $acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale)); } catch (\Exception $e) { Log::warning($e); } @@ -333,10 +348,29 @@ class AcceptanceController extends Controller $acceptance->decline($sig_filename, $request->input('note')); $acceptance->notify(new AcceptanceAssetDeclinedNotification($data)); + Log::debug('New event acceptance.'); event(new CheckoutDeclined($acceptance)); $return_msg = trans('admin/users/message.declined'); } + if ($acceptance->alert_on_response_id) { + try { + $recipient = User::find($acceptance->alert_on_response_id); + + if ($recipient) { + Log::debug('Attempting to send email acceptance.'); + Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail( + $acceptance, + $recipient, + $request->input('asset_acceptance') === 'accepted', + )); + Log::debug('Send email notification sucess on checkout acceptance response.'); + } + } catch (Exception $e) { + Log::error($e->getMessage()); + Log::warning($e); + } + } return redirect()->to('account/accept')->with('success', $return_msg); diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index 90486b40f2..7933b19057 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -288,32 +288,42 @@ class AccessoriesController extends Controller 'note' => $request->input('note'), ]); + $accessory_checkout->created_by = auth()->id(); $accessory_checkout->save(); + + $payload = [ + 'accessory_id' => $accessory->id, + 'assigned_to' => $target->id, + 'assigned_type' => $target::class, + 'note' => $request->input('note'), + 'created_by' => auth()->id(), + 'pivot' => $accessory_checkout->id, + ]; } // Set this value to be able to pass the qty through to the event event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note'))); - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success'))); + return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkout.success'))); } /** * Check in the item so that it can be checked out again to someone else * - * @uses Accessory::checkin_email() to determine if an email can and should be sent - * @author [A. Gianotto] [] * @param Request $request * @param int $accessoryUserId * @param string $backto - * @return \Illuminate\Http\RedirectResponse + * @return JsonResponse + * @uses Accessory::checkin_email() to determine if an email can and should be sent + * @author [A. Gianotto] [] * @internal param int $accessoryId */ public function checkin(Request $request, $accessoryUserId = null) { if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist', ['id' => $accessoryUserId]))); } $accessory = Accessory::find($accessory_checkout->accessory_id); @@ -327,7 +337,14 @@ class AccessoriesController extends Controller $user = User::find($accessory_checkout->assigned_to); } - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success'))); + $payload = [ + 'accessory_id' => $accessory->id, + 'note' => $request->input('note'), + 'created_by' => auth()->id(), + 'pivot' => $accessory_checkout->id, + ]; + + return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success'))); } return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error'))); diff --git a/app/Http/Controllers/Api/AssetFilesController.php b/app/Http/Controllers/Api/AssetFilesController.php deleted file mode 100644 index fabe9ebbb3..0000000000 --- a/app/Http/Controllers/Api/AssetFilesController.php +++ /dev/null @@ -1,200 +0,0 @@ - - * - * @version v1.0 - * @author [T. Scarsbrook] [] - */ -class AssetFilesController extends Controller -{ - /** - * Accepts a POST to upload a file to the server. - * - * @param \App\Http\Requests\UploadFileRequest $request - * @param int $assetId - * @since [v6.0] - * @author [T. Scarsbrook] [] - */ - public function store(UploadFileRequest $request, $assetId = null) : JsonResponse - { - // Start by checking if the asset being acted upon exists - if (! $asset = Asset::find($assetId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404); - } - - // Make sure we are allowed to update this asset - $this->authorize('update', $asset); - - if ($request->hasFile('file')) { - // If the file storage directory doesn't exist; create it - if (! Storage::exists('private_uploads/assets')) { - Storage::makeDirectory('private_uploads/assets', 775); - } - - // Loop over the attached files and add them to the asset - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file); - - $asset->logUpload($file_name, e($request->get('notes'))); - } - - // All done - report success - return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success'))); - } - - // We only reach here if no files were included in the POST, so tell the user this - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500); - } - - /** - * List the files for an asset. - * - * @param int $assetId - * @since [v6.0] - * @author [T. Scarsbrook] [] - */ - public function list(Asset $asset, Request $request) : JsonResponse | array - { - - $this->authorize('view', $asset); - - $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')); - } - - // 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()); - - } - - /** - * Check for permissions and display the file. - * - * @param int $assetId - * @param int $fileId - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - * @since [v6.0] - * @author [T. Scarsbrook] [] - */ - public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse - { - - // the asset is valid - if (isset($asset->id)) { - $this->authorize('view', $asset); - - // Check that the file being requested exists for the asset - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404); - } - - // Form the full filename with path - $file = 'private_uploads/assets/'.$log->filename; - Log::debug('Checking for '.$file); - - if ($log->action_type == 'audit') { - $file = 'private_uploads/audits/'.$log->filename; - } - - // Check the file actually exists on the filesystem - if (! Storage::exists($file)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404); - } - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); - } - - // Send back an error message - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500); - } - - /** - * Delete the associated file - * - * @param int $assetId - * @param int $fileId - * @since [v6.0] - * @author [T. Scarsbrook] [] - */ - public function destroy(Asset $asset, $fileId = null) : JsonResponse - { - - $rel_path = 'private_uploads/assets'; - - // the asset is valid - if (isset($asset->id)) { - $this->authorize('update', $asset); - - // Check for the file - $log = Actionlog::find($fileId); - - if ($log) { - // Check the file actually exists, and delete it - if (Storage::exists($rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); - } - - // Delete the record of the file - $log->delete(); - - // All deleting done - notify the user of success - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200); - } - - // The file doesn't seem to really exist, so report an error - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500); - } - - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500); - } -} diff --git a/app/Http/Controllers/Api/AssetModelFilesController.php b/app/Http/Controllers/Api/AssetModelFilesController.php deleted file mode 100644 index 7f0f06c635..0000000000 --- a/app/Http/Controllers/Api/AssetModelFilesController.php +++ /dev/null @@ -1,179 +0,0 @@ - - * - * @version v1.0 - * @author [T. Scarsbrook] [] - */ -class AssetModelFilesController extends Controller -{ - /** - * Accepts a POST to upload a file to the server. - * - * @param \App\Http\Requests\UploadFileRequest $request - * @param int $assetModelId - * @since [v7.0.12] - * @author [r-xyz] - */ - public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse - { - // Start by checking if the asset being acted upon exists - if (! $assetModel = AssetModel::find($assetModelId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); - } - - // Make sure we are allowed to update this asset - $this->authorize('update', $assetModel); - - if ($request->hasFile('file')) { - // If the file storage directory doesn't exist; create it - if (! Storage::exists('private_uploads/assetmodels')) { - Storage::makeDirectory('private_uploads/assetmodels', 775); - } - - // Loop over the attached files and add them to the asset - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file); - - $assetModel->logUpload($file_name, e($request->get('notes'))); - } - - // All done - report success - return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success'))); - } - - // We only reach here if no files were included in the POST, so tell the user this - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500); - } - - /** - * List the files for an asset. - * - * @param int $assetmodel - * @since [v7.0.12] - * @author [r-xyz] - */ - public function list($assetmodel_id) : JsonResponse | array - { - $assetmodel = AssetModel::with('uploads')->find($assetmodel_id); - $this->authorize('view', $assetmodel); - return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count()); - } - - /** - * Check for permissions and display the file. - * - * @param int $assetModelId - * @param int $fileId - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - * @since [v7.0.12] - * @author [r-xyz] - */ - public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse - { - // Start by checking if the asset being acted upon exists - if (! $assetModel = AssetModel::find($assetModelId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); - } - - // the asset is valid - if (isset($assetModel->id)) { - $this->authorize('view', $assetModel); - - // Check that the file being requested exists for the asset - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404); - } - - // Form the full filename with path - $file = 'private_uploads/assetmodels/'.$log->filename; - Log::debug('Checking for '.$file); - - if ($log->action_type == 'audit') { - $file = 'private_uploads/audits/'.$log->filename; - } - - // Check the file actually exists on the filesystem - if (! Storage::exists($file)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404); - } - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); - } - - // Send back an error message - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500); - } - - /** - * Delete the associated file - * - * @param int $assetModelId - * @param int $fileId - * @since [v7.0.12] - * @author [r-xyz] - */ - public function destroy($assetModelId = null, $fileId = null) : JsonResponse - { - // Start by checking if the asset being acted upon exists - if (! $assetModel = AssetModel::find($assetModelId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); - } - - $rel_path = 'private_uploads/assetmodels'; - - // the asset is valid - if (isset($assetModel->id)) { - $this->authorize('update', $assetModel); - - // Check for the file - $log = Actionlog::find($fileId); - if ($log) { - // Check the file actually exists, and delete it - if (Storage::exists($rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); - } - // Delete the record of the file - $log->delete(); - - // All deleting done - notify the user of success - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200); - } - - // The file doesn't seem to really exist, so report an error - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500); - } - - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500); - } -} diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index 954da30870..8b8bc01124 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -50,6 +50,7 @@ class AssetModelsController extends Controller 'fieldset', 'deleted_at', 'updated_at', + 'require_serial', ]; $assetmodels = AssetModel::select([ @@ -69,6 +70,7 @@ class AssetModelsController extends Controller 'models.fieldset_id', 'models.deleted_at', 'models.updated_at', + 'models.require_serial' ]) ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser') ->withCount('assets as assets_count'); @@ -85,6 +87,12 @@ class AssetModelsController extends Controller $assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number')); } + if ($request->input('requestable') == 'true') { + $assetmodels = $assetmodels->where('models.requestable', '=', '1'); + } elseif ($request->input('requestable') == 'false') { + $assetmodels = $assetmodels->where('models.requestable', '=', '0'); + } + if ($request->filled('notes')) { $assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes')); } @@ -148,7 +156,7 @@ class AssetModelsController extends Controller $assetmodel = $request->handleImages($assetmodel); if ($assetmodel->save()) { - return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success'))); + return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.create.success'))); } return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors())); @@ -201,7 +209,7 @@ class AssetModelsController extends Controller $assetmodel = AssetModel::findOrFail($id); $assetmodel->fill($request->all()); $assetmodel = $request->handleImages($assetmodel); - + /** * Allow custom_fieldset_id to override and populate fieldset_id. * This is stupid, but required for legacy API support. @@ -216,7 +224,7 @@ class AssetModelsController extends Controller if ($assetmodel->save()) { - return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success'))); + return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.update.success'))); } return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors())); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index bd95c9cce5..bfe071a6b5 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -114,17 +114,23 @@ class AssetsController extends Controller 'byod', 'asset_eol_date', 'requestable', + 'jobtitle', ]; + $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load + + foreach ($all_custom_fields as $field) { + $allowed_columns[] = $field->db_column_name(); + } + $filter = []; if ($request->filled('filter')) { $filter = json_decode($request->input('filter'), true); - } - $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load - foreach ($all_custom_fields as $field) { - $allowed_columns[] = $field->db_column_name(); + $filter = array_filter($filter, function ($key) use ($allowed_columns) { + return in_array($key, $allowed_columns); + }, ARRAY_FILTER_USE_KEY); } $assets = Asset::select('assets.*') @@ -140,6 +146,7 @@ class AssetsController extends Controller 'model.category', 'model.manufacturer', 'model.fieldset', + 'model.depreciation', 'supplier' ); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. @@ -298,9 +305,15 @@ class AssetsController extends Controller if ($request->input('requestable') == 'true') { $assets->where('assets.requestable', '=', '1'); } - + if ($request->filled('model_id')) { - $assets->InModelList([$request->input('model_id')]); + // If model_id is already an array, just use it as-is + if (is_array($request->input('model_id'))) { + $assets->InModelList($request->input('model_id')); + } else { + // Otherwise, turn it into an array + $assets->InModelList([$request->input('model_id')]); + } } if ($request->filled('category_id')) { @@ -389,6 +402,9 @@ class AssetsController extends Controller case 'assigned_to': $assets->OrderAssigned($order); break; + case 'jobtitle': + $assets->OrderByJobTitle($order); + break; case 'created_by': $assets->OrderByCreatedByName($order); break; @@ -594,7 +610,7 @@ class AssetsController extends Controller $asset->use_text = $asset->present()->fullName; if (($asset->checkedOutToUser()) && ($asset->assigned)) { - $asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute(); + $asset->use_text .= ' → ' . $asset->assigned->display_name; } @@ -695,7 +711,9 @@ class AssetsController extends Controller return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success'))); - return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + // below is what we want the _eventual_ return to look like - in a more standardized format. + // return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + } return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); @@ -1133,12 +1151,12 @@ class AssetsController extends Controller } } - // Validate custom fields - Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate(); + // 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())); + return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors())); } diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php index 319b51dd11..85cb70b126 100644 --- a/app/Http/Controllers/Api/CategoriesController.php +++ b/app/Http/Controllers/Api/CategoriesController.php @@ -56,7 +56,7 @@ class CategoriesController extends Controller 'notes', ]) ->with('adminuser') - ->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count'); + ->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count'); /* @@ -212,7 +212,7 @@ class CategoriesController extends Controller public function destroy($id) : JsonResponse { $this->authorize('delete', Category::class); - $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id); + $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id); if (! $category->isDeletable()) { return response()->json( diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php index fd7f57ddce..aee38301f4 100644 --- a/app/Http/Controllers/Api/CompaniesController.php +++ b/app/Http/Controllers/Api/CompaniesController.php @@ -43,7 +43,10 @@ class CompaniesController extends Controller $companies = Company::withCount(['assets as assets_count' => function ($query) { $query->AssetsForShow(); - }])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count'); + }]) + ->with('adminuser') + ->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count'); + if ($request->filled('search')) { $companies->TextSearch($request->input('search')); @@ -119,6 +122,7 @@ class CompaniesController extends Controller { $this->authorize('view', Company::class); $company = Company::findOrFail($id); + $this->authorize('view', $company); return (new CompaniesTransformer)->transformCompany($company); } @@ -136,6 +140,7 @@ class CompaniesController extends Controller { $this->authorize('update', Company::class); $company = Company::findOrFail($id); + $this->authorize('update', $company); $company->fill($request->all()); $company = $request->handleImages($company); @@ -188,6 +193,7 @@ class CompaniesController extends Controller 'companies.image', ]); + if ($request->filled('search')) { $companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%'); } diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 7bddde070c..e163f080aa 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -228,11 +228,16 @@ class ConsumablesController extends Controller foreach ($consumable->consumableAssignments as $consumable_assignment) { $rows[] = [ 'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '', - 'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User', + 'user' => ($consumable_assignment->user) ? [ + 'id' => (int) $consumable_assignment->user->id, + 'name'=> e($consumable_assignment->user->display_name), + ] : null, 'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'), 'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null, - 'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response - 'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, + 'created_by' => ($consumable_assignment->adminuser) ? [ + 'id' => (int) $consumable_assignment->adminuser->id, + 'name'=> e($consumable_assignment->adminuser->display_name), + ] : null, ]; } diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php index 41597cd2c5..79bffd1206 100644 --- a/app/Http/Controllers/Api/ImportController.php +++ b/app/Http/Controllers/Api/ImportController.php @@ -195,7 +195,7 @@ class ImportController extends Controller // Run a backup immediately before processing if ($request->get('run-backup')) { Log::debug('Backup manually requested via importer'); - Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]); + Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H-i-s')]); } else { Log::debug('NO BACKUP requested via importer'); } @@ -234,6 +234,15 @@ class ImportController extends Controller case 'location': $redirectTo = 'locations.index'; break; + case 'supplier': + $redirectTo = 'suppliers.index'; + break; + case 'manufacturer': + $redirectTo = 'manufacturers.index'; + break; + case 'category': + $redirectTo = 'categories.index'; + break; } if ($errors) { //Failure diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php index d8a0dc119b..247f71ff26 100644 --- a/app/Http/Controllers/Api/LicenseSeatsController.php +++ b/app/Http/Controllers/Api/LicenseSeatsController.php @@ -29,12 +29,21 @@ class LicenseSeatsController extends Controller $seats = LicenseSeat::with('license', 'user', 'asset', 'user.department') ->where('license_seats.license_id', $licenseId); + if ($request->input('status') == 'available') { + $seats->whereNull('license_seats.assigned_to'); + } + + if ($request->input('status') == 'assigned') { + $seats->ByAssigned(); + } + + $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; if ($request->input('sort') == 'department') { $seats->OrderDepartments($order); } else { - $seats->orderBy('id', $order); + $seats->orderBy('updated_at', $order); } $total = $seats->count(); diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index 638765928b..b5c911a6ca 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -87,7 +87,8 @@ class LocationsController extends Controller ->withCount('accessories as accessories_count') ->withCount('rtd_assets as rtd_assets_count') ->withCount('children as children_count') - ->withCount('users as users_count'); + ->withCount('users as users_count') + ->with('adminuser'); // Only scope locations if the setting is enabled if (Setting::getSettings()->scope_locations_fmcs) { @@ -218,6 +219,7 @@ class LocationsController extends Controller 'locations.updated_at', 'locations.image', 'locations.currency', + 'locations.company_id', 'locations.notes', ]) ->withCount('assignedAssets as assigned_assets_count') diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/MaintenancesController.php similarity index 75% rename from app/Http/Controllers/Api/AssetMaintenancesController.php rename to app/Http/Controllers/Api/MaintenancesController.php index b4e9b44196..86f561c86c 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/MaintenancesController.php @@ -4,11 +4,11 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; -use App\Http\Transformers\AssetMaintenancesTransformer; +use App\Http\Requests\ImageUploadRequest; +use App\Http\Transformers\MaintenancesTransformer; use App\Models\Asset; -use App\Models\AssetMaintenance; +use App\Models\Maintenance; use App\Models\Company; -use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; @@ -18,13 +18,13 @@ use Illuminate\Http\JsonResponse; * * @version v2.0 */ -class AssetMaintenancesController extends Controller +class MaintenancesController extends Controller { /** * Generates the JSON response for asset maintenances listing view. * - * @see AssetMaintenancesController::getIndex() method that generates view + * @see MaintenancesController::getIndex() method that generates view * @author Vincent Sposato * @version v1.0 * @since [v1.8] @@ -33,7 +33,7 @@ class AssetMaintenancesController extends Controller { $this->authorize('view', Asset::class); - $maintenances = AssetMaintenance::select('asset_maintenances.*') + $maintenances = Maintenance::select('maintenances.*') ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser'); if ($request->filled('search')) { @@ -45,11 +45,11 @@ class AssetMaintenancesController extends Controller } if ($request->filled('supplier_id')) { - $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id')); + $maintenances->where('maintenances.supplier_id', '=', $request->input('supplier_id')); } if ($request->filled('created_by')) { - $maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by')); + $maintenances->where('maintenances.created_by', '=', $request->input('created_by')); } if ($request->filled('asset_maintenance_type')) { @@ -63,7 +63,7 @@ class AssetMaintenancesController extends Controller $allowed_columns = [ 'id', - 'title', + 'name', 'asset_maintenance_time', 'asset_maintenance_type', 'cost', @@ -75,6 +75,7 @@ class AssetMaintenancesController extends Controller 'serial', 'created_by', 'supplier', + 'location', 'is_warranty', 'status_label', ]; @@ -98,6 +99,9 @@ class AssetMaintenancesController extends Controller case 'serial': $maintenances = $maintenances->OrderByAssetSerial($order); break; + case 'location': + $maintenances = $maintenances->OrderLocationName($order); + break; case 'status_label': $maintenances = $maintenances->OrderStatusName($order); break; @@ -108,7 +112,7 @@ class AssetMaintenancesController extends Controller $total = $maintenances->count(); $maintenances = $maintenances->skip($offset)->take($limit)->get(); - return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total); + return (new MaintenancesTransformer())->transformMaintenances($maintenances, $total); } @@ -117,22 +121,23 @@ class AssetMaintenancesController extends Controller /** * Validates and stores the new asset maintenance * - * @see AssetMaintenancesController::getCreate() method for the form + * @see MaintenancesController::getCreate() method for the form * @author Vincent Sposato * @version v1.0 * @since [v1.8] */ - public function store(Request $request) : JsonResponse | array + public function store(ImageUploadRequest $request) : JsonResponse | array { $this->authorize('update', Asset::class); + // create a new model instance - $maintenance = new AssetMaintenance(); + $maintenance = new Maintenance(); $maintenance->fill($request->all()); $maintenance->created_by = auth()->id(); - + $maintenance = $request->handleImages($maintenance); // Was the asset maintenance created? if ($maintenance->save()) { - return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.create.success'))); + return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.create.success'))); } @@ -153,11 +158,11 @@ class AssetMaintenancesController extends Controller { $this->authorize('update', Asset::class); - if ($maintenance = AssetMaintenance::with('asset')->find($id)) { + if ($maintenance = Maintenance::with('asset')->find($id)) { // Can this user manage this asset? if (! Company::isCurrentUserHasAccess($maintenance->asset)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')]))); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')]))); } // The asset this miantenance is attached to is not valid or has been deleted @@ -168,13 +173,13 @@ class AssetMaintenancesController extends Controller $maintenance->fill($request->all()); if ($maintenance->save()) { - return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.edit.success'))); + return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.edit.success'))); } return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors())); } - return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id]))); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id]))); } @@ -182,20 +187,20 @@ class AssetMaintenancesController extends Controller * Delete an asset maintenance * * @author A. Gianotto - * @param int $assetMaintenanceId + * @param int $maintenanceId * @version v1.0 * @since [v4.0] */ - public function destroy($assetMaintenanceId) : JsonResponse | array + public function destroy($maintenanceId) : JsonResponse | array { $this->authorize('update', Asset::class); // Check if the asset maintenance exists - $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); + $maintenance = Maintenance::findOrFail($maintenanceId); - $assetMaintenance->delete(); + $maintenance->delete(); - return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success'))); + return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.delete.success'))); } @@ -204,19 +209,19 @@ class AssetMaintenancesController extends Controller * View an asset maintenance * * @author A. Gianotto - * @param int $assetMaintenanceId + * @param int $maintenanceId * @version v1.0 * @since [v4.0] */ - public function show($assetMaintenanceId) : JsonResponse | array + public function show($maintenanceId) : JsonResponse | array { $this->authorize('view', Asset::class); - $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); - if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { + $maintenance = Maintenance::findOrFail($maintenanceId); + if (! Company::isCurrentUserHasAccess($maintenance->asset)) { return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset')); } - return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance); + return (new MaintenancesTransformer())->transformMaintenance($maintenance); } } diff --git a/app/Http/Controllers/Api/NotesController.php b/app/Http/Controllers/Api/NotesController.php new file mode 100644 index 0000000000..c1a16fd4d6 --- /dev/null +++ b/app/Http/Controllers/Api/NotesController.php @@ -0,0 +1,95 @@ +authorize('view', $asset); + + // Get the manual notes for the asset + $notes = ActionLog::with('user:id,username') + ->where('item_type', Asset::class) + ->where('item_id', $asset->id) + ->where('action_type', 'note added') + ->orderBy('created_at', 'desc') + ->get(['id', 'created_at', 'note', 'created_by', 'item_id', 'item_type', 'action_type', 'target_id', 'target_type']); + + $notesArray = $notes->map(function ($note) { + return [ + 'id' => $note->id, + 'created_at' => $note->created_at, + 'note' => $note->note, + 'created_by' => $note->created_by, + 'username' => $note->user?->username, // adding the username + 'item_id' => $note->item_id, + 'item_type' => $note->item_type, + 'action_type' => $note->action_type, + ]; + }); + + // Return a success response + return response()->json(Helper::formatStandardApiResponse('success', ['notes' => $notesArray, 'asset_id' => $asset->id])); + } + + /** + * Store a manual note on a specified asset and log the action. + * + * Checks authorization for updating assets, validates the presence of the 'note', + * attempts to find the asset by ID, and creates a new ActionLog entry if successful. + * Returns JSON responses indicating success or failure with appropriate HTTP status codes. + * + * @param \Illuminate\Http\Request $request The incoming HTTP request containing the 'note'. + * @param Asset $asset The ID of the asset to attach the note to. + * @return \Illuminate\Http\JsonResponse + */ + public function store(Request $request, Asset $asset): JsonResponse + { + $this->authorize('update', $asset); + + if ($request->input('note', '') == '') { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.required', ['attribute' => 'note'])), 422); + } + + // Create the note + $logaction = new ActionLog(); + $logaction->item_type = get_class($asset); + $logaction->created_by = Auth::id(); + $logaction->item_id = $asset->id; + $logaction->note = $request->input('note', ''); + + if ($logaction->logaction('note added')) { + // Return a success response + return response()->json(Helper::formatStandardApiResponse('success', ['note' => $logaction->note, 'item_id' => $asset->id], trans('general.note_added'))); + } + + // Return an error response if something went wrong + return response()->json(Helper::formatStandardApiResponse('error', null, 'Something went wrong'), 500); + } +} diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php index f7f91e094e..69db8aae04 100644 --- a/app/Http/Controllers/Api/ProfileController.php +++ b/app/Http/Controllers/Api/ProfileController.php @@ -4,15 +4,19 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; +use App\Http\Transformers\ProfileTransformer; use App\Models\CheckoutRequest; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Response; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; use Laravel\Passport\TokenRepository; use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Support\Facades\Gate; use App\Models\CustomField; use Illuminate\Support\Facades\DB; use Illuminate\Http\JsonResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class ProfileController extends Controller { @@ -167,6 +171,22 @@ class ProfileController extends Controller } + /** + * Display the EULAs accepted by the user. + * + * @param \App\Http\Transformers\ActionlogsTransformer $transformer + * @return \Illuminate\Http\JsonResponse + *@since [v8.1.16] + * @author [Godfrey Martinez] [] + */ + public function eulas(ProfileTransformer $transformer) + { + // Only return this user's EULAs + $eulas = auth()->user()->eulas; + return response()->json( + $transformer->transformFiles($eulas, $eulas->count()) + ); + } } diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 494c75104f..c03dddf6b7 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -5,6 +5,8 @@ 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; @@ -18,10 +20,11 @@ class ReportsController extends Controller */ public function index(Request $request) : JsonResponse | array { - $this->authorize('reports.view'); + $this->authorize('activity.view'); $actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location'); + if ($request->filled('search')) { $actionlogs = $actionlogs->TextSearch(e($request->input('search'))); } diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php index 7eb28a4815..f24dd25b17 100644 --- a/app/Http/Controllers/Api/SettingsController.php +++ b/app/Http/Controllers/Api/SettingsController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; -use App\Helpers\StorageHelper; use App\Http\Transformers\DatatablesTransformer; use Illuminate\Http\Request; use App\Http\Controllers\Controller; @@ -51,10 +50,22 @@ class SettingsController extends Controller })->slice(0, 10)->map(function ($item) use ($settings) { return (object) [ 'username' => $item[$settings['ldap_username_field']][0] ?? null, + 'display_name' => $item[$settings['ldap_display_name']][0] ?? null, 'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null, 'lastname' => $item[$settings['ldap_lname_field']][0] ?? null, 'firstname' => $item[$settings['ldap_fname_field']][0] ?? null, 'email' => $item[$settings['ldap_email']][0] ?? null, + 'phone' => $item[$settings['ldap_phone_field']][0] ?? null, + 'mobile' => $item[$settings['ldap_mobile']][0] ?? null, + 'jobtitle' => $item[$settings['ldap_jobtitle']][0] ?? null, + 'department' => $item[$settings['ldap_department']][0] ?? null, + 'manager' => $item[$settings['ldap_manager']][0] ?? null, + 'address' => $item[$settings['ldap_address']][0] ?? null, + 'city' => $item[$settings['ldap_city']][0] ?? null, + 'state' => $item[$settings['ldap_state']][0] ?? null, + 'zip' => $item[$settings['ldap_zip']][0] ?? null, + 'country' => $item[$settings['ldap_country']][0] ?? null, + 'location' => $item[$settings['ldap_location']][0] ?? null, ]; }); if ($users->count() > 0) { @@ -78,7 +89,7 @@ class SettingsController extends Controller } } catch (\Exception $e) { Log::debug('Connection failed but we cannot debug it any further on our end.'); - return response()->json(['message' => $e->getMessage()], 500); + return response()->json(['message' => $e->getMessage()], 400); } @@ -150,8 +161,11 @@ class SettingsController extends Controller if (!config('app.lock_passwords')) { try { Notification::send(Setting::first(), new MailTest()); + Log::debug('Attempting to sending to '.config('mail.reply_to.address')); return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200); } catch (\Exception $e) { + Log::error('Mail sent error using '.config('mail.reply_to.address') .': '. $e->getMessage()); + Log::debug($e); return response()->json(['message' => $e->getMessage()], 500); } } @@ -315,4 +329,4 @@ class SettingsController extends Controller } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php index f752f22241..6784ee82c1 100644 --- a/app/Http/Controllers/Api/SuppliersController.php +++ b/app/Http/Controllers/Api/SuppliersController.php @@ -24,10 +24,15 @@ class SuppliersController extends Controller public function index(Request $request): array { $this->authorize('view', Supplier::class); - $allowed_columns = [' - id', + $allowed_columns = [ + 'id', 'name', 'address', + 'address2', + 'city', + 'state', + 'country', + 'zip', 'phone', 'contact', 'fax', @@ -39,21 +44,24 @@ class SuppliersController extends Controller 'components_count', 'consumables_count', 'url', + 'notes', ]; $suppliers = Supplier::select( - ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url']) + ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip']) ->withCount('assets as assets_count') ->withCount('licenses as licenses_count') ->withCount('accessories as accessories_count') ->withCount('components as components_count') - ->withCount('consumables as consumables_count'); + ->withCount('consumables as consumables_count') + ->with('adminuser'); if ($request->filled('search')) { - $suppliers = $suppliers->TextSearch($request->input('search')); + $suppliers->TextSearch($request->input('search')); } + if ($request->filled('name')) { $suppliers->where('name', '=', $request->input('name')); } @@ -100,7 +108,15 @@ class SuppliersController extends Controller $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $suppliers->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $suppliers->OrderByCreatedByName($order); + break; + default: + $suppliers->orderBy($sort, $order); + break; + } $total = $suppliers->count(); $suppliers = $suppliers->skip($offset)->take($limit)->get(); @@ -178,7 +194,7 @@ class SuppliersController extends Controller public function destroy($id) : JsonResponse { $this->authorize('delete', Supplier::class); - $supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id); + $supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id); $this->authorize('delete', $supplier); @@ -186,8 +202,8 @@ class SuppliersController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]))); } - if ($supplier->asset_maintenances_count > 0) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count]))); + if ($supplier->maintenances_count > 0) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]))); } if ($supplier->licenses_count > 0) { diff --git a/app/Http/Controllers/Api/UploadedFilesController.php b/app/Http/Controllers/Api/UploadedFilesController.php new file mode 100644 index 0000000000..a4a2de0520 --- /dev/null +++ b/app/Http/Controllers/Api/UploadedFilesController.php @@ -0,0 +1,216 @@ +] + */ + public function index(Request $request, $object_type, $id) : JsonResponse | array + { + + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('view', $object); + + if (!$object) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object'))); + } + + // Columns allowed for sorting + $allowed_columns = + [ + 'id', + 'filename', + 'action_type', + 'action_date', + 'note', + 'created_at', + ]; + + + $uploads = self::$map_object_type[$object_type]::withTrashed()->find($id)->uploads() + ->with('adminuser'); + + $offset = ($request->input('offset') > $uploads->count()) ? $uploads->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'; + + // Text search on action_logs fields + // We could use the normal Actionlogs text scope, but it's a very heavy query since it's searching across all relations + // and we generally won't need that here + if ($request->filled('search')) { + + $uploads->where( + function ($query) use ($request) { + $query->where('filename', 'LIKE', '%' . $request->input('search') . '%') + ->orWhere('note', 'LIKE', '%' . $request->input('search') . '%'); + } + ); + } + + $total = $uploads->count(); + $uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get(); + + return (new UploadedFilesTransformer())->transformFiles($uploads, $total); + } + + + /** + * Accepts a POST to upload a file to the server. + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param string $object_type the type of object to upload the file to + * @param int $id the ID of the object to store so we can check permisisons + * @since [v8.1.17] + * @author [A. Gianotto ] + */ + public function store(UploadFileRequest $request, $object_type, $id) : JsonResponse + { + + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('view', $object); + + if (!$object) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object'))); + } + + // If the file storage directory doesn't exist, create it + if (! Storage::exists(self::$map_storage_path[$object_type])) { + Storage::makeDirectory(self::$map_storage_path[$object_type], 775); + } + + + if ($request->hasFile('file')) { + // Loop over the attached files and add them to the object + foreach ($request->file('file') as $file) { + $file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file); + $files[] = $file_name; + $object->logUpload($file_name, $request->get('notes')); + } + + $files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded') + ->where('item_type', '=', self::$map_object_type[$object_type]) + ->where('item_id', '=', $id)->whereIn('filename', $files) + ->get(); + + return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files)))); + } + + // No files were submitted + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.nofiles'))); + } + + + + /** + * Check for permissions and display the file. + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param string $object_type the type of object to upload the file to + * @param int $id the ID of the object to delete from so we can check permisisons + * @param $file_id the ID of the file to delete from the action_logs table + * @since [v8.1.17] + * @author [A. Gianotto ] + */ + public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse + { + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('view', $object); + + if (!$object) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object'))); + } + + + // Check that the file being requested exists for the object + if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id) + ) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_id')), 200); + } + + + if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.file_not_found'), 200)); + } + + if (request('inline') == 'true') { + $headers = [ + 'Content-Disposition' => 'inline', + ]; + return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers); + } + + return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename); + + } + + /** + * Delete the associated file + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param string $object_type the type of object to upload the file to + * @param int $id the ID of the object to delete from so we can check permisisons + * @param $file_id the ID of the file to delete from the action_logs table + * @since [v8.1.17] + * @author [A. Gianotto ] + */ + public function destroy($object_type, $id, $file_id) : JsonResponse + { + + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('update', self::$map_object_type[$object_type]); + + if (!$object) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object'))); + } + + + // Check for the file + $log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type]) + ->where('item_id', $object->id)->first(); + + if ($log) { + // Check the file actually exists, and delete it + if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) { + Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename); + } + // Delete the record of the file + if ($log->logUploadDelete($object, $log->filename)) { + return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200); + } + + + } + + // The file doesn't seem to really exist, so report an error + return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500); + + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 09dadbbd3c..1b06f5364f 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -6,6 +6,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\SaveUserRequest; use App\Http\Transformers\AccessoriesTransformer; +use App\Http\Transformers\ActionlogsTransformer; use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\ConsumablesTransformer; use App\Http\Transformers\LicensesTransformer; @@ -19,9 +20,12 @@ use App\Models\Consumable; use App\Models\License; use App\Models\User; use App\Notifications\CurrentInventory; +use App\Notifications\WelcomeNotification; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Log; @@ -61,12 +65,14 @@ class UsersController extends Controller 'users.jobtitle', 'users.last_login', 'users.last_name', + 'users.display_name', 'users.locale', 'users.location_id', 'users.manager_id', 'users.notes', 'users.permissions', 'users.phone', + 'users.mobile', 'users.state', 'users.two_factor_enrolled', 'users.two_factor_optin', @@ -80,7 +86,12 @@ class UsersController extends Controller 'users.autoassign_licenses', 'users.website', - ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations') + ])->with('manager') + ->with('groups') + ->with('userloc') + ->with('company') + ->with('department') + ->with('createdBy') ->withCount([ 'assets as assets_count' => function(Builder $query) { $query->withoutTrashed(); @@ -101,10 +112,26 @@ class UsersController extends Controller $users = $users->where('users.activated', '=', $request->input('activated')); } + if ($request->input('admins') == 'true') { + $users = $users->OnlyAdminsAndSuperAdmins(); + } + + if ($request->input('superadmins') == 'true') { + $users = $users->OnlySuperAdmins(); + } + if ($request->filled('company_id')) { $users = $users->where('users.company_id', '=', $request->input('company_id')); } + if ($request->filled('phone')) { + $users = $users->where('users.phone', '=', $request->input('phone')); + } + + if ($request->filled('mobile')) { + $users = $users->where('users.mobile', '=', $request->input('mobile')); + } + if ($request->filled('location_id')) { $users = $users->where('users.location_id', '=', $request->input('location_id')); } @@ -129,6 +156,10 @@ class UsersController extends Controller $users = $users->where('users.last_name', '=', $request->input('last_name')); } + if ($request->filled('display_name')) { + $users = $users->where('users.display_name', '=', $request->input('display_name')); + } + if ($request->filled('employee_num')) { $users = $users->where('users.employee_num', '=', $request->input('employee_num')); } @@ -206,11 +237,11 @@ class UsersController extends Controller } if ($request->filled('manages_users_count')) { - $users->has('manages_users_count', '=', $request->input('manages_users_count')); + $users->has('managesUsers', '=', $request->input('manages_users_count')); } if ($request->filled('manages_locations_count')) { - $users->has('manages_locations_count', '=', $request->input('manages_locations_count')); + $users->has('managedLocations', '=', $request->input('manages_locations_count')); } if ($request->filled('autoassign_licenses')) { @@ -259,6 +290,7 @@ class UsersController extends Controller [ 'last_name', 'first_name', + 'display_name', 'email', 'jobtitle', 'username', @@ -277,6 +309,7 @@ class UsersController extends Controller 'manages_users_count', 'manages_locations_count', 'phone', + 'mobile', 'address', 'city', 'state', @@ -329,6 +362,7 @@ class UsersController extends Controller 'users.employee_num', 'users.first_name', 'users.last_name', + 'users.display_name', 'users.gravatar', 'users.avatar', 'users.email', @@ -339,20 +373,17 @@ class UsersController extends Controller $users = $users->where(function ($query) use ($request) { $query->SimpleNameSearch($request->get('search')) ->orWhere('username', 'LIKE', '%'.$request->get('search').'%') + ->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%') ->orWhere('email', 'LIKE', '%'.$request->get('search').'%') ->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%'); }); } - $users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc'); + $users = $users->orderBy('display_name', 'asc')->orderBy('last_name', 'asc')->orderBy('first_name', 'asc'); $users = $users->paginate(50); foreach ($users as $user) { - $name_str = ''; - if ($user->last_name != '') { - $name_str .= $user->last_name.', '; - } - $name_str .= $user->first_name; + $name_str = $user->display_name; if ($user->username != '') { $name_str .= ' ('.$user->username.')'; @@ -404,9 +435,20 @@ class UsersController extends Controller $user->password = $user->noPassword(); } - app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); + app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); if ($user->save()) { + + if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) { + + try { + $user->notify(new WelcomeNotification($user)); + } catch (\Exception $e) { + Log::warning('Could not send welcome notification for user: ' . $e->getMessage()); + } + + } + if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); } else { @@ -474,8 +516,29 @@ class UsersController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager')); } - if ($request->filled('password')) { - $user->password = bcrypt($request->input('password')); + // check for permissions related fields and pull them out if the current user cannot edit them + if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) { + + if ($request->filled('password')) { + $user->password = bcrypt($request->input('password')); + } + + if ($request->filled('username')) { + $user->username = $request->input('username'); + } + + if ($request->filled('display_name')) { + $user->display_name = $request->input('display_name'); + } + + if ($request->filled('email')) { + $user->email = $request->input('email'); + } + + if ($request->filled('activated')) { + $user->activated = $request->input('activated'); + } + } // We need to use has() instead of filled() @@ -497,7 +560,7 @@ class UsersController extends Controller Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); } - app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); + app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); if ($user->save()) { // Check if the request has groups passed and has a value, AND that the user us a superuser @@ -676,7 +739,6 @@ class UsersController extends Controller $this->authorize('view', License::class); if ($user = User::where('id', $id)->withTrashed()->first()) { - $this->authorize('update', $user); $licenses = $user->licenses()->get(); return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count()); } @@ -736,6 +798,25 @@ class UsersController extends Controller return (new UsersTransformer)->transformUser($request->user()); } + /** + * Display the EULAs accepted by the user. + * + * @param \App\Models\User $user + * @param \App\Http\Transformers\ActionlogsTransformer $transformer + * @return \Illuminate\Http\JsonResponse + *@since [v8.1.16] + * @author [Godfrey Martinez] [] + */ + public function eulas(User $user, ActionlogsTransformer $transformer) + { + $this->authorize('view', User::class); + + $eulas = $user->eulas; + return response()->json( + $transformer->transformActionlogs($eulas, $eulas->count()) + ); + } + /** * Restore a soft-deleted user. * @@ -772,4 +853,37 @@ class UsersController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200); } + + + /** + * Run the LDAP sync command to import users from LDAP via API. + * + * @author A. Gianotto + * @since 8.2.2 + * + * @return \Illuminate\Http\JsonResponse + */ + public function syncLdapUsers(Request $request) + { + $this->authorize('update', User::class); + // Call Artisan LDAP import command. + + Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]); + + // Collect and parse JSON summary. + $ldap_results_json = Artisan::output(); + $ldap_results = json_decode($ldap_results_json, true); + + if (!$ldap_results) { + return response()->json(Helper::formatStandardApiResponse('error', null,trans('general.no_results')), 200); + } + + // Direct user to appropriate status page. + if ($ldap_results['error']) { + return response()->json(Helper::formatStandardApiResponse('error', null, $ldap_results['error_message']), 200); + } + + return response()->json(Helper::formatStandardApiResponse('success', null, $ldap_results['summary']), 200); + + } } diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php deleted file mode 100644 index 8ab710b03b..0000000000 --- a/app/Http/Controllers/AssetMaintenancesController.php +++ /dev/null @@ -1,272 +0,0 @@ - - * @version v1.0 - * @since [v1.8] - */ - private static function getInsufficientPermissionsRedirect(): RedirectResponse - { - return redirect()->route('maintenances.index') - ->with('error', trans('general.insufficient_permissions')); - } - - /** - * Returns a view that invokes the ajax tables which actually contains - * the content for the asset maintenances listing, which is generated in getDatatable. - * - * @todo This should be replaced with middleware and/or policies - * @see AssetMaintenancesController::getDatatable() method that generates the JSON response - * @author Vincent Sposato - * @version v1.0 - * @since [v1.8] - */ - public function index() : View - { - $this->authorize('view', Asset::class); - return view('asset_maintenances/index'); - } - - /** - * Returns a form view to create a new asset maintenance. - * - * @see AssetMaintenancesController::postCreate() method that stores the data - * @author Vincent Sposato - * @version v1.0 - * @since [v1.8] - * @return mixed - */ - public function create() : View - { - $this->authorize('update', Asset::class); - $asset = null; - - if ($asset = Asset::find(request('asset_id'))) { - // We have to set this so that the correct property is set in the select2 ajax dropdown - $asset->asset_id = $asset->id; - } - - // Prepare Asset Maintenance Type List - $assetMaintenanceType = [ - '' => 'Select an asset maintenance type', - ] + AssetMaintenance::getImprovementOptions(); - // Mark the selected asset, if it came in - - return view('asset_maintenances/edit') - ->with('asset', $asset) - ->with('assetMaintenanceType', $assetMaintenanceType) - ->with('item', new AssetMaintenance); - } - - /** - * Validates and stores the new asset maintenance - * - * @see AssetMaintenancesController::getCreate() method for the form - * @author Vincent Sposato - * @version v1.0 - * @since [v1.8] - */ - public function store(Request $request) : RedirectResponse - { - $this->authorize('update', Asset::class); - // create a new model instance - $assetMaintenance = new AssetMaintenance(); - $assetMaintenance->supplier_id = $request->input('supplier_id'); - $assetMaintenance->is_warranty = $request->input('is_warranty'); - $assetMaintenance->cost = $request->input('cost'); - $assetMaintenance->notes = $request->input('notes'); - $asset = Asset::find($request->input('asset_id')); - - if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) { - return static::getInsufficientPermissionsRedirect(); - } - - // Save the asset maintenance data - $assetMaintenance->asset_id = $request->input('asset_id'); - $assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); - $assetMaintenance->title = $request->input('title'); - $assetMaintenance->start_date = $request->input('start_date'); - $assetMaintenance->completion_date = $request->input('completion_date'); - $assetMaintenance->created_by = auth()->id(); - - if (($assetMaintenance->completion_date !== null) - && ($assetMaintenance->start_date !== '') - && ($assetMaintenance->start_date !== '0000-00-00') - ) { - $startDate = Carbon::parse($assetMaintenance->start_date); - $completionDate = Carbon::parse($assetMaintenance->completion_date); - $assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true); - } - - // Was the asset maintenance created? - if ($assetMaintenance->save()) { - // Redirect to the new asset maintenance page - return redirect()->route('maintenances.index') - ->with('success', trans('admin/asset_maintenances/message.create.success')); - } - - return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors()); - } - - /** - * Returns a form view to edit a selected asset maintenance. - * - * @see AssetMaintenancesController::postEdit() method that stores the data - * @author Vincent Sposato - * @param int $assetMaintenanceId - * @version v1.0 - * @since [v1.8] - */ - public function edit(AssetMaintenance $maintenance) : View | RedirectResponse - { - $this->authorize('update', Asset::class); - if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) { - return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); - } elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - // Prepare Improvement Type List - $assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions(); - - return view('asset_maintenances/edit') - ->with('selectedAsset', null) - ->with('assetMaintenanceType', $assetMaintenanceType) - ->with('item', $maintenance); - } - - /** - * Validates and stores an update to an asset maintenance - * - * @see AssetMaintenancesController::postEdit() method that stores the data - * @author Vincent Sposato - * @param Request $request - * @param int $assetMaintenanceId - * @version v1.0 - * @since [v1.8] - */ - public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse - { - $this->authorize('update', Asset::class); - - if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) { - return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); - } elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - $maintenance->supplier_id = $request->input('supplier_id'); - $maintenance->is_warranty = $request->input('is_warranty'); - $maintenance->cost = $request->input('cost'); - $maintenance->notes = $request->input('notes'); - - $asset = Asset::find(request('asset_id')); - - if (! Company::isCurrentUserHasAccess($asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - // Save the asset maintenance data - $maintenance->asset_id = $request->input('asset_id'); - $maintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); - $maintenance->title = $request->input('title'); - $maintenance->start_date = $request->input('start_date'); - $maintenance->completion_date = $request->input('completion_date'); - - if (($maintenance->completion_date == null) - ) { - if (($maintenance->asset_maintenance_time !== 0) - || (! is_null($maintenance->asset_maintenance_time)) - ) { - $maintenance->asset_maintenance_time = null; - } - } - - if (($maintenance->completion_date !== null) - && ($maintenance->start_date !== '') - && ($maintenance->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); - } - - // Was the asset maintenance created? - if ($maintenance->save()) { - - // Redirect to the new asset maintenance page - return redirect()->route('maintenances.index') - ->with('success', trans('admin/asset_maintenances/message.edit.success')); - } - - return redirect()->back()->withInput()->withErrors($maintenance->getErrors()); - } - - /** - * Delete an asset maintenance - * - * @author Vincent Sposato - * @param int $assetMaintenanceId - * @version v1.0 - * @since [v1.8] - */ - public function destroy($assetMaintenanceId) : RedirectResponse - { - $this->authorize('update', Asset::class); - // Check if the asset maintenance exists - if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index') - ->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - // Delete the asset maintenance - $assetMaintenance->delete(); - - // Redirect to the asset_maintenance management page - return redirect()->route('maintenances.index') - ->with('success', trans('admin/asset_maintenances/message.delete.success')); - } - - /** - * View an asset maintenance - * - * @author Vincent Sposato - * @param int $assetMaintenanceId - * @version v1.0 - * @since [v1.8] - */ - public function show(AssetMaintenance $maintenance) : View | RedirectResponse - { - $this->authorize('view', Asset::class); - if (! Company::isCurrentUserHasAccess($maintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - return view('asset_maintenances/view')->with('assetMaintenance', $maintenance); - } -} diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 0795588776..a74365298e 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -82,12 +82,26 @@ class AssetModelsController extends Controller $model->notes = $request->input('notes'); $model->created_by = auth()->id(); $model->requestable = $request->has('requestable'); + $model->require_serial = $request->input('require_serial', 0); if ($request->input('fieldset_id') != '') { $model->fieldset_id = $request->input('fieldset_id'); } - $model = $request->handleImages($model); + if ($request->has('use_cloned_image')) { + $cloned_model_img = AssetModel::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; + $new_image = 'models/'.$new_image_name; + Storage::disk('public')->copy('models/'.$cloned_model_img->image, $new_image); + $model->image = $new_image_name; + } + + } else { + $model = $request->handleImages($model); + } + + if ($model->save()) { if ($this->shouldAddDefaultValues($request->input())) { @@ -142,7 +156,7 @@ class AssetModelsController extends Controller $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); $model->requestable = $request->input('requestable', '0'); - + $model->require_serial = $request->input('require_serial', 0); $model->fieldset_id = $request->input('fieldset_id'); if ($model->save()) { @@ -271,7 +285,7 @@ class AssetModelsController extends Controller ->with('depreciation_list', Helper::depreciationList()) ->with('item', $model) ->with('model_id', $model->id) - ->with('clone_model', $cloned_model); + ->with('cloned_model', $cloned_model); } diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php deleted file mode 100644 index 14b2c1fc0b..0000000000 --- a/app/Http/Controllers/AssetModelsFilesController.php +++ /dev/null @@ -1,115 +0,0 @@ -] - */ - public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse - { - if (! $model = AssetModel::find($modelId)) { - return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } - - $this->authorize('update', $model); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/assetmodels')) { - Storage::makeDirectory('private_uploads/assetmodels', 775); - } - - foreach ($request->file('file') as $file) { - - $file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file); - - $model->logUpload($file_name, $request->get('notes')); - } - - return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success')); - } - - return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles')); - } - - /** - * Check for permissions and display the file. - * - * @author [A. Gianotto] [] - * @param int $modelId - * @param int $fileId - * @since [v1.0] - */ - public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse - { - - $this->authorize('view', $model); - - if (! $log = Actionlog::find($fileId)) { - return response('No matching record for that model/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/assetmodels/'.$log->filename; - - if (! Storage::exists($file)) { - return response('File '.$file.' not found on server', 404) - ->header('Content-Type', 'text/plain'); - } - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); - } - - /** - * Delete the associated file - * - * @author [A. Gianotto] [] - * @param int $modelId - * @param int $fileId - * @since [v1.0] - */ - public function destroy(AssetModel $model, $fileId = null) : RedirectResponse - { - $rel_path = 'private_uploads/assetmodels'; - $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')); - } - - return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); - - } -} diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php index cf881b57c7..62e8e59ca8 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -14,6 +14,7 @@ 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 { @@ -40,6 +41,14 @@ class AssetCheckinController extends Controller if (!$asset->model) { 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')]), @@ -87,7 +96,6 @@ class AssetCheckinController extends Controller }); $asset->expected_checkin = null; - $asset->last_checkin = now(); $asset->assignedTo()->disassociate($asset); $asset->accepted = null; $asset->name = $request->get('name'); @@ -114,11 +122,14 @@ class AssetCheckinController extends Controller $originalValues = $asset->getRawOriginal(); + // Handle last checkin date $checkin_at = date('Y-m-d H:i:s'); if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) { $originalValues['action_date'] = $checkin_at; $checkin_at = $request->get('checkin_at'); + } + $asset->last_checkin = $checkin_at; $asset->licenseseats->each(function (LicenseSeat $seat) { $seat->update(['assigned_to' => null]); @@ -142,7 +153,8 @@ class AssetCheckinController extends Controller if ($asset->save()) { event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success')); + return Helper::getRedirectOption($request, $asset->id, 'Assets') + ->with('success', trans('admin/hardware/message.checkin.success')); } // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors()); diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index 4d8c9ffda2..bfbbbfcad4 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -12,6 +12,7 @@ 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 { @@ -36,6 +37,14 @@ class AssetCheckoutController extends Controller ->with('error', trans('admin/hardware/general.model_invalid_fix')); } + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + if ($asset->isInvalid()) { + return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); + } + + if ($asset->availableForCheckout()) { return view('hardware/checkout', compact('asset')) ->with('statusLabel_list', Helper::deployableStatusLabelList()) @@ -56,6 +65,8 @@ class AssetCheckoutController extends Controller */ public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse { + + try { // Check if the asset exists if (! $asset = Asset::find($assetId)) { @@ -72,6 +83,7 @@ class AssetCheckoutController extends Controller $admin = auth()->user(); $target = $this->determineCheckoutTarget(); + session()->put(['checkout_to_type' => $target]); $asset = $this->updateAssetLocation($asset, $target); @@ -114,7 +126,7 @@ class AssetCheckoutController extends Controller session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) { - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success', trans('admin/hardware/message.checkout.success')); } // Redirect to the asset management page with error diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php deleted file mode 100644 index cf119edddc..0000000000 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ /dev/null @@ -1,108 +0,0 @@ -] - */ - public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse - { - - $this->authorize('update', $asset); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/assets')) { - Storage::makeDirectory('private_uploads/assets', 775); - } - - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file); - - $asset->logUpload($file_name, $request->get('notes')); - } - - return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success')); - } - - return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles')); - } - - /** - * Check for permissions and display the file. - * - * @author [A. Gianotto] [] - * @param int $assetId - * @param int $fileId - * @since [v1.0] - */ - public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse - { - - $this->authorize('view', $asset); - - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { - $file = 'private_uploads/assets/'.$log->filename; - - if ($log->action_type == 'audit') { - $file = 'private_uploads/audits/'.$log->filename; - } - - 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', $asset)->with('error', trans('general.log_record_not_found')); - - - } - - /** - * Delete the associated file - * - * @author [A. Gianotto] [] - * @param int $assetId - * @param int $fileId - * @since [v1.0] - */ - public function destroy(Asset $asset, $fileId = null) : RedirectResponse - { - $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); - } - $log->delete(); - return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); - } - - return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found')); - } - -} diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 6391a3dd9f..13201c51ec 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -110,17 +110,35 @@ class AssetsController extends Controller // This is only necessary on create, not update, since bulk editing is handled // differently $asset_tags = $request->input('asset_tags'); + $model = AssetModel::find($request->input('model_id')); + $serial_errors = []; + $serials = $request->input('serials'); $settings = Setting::getSettings(); + //Validate required serial based on model setting + for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { + if ($model && $model->require_serial === 1 && empty($serials[$a])) { + $serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]); + } + + } + + if (!empty($serial_errors)) { + return redirect()->back() + ->withInput() + ->withErrors($serial_errors); + } + + $asset = null; + $companyId = Company::getIdForCurrentUser($request->input('company_id')); $successes = []; $failures = []; - $serials = $request->input('serials'); - $asset = null; - for ($a = 1; $a <= count($asset_tags); $a++) { + for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { $asset = new Asset(); - $asset->model()->associate(AssetModel::find($request->input('model_id'))); + + $asset->model()->associate($model); $asset->name = $request->input('name'); // Check for a corresponding serial @@ -132,7 +150,7 @@ class AssetsController extends Controller $asset->asset_tag = $asset_tags[$a]; } - $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); + $asset->company_id = $companyId; $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); @@ -149,7 +167,7 @@ class AssetsController extends Controller $asset->byod = request('byod', 0); if (! empty($settings->audit_interval)) { - $asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); + $asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString(); } // Set location_id to rtd_location_id ONLY if the asset isn't being checked out @@ -157,14 +175,21 @@ class AssetsController extends Controller $asset->location_id = $request->input('rtd_location_id', null); } - // Create the image (if one was chosen.) - if ($request->has('image')) { + if ($request->has('use_cloned_image')) { + $cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; + $new_image = 'assets/'.$new_image_name; + Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image); + $asset->image = $new_image_name; + } + + } else { $asset = $request->handleImages($asset); } // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request - $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { @@ -188,14 +213,31 @@ class AssetsController extends Controller // Validate the asset before saving if ($asset->isValid() && $asset->save()) { - if (request('assigned_user')) { - $target = User::find(request('assigned_user')); + $target = null; + $location = null; + + if ($userId = request('assigned_user')) { + $target = User::find($userId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user')); + } $location = $target->location_id; - } elseif (request('assigned_asset')) { - $target = Asset::find(request('assigned_asset')); + + } elseif ($assetId = request('assigned_asset')) { + $target = Asset::find($assetId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset')); + } $location = $target->location_id; - } elseif (request('assigned_location')) { - $target = Location::find(request('assigned_location')); + + } elseif ($locationId = request('assigned_location')) { + $target = Location::find($locationId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location')); + } $location = $target->id; } @@ -209,25 +251,32 @@ class AssetsController extends Controller $failures[] = join(",", $asset->getErrors()->all()); } } + if($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } + + session()->put(['checkout_to_type' => $request->get('checkout_to_type'), + 'other_redirect' => 'model' ]); - session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($successes) { if ($failures) { //some succeeded, some failed - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested + return Helper::getRedirectOption($request, $asset->id, 'Assets') //FIXME - not tested ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])) ->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)])); } else { if (count($successes) == 1) { //the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed //and re-translated - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)])); } else { //multi-success - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])); } } @@ -248,6 +297,7 @@ class AssetsController extends Controller public function edit(Asset $asset) : View | RedirectResponse { $this->authorize($asset); + session()->put('back_url', url()->previous()); return view('hardware/edit') ->with('item', $asset) ->with('statuslabel_list', Helper::statusLabelList()) @@ -391,6 +441,9 @@ class AssetsController extends Controller $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { + if ($field->element == 'checkbox' && !$request->has($field->db_column)) { + $asset->{$field->db_column} = null; + } if ($request->has($field->db_column)) { if ($field->field_encrypted == '1') { if (Gate::allows('assets.view.encrypted_custom_fields')) { @@ -410,11 +463,22 @@ class AssetsController extends Controller } } } + session()->put([ + 'redirect_option' => $request->get('redirect_option'), + 'checkout_to_type' => $request->get('checkout_to_type'), + 'other_redirect' => $request->get('redirect_option') === 'other_redirect' ? 'model' : null, + ]); - session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); - if ($asset->save()) { + //Validate required serial based on model setting + if ($model && $model->require_serial === 1 && empty($serial[1])) { return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [ + 'asset_model' => $model->name + ])); + } + if ($asset->save()) { + return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success', trans('admin/hardware/message.update.success')); } @@ -446,7 +510,7 @@ class AssetsController extends Controller event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); DB::table('assets') ->where('id', $asset->id) - ->update(['assigned_to' => null]); + ->update(['assigned_to' => null, 'assigned_type' => null]); } @@ -458,6 +522,7 @@ class AssetsController extends Controller } } + $asset->delete(); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success')); @@ -519,7 +584,7 @@ class AssetsController extends Controller { $settings = Setting::getSettings(); - if (($settings->qr_code == '1') && ($settings->label2_2d_type !== 'none')) { + if ($settings->label2_2d_type !== 'none') { if ($asset) { $size = Helper::barcodeDimensions($settings->label2_2d_type); @@ -618,8 +683,9 @@ class AssetsController extends Controller */ public function getClone(Asset $asset) { - $this->authorize('create', $asset); + $this->authorize('create', Asset::class); $cloned = clone $asset; + $cloned_model = $asset; $cloned->id = null; $cloned->asset_tag = ''; $cloned->serial = ''; @@ -629,6 +695,7 @@ class AssetsController extends Controller return view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()) + ->with('cloned_model', $cloned_model) ->with('item', $cloned); } @@ -754,7 +821,7 @@ class AssetsController extends Controller 'item_id' => $asset->id, 'item_type' => Asset::class, 'created_by' => auth()->id(), - 'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer', + 'note' => 'Checkout imported by '.auth()->user()->display_name.' from history importer', 'target_id' => $item[$asset_tag][$batch_counter]['user_id'], 'target_type' => User::class, 'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'], @@ -782,7 +849,7 @@ class AssetsController extends Controller 'item_id' => $item[$asset_tag][$batch_counter]['asset_id'], 'item_type' => Asset::class, 'created_by' => auth()->id(), - 'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer', + 'note' => 'Checkin imported by '.auth()->user()->display_name.' from history importer', 'target_id' => null, 'created_at' => $checkin_date, 'action_type' => 'checkin', @@ -877,11 +944,20 @@ class AssetsController extends Controller } - public function audit(Asset $asset) + public function audit(Asset $asset): View | RedirectResponse { - $settings = Setting::getSettings(); $this->authorize('audit', Asset::class); - $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); + $settings = Setting::getSettings(); + + + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + if ($asset->isInvalid()) { + return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); + } + + $dt = Carbon::now()->addMonths( (int) $settings->audit_interval)->toDateString(); return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list'); } @@ -890,6 +966,10 @@ class AssetsController extends Controller $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'); @@ -924,8 +1004,8 @@ class AssetsController extends Controller } } - // Validate custom fields - Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate(); + // 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()) { @@ -965,7 +1045,7 @@ class AssetsController extends Controller } $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues); - return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success')); + return Helper::getRedirectOption($request, $asset->id, 'Assets')->with('success', trans('admin/hardware/message.audit.success')); } return redirect()->back()->withInput()->withErrors($asset->getErrors()); diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index e433475d39..6c75ae06db 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -52,11 +52,26 @@ class BulkAssetsController extends Controller } $asset_ids = $request->input('ids'); + if ($request->input('bulk_actions') === 'checkout') { + $status_check =$this->hasUndeployableStatus($asset_ids); + if($status_check && $status_check['status'] === true){ + + $asset_tags = implode(', ', array_column($status_check['tags'], 'asset_tag')); + $asset_ids = $status_check['asset_ids']; + + session()->flash('warning', trans('admin/hardware/message.undeployable', ['asset_tags' => $asset_tags])); + } + $request->session()->flashInput(['selected_assets' => $asset_ids]); return redirect()->route('hardware.bulkcheckout.show'); } + if ($request->input('bulk_actions') === 'maintenance') { + $request->session()->flashInput(['selected_assets' => $asset_ids]); + return redirect()->route('maintenances.create'); + } + // Figure out where we need to send the user after the update is complete, and store that in the session $bulk_back_url = request()->headers->get('referer'); session(['bulk_back_url' => $bulk_back_url]); @@ -97,11 +112,47 @@ class BulkAssetsController extends Controller // This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array) $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id'; - $assets = Asset::with('assignedTo', 'location', 'model') + $query = Asset::with('assignedTo', 'location', 'model') ->whereIn('assets.id', $asset_ids) ->withTrashed(); - $assets = $assets->get(); + + switch ($sort_override) { + case 'model': + $query->OrderModels($order); + break; + case 'model_number': + $query->OrderModelNumber($order); + break; + case 'category': + $query->OrderCategory($order); + break; + case 'manufacturer': + $query->OrderManufacturer($order); + break; + case 'company': + $query->OrderCompany($order); + break; + case 'location': + $query->OrderLocation($order); + break; + case 'rtd_location': + $query->OrderRtdLocation($order); + break; + case 'status_label': + $query->OrderStatus($order); + break; + case 'supplier': + $query->OrderSupplier($order); + break; + case 'assigned_to': + $query->OrderAssigned($order); + break; + default: + $query->orderBy($column_sort, $order); + break; + } + $assets = $query->get(); if ($assets->isEmpty()) { Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]); @@ -110,6 +161,7 @@ class BulkAssetsController extends Controller $models = $assets->unique('model_id'); $modelNames = []; + foreach($models as $model) { $modelNames[] = $model->model->name; } @@ -145,7 +197,6 @@ class BulkAssetsController extends Controller case 'edit': $this->authorize('update', Asset::class); - return view('hardware/bulk') ->with('assets', $asset_ids) ->with('statuslabel_list', Helper::statusLabelList()) @@ -154,40 +205,7 @@ class BulkAssetsController extends Controller } } - switch ($sort_override) { - case 'model': - $assets->OrderModels($order); - break; - case 'model_number': - $assets->OrderModelNumber($order); - break; - case 'category': - $assets->OrderCategory($order); - break; - case 'manufacturer': - $assets->OrderManufacturer($order); - break; - case 'company': - $assets->OrderCompany($order); - break; - case 'location': - $assets->OrderLocation($order); - case 'rtd_location': - $assets->OrderRtdLocation($order); - break; - case 'status_label': - $assets->OrderStatus($order); - break; - case 'supplier': - $assets->OrderSupplier($order); - break; - case 'assigned_to': - $assets->OrderAssigned($order); - break; - default: - $assets->orderBy($column_sort, $order); - break; - } + return redirect()->back()->with('error', 'No action selected'); } @@ -206,14 +224,26 @@ class BulkAssetsController extends Controller $error_array = array(); // Get the back url from the session and then destroy the session - $bulk_back_url = route('hardware.index'); - if ($request->session()->has('bulk_back_url')) { - $bulk_back_url = $request->session()->pull('bulk_back_url'); - } + $bulk_back_url = $request->session()->pull('bulk_back_url', url()->previous()); $custom_field_columns = CustomField::all()->pluck('db_column')->toArray(); + // find custom field input attributes that start with 'null_' + $null_custom_fields_inputs = array_filter($request->all(), function ($key) { + // filter out all keys that start with 'null_' + return (strpos($key, 'null_') === 0); + }, ARRAY_FILTER_USE_KEY);; + // remove 'null' from the keys + $custom_fields_to_null = []; + foreach ($null_custom_fields_inputs as $key => $value) { + $custom_fields_to_null[str_replace('null', '', $key)] = $value; + } + + + + + if (! $request->filled('ids') || count($request->input('ids')) == 0) { return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected')); @@ -251,7 +281,9 @@ class BulkAssetsController extends Controller || ($request->filled('null_expected_checkin_date')) || ($request->filled('null_next_audit_date')) || ($request->filled('null_asset_eol_date')) + || ($request->filled('null_notes')) || ($request->anyFilled($custom_field_columns)) + || ($request->anyFilled(array_keys($null_custom_fields_inputs))) ) { // Let's loop through those assets and build an update array @@ -274,10 +306,14 @@ class BulkAssetsController extends Controller ->conditionallyAddItem('supplier_id') ->conditionallyAddItem('warranty_months') ->conditionallyAddItem('next_audit_date') - ->conditionallyAddItem('asset_eol_date'); + ->conditionallyAddItem('asset_eol_date') + ->conditionallyAddItem('notes'); foreach ($custom_field_columns as $key => $custom_field_column) { $this->conditionallyAddItem($custom_field_column); } + foreach ($custom_fields_to_null as $key => $custom_field_to_null) { + $this->conditionallyAddItem($key); + } if (!($asset->eol_explicit)) { if ($request->filled('model_id')) { @@ -328,6 +364,10 @@ class BulkAssetsController extends Controller } } + if ($request->input('null_notes')=='1') { + $this->update_array['notes'] = null; + } + if ($request->filled('purchase_cost')) { @@ -368,10 +408,12 @@ class BulkAssetsController extends Controller // This could probably be added to a form request. // If the asset isn't assigned, we don't care what the status is. // Otherwise we need to make sure the status type is still a deployable one. - if ( - ($asset->assigned_to == '') - || ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1') - ) { + + $unassigned = $asset->assigned_to == ''; + $deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1'; + $pending = $updated_status->pending === 1; + + if ($unassigned || $deployable || $pending) { $this->update_array['status_id'] = $updated_status->id; } @@ -423,6 +465,7 @@ class BulkAssetsController extends Controller } /** + * * Start all the custom fields shenanigans */ @@ -430,6 +473,15 @@ class BulkAssetsController extends Controller if ($asset->model->fieldset) { foreach ($asset->model->fieldset->fields as $field) { + // null custom fields + if ($custom_fields_to_null) { + foreach ($custom_fields_to_null as $key => $custom_field_to_null) { + if ($field->db_column == $key) { + $this->update_array[$field->db_column] = null; + } + } + } + if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) { if (Gate::allows('admin')) { $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); @@ -488,7 +540,13 @@ class BulkAssetsController extends Controller } // end asset foreach if ($has_errors > 0) { - return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array); + session()->put('bulkedit_ids', $request->input('ids')); + session()->put('bulk_asset_errors',$error_array); + + return redirect() + ->route('hardware.index') + ->with('bulk_asset_errors', $error_array) + ->withInput(); } return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success')); @@ -562,7 +620,10 @@ class BulkAssetsController extends Controller public function showCheckout() : View { $this->authorize('checkout', Asset::class); - return view('hardware/bulk-checkout'); + + $do_not_change = ['' => trans('general.do_not_change')]; + $status_label_list = $do_not_change + Helper::deployableStatusLabelList(); + return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list); } /** @@ -576,6 +637,7 @@ class BulkAssetsController extends Controller $admin = auth()->user(); $target = $this->determineCheckoutTarget(); + session()->put(['checkout_to_type' => $target]); if (! is_array($request->get('selected_assets'))) { return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected')); @@ -594,13 +656,13 @@ class BulkAssetsController extends Controller } $checkout_at = date('Y-m-d H:i:s'); if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) { - $checkout_at = e($request->get('checkout_at')); + $checkout_at = $request->get('checkout_at'); } $expected_checkin = ''; if ($request->filled('expected_checkin')) { - $expected_checkin = e($request->get('expected_checkin')); + $expected_checkin = $request->get('expected_checkin'); } $errors = []; @@ -608,6 +670,11 @@ class BulkAssetsController extends Controller foreach ($assets as $asset) { $this->authorize('checkout', $asset); + // See if there is a status label passed + if ($request->filled('status_id')) { + $asset->status_id = $request->get('status_id'); + } + $checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null); //TODO - I think this logic is duplicated in the checkOut method? @@ -651,4 +718,54 @@ class BulkAssetsController extends Controller return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); } } + public function hasUndeployableStatus (array $asset_ids) + { + $undeployable = Asset::whereIn('id', $asset_ids) + ->undeployable() + ->get(); + + $undeployableTags = $undeployable->map(function ($asset) { + return [ + 'id' => $asset->id, + 'asset_tag' => $asset->asset_tag, + ]; + })->toArray(); + + $undeployableIds = array_column($undeployableTags, 'id'); + $filtered_ids = array_diff($asset_ids, $undeployableIds); + + if($undeployable->isNotEmpty()) { + return ['status' => true, 'tags' => $undeployableTags, 'asset_ids' => $filtered_ids]; + } + return false; + } + + public function bulkEditForm(): View|RedirectResponse + { + $this->authorize('update', Asset::class); + + $asset_ids = session()->pull('bulkedit_ids', []); + + if (empty($asset_ids)) { + return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.no_assets_selected')); + } + + $assets = Asset::with('model')->withTrashed()->whereIn('id', $asset_ids)->get(); + + if ($assets->isEmpty()) { + return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid')); + } + + $models = $assets->unique('model_id'); + $modelNames = []; + foreach ($models as $model) { + $modelNames[] = $model->model->name; + } + + return view('hardware/bulk') + ->with('assets', $asset_ids) + ->with('statuslabel_list', Helper::statusLabelList()) + ->with('models', $models->pluck(['model'])) + ->with('modelNames', $modelNames); + } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 749abd6c1e..0b87865455 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -484,6 +484,7 @@ class LoginController extends Controller } $request->session()->regenerate(true); + $request->session()->forget('2fa_authed'); if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){ $request->session()->remove('password_hash_'.Auth::getDefaultDriver()); diff --git a/app/Http/Controllers/BulkAssetModelsController.php b/app/Http/Controllers/BulkAssetModelsController.php index 5f64ea0838..c1ecf309fb 100644 --- a/app/Http/Controllers/BulkAssetModelsController.php +++ b/app/Http/Controllers/BulkAssetModelsController.php @@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller $update_array['min_amt'] = $request->input('min_amt'); } - + if ($request->filled('require_serial')) { + $update_array['require_serial'] = $request->input('require_serial'); + } if (count($update_array) > 0) { AssetModel::whereIn('id', $models_raw_array)->update($update_array); diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php index da8b57a552..3e902541b3 100755 --- a/app/Http/Controllers/CategoriesController.php +++ b/app/Http/Controllers/CategoriesController.php @@ -68,6 +68,7 @@ class CategoriesController extends Controller $category->eula_text = $request->input('eula_text'); $category->use_default_eula = $request->input('use_default_eula', '0'); $category->require_acceptance = $request->input('require_acceptance', '0'); + $category->alert_on_response = $request->input('alert_on_response', '0'); $category->checkin_email = $request->input('checkin_email', '0'); $category->notes = $request->input('notes'); $category->created_by = auth()->id(); @@ -121,6 +122,7 @@ class CategoriesController extends Controller $category->eula_text = $request->input('eula_text'); $category->use_default_eula = $request->input('use_default_eula', '0'); $category->require_acceptance = $request->input('require_acceptance', '0'); + $category->alert_on_response = $request->input('alert_on_response', '0'); $category->checkin_email = $request->input('checkin_email', '0'); $category->notes = $request->input('notes'); @@ -145,7 +147,7 @@ class CategoriesController extends Controller { $this->authorize('delete', Category::class); // Check if the category exists - if (is_null($category = Category::findOrFail($categoryId))) { + if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) { return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found')); } @@ -155,7 +157,6 @@ class CategoriesController extends Controller Storage::disk('public')->delete('categories'.'/'.$category->image); $category->delete(); - // Redirect to the locations management page return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success')); } diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php index 96a80e87e6..db6118d37f 100644 --- a/app/Http/Controllers/CompaniesController.php +++ b/app/Http/Controllers/CompaniesController.php @@ -123,11 +123,13 @@ final class CompaniesController extends Controller */ public function destroy($companyId) : RedirectResponse { + if (is_null($company = Company::find($companyId))) { return redirect()->route('companies.index') ->with('error', trans('admin/companies/message.not_found')); } + $this->authorize('delete', $company); if (! $company->isDeletable()) { return redirect()->route('companies.index') diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php index 379882c3c5..b784576122 100644 --- a/app/Http/Controllers/Components/ComponentCheckinController.php +++ b/app/Http/Controllers/Components/ComponentCheckinController.php @@ -100,8 +100,8 @@ class ComponentCheckinController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); - return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', - trans('admin/components/message.checkin.success')); + return Helper::getRedirectOption($request, $component->id, 'Components') + ->with('success', trans('admin/components/message.checkin.success')); } return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist')); diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php index b40d592369..4abf426de3 100644 --- a/app/Http/Controllers/Components/ComponentCheckoutController.php +++ b/app/Http/Controllers/Components/ComponentCheckoutController.php @@ -120,6 +120,7 @@ class ComponentCheckoutController extends Controller session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); - return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success')); + return Helper::getRedirectOption($request, $component->id, 'Components') + ->with('success', trans('admin/components/message.checkout.success')); } } diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 74594d312b..ff1b0061b4 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -88,10 +88,16 @@ class ComponentsController extends Controller $component = $request->handleImages($component); - session()->put(['redirect_option' => $request->get('redirect_option')]); + if($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } + if ($component->save()) { - return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success')); + return Helper::getRedirectOption($request, $component->id, 'Components') + ->with('success', trans('admin/components/message.create.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); @@ -111,6 +117,7 @@ class ComponentsController extends Controller { $this->authorize('update', $component); + session()->put('back_url', url()->previous()); return view('components/edit') ->with('item', $component) ->with('category_type', 'component'); @@ -164,7 +171,8 @@ class ComponentsController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); if ($component->save()) { - return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success')); + return Helper::getRedirectOption($request, $component->id, 'Components') + ->with('success', trans('admin/components/message.update.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php deleted file mode 100644 index b5e30aa694..0000000000 --- a/app/Http/Controllers/Components/ComponentsFilesController.php +++ /dev/null @@ -1,138 +0,0 @@ -] - * @since [v1.0] - * @todo Switch to using the AssetFileRequest form request validator. - */ - public function store(UploadFileRequest $request, $componentId = null) - { - - if (config('app.lock_passwords')) { - return redirect()->route('components.show', ['component'=>$componentId])->with('error', trans('general.feature_disabled')); - } - - $component = Component::find($componentId); - - if (isset($component->id)) { - $this->authorize('update', $component); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/components')) { - Storage::makeDirectory('private_uploads/components', 775); - } - - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file); - - //Log the upload to the log - $component->logUpload($file_name, e($request->input('notes'))); - } - - - return redirect()->route('components.show', $component->id)->withFragment('files')->with('success', trans('general.file_upload_success')); - - } - - return redirect()->route('components.show', $component->id)->with('error', trans('general.no_files_uploaded')); - } - // Prepare the error message - return redirect()->route('components.index') - ->with('error', trans('general.file_does_not_exist')); - } - - /** - * Deletes the selected component file. - * - * @author [A. Gianotto] [] - * @since [v1.0] - * @param int $componentId - * @param int $fileId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function destroy($componentId = null, $fileId = null) - { - $component = Component::find($componentId); - - // the asset is valid - if (isset($component->id)) { - $this->authorize('update', $component); - $log = Actionlog::find($fileId); - - // Remove the file if one exists - if (Storage::exists('components/'.$log->filename)) { - try { - Storage::delete('components/'.$log->filename); - } catch (\Exception $e) { - Log::debug($e); - } - } - - $log->delete(); - - return redirect()->back()->withFragment('files') - ->with('success', trans('admin/hardware/message.deletefile.success')); - } - - // Redirect to the licence management page - return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist')); - } - - /** - * Allows the selected file to be viewed. - * - * @author [A. Gianotto] [] - * @since [v1.4] - * @param int $componentId - * @param int $fileId - * @return \Symfony\Component\HttpFoundation\Response - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function show($componentId = null, $fileId = null) - { - Log::debug('Private filesystem is: '.config('filesystems.default')); - - - // the component is valid - if ($component = Component::find($componentId)) { - $this->authorize('view', $component); - $this->authorize('components.files', $component); - - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - - $file = 'private_uploads/components/'.$log->filename; - - try { - return StorageHelper::showOrDownloadFile($file, $log->filename); - } catch (\Exception $e) { - return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found')); - } - } - return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found')); - - } - - return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); - } -} diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index e08da41229..3e972e1085 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -111,6 +111,7 @@ class ConsumableCheckoutController extends Controller // Redirect to the new consumable page - return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success')); + return Helper::getRedirectOption($request, $consumable->id, 'Consumables') + ->with('success', trans('admin/consumables/message.checkout.success')); } } diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index c96c2db975..2601b69edd 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -7,7 +7,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; use App\Models\Company; use App\Models\Consumable; -use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; use Illuminate\Http\RedirectResponse; use \Illuminate\Contracts\View\View; @@ -81,16 +81,33 @@ class ConsumablesController extends Controller $consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_cost = $request->input('purchase_cost'); $consumable->qty = $request->input('qty'); - $consumable->created_by = auth()->id(); + $consumable->created_by = auth()->id(); $consumable->notes = $request->input('notes'); - $consumable = $request->handleImages($consumable); + if ($request->has('use_cloned_image')) { + $cloned_model_img = Consumable::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; + $new_image = 'consumables/'.$new_image_name; + Storage::disk('public')->copy('consumables/'.$cloned_model_img->image, $new_image); + $consumable->image = $new_image_name; + } + + } else { + $consumable = $request->handleImages($consumable); + } + + if($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } - session()->put(['redirect_option' => $request->get('redirect_option')]); if ($consumable->save()) { - return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success')); + return Helper::getRedirectOption($request, $consumable->id, 'Consumables') + ->with('success', trans('admin/consumables/message.create.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); @@ -107,6 +124,7 @@ class ConsumablesController extends Controller public function edit(Consumable $consumable) : View | RedirectResponse { $this->authorize($consumable); + session()->put('back_url', url()->previous()); return view('consumables/edit') ->with('item', $consumable) ->with('category_type', 'consumable'); @@ -160,7 +178,8 @@ class ConsumablesController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); if ($consumable->save()) { - return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success')); + return Helper::getRedirectOption($request, $consumable->id, 'Consumables') + ->with('success', trans('admin/consumables/message.update.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); @@ -210,9 +229,10 @@ class ConsumablesController extends Controller $consumable_to_close = $consumable; $consumable = clone $consumable_to_close; $consumable->id = null; - $consumable->image = null; $consumable->created_by = null; - return view('consumables/edit')->with('item', $consumable); + return view('consumables/edit') + ->with('cloned_model', $consumable_to_close) + ->with('item', $consumable); } } diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php deleted file mode 100644 index 545b008dc0..0000000000 --- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php +++ /dev/null @@ -1,134 +0,0 @@ -] - * @since [v1.0] - * @todo Switch to using the AssetFileRequest form request validator. - */ - public function store(UploadFileRequest $request, $consumableId = null) - { - if (config('app.lock_passwords')) { - return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled')); - } - - $consumable = Consumable::find($consumableId); - - if (isset($consumable->id)) { - $this->authorize('update', $consumable); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/consumables')) { - Storage::makeDirectory('private_uploads/consumables', 775); - } - - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file); - - //Log the upload to the log - $consumable->logUpload($file_name, e($request->input('notes'))); - } - - - return redirect()->route('consumables.show', $consumable->id)->withFragment('files')->with('success', trans('general.file_upload_success')); - - } - - return redirect()->route('consumables.show', $consumable->id)->with('error', trans('general.no_files_uploaded')); - } - // Prepare the error message - return redirect()->route('consumables.index') - ->with('error', trans('general.file_does_not_exist')); - } - - /** - * Deletes the selected consumable file. - * - * @author [A. Gianotto] [] - * @since [v1.0] - * @param int $consumableId - * @param int $fileId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function destroy($consumableId = null, $fileId = null) - { - $consumable = Consumable::find($consumableId); - - // the asset is valid - if (isset($consumable->id)) { - $this->authorize('update', $consumable); - $log = Actionlog::find($fileId); - - // Remove the file if one exists - if (Storage::exists('consumables/'.$log->filename)) { - try { - Storage::delete('consumables/'.$log->filename); - } catch (\Exception $e) { - Log::debug($e); - } - } - - $log->delete(); - - return redirect()->back()->withFragment('files') - ->with('success', trans('admin/hardware/message.deletefile.success')); - } - - // Redirect to the licence management page - return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist')); - } - - /** - * Allows the selected file to be viewed. - * - * @author [A. Gianotto] [] - * @since [v1.4] - * @param int $consumableId - * @param int $fileId - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function show($consumableId = null, $fileId = null) - { - $consumable = Consumable::find($consumableId); - - // the consumable is valid - if (isset($consumable->id)) { - $this->authorize('view', $consumable); - $this->authorize('consumables.files', $consumable); - - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { - $file = 'private_uploads/consumables/'.$log->filename; - - try { - return StorageHelper::showOrDownloadFile($file, $log->filename); - } catch (\Exception $e) { - return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found')); - } - } - // The log record doesn't exist somehow - return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found')); - - } - - return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); - } -} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 74fff19a37..266769ffb2 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -22,6 +22,15 @@ namespace App\Http\Controllers; +use App\Models\Accessory; +use App\Models\Asset; +use App\Models\AssetModel; +use App\Models\Component; +use App\Models\Consumable; +use App\Models\License; +use App\Models\Location; +use App\Models\Maintenance; +use App\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; @@ -32,6 +41,45 @@ abstract class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; + static $map_object_type = [ + 'accessories' => Accessory::class, + 'maintenances' => Maintenance::class, + 'assets' => Asset::class, + 'components' => Component::class, + 'consumables' => Consumable::class, + 'hardware' => Asset::class, + 'licenses' => License::class, + 'locations' => Location::class, + 'models' => AssetModel::class, + 'users' => User::class, + ]; + + static $map_storage_path = [ + 'accessories' => 'private_uploads/accessories/', + 'maintenances' => 'private_uploads/maintenances/', + 'assets' => 'private_uploads/assets/', + 'components' => 'private_uploads/components/', + 'consumables' => 'private_uploads/consumables/', + 'hardware' => 'private_uploads/assets/', + 'licenses' => 'private_uploads/licenses/', + 'locations' => 'private_uploads/locations/', + 'models' => 'private_uploads/models/', + 'users' => 'private_uploads/users/', + ]; + + static $map_file_prefix= [ + 'accessories' => 'accessory', + 'maintenances' => 'maintenance', + 'assets' => 'asset', + 'components' => 'component', + 'consumables' => 'consumable', + 'hardware' => 'asset', + 'licenses' => 'license', + 'locations' => 'location', + 'models' => 'model', + 'users' => 'user', + ]; + public function __construct() { view()->share('signedIn', Auth::check()); diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index 4c63179d59..73e17f8942 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -83,30 +83,30 @@ class CustomFieldsController extends Controller { $this->authorize('create', CustomField::class); - $show_in_email = $request->get("show_in_email", 0); - $display_in_user_view = $request->get("display_in_user_view", 0); + $show_in_email = $request->input("show_in_email", 0); + $display_in_user_view = $request->input("display_in_user_view", 0); // Override the display settings if the field is encrypted - if ($request->get("field_encrypted") == '1') { + if ($request->input("field_encrypted") == '1') { $show_in_email = '0'; $display_in_user_view = '0'; } - + $field = new CustomField([ - "name" => trim($request->get("name")), - "element" => $request->get("element"), - "help_text" => $request->get("help_text"), - "field_values" => $request->get("field_values"), - "field_encrypted" => $request->get("field_encrypted", 0), + "name" => trim($request->input("name")), + "element" => $request->input("element"), + "help_text" => $request->input("help_text"), + "field_values" => $request->input("field_values"), + "field_encrypted" => $request->input("field_encrypted", 0), "show_in_email" => $show_in_email, - "is_unique" => $request->get("is_unique", 0), + "is_unique" => $request->input("is_unique", 0), "display_in_user_view" => $display_in_user_view, - "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), - "show_in_listview" => $request->get("show_in_listview", 0), - "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), - "display_checkin" => $request->get("display_checkin", 0), - "display_checkout" => $request->get("display_checkout", 0), - "display_audit" => $request->get("display_audit", 0), + "auto_add_to_fieldsets" => $request->input("auto_add_to_fieldsets", 0), + "show_in_listview" => $request->input("show_in_listview", 0), + "show_in_requestable_list" => $request->input("show_in_requestable_list", 0), + "display_checkin" => $request->input("display_checkin", 0), + "display_checkout" => $request->input("display_checkout", 0), + "display_audit" => $request->input("display_audit", 0), "created_by" => auth()->id() ]); @@ -144,10 +144,9 @@ class CustomFieldsController extends Controller */ public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse { + $this->authorize('update', CustomField::class); $field = CustomField::find($field_id); - $this->authorize('update', $field); - // Check that the field exists - this is mostly related to the demo, where we // rewrite the data every x minutes, so it's possible someone might be disassociating // a field from a fieldset just as we're wiping the database @@ -157,11 +156,12 @@ class CustomFieldsController extends Controller return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id]) ->with('success', trans('admin/custom_fields/message.field.delete.success')); } else { - return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]); + return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error')) + ->withInput(); } } - return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]); + return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error')); } @@ -172,20 +172,16 @@ class CustomFieldsController extends Controller * @author [Brady Wetherington] [] * @since [v1.8] */ - public function destroy($field_id) : RedirectResponse + public function destroy(CustomField $field) : RedirectResponse { - if ($field = CustomField::find($field_id)) { - $this->authorize('delete', $field); + $this->authorize('delete', CustomField::class); - if (($field->fieldset) && ($field->fieldset->count() > 0)) { - return redirect()->back()->withErrors(['message' => 'Field is in-use']); - } - $field->delete(); - return redirect()->route("fields.index") - ->with("success", trans('admin/custom_fields/message.field.delete.success')); + if (($field->fieldset) && ($field->fieldset->count() > 0)) { + return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.in_use')); } - - return redirect()->back()->withErrors(['message' => 'Field does not exist']); + $field->delete(); + return redirect()->route("fields.index") + ->with("success", trans('admin/custom_fields/message.field.delete.success')); } @@ -198,7 +194,7 @@ class CustomFieldsController extends Controller */ public function edit(Request $request, CustomField $field) : View | RedirectResponse { - $this->authorize('update', $field); + $this->authorize('update', CustomField::class); $fieldsets = CustomFieldset::get(); $customFormat = ''; if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) { @@ -228,7 +224,7 @@ class CustomFieldsController extends Controller */ public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse { - $this->authorize('update', $field); + $this->authorize('update', CustomField::class); $show_in_email = $request->get("show_in_email", 0); $display_in_user_view = $request->get("display_in_user_view", 0); @@ -238,8 +234,8 @@ class CustomFieldsController extends Controller $display_in_user_view = '0'; } - $field->name = trim(e($request->get("name"))); - $field->element = e($request->get("element")); + $field->name = trim($request->get("name")); + $field->element = $request->get("element"); $field->field_values = $request->get("field_values"); $field->created_by = auth()->id(); $field->help_text = $request->get("help_text"); @@ -254,9 +250,9 @@ class CustomFieldsController extends Controller $field->display_audit = $request->get("display_audit", 0); if ($request->get('format') == 'CUSTOM REGEX') { - $field->format = e($request->get('custom_format')); + $field->format = $request->get('custom_format'); } else { - $field->format = e($request->get('format')); + $field->format = $request->get('format'); } if ($field->element == 'checkbox' || $field->element == 'radio'){ @@ -265,7 +261,6 @@ class CustomFieldsController extends Controller if ($field->save()) { - // Sync fields with fieldsets $fieldset_array = $request->input('associate_fieldsets'); if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) { diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index 4f7f7397ef..2bb2d5e68e 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -81,8 +81,10 @@ class LicenseCheckinController extends Controller } if($licenseSeat->assigned_to != null){ - $return_to = User::find($licenseSeat->assigned_to); - session()->put('checkedInFrom', $return_to->id); + $return_to = User::withTrashed()->find($licenseSeat->assigned_to); + if ($return_to) { + session()->put('checkedInFrom', $return_to->id); + } } else { $return_to = Asset::find($licenseSeat->asset_id); } @@ -96,14 +98,17 @@ class LicenseCheckinController extends Controller } session()->put(['redirect_option' => $request->get('redirect_option')]); - + if ($request->get('redirect_option') === 'target'){ + session()->put(['checkout_to_type' => 'user']); + } // Was the asset updated? if ($licenseSeat->save()) { event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes)); - return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success')); + return Helper::getRedirectOption($request, $license->id, 'Licenses') + ->with('success', trans('admin/licenses/message.checkin.success')); } // Redirect to the license page with error diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 564ce97a89..5b2d344ba5 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -39,6 +39,11 @@ class LicenseCheckoutController extends Controller return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')); } + // We don't currently allow checking out licenses to locations, so we'll reset that to user if needed + if (session()->get('checkout_to_type') == 'location') { + session()->put(['checkout_to_type' => 'user']); + } + // Return the checkout view return view('licenses/checkout', compact('license')); } @@ -70,17 +75,15 @@ class LicenseCheckoutController extends Controller $licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId); $licenseSeat->created_by = auth()->id(); $licenseSeat->notes = $request->input('notes'); - - - $checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type')); if ($request->filled('asset_id')) { - + session()->put(['checkout_to_type' => 'asset']); $checkoutTarget = $this->checkoutToAsset($licenseSeat); $request->request->add(['assigned_asset' => $checkoutTarget->id]); session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']); } elseif ($request->filled('assigned_to')) { + session()->put(['checkout_to_type' => 'user']); $checkoutTarget = $this->checkoutToUser($licenseSeat); $request->request->add(['assigned_user' => $checkoutTarget->id]); session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']); @@ -89,7 +92,9 @@ class LicenseCheckoutController extends Controller if ($checkoutTarget) { - return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success')); + + return Helper::getRedirectOption($request, $license->id, 'Licenses') + ->with('success', trans('admin/licenses/message.checkout.success')); } diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php deleted file mode 100644 index 6ab3cb7703..0000000000 --- a/app/Http/Controllers/Licenses/LicenseFilesController.php +++ /dev/null @@ -1,132 +0,0 @@ -] - * @since [v1.0] - * @todo Switch to using the AssetFileRequest form request validator. - */ - public function store(UploadFileRequest $request, $licenseId = null) - { - $license = License::find($licenseId); - - if (isset($license->id)) { - $this->authorize('update', $license); - - if ($request->hasFile('file')) { - if (! Storage::exists('private_uploads/licenses')) { - Storage::makeDirectory('private_uploads/licenses', 775); - } - - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file); - - //Log the upload to the log - $license->logUpload($file_name, e($request->input('notes'))); - } - - - return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success')); - - } - - return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles')); - } - // Prepare the error message - return redirect()->route('licenses.index') - ->with('error', trans('admin/licenses/message.does_not_exist')); - } - - /** - * Deletes the selected license file. - * - * @author [A. Gianotto] [] - * @since [v1.0] - * @param int $licenseId - * @param int $fileId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function destroy($licenseId = null, $fileId = null) - { - if ($license = License::find($licenseId)) { - - $this->authorize('update', $license); - - if ($log = Actionlog::find($fileId)) { - - // Remove the file if one exists - if (Storage::exists('licenses/'.$log->filename)) { - try { - Storage::delete('licenses/'.$log->filename); - } catch (\Exception $e) { - Log::debug($e); - } - } - - $log->delete(); - - return redirect()->back() - ->with('success', trans('admin/hardware/message.deletefile.success')); - } - - return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist')); - } - - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); - } - - /** - * Allows the selected file to be viewed. - * - * @author [A. Gianotto] [] - * @since [v1.4] - * @param int $licenseId - * @param int $fileId - * @return \Symfony\Component\HttpFoundation\Response - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function show($licenseId = null, $fileId = null, $download = true) - { - $license = License::find($licenseId); - - // the license is valid - if (isset($license->id)) { - $this->authorize('view', $license); - $this->authorize('licenses.files', $license); - - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { - $file = 'private_uploads/licenses/'.$log->filename; - - try { - return StorageHelper::showOrDownloadFile($file, $log->filename); - } catch (\Exception $e) { - return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found')); - } - } - - // The log record doesn't exist somehow - return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found')); - - } - - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId])); - } -} diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index 53841e5030..b1728469b4 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -102,10 +102,15 @@ class LicensesController extends Controller $license->created_by = auth()->id(); $license->min_amt = $request->input('min_amt'); - session()->put(['redirect_option' => $request->get('redirect_option')]); + if($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } if ($license->save()) { - return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success')); + return Helper::getRedirectOption($request, $license->id, 'Licenses') + ->with('success', trans('admin/licenses/message.create.success')); } return redirect()->back()->withInput()->withErrors($license->getErrors()); @@ -125,7 +130,7 @@ class LicensesController extends Controller { $this->authorize('update', $license); - + session()->put('back_url', url()->previous()); $maintained_list = [ '' => 'Maintained', '1' => 'Yes', @@ -181,7 +186,8 @@ class LicensesController extends Controller session()->put(['redirect_option' => $request->get('redirect_option')]); if ($license->save()) { - return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success')); + return Helper::getRedirectOption($request, $license->id, 'Licenses') + ->with('success', trans('admin/licenses/message.update.success')); } // If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php return redirect()->back()->withInput()->withErrors($license->getErrors()); @@ -311,13 +317,16 @@ class LicensesController extends Controller $response = new StreamedResponse(function () { // Open output stream $handle = fopen('php://output', 'w'); - $licenses= License::with('company', + $licenses = License::with('company', 'manufacturer', 'category', 'supplier', 'adminuser', - 'assignedusers') - ->orderBy('created_at', 'DESC'); + 'assignedusers'); + if (request()->filled('category_id')) { + $licenses = $licenses->where('category_id', request()->input('category_id')); + } + $licenses = $licenses->orderBy('created_at', 'DESC'); Company::scopeCompanyables($licenses) ->chunk(500, function ($licenses) use ($handle) { $headers = [ @@ -364,7 +373,7 @@ class LicensesController extends Controller $license->order_number, $license->free_seat_count, $license->seats, - ($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')), + ($license->adminuser ? $license->adminuser->display_name : trans('admin/reports/general.deleted_user')), $license->depreciation ? $license->depreciation->name: '', $license->updated_at, $license->deleted_at, diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index da4e6a7e44..46eff73df7 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -96,7 +96,18 @@ class LocationsController extends Controller $location->company_id = $request->input('company_id'); } - $location = $request->handleImages($location); + if ($request->has('use_cloned_image')) { + $cloned_model_img = Location::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; + $new_image = 'locations/'.$new_image_name; + Storage::disk('public')->copy('locations/'.$cloned_model_img->image, $new_image); + $location->image = $new_image_name; + } + + } else { + $location = $request->handleImages($location); + } if ($location->save()) { return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success')); @@ -275,9 +286,9 @@ class LocationsController extends Controller // unset these values $location->id = null; - $location->image = null; return view('locations/edit') + ->with('cloned_model', $location_to_clone) ->with('item', $location); } diff --git a/app/Http/Controllers/LocationsFilesController.php b/app/Http/Controllers/LocationsFilesController.php deleted file mode 100644 index 3aaec0e089..0000000000 --- a/app/Http/Controllers/LocationsFilesController.php +++ /dev/null @@ -1,111 +0,0 @@ -] - */ - public function store(UploadFileRequest $request, Location $location) : RedirectResponse - { - $this->authorize('update', $location); - - if ($request->hasFile('file')) { - - if (! Storage::exists('private_uploads/locations')) { - Storage::makeDirectory('private_uploads/locations', 775); - } - - foreach ($request->file('file') as $file) { - $file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file); - $location->logUpload($file_name, $request->get('notes')); - } - - return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success')); - } - - return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles')); - } - - /** - * Check for permissions and display the file. - * - * @author [A. Gianotto] [] - * @param int $modelId - * @param int $fileId - * @since [v1.0] - */ - public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse - { - - $this->authorize('view', $location); - - if (! $log = Actionlog::find($fileId)) { - return redirect()->back()->withFragment('files')->with('error', 'No matching file record'); - } - - $file = 'private_uploads/locations/'.$log->filename; - - if (! Storage::exists($file)) { - return redirect()->back()->withFragment('files')->with('error', 'No matching file on server'); - } - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); - } - - /** - * Delete the associated file - * - * @author [A. Gianotto] [] - * @param int $modelId - * @param int $fileId - * @since [v1.0] - */ - public function destroy(Location $location, $fileId = null) : RedirectResponse - { - $rel_path = 'private_uploads/locations'; - $this->authorize('update', $location); - $log = Actionlog::find($fileId); - - if ($log) { - - // This should be moved to purge -// if (Storage::exists($rel_path.'/'.$log->filename)) { -// Storage::delete($rel_path.'/'.$log->filename); -// } - $log->delete(); - - return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); - } - - return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); - - } -} diff --git a/app/Http/Controllers/MaintenancesController.php b/app/Http/Controllers/MaintenancesController.php new file mode 100644 index 0000000000..e893b75f39 --- /dev/null +++ b/app/Http/Controllers/MaintenancesController.php @@ -0,0 +1,216 @@ +authorize('view', Asset::class); + return view('maintenances.index'); + } + + /** + * Returns a form view to create a new asset maintenance. + * + * @see MaintenancesController::postCreate() method that stores the data + * @author Vincent Sposato + * @version v1.0 + * @since [v1.8] + * @return mixed + */ + public function create() : View + { + $this->authorize('update', Asset::class); + $asset = null; + + if ($asset = Asset::find(request('asset_id'))) { + // We have to set this so that the correct property is set in the select2 ajax dropdown + $asset->asset_id = $asset->id; + } + + return view('maintenances/edit') + ->with('maintenanceType', Maintenance::getImprovementOptions()) + ->with('asset', $asset) + ->with('item', new Maintenance); + } + + /** + * Validates and stores the new asset maintenance + * + * @see MaintenancesController::getCreate() method for the form + * @author Vincent Sposato + * @version v1.0 + * @since [v1.8] + */ + public function store(ImageUploadRequest $request) : RedirectResponse + { + $this->authorize('update', Asset::class); + + $assets = Asset::whereIn('id', $request->input('selected_assets'))->get(); + + // Loop through the selected assets + foreach ($assets as $asset) { + + $maintenance = new Maintenance(); + $maintenance->supplier_id = $request->input('supplier_id'); + $maintenance->is_warranty = $request->input('is_warranty'); + $maintenance->cost = $request->input('cost'); + $maintenance->notes = $request->input('notes'); + + // Save the asset maintenance data + $maintenance->asset_id = $asset->id; + $maintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); + $maintenance->name = $request->input('name'); + $maintenance->start_date = $request->input('start_date'); + $maintenance->completion_date = $request->input('completion_date'); + $maintenance->created_by = auth()->id(); + + if (($maintenance->completion_date !== null) + && ($maintenance->start_date !== '') + && ($maintenance->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); + } + + $maintenance = $request->handleImages($maintenance); + + // Was the asset maintenance created? + if (!$maintenance->save()) { + return redirect()->back()->withInput()->withErrors($maintenance->getErrors()); + } + } + + return redirect()->route('maintenances.index') + ->with('success', trans('admin/maintenances/message.create.success')); + + } + + /** + * Returns a form view to edit a selected asset maintenance. + * + * @see MaintenancesController::postEdit() method that stores the data + * @author Vincent Sposato + * @version v1.0 + * @since [v1.8] + */ + public function edit(Maintenance $maintenance) : View | RedirectResponse + { + $this->authorize('update', Asset::class); + $this->authorize('update', $maintenance->asset); + + return view('maintenances/edit') + ->with('selected_assets', $maintenance->asset->pluck('id')->toArray()) + ->with('asset_ids', request()->input('asset_ids', [])) + ->with('maintenanceType', Maintenance::getImprovementOptions()) + ->with('item', $maintenance); + } + + /** + * Validates and stores an update to an asset maintenance + * + * @see MaintenancesController::postEdit() method that stores the data + * @author Vincent Sposato + * @param Request $request + * @param int $maintenanceId + * @version v1.0 + * @since [v1.8] + */ + public function update(ImageUploadRequest $request, Maintenance $maintenance) : View | RedirectResponse + { + $this->authorize('update', Asset::class); + $this->authorize('update', $maintenance->asset); + + $maintenance->supplier_id = $request->input('supplier_id'); + $maintenance->is_warranty = $request->input('is_warranty', 0); + $maintenance->cost = $request->input('cost'); + $maintenance->notes = $request->input('notes'); + $maintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); + $maintenance->name = $request->input('name'); + $maintenance->start_date = $request->input('start_date'); + $maintenance->completion_date = $request->input('completion_date'); + + + // Todo - put this in a getter/setter? + if (($maintenance->completion_date == null)) + { + if (($maintenance->asset_maintenance_time !== 0) + || (! is_null($maintenance->asset_maintenance_time)) + ) { + $maintenance->asset_maintenance_time = null; + } + } + + if (($maintenance->completion_date !== null) + && ($maintenance->start_date !== '') + && ($maintenance->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); + } + $maintenance = $request->handleImages($maintenance); + + if ($maintenance->save()) { + return redirect()->route('maintenances.index') + ->with('success', trans('admin/maintenances/message.edit.success')); + } + + return redirect()->back()->withInput()->withErrors($maintenance->getErrors()); + } + + /** + * Delete an asset maintenance + * + * @author Vincent Sposato + * @param int $maintenanceId + * @version v1.0 + * @since [v1.8] + */ + public function destroy(Maintenance $maintenance) : RedirectResponse + { + $this->authorize('update', Asset::class); + $this->authorize('update', $maintenance->asset); + // Delete the asset maintenance + $maintenance->delete(); + // Redirect to the asset_maintenance management page + return redirect()->route('maintenances.index') + ->with('success', trans('admin/maintenances/message.delete.success')); + } + + /** + * View an asset maintenance + * + * @author Vincent Sposato + * @param int $maintenanceId + * @version v1.0 + * @since [v1.8] + */ + public function show(Maintenance $maintenance) : View | RedirectResponse + { + return view('maintenances.view')->with('maintenance', $maintenance); + } +} diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php index 985ec769fc..ac0b2818f4 100755 --- a/app/Http/Controllers/ManufacturersController.php +++ b/app/Http/Controllers/ManufacturersController.php @@ -6,6 +6,7 @@ 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; @@ -31,7 +32,30 @@ class ManufacturersController extends Controller public function index() : View { $this->authorize('index', Manufacturer::class); - return view('manufacturers/index'); + $manufacturer_count = Manufacturer::withTrashed()->count(); + return view('manufacturers/index')->with('manufacturer_count', $manufacturer_count); + } + + /** + * Returns a view that invokes the ajax tables which actually contains + * the content for the manufacturers listing, which is generated in getDatatable. + * + * @author [A. Gianotto] [] + * @see Api\ManufacturersController::index() method that generates the JSON response + * @since [v1.0] + */ + public function seed() : RedirectResponse + { + $this->authorize('index', Manufacturer::class); + + $manufacturers_count = Manufacturer::withTrashed()->count(); + + if ($manufacturers_count == 0) { + Artisan::call('db:seed', ['--class' => 'Database\\Seeders\\ManufacturerSeeder', '--force' => true]); + 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])); } /** diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index bbbc46da6f..a2fe612b6e 100755 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -3,15 +3,21 @@ namespace App\Http\Controllers; use App\Http\Requests\ImageUploadRequest; +use App\Http\Transformers\ProfileTransformer; +use App\Models\Actionlog; use App\Models\Setting; use App\Models\User; use App\Notifications\CurrentInventory; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Http\RedirectResponse; use \Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\Storage; +use Symfony\Component\HttpFoundation\BinaryFileResponse; + /** * This controller handles all actions related to User Profiles for * the Snipe-IT Asset Management application. @@ -220,7 +226,7 @@ class ProfileController extends Controller if (!$user = User::find(auth()->id())) { return redirect()->back() - ->with('error', trans('admin/users/message.user_not_found', ['id' => $id])); + ->with('error', trans('admin/users/message.user_not_found', ['id' => auth()->id()])); } if (empty($user->email)) { return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email')); @@ -234,4 +240,28 @@ class ProfileController extends Controller return redirect()->back()->with('success', trans('admin/users/general.user_notified')); } + + + + public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse + { + + $logentry = Actionlog::where('filename', $filename)->first(); + + // Make sure the user has permission to view this file + if (auth()->id() != $logentry->target_id) { + return redirect()->route('account')->with('error', trans('general.generic_model_not_found', ['model' => 'file'])); + } + + if (config('filesystems.default') == 's3_private') { + return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5))); + } + + if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) { + return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename); + } + + return redirect()->back()->with('error', trans('general.file_does_not_exist')); + + } } diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 33afac5312..89a0a07a21 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -9,7 +9,7 @@ use App\Models\Actionlog; use App\Models\Asset; use App\Models\AssetModel; use App\Models\Category; -use App\Models\AssetMaintenance; +use App\Models\Maintenance; use App\Models\CheckoutAcceptance; use App\Models\Company; use App\Models\CustomField; @@ -17,13 +17,11 @@ use App\Models\Depreciation; use App\Models\License; use App\Models\ReportTemplate; use App\Models\Setting; -use App\Notifications\CheckoutAssetNotification; use Carbon\Carbon; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Facades\Notification; use \Illuminate\Contracts\View\View; use League\Csv\Reader; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -184,7 +182,7 @@ class ReportsController extends Controller $currency = e(Setting::getSettings()->default_currency); } - $row[] = $asset->purchase_date; + $row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false); $row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost); $row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue()); $row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())); @@ -277,7 +275,7 @@ class ReportsController extends Controller if ($actionlog->target) { if ($actionlog->targetType() == 'user') { - $target_name = $actionlog->target->getFullNameAttribute(); + $target_name = $actionlog->target->display_name; } else { $target_name = $actionlog->target->getDisplayNameAttribute(); } @@ -291,7 +289,7 @@ class ReportsController extends Controller $row = [ $actionlog->created_at, - ($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '', + ($actionlog->adminuser) ? e($actionlog->adminuser->display_name) : '', $actionlog->present()->actionType(), e($actionlog->itemType()), ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, @@ -485,7 +483,7 @@ class ReportsController extends Controller $header[] = trans('admin/hardware/table.purchase_date'); } - if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) { + if ($request->filled('purchase_cost')) { $header[] = trans('admin/hardware/table.purchase_cost'); } @@ -737,6 +735,11 @@ class ReportsController extends Controller if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) { $assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]); } + + if (($request->filled('last_updated_start')) && ($request->filled('last_updated_end'))) { + $assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]); + } + if ($request->filled('exclude_archived')) { $assets->notArchived(); } @@ -827,7 +830,7 @@ class ReportsController extends Controller } if ($request->filled('location')) { - $row[] = ($asset->location) ? $asset->location->present()->name() : ''; + $row[] = ($asset->location) ? $asset->location->display_name : ''; } if ($request->filled('location_address')) { @@ -840,7 +843,7 @@ class ReportsController extends Controller } if ($request->filled('rtd_location')) { - $row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : ''; + $row[] = ($asset->defaultLoc) ? $asset->defaultLoc->display_name : ''; } if ($request->filled('rtd_location_address')) { @@ -853,7 +856,7 @@ class ReportsController extends Controller } if ($request->filled('assigned_to')) { - $row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : ''); + $row[] = ($asset->checkedOutToUser() && $asset->assigned) ?? $asset->assigned->display_name; $row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType(); } @@ -1033,11 +1036,11 @@ class ReportsController extends Controller * @author Vincent Sposato * @version v1.0 */ - public function getAssetMaintenancesReport() : View + public function getMaintenancesReport() : View { $this->authorize('reports.view'); - return view('reports.asset_maintenances'); + return view('reports.maintenances'); } /** @@ -1046,11 +1049,11 @@ class ReportsController extends Controller * @author Vincent Sposato * @version v1.0 */ - public function exportAssetMaintenancesReport() : Response + public function exportMaintenancesReport() : Response { $this->authorize('reports.view'); // Grab all the improvements - $assetMaintenances = AssetMaintenance::with('asset', 'supplier') + $Maintenances = Maintenance::with('asset', 'supplier') ->orderBy('created_at', 'DESC') ->get(); @@ -1058,36 +1061,36 @@ class ReportsController extends Controller $header = [ trans('admin/hardware/table.asset_tag'), - trans('admin/asset_maintenances/table.asset_name'), + trans('admin/maintenances/table.asset_name'), trans('general.supplier'), - trans('admin/asset_maintenances/form.asset_maintenance_type'), - trans('admin/asset_maintenances/form.title'), - trans('admin/asset_maintenances/form.start_date'), - trans('admin/asset_maintenances/form.completion_date'), - trans('admin/asset_maintenances/form.asset_maintenance_time'), - trans('admin/asset_maintenances/form.cost'), + trans('admin/maintenances/form.asset_maintenance_type'), + trans('admin/maintenances/form.title'), + trans('admin/maintenances/form.start_date'), + trans('admin/maintenances/form.completion_date'), + trans('admin/maintenances/form.asset_maintenance_time'), + trans('admin/maintenances/form.cost'), ]; $header = array_map('trim', $header); $rows[] = implode(',', $header); - foreach ($assetMaintenances as $assetMaintenance) { + foreach ($Maintenances as $maintenance) { $row = []; - $row[] = str_replace(',', '', e($assetMaintenance->asset->asset_tag)); - $row[] = str_replace(',', '', e($assetMaintenance->asset->name)); - $row[] = str_replace(',', '', e($assetMaintenance->supplier->name)); - $row[] = e($assetMaintenance->improvement_type); - $row[] = e($assetMaintenance->title); - $row[] = e($assetMaintenance->start_date); - $row[] = e($assetMaintenance->completion_date); - if (is_null($assetMaintenance->asset_maintenance_time)) { + $row[] = str_replace(',', '', e($maintenance->asset->asset_tag)); + $row[] = str_replace(',', '', e($maintenance->asset->name)); + $row[] = str_replace(',', '', e($maintenance->supplier->name)); + $row[] = e($maintenance->improvement_type); + $row[] = e($maintenance->name); + $row[] = e($maintenance->start_date); + $row[] = e($maintenance->completion_date); + if (is_null($maintenance->asset_maintenance_time)) { $improvementTime = (int) Carbon::now() - ->diffInDays(Carbon::parse($assetMaintenance->start_date), true); + ->diffInDays(Carbon::parse($maintenance->start_date), true); } else { - $improvementTime = (int) $assetMaintenance->asset_maintenance_time; + $improvementTime = (int) $maintenance->asset_maintenance_time; } $row[] = $improvementTime; - $row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost); + $row[] = trans('general.currency') . Helper::formatCurrencyOutput($maintenance->cost); $rows[] = implode(',', $row); } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index a6de4466dd..8c57efa5eb 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -14,6 +14,7 @@ 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; @@ -290,7 +291,6 @@ class SettingsController extends Controller public function getSettings() : View { $setting = Setting::getSettings(); - return view('settings/general', compact('setting')); } @@ -352,6 +352,7 @@ class SettingsController extends Controller $setting->dash_chart_type = $request->input('dash_chart_type'); $setting->profile_edit = $request->input('profile_edit', 0); $setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0); + $setting->manager_view_enabled = $request->input('manager_view_enabled', 0); if ($request->input('per_page') != '') { @@ -650,6 +651,7 @@ class SettingsController extends Controller $setting->alert_email = $alert_email; $setting->admin_cc_email = $admin_cc_email; + $setting->admin_cc_always = $request->validated('admin_cc_always'); $setting->alerts_enabled = $request->input('alerts_enabled', '0'); $setting->alert_interval = $request->input('alert_interval'); $setting->alert_threshold = $request->input('alert_threshold'); @@ -772,6 +774,7 @@ 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'); @@ -870,6 +873,7 @@ class SettingsController extends Controller $setting->ldap_default_group = $request->input('ldap_default_group'); $setting->ldap_filter = $request->input('ldap_filter'); $setting->ldap_username_field = $request->input('ldap_username_field'); + $setting->ldap_display_name = $request->input('ldap_display_name'); $setting->ldap_lname_field = $request->input('ldap_lname_field'); $setting->ldap_fname_field = $request->input('ldap_fname_field'); $setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query'); @@ -886,7 +890,12 @@ class SettingsController extends Controller $setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0'); $setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url'); $setting->ldap_phone_field = $request->input('ldap_phone'); + $setting->ldap_mobile = $request->input('ldap_mobile'); $setting->ldap_jobtitle = $request->input('ldap_jobtitle'); + $setting->ldap_address = $request->input('ldap_address'); + $setting->ldap_city = $request->input('ldap_city'); + $setting->ldap_state = $request->input('ldap_state'); + $setting->ldap_zip = $request->input('ldap_zip'); $setting->ldap_country = $request->input('ldap_country'); $setting->ldap_location = $request->input('ldap_location'); $setting->ldap_dept = $request->input('ldap_dept'); @@ -921,7 +930,7 @@ class SettingsController extends Controller * @since v5.0.0 */ public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse - { + { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); } @@ -1081,6 +1090,7 @@ class SettingsController extends Controller if (! config('app.lock_passwords')) { if (Storage::exists($path.'/'.$filename)) { + Log::warning('User '.auth()->user()->username.' is attempting to download backup file: '.$filename); return StorageHelper::downloader($path.'/'.$filename); } else { // Redirect to the backup page @@ -1108,6 +1118,7 @@ class SettingsController extends Controller if (Storage::exists($path . '/' . $filename)) { try { + Log::warning('User '.auth()->user()->username.' is attempting to delete backup file: '.$filename); Storage::delete($path . '/' . $filename); return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted')); } catch (\Exception $e) { @@ -1187,7 +1198,7 @@ class SettingsController extends Controller '--force' => true, ]); - Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename); + Log::warning('User '.auth()->user()->username.' is attempting to restore from: '. storage_path($path).'/'.$filename); $restore_params = [ '--force' => true, @@ -1336,9 +1347,11 @@ class SettingsController extends Controller 'name' => config('mail.from.name'), 'email' => config('mail.from.address'), ])->notify(new MailTest()); - + Log::debug('Attempting to send mail to '.config('mail.from.address')); return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent'))); } catch (\Exception $e) { + Log::error('Mail sent from '.config('mail.from.address') .' with errors '. $e->getMessage()); + Log::debug($e); return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage())); } } diff --git a/app/Http/Controllers/SuppliersController.php b/app/Http/Controllers/SuppliersController.php index d96031a9aa..2a3c73bebe 100755 --- a/app/Http/Controllers/SuppliersController.php +++ b/app/Http/Controllers/SuppliersController.php @@ -4,7 +4,6 @@ namespace App\Http\Controllers; use App\Http\Requests\ImageUploadRequest; use App\Models\Supplier; -use Illuminate\Support\Facades\Auth; use Illuminate\Http\RedirectResponse; use \Illuminate\Contracts\View\View; @@ -122,7 +121,7 @@ class SuppliersController extends Controller public function destroy($supplierId) : RedirectResponse { $this->authorize('delete', Supplier::class); - if (is_null($supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) { + if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) { return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found')); } @@ -130,8 +129,8 @@ class SuppliersController extends Controller return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])); } - if ($supplier->asset_maintenances_count > 0) { - return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count])); + if ($supplier->maintenances_count > 0) { + return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])); } if ($supplier->licenses_count > 0) { diff --git a/app/Http/Controllers/UploadedFilesController.php b/app/Http/Controllers/UploadedFilesController.php new file mode 100644 index 0000000000..8a9f4304ad --- /dev/null +++ b/app/Http/Controllers/UploadedFilesController.php @@ -0,0 +1,162 @@ +] + */ + public function store(UploadFileRequest $request, $object_type, $id) : RedirectResponse + { + + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('update', $object); + + if (!$object) { + return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object')); + } + + // If the file storage directory doesn't exist, create it + if (! Storage::exists(self::$map_storage_path[$object_type])) { + Storage::makeDirectory(self::$map_storage_path[$object_type], 775); + } + + + if ($request->hasFile('file')) { + // Loop over the attached files and add them to the object + foreach ($request->file('file') as $file) { + $file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file); + $files[] = $file_name; + $object->logUpload($file_name, $request->get('notes')); + } + + $files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded') + ->where('item_type', '=', self::$map_object_type[$object_type]) + ->where('item_id', '=', $id)->whereIn('filename', $files) + ->get(); + + return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.upload.success', count($files))); + } + + // No files were submitted + return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.nofiles')); + } + + + + /** + * Check for permissions and display the file. + * This isn't currently used, but is here for future use. + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param string $object_type the type of object to upload the file to + * @param int $id the ID of the object to delete from so we can check permisisons + * @param $file_id the ID of the file to show from the action_logs table + * @since [v8.2.2] + * @author [A. Gianotto ] + */ + public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse + { + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('view', $object); + + if (!$object) { + return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object')); + } + + + // Check that the file being requested exists for the object + if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id)) + { + return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.invalid_id')); + } + + + if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) + { + return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.file_not_found')); + } + + if (request('inline') == 'true') { + $headers = [ + 'Content-Disposition' => 'inline', + ]; + return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers); + } + + return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename); + + } + + /** + * Delete the associated file + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param string $object_type the type of object to upload the file to + * @param int $id the ID of the object to delete from so we can check permisisons + * @param $file_id the ID of the file to delete from the action_logs table + * @since [v8.2.2] + * @author [A. Gianotto ] + */ + public function destroy($object_type, $id, $file_id) : RedirectResponse + { + + // Check the permissions to make sure the user can view the object + $object = self::$map_object_type[$object_type]::withTrashed()->find($id); + $this->authorize('update', self::$map_object_type[$object_type]); + + if (!$object) { + return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object')); + } + + + // Check for the file + $log = Actionlog::where('id',$file_id)->where('item_type', self::$map_object_type[$object_type]) + ->where('item_id', $object->id)->first(); + + if ($log) { + // Check the file actually exists, and delete it + if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) { + Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename); + } + // Delete the record of the file + if ($log->logUploadDelete($object, $log->filename)) { + return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1)); + } + + } + + // The file doesn't seem to really exist, so report an error + return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.error', 1)); + + } + +} diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php deleted file mode 100644 index 45bd0c6329..0000000000 --- a/app/Http/Controllers/Users/UserFilesController.php +++ /dev/null @@ -1,129 +0,0 @@ -] - * @since [v1.6] - */ - public function store(UploadFileRequest $request, User $user) - { - $this->authorize('update', $user); - $files = $request->file('file'); - - 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); - - //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); - } - - return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success')); - } - - - } - - /** - * Delete file - * - * @author [A. Gianotto] [] - * @since [v1.6] - * @param int $userId - * @param int $fileId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function destroy($userId = null, $fileId = null) - { - if ($user = User::find($userId)) { - - $this->authorize('delete', $user); - $rel_path = 'private_uploads/users'; - - - if ($log = Actionlog::find($fileId)) { - $filename = $log->filename; - $log->delete(); - - if (Storage::exists($rel_path.'/'.$filename)) { - Storage::delete($rel_path.'/'.$filename); - return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.deletefile.success')); - } - - } - - // The log record doesn't exist somehow - return redirect()->back()->with('success', trans('admin/users/message.deletefile.success')); - } - - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId])); - - } - - /** - * Display/download the uploaded file - * - * @author [A. Gianotto] [] - * @since [v1.6] - * @param int $userId - * @param int $fileId - * @return mixed - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function show(User $user, $fileId = null) - { - - - if (empty($fileId)) { - return redirect()->route('users.show')->with('error', 'Invalid file request'); - } - - $this->authorize('view', $user); - - 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')); - } - } - - // The log record doesn't exist somehow - return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found')); - - } - -} diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 613524d5c3..aa42415065 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -14,14 +14,9 @@ use App\Models\Group; use App\Models\Setting; use App\Models\User; use App\Notifications\WelcomeNotification; -use Illuminate\Support\Facades\Auth; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Password; -use Illuminate\Support\Facades\Storage; -use Redirect; -use Str; use Symfony\Component\HttpFoundation\StreamedResponse; use App\Notifications\CurrentInventory; @@ -95,6 +90,7 @@ class UsersController extends Controller //Username, email, and password need to be handled specially because the need to respect config values on an edit. $user->email = trim($request->input('email')); $user->username = trim($request->input('username')); + $user->display_name = $request->input('display_name'); if ($request->filled('password')) { $user->password = bcrypt($request->input('password')); } @@ -105,6 +101,7 @@ class UsersController extends Controller $user->activated = $request->input('activated', 0); $user->jobtitle = $request->input('jobtitle'); $user->phone = $request->input('phone'); + $user->mobile = $request->input('mobile'); $user->location_id = $request->input('location_id', null); $user->department_id = $request->input('department_id', null); $user->company_id = Company::getIdForUser($request->input('company_id', null)); @@ -130,31 +127,37 @@ class UsersController extends Controller } $user->permissions = json_encode($permissions_array); - // we have to invoke the + // we have to invoke the form request here to handle image uploads app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); - session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($request->get('redirect_option') === 'back'){ + session()->put(['redirect_option' => 'index']); + } else { + session()->put(['redirect_option' => $request->get('redirect_option')]); + } + if ($user->save()) { + + if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) { + + try { + $user->notify(new WelcomeNotification($user)); + } catch (\Exception $e) { + Log::warning('Could not send welcome notification for user: ' . $e->getMessage()); + } + + + } + if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); } else { $user->groups()->sync([]); } - if (($request->input('email_user') == 1) && ($request->filled('email'))) { - // Send the credentials through email - $data = []; - $data['email'] = e($request->input('email')); - $data['username'] = e($request->input('username')); - $data['first_name'] = e($request->input('first_name')); - $data['last_name'] = e($request->input('last_name')); - $data['password'] = e($request->input('password')); - - $user->notify(new WelcomeNotification($data)); - } - - return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create')); + return Helper::getRedirectOption($request, $user->id, 'Users') + ->with('success', trans('admin/users/message.success.create')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); @@ -178,7 +181,7 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param $permissions - * @return \Illuminate\Contracts\View\View + * @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse * @internal param int $id * @throws \Illuminate\Auth\Access\AuthorizationException */ @@ -186,10 +189,15 @@ class UsersController extends Controller { $this->authorize('update', User::class); + session()->put('back_url', url()->previous()); $user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id); if ($user) { + if ($user->trashed()) { + return redirect()->route('users.show', $user->id); + } + $permissions = config('permissions'); $groups = Group::pluck('name', 'id'); @@ -242,22 +250,18 @@ class UsersController extends Controller } } - // Only save groups if the user is a superuser - if (auth()->user()->isSuperUser()) { - $user->groups()->sync($request->input('groups')); - } // Update the user fields - $user->username = trim($request->input('username')); - $user->email = trim($request->input('email')); + $user->first_name = $request->input('first_name'); $user->last_name = $request->input('last_name'); + $user->display_name = $request->input('display_name'); $user->two_factor_optin = $request->input('two_factor_optin') ?: 0; $user->locale = $request->input('locale'); $user->employee_num = $request->input('employee_num'); - $user->activated = $request->input('activated', 0); $user->jobtitle = $request->input('jobtitle', null); $user->phone = $request->input('phone'); + $user->mobile = $request->input('mobile'); $user->location_id = $request->input('location_id', null); $user->company_id = Company::getIdForUser($request->input('company_id', null)); $user->manager_id = $request->input('manager_id', null); @@ -267,8 +271,6 @@ class UsersController extends Controller $user->city = $request->input('city', null); $user->state = $request->input('state', null); $user->country = $request->input('country', null); - // if a user is editing themselves we should always keep activated true - $user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0); $user->zip = $request->input('zip', null); $user->remote = $request->input('remote', 0); $user->vip = $request->input('vip', 0); @@ -277,30 +279,49 @@ class UsersController extends Controller $user->end_date = $request->input('end_date', null); $user->autoassign_licenses = $request->input('autoassign_licenses', 0); + // Set this here so that we can overwrite it later if the user is an admin or superadmin + $user->activated = $request->input('activated', auth()->user()->is($user) ? 1 : $user->activated); + + // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id) ->update(['location_id' => $request->input('location_id', null)]); - // Do we want to update the user password? - if ($request->filled('password')) { - $user->password = bcrypt($request->input('password')); + // check for permissions related fields and only set them if the user has permission to edit them + if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) { + + $user->username = trim($request->input('username')); + $user->email = trim($request->input('email')); + $user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0); + + // Do we want to update the user password? + if ($request->filled('password')) { + $user->password = bcrypt($request->input('password')); + } + + $permissions_array = $request->input('permission'); + + // Strip out the superuser permission if the user isn't a superadmin + if (! auth()->user()->isSuperUser()) { + unset($permissions_array['superuser']); + $permissions_array['superuser'] = $orig_superuser; + } + + $user->permissions = json_encode($permissions_array); + + // Only save groups if the user is a superuser + if (auth()->user()->isSuperUser()) { + $user->groups()->sync($request->input('groups')); + } } + // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id) ->update(['location_id' => $user->location_id]); - $permissions_array = $request->input('permission'); - - // Strip out the superuser permission if the user isn't a superadmin - if (! auth()->user()->isSuperUser()) { - unset($permissions_array['superuser']); - $permissions_array['superuser'] = $orig_superuser; - } - - $user->permissions = json_encode($permissions_array); // Handle uploaded avatar app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); @@ -308,7 +329,7 @@ class UsersController extends Controller if ($user->save()) { // Redirect to the user page - return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users')) + return Helper::getRedirectOption($request, $user->id, 'Users') ->with('success', trans('admin/users/message.success.update')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); @@ -434,7 +455,7 @@ class UsersController extends Controller app('request')->request->set('permissions', $permissions); - $user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id); + $user_to_clone = User::with('userloc')->withTrashed()->find($user->id); // Make sure they can view this particular user $this->authorize('view', $user_to_clone); @@ -449,6 +470,8 @@ class UsersController extends Controller $user->last_name = ''; $user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0); $user->id = null; + $user->username = null; + $user->avatar = null; // Get this user's groups $userGroups = $user_to_clone->groups()->pluck('name', 'id'); @@ -464,7 +487,7 @@ class UsersController extends Controller ->with('user', $user) ->with('groups', Group::pluck('name', 'id')) ->with('userGroups', $userGroups) - ->with('clone_user', $user_to_clone) + ->with('cloned_model', $user_to_clone) ->with('item', $user); } @@ -506,6 +529,8 @@ class UsersController extends Controller trans('admin/companies/table.title'), trans('admin/users/table.title'), trans('general.employee_number'), + trans('admin/users/table.first_name'), + trans('admin/users/table.last_name'), trans('admin/users/table.name'), trans('admin/users/table.username'), trans('admin/users/table.email'), @@ -551,10 +576,12 @@ class UsersController extends Controller ($user->company) ? $user->company->name : '', $user->jobtitle, $user->employee_num, - $user->present()->fullName(), + $user->first_name, + $user->last_name, + $user->display_name, $user->username, $user->email, - ($user->manager) ? $user->manager->present()->fullName() : '', + ($user->manager) ? $user->manager->display_name : '', ($user->userloc) ? $user->userloc->name : '', ($user->department) ? $user->department->name : '', $user->assets->count(), diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index bbff6ba4f7..2b767650ad 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -27,50 +27,126 @@ use Exception; class ViewAssetsController extends Controller { /** - * Redirect to the profile page. + * Extract custom fields that should be displayed in user view. + * + * @param User $user + * @return array + */ + private function extractCustomFields(User $user): array + { + $fieldArray = []; + foreach ($user->assets as $asset) { + if ($asset->model && $asset->model->fieldset) { + foreach ($asset->model->fieldset->fields as $field) { + if ($field->display_in_user_view == '1') { + $fieldArray[$field->db_column] = $field->name; + } + } + } + } + return array_unique($fieldArray); + } + + /** + * Get list of users viewable by the current user. + * + * @param User $authUser + * @return \Illuminate\Support\Collection + */ + private function getViewableUsers(User $authUser): \Illuminate\Support\Collection + { + // SuperAdmin sees all users + if ($authUser->isSuperUser()) { + return User::select('id', 'first_name', 'last_name', 'username') + ->where('activated', 1) + ->orderBy('last_name') + ->orderBy('first_name') + ->get(); + } + + // Regular manager sees only their subordinates + self + $managedUsers = $authUser->getAllSubordinates(); + + // If user has subordinates, show them with self at beginning + if ($managedUsers->count() > 0) { + return collect([$authUser])->merge($managedUsers) + ->sortBy('last_name') + ->sortBy('first_name'); + } + + // User has no subordinates, only sees themselves + return collect([$authUser]); + } + + /** + * Get the selected user ID from request or default to current user. + * + * @param Request $request + * @param \Illuminate\Support\Collection $subordinates + * @param int $defaultUserId + * @return int + */ + private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int + { + // If no subordinates or no user_id in request, return default + if ($subordinates->count() <= 1 || !$request->filled('user_id')) { + return $defaultUserId; + } + + $requestedUserId = (int) $request->input('user_id'); + + // Validate if the requested user is allowed + if ($subordinates->contains('id', $requestedUserId)) { + return $requestedUserId; + } + + // If invalid ID or not authorized, return default + return $defaultUserId; + } + + /** + * Show user's assigned assets with optional manager view functionality. * */ - public function getIndex() : View | RedirectResponse + public function getIndex(Request $request) : View | RedirectResponse { - $user = User::with( + $authUser = auth()->user(); + $settings = Setting::getSettings(); + $subordinates = collect(); + $selectedUserId = $authUser->id; + + // Process manager view if enabled + if ($settings->manager_view_enabled) { + $subordinates = $this->getViewableUsers($authUser); + $selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id); + } + + // Load the data for the user to be viewed (either auth user or selected subordinate) + $userToView = User::with([ 'assets', 'assets.model', 'assets.model.fieldset.fields', 'consumables', 'accessories', - 'licenses', - )->find(auth()->id()); - - $field_array = array(); - - // Loop through all the custom fields that are applied to any model the user has assigned - foreach ($user->assets as $asset) { - - // Make sure the model has a custom fieldset before trying to loop through the associated fields - if ($asset->model->fieldset) { - - foreach ($asset->model->fieldset->fields as $field) { - // check and make sure they're allowed to see the value of the custom field - if ($field->display_in_user_view == '1') { - $field_array[$field->db_column] = $field->name; - } - - } - } + 'licenses' + ])->find($selectedUserId); + // If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error + if (!$userToView) { + return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found')); } - // Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns - array_unique($field_array); + // Process custom fields for the user being viewed + $fieldArray = $this->extractCustomFields($userToView); - if (isset($user->id)) { - return view('account/view-assets', compact('user', 'field_array' )) - ->with('settings', Setting::getSettings()); - } - - // Redirect to the user management page - return redirect()->route('users.index') - ->with('error', trans('admin/users/message.user_not_found', $user->id)); + // Pass the necessary data to the view + return view('account/view-assets', [ + 'user' => $userToView, // Use 'user' for compatibility with the existing view + 'field_array' => $fieldArray, + 'settings' => $settings, + 'subordinates' => $subordinates, + 'selectedUserId' => $selectedUserId + ]); } /** @@ -109,7 +185,7 @@ class ViewAssetsController extends Controller $logaction->target_type = User::class; $data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1; - $data['requested_by'] = $user->present()->fullName(); + $data['requested_by'] = $user->display_name; $data['item'] = $item; $data['item_type'] = $itemType; $data['target'] = auth()->user(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b69e22e4f9..729fe75175 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,6 +73,7 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'api-throttle' => \App\Http\Middleware\SetAPIResponseHeaders::class, 'health' => null, ]; } diff --git a/app/Http/Middleware/SecurityHeaders.php b/app/Http/Middleware/SecurityHeaders.php index 8e6c17b4e7..e740c76678 100644 --- a/app/Http/Middleware/SecurityHeaders.php +++ b/app/Http/Middleware/SecurityHeaders.php @@ -26,7 +26,6 @@ class SecurityHeaders $response = $next($request); $response->headers->set('X-Content-Type-Options', 'nosniff'); - $response->headers->set('X-XSS-Protection', '1; mode=block'); // Ugh. Feature-Policy is dumb and clumsy and mostly irrelevant for Snipe-IT, // since we don't provide any way to IFRAME anything in in the first place. diff --git a/app/Http/Middleware/SetAPIResponseHeaders.php b/app/Http/Middleware/SetAPIResponseHeaders.php new file mode 100644 index 0000000000..ac277e785c --- /dev/null +++ b/app/Http/Middleware/SetAPIResponseHeaders.php @@ -0,0 +1,82 @@ +headers->get('X-RateLimit-Remaining')) && + (int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) { + $headers = []; + $headers['Retry-After'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed + return $headers; + } + + $headers = [ + 'X-RateLimit-Limit' => $maxAttempts, + 'X-RateLimit-Remaining' => $remainingAttempts, + ]; + + if (! is_null($retryAfter)) { + $headers['Retry-After'] = $retryAfter; + $headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed + } + + return $headers; + } + + + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + protected function handleRequest($request, Closure $next, array $limits) + { + foreach ($limits as $limit) { + if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { + throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); + } + + $this->limiter->hit($limit->key, $limit->decaySeconds); + } + + $response = $next($request); + + foreach ($limits as $limit) { + $response = $this->addHeaders( + $response, + $limit->maxAttempts, + $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts), + $this->getTimeUntilNextRetry($limit->key) // this is the only line we changed + ); + } + + return $response; + } + +} \ No newline at end of file diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index abb0cee5f7..3a62212e36 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Storage; use Intervention\Image\Exception\NotReadableException; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; class ImageUploadRequest extends Request { @@ -70,19 +71,25 @@ class ImageUploadRequest extends Request public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image') { - $type = strtolower(class_basename(get_class($item))); + $type = class_basename(get_class($item)); if (is_null($path)) { - $path = str_plural($type); + $path = strtolower(str_plural($type)); - if ($type == 'assetmodel') { + if ($type == 'AssetModel') { $path = 'models'; } if ($type == 'user') { $path = 'avatars'; } + + } + + + if (!Storage::disk('public')->exists($path)) { + Storage::disk('public')->makeDirectory($path); } if ($this->offsetGet($form_fieldname) instanceof UploadedFile) { @@ -93,10 +100,9 @@ class ImageUploadRequest extends Request if (isset($image)) { - if (!config('app.lock_passwords')) { $ext = $image->guessExtension(); - $file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext; + $file_name = $type.'-'.$form_fieldname.($item->id ?? '-'.$item->id).'-'.str_random(10).'.'.$ext; if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) { // If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing @@ -138,7 +144,7 @@ class ImageUploadRequest extends Request // Remove Current image if exists $item = $this->deleteExistingImage($item, $path, $db_fieldname); $item->{$db_fieldname} = $file_name; - } + // If the user isn't uploading anything new but wants to delete their old image, do so diff --git a/app/Http/Requests/SettingsSamlRequest.php b/app/Http/Requests/SettingsSamlRequest.php index 2ab876141a..53a3521018 100644 --- a/app/Http/Requests/SettingsSamlRequest.php +++ b/app/Http/Requests/SettingsSamlRequest.php @@ -41,6 +41,7 @@ class SettingsSamlRequest extends FormRequest public function withValidator($validator) { $validator->after(function ($validator) { + $setting = Setting::getSettings(); if ($this->input('saml_enabled') == '1') { $idpMetadata = $this->input('saml_idp_metadata'); if (! empty($idpMetadata)) { @@ -56,7 +57,7 @@ class SettingsSamlRequest extends FormRequest } } - $was_custom_x509cert = strpos(Setting::getSettings()->saml_custom_settings, 'sp_x509cert') !== false; + $was_custom_x509cert = strpos($setting->saml_custom_settings, 'sp_x509cert') !== false; $custom_x509cert = ''; $custom_privateKey = ''; @@ -108,7 +109,7 @@ class SettingsSamlRequest extends FormRequest ]; $pkey = openssl_pkey_new([ - 'private_key_bits' => 2048, + 'private_key_bits' => config('app.saml_key_size'), 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); @@ -126,10 +127,14 @@ class SettingsSamlRequest extends FormRequest } if (! (empty($x509cert) && empty($privateKey))) { - $this->merge([ - 'saml_sp_x509cert' => $x509cert, - 'saml_sp_privatekey' => $privateKey, - ]); +// $this->merge([ +// 'saml_sp_x509cert' => $x509cert, +// 'saml_sp_privatekey' => $privateKey, +// ]); + $setting->saml_sp_x509cert = $x509cert; + $setting->saml_sp_privatekey = $privateKey; + $setting->save(); + } } else { $validator->errors()->add('saml_integration', 'openssl.cnf is missing/invalid'); @@ -145,15 +150,21 @@ class SettingsSamlRequest extends FormRequest } if (! empty($x509certNew)) { - $this->merge([ - 'saml_sp_x509certNew' => $x509certNew, - ]); +// $this->merge([ +// 'saml_sp_x509certNew' => $x509certNew, +// ]); + $setting->saml_sp_x509certNew = $x509certNew; + $setting->save(); } } else { - $this->merge([ - 'saml_sp_x509certNew' => '', - ]); +// $this->merge([ +// 'saml_sp_x509certNew' => '', +// ]); + $setting->saml_sp_x509certNew = ''; + $setting->save(); } + + }); } } diff --git a/app/Http/Requests/StoreAssetModelRequest.php b/app/Http/Requests/StoreAssetModelRequest.php index 635d45cf89..0da47b0c72 100644 --- a/app/Http/Requests/StoreAssetModelRequest.php +++ b/app/Http/Requests/StoreAssetModelRequest.php @@ -19,6 +19,7 @@ class StoreAssetModelRequest extends ImageUploadRequest public function prepareForValidation(): void { + parent::prepareForValidation(); if ($this->category_id) { if ($category = Category::find($this->category_id)) { diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index fb7469ac88..66179ac739 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -39,7 +39,6 @@ class StoreAssetRequest extends ImageUploadRequest $this->merge([ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(), 'company_id' => $idForCurrentUser, - 'assigned_to' => $assigned_to ?? null, ]); } diff --git a/app/Http/Requests/StoreLabelSettings.php b/app/Http/Requests/StoreLabelSettings.php index f9ede7ea89..2b6fd83c6f 100644 --- a/app/Http/Requests/StoreLabelSettings.php +++ b/app/Http/Requests/StoreLabelSettings.php @@ -37,8 +37,8 @@ class StoreLabelSettings extends FormRequest return [ 'labels_per_page' => 'numeric', - 'labels_width' => 'numeric', - 'labels_height' => 'numeric', + 'labels_width' => 'numeric|min:0.1', + 'labels_height' => 'numeric|min:0.1', 'labels_pmargin_left' => 'numeric|nullable', 'labels_pmargin_right' => 'numeric|nullable', 'labels_pmargin_top' => 'numeric|nullable', diff --git a/app/Http/Requests/StoreNotificationSettings.php b/app/Http/Requests/StoreNotificationSettings.php index bf5f5b3d4e..f58d014c76 100644 --- a/app/Http/Requests/StoreNotificationSettings.php +++ b/app/Http/Requests/StoreNotificationSettings.php @@ -5,6 +5,7 @@ namespace App\Http\Requests; use App\Models\Accessory; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Gate; +use Illuminate\Validation\Rule; class StoreNotificationSettings extends FormRequest { @@ -26,6 +27,9 @@ class StoreNotificationSettings extends FormRequest return [ 'alert_email' => 'email_array|nullable', 'admin_cc_email' => 'email_array|nullable', + 'admin_cc_always' => [ + Rule::in('0', '1'), + ], 'alert_threshold' => 'numeric|nullable', 'alert_interval' => 'numeric|nullable|gt:0', 'audit_warning_days' => 'numeric|nullable', diff --git a/app/Http/Requests/UploadFileRequest.php b/app/Http/Requests/UploadFileRequest.php index e58f1a1be3..82f4a35be1 100644 --- a/app/Http/Requests/UploadFileRequest.php +++ b/app/Http/Requests/UploadFileRequest.php @@ -6,6 +6,7 @@ use App\Http\Traits\ConvertsBase64ToFiles; use enshrined\svgSanitize\Sanitizer; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Log; +use \App\Helpers\Helper; class UploadFileRequest extends Request { @@ -27,44 +28,76 @@ class UploadFileRequest extends Request */ public function rules() { - $max_file_size = \App\Helpers\Helper::file_upload_max_size(); + $max_file_size = Helper::file_upload_max_size(); return [ - 'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size, + 'file.*' => 'required|mimes:'.config('filesystems.allowed_upload_extensions_for_validator').'|max:'.$max_file_size, ]; } /** * Sanitizes (if needed) and Saves a file to the appropriate location * Returns the 'short' (storage-relative) filename - * - * TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there - * a way to merge them or extend one into the other? */ public function handleFile(string $dirname, string $name_prefix, $file): string { + $extension = $file->getClientOriginalExtension(); $file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension(); // Check for SVG and sanitize it if ($file->getMimeType() === 'image/svg+xml') { - Log::debug('This is an SVG'); - Log::debug($file_name); - - $sanitizer = new Sanitizer(); - $dirtySVG = file_get_contents($file->getRealPath()); - $cleanSVG = $sanitizer->sanitize($dirtySVG); - - try { - Storage::put($dirname.$file_name, $cleanSVG); - } catch (\Exception $e) { - Log::debug('Upload no workie :( '); - Log::debug($e); - } - + $uploaded_file = $this->handleSVG($file); } else { - $put_results = Storage::put($dirname.$file_name, file_get_contents($file)); + $uploaded_file = file_get_contents($file); } + + try { + Storage::put($dirname.$file_name, $uploaded_file); + } catch (\Exception $e) { + Log::debug($e); + } + return $file_name; } -} + + public function handleSVG($file) + { + $sanitizer = new Sanitizer(); + $dirtySVG = file_get_contents($file->getRealPath()); + return $sanitizer->sanitize($dirtySVG); + } + + + /** + * Get the validation error messages that apply to the request, but + * replace the attribute name with the name of the file that was attempted and failed + * to make it clearer to the user which file is the bad one. + * + * @return array + */ + public function attributes(): array + { + $attributes = []; + + if (($this->file) && (is_array($this->file))) { + + for ($i = 0; $i < count($this->file); $i++) { + + try { + + if ($this->file[$i]) { + $attributes['file.'.$i] = $this->file[$i]->getClientOriginalName(); + } + + } catch (\Exception $e) { + $attributes['file.'.$i] = 'Invalid file'; + } + + } + } + + return $attributes; + + } +} \ No newline at end of file diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index b502490884..491871e122 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -44,7 +44,7 @@ class AccessoriesTransformer 'checkouts_count' => $accessory->checkouts_count, 'created_by' => ($accessory->adminuser) ? [ 'id' => (int) $accessory->adminuser->id, - 'name'=> e($accessory->adminuser->present()->fullName()), + 'name'=> e($accessory->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 702ea123d8..c62e3031f5 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -2,6 +2,7 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Helpers\StorageHelper; use App\Models\Actionlog; use App\Models\Asset; use App\Models\CustomField; @@ -16,6 +17,7 @@ use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class ActionlogsTransformer { @@ -48,17 +50,20 @@ class ActionlogsTransformer public function transformActionlog (Actionlog $actionlog, $settings = null) { + $icon = $actionlog->present()->icon(); + if (($actionlog->filename!='') && ($actionlog->action_type!='upload deleted')) { + $icon = Helper::filetype_icon($actionlog->filename); + } + static $custom_fields = false; if ($custom_fields === false) { $custom_fields = CustomField::all(); } - if ($actionlog->filename!='') { - $icon = Helper::filetype_icon($actionlog->filename); - } + // This is necessary since we can't escape special characters within a JSON object if (($actionlog->log_meta) && ($actionlog->log_meta!='')) { @@ -113,8 +118,8 @@ class ActionlogsTransformer // Display the changes if the user is an admin or superadmin if (Gate::allows('admin')) { - $clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): ''; - $clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): ''; + $clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old, ['allowed_classes' => false]) : ''; + $clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new, ['allowed_classes' => false]) : ''; } } @@ -133,24 +138,6 @@ class ActionlogsTransformer $clean_meta= $this->changedInfo($clean_meta); } - $file_url = ''; - if($actionlog->filename!='') { - if ($actionlog->action_type == 'accepted') { - $file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]); - } else { - if ($actionlog->item) { - if ($actionlog->itemType() == 'asset') { - $file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]); - } elseif ($actionlog->itemType() == 'accessory') { - $file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]); - } elseif ($actionlog->itemType() == 'license') { - $file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]); - } elseif ($actionlog->itemType() == 'user') { - $file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]); - } - } - } - } $array = [ 'id' => (int) $actionlog->id, @@ -158,13 +145,15 @@ class ActionlogsTransformer 'file' => ($actionlog->filename!='') ? [ - 'url' => $file_url, + 'url' => $actionlog->uploads_file_url(), 'filename' => $actionlog->filename, + 'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()), + 'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false, ] : null, 'item' => ($actionlog->item) ? [ 'id' => (int) $actionlog->item->id, - 'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()), + 'name' => e($actionlog->item->display_name) ?? null, 'type' => e($actionlog->itemType()), 'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null ] : null, @@ -179,27 +168,27 @@ class ActionlogsTransformer 'action_type' => $actionlog->present()->actionType(), 'admin' => ($actionlog->adminuser) ? [ 'id' => (int) $actionlog->adminuser->id, - 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'name' => e($actionlog->adminuser->display_name), 'first_name'=> e($actionlog->adminuser->first_name), 'last_name'=> e($actionlog->adminuser->last_name) ] : null, 'created_by' => ($actionlog->adminuser) ? [ 'id' => (int) $actionlog->adminuser->id, - 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'name' => e($actionlog->adminuser->display_name), 'first_name'=> e($actionlog->adminuser->first_name), 'last_name'=> e($actionlog->adminuser->last_name) ] : null, 'target' => ($actionlog->target) ? [ 'id' => (int) $actionlog->target->id, - 'name' => ($actionlog->targetType()=='user') ? e($actionlog->target->getFullNameAttribute()) : e($actionlog->target->getDisplayNameAttribute()), + 'name' => ($actionlog->target->display_name) ?? null, 'type' => e($actionlog->targetType()), ] : null, 'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null, 'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null, 'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null, - 'remote_ip' => ($actionlog->remote_ip) ?? null, - 'user_agent' => ($actionlog->user_agent) ?? null, + 'remote_ip' => e($actionlog->remote_ip) ?? null, + 'user_agent' => e($actionlog->user_agent) ?? null, 'action_source' => ($actionlog->action_source) ?? null, 'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'), ]; diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index 2d47ca47db..793ab64597 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -65,10 +65,11 @@ class AssetModelsTransformer 'default_fieldset_values' => $default_field_values, 'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None', 'requestable' => ($assetmodel->requestable == '1') ? true : false, + 'require_serial' => $assetmodel->require_serial, 'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes), 'created_by' => ($assetmodel->adminuser) ? [ 'id' => (int) $assetmodel->adminuser->id, - 'name'=> e($assetmodel->adminuser->present()->fullName()), + 'name'=> e($assetmodel->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'), diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index 8b16a9a726..e971f1e7ae 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -58,6 +58,13 @@ class AssetsTransformer 'id' => (int) $asset->model->manufacturer->id, 'name'=> e($asset->model->manufacturer->name), ] : null, + 'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? [ + 'id' => (int) $asset->model->depreciation->id, + 'name'=> e($asset->model->depreciation->name), + 'months'=> (int) $asset->model->depreciation->months, + 'type'=> e($asset->model->depreciation->depreciation_type), + 'minimum'=> ($asset->model->depreciation->depreciation_min) ? (int) $asset->model->depreciation->depreciation_min : null, + ] : null, 'supplier' => ($asset->supplier) ? [ 'id' => (int) $asset->supplier->id, 'name'=> e($asset->supplier->name), @@ -84,7 +91,7 @@ class AssetsTransformer 'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null, 'created_by' => ($asset->adminuser) ? [ 'id' => (int) $asset->adminuser->id, - 'name'=> e($asset->adminuser->present()->fullName()), + 'name'=> e($asset->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'), @@ -101,7 +108,7 @@ class AssetsTransformer 'checkout_counter' => (int) $asset->checkout_counter, 'requests_counter' => (int) $asset->requests_counter, 'user_can_checkout' => (bool) $asset->availableForCheckout(), - 'book_value' => Helper::formatCurrencyOutput($asset->getLinearDepreciatedValue()), + 'book_value' => Helper::formatCurrencyOutput($asset->getDepreciatedValue()), ]; @@ -155,6 +162,7 @@ class AssetsTransformer 'clone' => Gate::allows('create', Asset::class) ? true : false, 'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false, 'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false, + 'audit' => Gate::allows('audit', Asset::class) ? true : false, 'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false, ]; @@ -202,6 +210,7 @@ class AssetsTransformer 'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null, 'email'=> ($asset->assigned->email) ? e($asset->assigned->email) : null, 'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null, + 'jobtitle' => $asset->assigned->jobtitle ? e($asset->assigned->jobtitle) : null, 'type' => 'user', ] : null; } @@ -278,7 +287,7 @@ class AssetsTransformer 'id' => (int) $asset->id, 'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null, 'type' => 'asset', - 'name' => e($asset->present()->fullName()), + 'name' => e($asset->display_name), 'model' => ($asset->model) ? e($asset->model->name) : null, 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, 'asset_tag' => e($asset->asset_tag), diff --git a/app/Http/Transformers/CategoriesTransformer.php b/app/Http/Transformers/CategoriesTransformer.php index 0d1834649d..348c5d4552 100644 --- a/app/Http/Transformers/CategoriesTransformer.php +++ b/app/Http/Transformers/CategoriesTransformer.php @@ -64,7 +64,7 @@ class CategoriesTransformer 'licenses_count' => (int) $category->licenses_count, 'created_by' => ($category->adminuser) ? [ 'id' => (int) $category->adminuser->id, - 'name'=> e($category->adminuser->present()->fullName()), + 'name'=> e($category->adminuser->display_name), ] : null, 'notes' => Helper::parseEscapedMarkedownInline($category->notes), 'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'), diff --git a/app/Http/Transformers/CompaniesTransformer.php b/app/Http/Transformers/CompaniesTransformer.php index 8ca5344de6..13f9a05e27 100644 --- a/app/Http/Transformers/CompaniesTransformer.php +++ b/app/Http/Transformers/CompaniesTransformer.php @@ -38,7 +38,7 @@ class CompaniesTransformer 'users_count' => (int) $company->users_count, 'created_by' => ($company->adminuser) ? [ 'id' => (int) $company->adminuser->id, - 'name'=> e($company->adminuser->present()->fullName()), + 'name'=> e($company->adminuser->display_name), ] : null, 'notes' => Helper::parseEscapedMarkedownInline($company->notes), 'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'), diff --git a/app/Http/Transformers/ComponentsTransformer.php b/app/Http/Transformers/ComponentsTransformer.php index 90d10ba9a5..f7f8c337bf 100644 --- a/app/Http/Transformers/ComponentsTransformer.php +++ b/app/Http/Transformers/ComponentsTransformer.php @@ -51,7 +51,7 @@ class ComponentsTransformer 'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null, 'created_by' => ($component->adminuser) ? [ 'id' => (int) $component->adminuser->id, - 'name'=> e($component->adminuser->present()->fullName()), + 'name'=> e($component->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'), @@ -76,7 +76,7 @@ class ComponentsTransformer $array[] = [ 'assigned_pivot_id' => $asset->pivot->id, 'id' => (int) $asset->id, - 'name' => e($asset->model->present()->name).' '.e($asset->present()->name), + 'name' => e($asset->model->display_name).' '.e($asset->display_name), 'qty' => $asset->pivot->assigned_qty, 'note' => $asset->pivot->note, 'type' => 'asset', diff --git a/app/Http/Transformers/ConsumablesTransformer.php b/app/Http/Transformers/ConsumablesTransformer.php index b31e31ac96..4c7dbf9cc5 100644 --- a/app/Http/Transformers/ConsumablesTransformer.php +++ b/app/Http/Transformers/ConsumablesTransformer.php @@ -25,7 +25,7 @@ class ConsumablesTransformer $array = [ 'id' => (int) $consumable->id, 'name' => e($consumable->name), - 'image' => ($consumable->image) ? Storage::disk('public')->url('consumables/'.e($consumable->image)) : null, + 'image' => ($consumable->getImageUrl()) ? ($consumable->getImageUrl()) : null, 'category' => ($consumable->category) ? ['id' => $consumable->category->id, 'name' => e($consumable->category->name)] : null, 'company' => ($consumable->company) ? ['id' => (int) $consumable->company->id, 'name' => e($consumable->company->name)] : null, 'item_no' => e($consumable->item_no), @@ -42,7 +42,7 @@ class ConsumablesTransformer 'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null, 'created_by' => ($consumable->adminuser) ? [ 'id' => (int) $consumable->adminuser->id, - 'name'=> e($consumable->adminuser->present()->fullName()), + 'name'=> e($consumable->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'), diff --git a/app/Http/Transformers/DatatablesTransformer.php b/app/Http/Transformers/DatatablesTransformer.php index 0e69109391..2ec993536d 100644 --- a/app/Http/Transformers/DatatablesTransformer.php +++ b/app/Http/Transformers/DatatablesTransformer.php @@ -4,6 +4,10 @@ namespace App\Http\Transformers; class DatatablesTransformer { + + /** + * Transform data for bootstrap tables and API responses for lists of things + **/ public function transformDatatables($objects, $total = null) { (isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects); @@ -11,4 +15,15 @@ class DatatablesTransformer return $objects_array; } -} + + /** + * Transform data for returning the status of items within a bulk action + **/ + public function transformBulkResponseWithStatusAndObjects($objects, $total) + { + (isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects); + $objects_array['rows'] = $objects; + + return $objects_array; + } +} \ No newline at end of file diff --git a/app/Http/Transformers/DepartmentsTransformer.php b/app/Http/Transformers/DepartmentsTransformer.php index 3d1e4c6f90..e072585a12 100644 --- a/app/Http/Transformers/DepartmentsTransformer.php +++ b/app/Http/Transformers/DepartmentsTransformer.php @@ -35,7 +35,7 @@ class DepartmentsTransformer ] : null, 'manager' => ($department->manager) ? [ 'id' => (int) $department->manager->id, - 'name' => e($department->manager->getFullNameAttribute()), + 'name' => e($department->manager->display_name), 'first_name'=> e($department->manager->first_name), 'last_name'=> e($department->manager->last_name), ] : null, diff --git a/app/Http/Transformers/DepreciationsTransformer.php b/app/Http/Transformers/DepreciationsTransformer.php index 64d4c88f7e..3b0d68392c 100644 --- a/app/Http/Transformers/DepreciationsTransformer.php +++ b/app/Http/Transformers/DepreciationsTransformer.php @@ -26,14 +26,14 @@ class DepreciationsTransformer $array = [ 'id' => (int) $depreciation->id, 'name' => e($depreciation->name), - 'months' => $depreciation->months.' '.trans('general.months'), + 'months' => trans_choice('general.months_plural', $depreciation->months), 'depreciation_min' => $depreciation->depreciation_type === 'percent' ? $depreciation->depreciation_min.'%' : $depreciation->depreciation_min, - 'assets_count' => $depreciation->assets_count, - 'models_count' => $depreciation->models_count, - 'licenses_count' => $depreciation->licenses_count, + 'assets_count' => ($depreciation->assets_count > 0) ? (int) $depreciation->assets_count : 0, + 'models_count' => ($depreciation->models_count > 0) ? (int) $depreciation->models_count : 0, + 'licenses_count' => ($depreciation->licenses_count > 0) ? (int) $depreciation->licenses_count : 0, 'created_by' => ($depreciation->adminuser) ? [ 'id' => (int) $depreciation->adminuser->id, - 'name'=> e($depreciation->adminuser->present()->fullName()), + 'name'=> e($depreciation->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime') diff --git a/app/Http/Transformers/GroupsTransformer.php b/app/Http/Transformers/GroupsTransformer.php index 9495aeeecc..7593926155 100644 --- a/app/Http/Transformers/GroupsTransformer.php +++ b/app/Http/Transformers/GroupsTransformer.php @@ -29,7 +29,7 @@ class GroupsTransformer 'notes' => Helper::parseEscapedMarkedownInline($group->notes), 'created_by' => ($group->adminuser) ? [ 'id' => (int) $group->adminuser->id, - 'name'=> e($group->adminuser->present()->fullName()), + 'name'=> e($group->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'), diff --git a/app/Http/Transformers/LicenseSeatsTransformer.php b/app/Http/Transformers/LicenseSeatsTransformer.php index 9868876295..17025e7f9f 100644 --- a/app/Http/Transformers/LicenseSeatsTransformer.php +++ b/app/Http/Transformers/LicenseSeatsTransformer.php @@ -2,6 +2,7 @@ namespace App\Http\Transformers; +use App\Helpers\Helper; use App\Models\License; use App\Models\LicenseSeat; use Illuminate\Support\Facades\Gate; @@ -11,20 +12,20 @@ class LicenseSeatsTransformer public function transformLicenseSeats(Collection $seats, $total) { $array = []; - $seat_count = 0; + foreach ($seats as $seat) { - $seat_count++; - $array[] = self::transformLicenseSeat($seat, $seat_count); + $array[] = self::transformLicenseSeat($seat); } return (new DatatablesTransformer)->transformDatatables($array, $total); } - public function transformLicenseSeat(LicenseSeat $seat, $seat_count = 0) + public function transformLicenseSeat(LicenseSeat $seat) { $array = [ 'id' => (int) $seat->id, 'license_id' => (int) $seat->license->id, + 'updated_at' => Helper::getFormattedDateObject($seat->updated_at, 'datetime'), // we use updated_at here because the record gets updated when it's checked in or out 'assigned_user' => ($seat->user) ? [ 'id' => (int) $seat->user->id, 'name'=> e($seat->user->present()->fullName), @@ -35,14 +36,17 @@ class LicenseSeatsTransformer 'name' => e($seat->user->department->name), ] : null, + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'assigned_asset' => ($seat->asset) ? [ 'id' => (int) $seat->asset->id, 'name'=> e($seat->asset->present()->fullName), + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'location' => ($seat->location()) ? [ 'id' => (int) $seat->location()->id, 'name'=> e($seat->location()->name), + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'reassignable' => (bool) $seat->license->reassignable, 'notes' => e($seat->notes), @@ -50,10 +54,6 @@ class LicenseSeatsTransformer 'disabled' => $seat->unreassignable_seat, ]; - if ($seat_count != 0) { - $array['name'] = trans('admin/licenses/general.seat_count', ['count' => $seat_count]); - } - $permissions_array['available_actions'] = [ 'checkout' => Gate::allows('checkout', License::class), 'checkin' => Gate::allows('checkin', License::class), diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index d8c6a56ca5..24822efeca 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -48,7 +48,7 @@ class LicensesTransformer 'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null, 'created_by' => ($license->adminuser) ? [ 'id' => (int) $license->adminuser->id, - 'name'=> e($license->adminuser->present()->fullName()), + 'name'=> e($license->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'), @@ -62,7 +62,7 @@ class LicensesTransformer 'checkin' => Gate::allows('checkin', License::class), 'clone' => Gate::allows('create', License::class), 'update' => Gate::allows('update', License::class), - 'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count > 0)) ? true : false, + 'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count == $license->seats)) ? true : false, ]; $array += $permissions_array; diff --git a/app/Http/Transformers/LocationsTransformer.php b/app/Http/Transformers/LocationsTransformer.php index b1553c69f4..4965ff99d5 100644 --- a/app/Http/Transformers/LocationsTransformer.php +++ b/app/Http/Transformers/LocationsTransformer.php @@ -57,6 +57,10 @@ class LocationsTransformer 'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null, 'notes' => Helper::parseEscapedMarkedownInline($location->notes), 'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'), + 'created_by' => $location->adminuser ? [ + 'id' => (int) $location->adminuser->id, + 'name'=> e($location->adminuser->present()->fullName), + ]: null, 'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'), 'parent' => ($location->parent) ? [ 'id' => (int) $location->parent->id, diff --git a/app/Http/Transformers/AssetMaintenancesTransformer.php b/app/Http/Transformers/MaintenancesTransformer.php similarity index 79% rename from app/Http/Transformers/AssetMaintenancesTransformer.php rename to app/Http/Transformers/MaintenancesTransformer.php index ab044260f7..c20c254869 100644 --- a/app/Http/Transformers/AssetMaintenancesTransformer.php +++ b/app/Http/Transformers/MaintenancesTransformer.php @@ -4,23 +4,24 @@ namespace App\Http\Transformers; use App\Helpers\Helper; use App\Models\Asset; -use App\Models\AssetMaintenance; +use App\Models\Maintenance; use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Storage; -class AssetMaintenancesTransformer +class MaintenancesTransformer { - public function transformAssetMaintenances(Collection $assetmaintenances, $total) + public function transformMaintenances(Collection $maintenances, $total) { $array = []; - foreach ($assetmaintenances as $assetmaintenance) { - $array[] = self::transformAssetMaintenance($assetmaintenance); + foreach ($maintenances as $assetmaintenance) { + $array[] = self::transformMaintenance($assetmaintenance); } return (new DatatablesTransformer)->transformDatatables($array, $total); } - public function transformAssetMaintenance(AssetMaintenance $assetmaintenance) + public function transformMaintenance(Maintenance $assetmaintenance) { $array = [ 'id' => (int) $assetmaintenance->id, @@ -33,6 +34,7 @@ class AssetMaintenancesTransformer 'created_at' => Helper::getFormattedDateObject($assetmaintenance->asset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmaintenance->asset->updated_at, 'datetime'), ] : null, + 'image' => ($assetmaintenance->image != '') ? Storage::disk('public')->url('maintenances/'.e($assetmaintenance->image)) : null, 'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [ 'id' => (int) $assetmaintenance->asset->model->id, 'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null, @@ -48,7 +50,8 @@ class AssetMaintenancesTransformer 'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null, ] : null, - 'title' => ($assetmaintenance->title) ? e($assetmaintenance->title) : null, + 'name' => ($assetmaintenance->name) ? e($assetmaintenance->name) : null, + 'title' => ($assetmaintenance->name) ? e($assetmaintenance->name) : null, // legacy to not change the shape of the API 'location' => (($assetmaintenance->asset) && ($assetmaintenance->asset->location)) ? [ 'id' => (int) $assetmaintenance->asset->location->id, 'name'=> e($assetmaintenance->asset->location->name), @@ -59,7 +62,10 @@ class AssetMaintenancesTransformer 'name'=> e($assetmaintenance->asset->defaultLoc->name), ] : null, 'notes' => ($assetmaintenance->notes) ? Helper::parseEscapedMarkedownInline($assetmaintenance->notes) : null, - 'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null, + 'supplier' => ($assetmaintenance->supplier) ? [ + 'id' => $assetmaintenance->supplier->id, + 'name'=> e($assetmaintenance->supplier->name) + ] : null, 'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost), 'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type), 'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'), @@ -67,11 +73,11 @@ class AssetMaintenancesTransformer 'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'), 'user_id' => ($assetmaintenance->adminuser) ? [ 'id' => $assetmaintenance->adminuser->id, - 'name'=> e($assetmaintenance->adminuser->present()->fullName()) + 'name'=> e($assetmaintenance->adminuser->display_name) ] : null, // legacy to not change the shape of the API 'created_by' => ($assetmaintenance->adminuser) ? [ 'id' => (int) $assetmaintenance->adminuser->id, - 'name'=> e($assetmaintenance->adminuser->present()->fullName()), + 'name'=> e($assetmaintenance->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'), diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index cf17eb7764..0d1373414c 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -40,7 +40,7 @@ class ManufacturersTransformer 'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes), 'created_by' => ($manufacturer->adminuser) ? [ 'id' => (int) $manufacturer->adminuser->id, - 'name'=> e($manufacturer->adminuser->present()->fullName()), + 'name'=> e($manufacturer->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'), diff --git a/app/Http/Transformers/PredefinedKitsTransformer.php b/app/Http/Transformers/PredefinedKitsTransformer.php index 61c9e476a9..3660ff269e 100644 --- a/app/Http/Transformers/PredefinedKitsTransformer.php +++ b/app/Http/Transformers/PredefinedKitsTransformer.php @@ -34,7 +34,7 @@ class PredefinedKitsTransformer 'name' => e($kit->name), 'created_by' => ($kit->adminuser) ? [ 'id' => (int) $kit->adminuser->id, - 'name'=> e($kit->adminuser->present()->fullName()), + 'name'=> e($kit->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'), diff --git a/app/Http/Transformers/ProfileTransformer.php b/app/Http/Transformers/ProfileTransformer.php new file mode 100644 index 0000000000..3b8e58e133 --- /dev/null +++ b/app/Http/Transformers/ProfileTransformer.php @@ -0,0 +1,42 @@ +transformDatatables($array, $total); + } + + + public function transformFile(Actionlog $file) + { + $array = [ + 'id' => (int) $file->id, + 'icon' => Helper::filetype_icon($file->filename), + 'item' => ($file->item) ? [ + 'name' => $file->item->display_name ? e($file->item->display_name) : null, + 'type' => e($file->itemType()), + ] : null, + 'filename' => e($file->filename), + 'signature_file' => ($file->accept_signature) ? route('profile.signature.view', ['filename' => $file->accept_signature ]) : null, + 'note' => e($file->note), + 'url' => route('profile.storedeula.download', ['filename' => $file->filename]), + 'file' => route('profile.storedeula.download', ['filename' => $file->filename]), + 'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'), + ]; + + return $array; + } + +} diff --git a/app/Http/Transformers/StatuslabelsTransformer.php b/app/Http/Transformers/StatuslabelsTransformer.php index 751edb7016..6409795994 100644 --- a/app/Http/Transformers/StatuslabelsTransformer.php +++ b/app/Http/Transformers/StatuslabelsTransformer.php @@ -32,7 +32,7 @@ class StatuslabelsTransformer 'notes' => e($statuslabel->notes), 'created_by' => ($statuslabel->adminuser) ? [ 'id' => (int) $statuslabel->adminuser->id, - 'name'=> e($statuslabel->adminuser->present()->fullName()), + 'name'=> e($statuslabel->adminuser->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'), diff --git a/app/Http/Transformers/SuppliersTransformer.php b/app/Http/Transformers/SuppliersTransformer.php index 1fdc93c193..750c969c63 100644 --- a/app/Http/Transformers/SuppliersTransformer.php +++ b/app/Http/Transformers/SuppliersTransformer.php @@ -45,6 +45,10 @@ class SuppliersTransformer 'components_count' => (int) $supplier->components_count, 'notes' => ($supplier->notes) ? Helper::parseEscapedMarkedownInline($supplier->notes) : null, 'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'), + 'created_by' => $supplier->adminuser ? [ + 'id' => (int) $supplier->adminuser->id, + 'name'=> e($supplier->adminuser->present()->fullName), + ]: null, 'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/UploadedFilesTransformer.php b/app/Http/Transformers/UploadedFilesTransformer.php index a18c9f9b65..e32aabc8e5 100644 --- a/app/Http/Transformers/UploadedFilesTransformer.php +++ b/app/Http/Transformers/UploadedFilesTransformer.php @@ -3,10 +3,10 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Helpers\StorageHelper; use App\Models\Actionlog; -use App\Models\Asset; -use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Storage; class UploadedFilesTransformer @@ -26,23 +26,27 @@ class UploadedFilesTransformer { $snipeModel = $file->item_type; - - // This will be used later as we extend out this transformer to handle more types of uploads - if ($file->item_type == Asset::class) { - $file_url = route('show/assetfile', [$file->item_id, $file->id]); - } - $array = [ 'id' => (int) $file->id, + 'icon' => Helper::filetype_icon($file->filename), + 'name' => e($file->filename), + 'item' => ($file->item_type) ? [ + 'id' => (int) $file->item_id, + 'type' => str_plural(strtolower(class_basename($file->item_type))), + ] : null, 'filename' => e($file->filename), - 'url' => $file_url, + 'filetype' => StorageHelper::getFiletype($file->uploads_file_path()), + 'mediatype' => StorageHelper::getMediaType($file->uploads_file_path()), + 'url' => $file->uploads_file_url(), + 'note' => ($file->note) ? e($file->note) : null, 'created_by' => ($file->adminuser) ? [ 'id' => (int) $file->adminuser->id, 'name'=> e($file->adminuser->present()->fullName), ] : null, 'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'), - 'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'), + 'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()) ?? false, + 'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false), ]; $permissions_array['available_actions'] = [ @@ -53,4 +57,5 @@ class UploadedFilesTransformer return $array; } -} + +} \ No newline at end of file diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 3bf3ee9702..1de71615cf 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -22,23 +22,31 @@ class UsersTransformer public function transformUser(User $user) { + $role = null; + if ($user->isSuperUser()) { + $role = 'superadmin'; + } elseif ($user->isAdmin()) { + $role = 'admin'; + } $array = [ 'id' => (int) $user->id, 'avatar' => e($user->present()->gravatar) ?? null, - 'name' => e($user->getFullNameAttribute()), - 'first_name' => e($user->first_name), - 'last_name' => e($user->last_name), - 'username' => e($user->username), + 'name' => e($user->getFullNameAttribute()) ?? null, + 'first_name' => e($user->first_name) ?? null, + 'last_name' => e($user->last_name) ?? null, + 'display_name' => ($user->getRawOriginal('display_name')) ? e($user->getRawOriginal('display_name')) : null, + 'username' => e($user->username) ?? null, 'remote' => ($user->remote == '1') ? true : false, 'locale' => ($user->locale) ? e($user->locale) : null, 'employee_num' => ($user->employee_num) ? e($user->employee_num) : null, 'manager' => ($user->manager) ? [ 'id' => (int) $user->manager->id, - 'name'=> e($user->manager->first_name).' '.e($user->manager->last_name), + 'name'=> e($user->manager->display_name), ] : null, 'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null, 'vip' => ($user->vip == '1') ? true : false, 'phone' => ($user->phone) ? e($user->phone) : null, + 'mobile' => ($user->mobile) ? e($user->mobile) : null, 'website' => ($user->website) ? e($user->website) : null, 'address' => ($user->address) ? e($user->address) : null, 'city' => ($user->city) ? e($user->city) : null, @@ -50,11 +58,16 @@ class UsersTransformer 'id' => (int) $user->department->id, 'name'=> e($user->department->name), ] : null, + 'department_manager' => ($user->department?->manager) ? [ + 'id' => (int) $user->department->manager->id, + 'name'=> e($user->department->manager->display_name), + ] : null, 'location' => ($user->userloc) ? [ 'id' => (int) $user->userloc->id, 'name'=> e($user->userloc->name), ] : null, 'notes'=> Helper::parseEscapedMarkedownInline($user->notes), + 'role' => $role, 'permissions' => $user->decodePermissions(), 'activated' => ($user->activated == '1') ? true : false, 'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false, @@ -70,7 +83,7 @@ class UsersTransformer 'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null, 'created_by' => ($user->createdBy) ? [ 'id' => (int) $user->createdBy->id, - 'name'=> e($user->createdBy->present()->fullName), + 'name'=> e($user->createdBy->display_name), ] : null, 'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'), @@ -126,6 +139,7 @@ class UsersTransformer 'first_name' => e($user->first_name), 'last_name' => e($user->last_name), 'username' => e($user->username), + 'display_name' => e($user->display_name), 'created_by' => $user->adminuser ? [ 'id' => (int) $user->adminuser->id, 'name'=> e($user->adminuser->present()->fullName), diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index ad69028055..1d03d6f95b 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -80,7 +80,16 @@ class AssetImporter extends ItemImporter $asset_tag = Asset::autoincrement_asset(); } - $asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first(); + + + if ($this->findCsvMatch($row, 'id')!='') { + // Override asset if an ID was given + \Log::debug('Finding asset by ID: '.$this->findCsvMatch($row, 'id')); + $asset = Asset::find($this->findCsvMatch($row, 'id')); + } else { + $asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first(); + } + if ($asset) { if (! $this->updating) { $exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]); diff --git a/app/Importer/AssetModelImporter.php b/app/Importer/AssetModelImporter.php index 7cfd8a530d..b60ad1d0fb 100644 --- a/app/Importer/AssetModelImporter.php +++ b/app/Importer/AssetModelImporter.php @@ -66,6 +66,7 @@ class AssetModelImporter extends ItemImporter $this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset')); $this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation')); $this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0; + $this->item['require_serial'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_serial'))) == 1) ? 1 : 0; if (!empty($this->item['category'])) { if ($category = $this->createOrFetchCategory($this->item['category'])) { diff --git a/app/Importer/CategoryImporter.php b/app/Importer/CategoryImporter.php new file mode 100644 index 0000000000..39b477c96a --- /dev/null +++ b/app/Importer/CategoryImporter.php @@ -0,0 +1,99 @@ +createCategoryIfNotExists($row); + } + + /** + * Create a category if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createCategoryIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createCategoryIfNotExists(array $row) + { + + $editingCategory = false; + + $category = Category::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override category if an ID was given + \Log::debug('Finding category by ID: '.$this->findCsvMatch($row, 'id')); + $category = Category::find($this->findCsvMatch($row, 'id')); + } + + + if ($category) { + if (! $this->updating) { + $this->log('A matching Category '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Category'); + $editingCategory = true; + } else { + $this->log('No Matching Category, Create a new one'); + $category = new Category; + $category->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + $this->item['eula_text'] = trim($this->findCsvMatch($row, 'eula_text')); + $this->item['category_type'] = trim(strtolower($this->findCsvMatch($row, 'category_type'))); + $this->item['use_default_eula'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'use_default_eula'))) == 1) ? 1 : 0; + $this->item['require_acceptance'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_acceptance'))) == 1) ? 1 : 0; + $this->item['checkin_email'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'checkin_email'))) == 1) ? 1 : 0; + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingCategory) { + Log::debug('Updating existing category'); + $category->update($this->sanitizeItemForUpdating($category)); + } else { + Log::debug('Creating category'); + $category->fill($this->sanitizeItemForStoring($category)); + } + + if ($category->save()) { + $this->log('Category '.$category->name.' created or updated from CSV import'); + return $category; + + } else { + Log::debug($category->getErrors()); + $this->logError($category, 'Category "'.$this->item['name'].'"'); + return $category->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 0d4b8d4932..a5142f4380 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -72,6 +72,7 @@ abstract class Importer 'termination_date' => 'termination date', 'warranty_months' => 'warranty', 'full_name' => 'full name', + 'display_name' => 'display name', 'email' => 'email', 'username' => 'username', 'address' => 'address', @@ -88,6 +89,7 @@ abstract class Importer 'department' => 'department', 'manager_name' => 'manager full name', 'manager_username' => 'manager username', + 'manager_employee_num' => 'manager employee number', 'min_amt' => 'minimum quantity', 'remote' => 'remote', 'vip' => 'vip', @@ -132,7 +134,7 @@ abstract class Importer } else { $this->csv = Reader::createFromString($file); } - $this->tempPassword = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40); + $this->tempPassword = '*** NO PASSWORD - IMPORTED VIA CSV ***'; } // Cached Values for import lookups @@ -298,6 +300,7 @@ abstract class Importer 'full_name' => $this->findCsvMatch($row, 'full_name'), 'first_name' => $this->findCsvMatch($row, 'first_name'), 'last_name' => $this->findCsvMatch($row, 'last_name'), + 'display_name' => $this->findCsvMatch($row, 'display_name'), 'email' => $this->findCsvMatch($row, 'email'), 'manager_id'=> '', 'department_id' => '', @@ -368,6 +371,7 @@ abstract class Importer $user->first_name = $user_array['first_name']; $user->last_name = $user_array['last_name']; $user->username = $user_array['username']; + $user->display_name = $user_array['display_name'] ?? null; $user->email = $user_array['email']; $user->manager_id = $user_array['manager_id'] ?? null; $user->department_id = $user_array['department_id'] ?? null; diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index dfcd644c9d..92f44d991d 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -110,7 +110,7 @@ class ItemImporter extends Importer protected function determineCheckout($row) { // Locations don't get checked out to anyone/anything - if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class)) { + if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class) || (get_class($this) == SupplierImporter::class) || (get_class($this) == ManufacturerImporter::class) || (get_class($this) == CategoryImporter::class)) { return; } @@ -353,16 +353,27 @@ class ItemImporter extends Importer * @param $user_manager string * @return int id of company created/found */ - public function fetchManager($user_manager_first_name, $user_manager_last_name) + public function fetchManager($user_manager_username = null, $user_manager_employee_num = null, $user_manager_first_name = null, $user_manager_last_name = null) { - $manager = User::where('first_name', '=', $user_manager_first_name) - ->where('last_name', '=', $user_manager_last_name)->first(); + if ($user_manager_username!='') { + $manager = User::where('username', '=', $user_manager_username)->first(); + $this->log('Checking on username '.$user_manager_username); + } elseif ($user_manager_employee_num!='') { + $manager = User::where('employee_num', '=', $user_manager_employee_num)->first(); + $this->log('Checking on employee_num '.$user_manager_employee_num); + } else { + $manager = User::where('first_name', '=', $user_manager_first_name) + ->where('last_name', '=', $user_manager_last_name)->first(); + $this->log('Checking on full name'); + } + if ($manager) { $this->log('A matching Manager '.$user_manager_first_name.' '.$user_manager_last_name.' already exists'); return $manager->id; } - $this->log('No matching Manager '.$user_manager_first_name.' '.$user_manager_last_name.' found. If their user account is being created through this import, you should re-process this file again. '); + + $this->log('No matching Manager found. If their user account is being created through this import, you should re-process this file again. '); return null; } diff --git a/app/Importer/ManufacturerImporter.php b/app/Importer/ManufacturerImporter.php new file mode 100644 index 0000000000..4c627e9d99 --- /dev/null +++ b/app/Importer/ManufacturerImporter.php @@ -0,0 +1,101 @@ +createManufacturerIfNotExists($row); + } + + /** + * Create a manufacturer if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createManufacturerIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createManufacturerIfNotExists(array $row) + { + + $editingManufacturer = false; + + $manufacturer = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override manufacturer if an ID was given + \Log::debug('Finding manufacturer by ID: '.$this->findCsvMatch($row, 'id')); + $manufacturer = Manufacturer::find($this->findCsvMatch($row, 'id')); + } + + + if ($manufacturer) { + if (! $this->updating) { + $this->log('A matching Manufacturer '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Manufacturer'); + $editingManufacturer = true; + } else { + $this->log('No Matching Manufacturer, Create a new one'); + $manufacturer = new Manufacturer; + $manufacturer->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['support_phone'] = trim($this->findCsvMatch($row, 'support_phone')); + $this->item['fax'] = trim($this->findCsvMatch($row, 'fax')); + $this->item['support_email'] = trim($this->findCsvMatch($row, 'support_email')); + $this->item['contact'] = trim($this->findCsvMatch($row, 'contact')); + $this->item['url'] = trim($this->findCsvMatch($row, 'url')); + $this->item['support_url'] = trim($this->findCsvMatch($row, 'support_url')); + $this->item['warranty_lookup_url'] = trim($this->findCsvMatch($row, 'warranty_lookup_url')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingManufacturer) { + Log::debug('Updating existing manufacturer'); + $manufacturer->update($this->sanitizeItemForUpdating($manufacturer)); + } else { + Log::debug('Creating manufacturer'); + $manufacturer->fill($this->sanitizeItemForStoring($manufacturer)); + } + + if ($manufacturer->save()) { + $this->log('Manufacturer '.$manufacturer->name.' created or updated from CSV import'); + return $manufacturer; + + } else { + Log::debug($manufacturer->getErrors()); + $this->logError($manufacturer, 'Manufacturer "'.$this->item['name'].'"'); + return $manufacturer->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/SupplierImporter.php b/app/Importer/SupplierImporter.php new file mode 100644 index 0000000000..7878aff835 --- /dev/null +++ b/app/Importer/SupplierImporter.php @@ -0,0 +1,105 @@ +createSupplierIfNotExists($row); + } + + /** + * Create a supplier if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createSupplierIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createSupplierIfNotExists(array $row) + { + + $editingSupplier = false; + + $supplier = Supplier::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override supplier if an ID was given + \Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id')); + $supplier = Supplier::find($this->findCsvMatch($row, 'id')); + } + + + if ($supplier) { + if (! $this->updating) { + $this->log('A matching Supplier '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Supplier'); + $editingSupplier = true; + } else { + $this->log('No Matching Supplier, Create a new one'); + $supplier = new Supplier; + $supplier->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['address'] = trim($this->findCsvMatch($row, 'address')); + $this->item['address2'] = trim($this->findCsvMatch($row, 'address2')); + $this->item['city'] = trim($this->findCsvMatch($row, 'city')); + $this->item['state'] = trim($this->findCsvMatch($row, 'state')); + $this->item['country'] = trim($this->findCsvMatch($row, 'country')); + $this->item['zip'] = trim($this->findCsvMatch($row, 'zip')); + $this->item['phone'] = trim($this->findCsvMatch($row, 'phone')); + $this->item['fax'] = trim($this->findCsvMatch($row, 'fax')); + $this->item['email'] = trim($this->findCsvMatch($row, 'email')); + $this->item['contact'] = trim($this->findCsvMatch($row, 'contact')); + $this->item['url'] = trim($this->findCsvMatch($row, 'url')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingSupplier) { + Log::debug('Updating existing supplier'); + $supplier->update($this->sanitizeItemForUpdating($supplier)); + } else { + Log::debug('Creating supplier'); + $supplier->fill($this->sanitizeItemForStoring($supplier)); + } + + if ($supplier->save()) { + $this->log('Supplier '.$supplier->name.' created or updated from CSV import'); + return $supplier; + + } else { + Log::debug($supplier->getErrors()); + $this->logError($supplier, 'Supplier "'.$this->item['name'].'"'); + return $supplier->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index 77317b3d09..942f1cf4a2 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -7,7 +7,9 @@ use App\Models\Department; use App\Models\Setting; use App\Models\User; use App\Notifications\WelcomeNotification; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Password; /** * This is ONLY used for the User Import. When we are importing users @@ -45,11 +47,13 @@ class UserImporter extends ItemImporter // Pull the records from the CSV to determine their values $this->item['id'] = trim($this->findCsvMatch($row, 'id')); $this->item['username'] = trim($this->findCsvMatch($row, 'username')); + $this->item['display_name'] = trim($this->findCsvMatch($row, 'display_name')); $this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name')); $this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name')); $this->item['email'] = trim($this->findCsvMatch($row, 'email')); $this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar')); $this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number')); + $this->item['mobile'] = trim($this->findCsvMatch($row, 'mobile_number')); $this->item['website'] = trim($this->findCsvMatch($row, 'website')); $this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle')); $this->item['address'] = trim($this->findCsvMatch($row, 'address')); @@ -62,7 +66,7 @@ class UserImporter extends ItemImporter $this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0; $this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num')); $this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department')))); - $this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name'))); + $this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_username')), trim($this->findCsvMatch($row, 'manager_employee_num')), trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name'))); $this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0; $this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0; $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0; @@ -80,6 +84,7 @@ class UserImporter extends ItemImporter $this->item['username'] = $user_formatted_array['username']; } + // Check if a numeric ID was passed. If it does, use that above all else. if ((array_key_exists('id', $this->item) && ($this->item['id'] != "") && (is_numeric($this->item['id'])))) { $user = User::find($this->item['id']); @@ -89,12 +94,25 @@ class UserImporter extends ItemImporter if ($user) { + // If the user does not want to update existing values, only add new ones, bail out if (! $this->updating) { Log::debug('A matching User '.$this->item['name'].' already exists. '); return; } + $this->log('Updating User'); + + // Todo - check that this works + if (!Gate::allows('canEditAuthFields', $user)) { + unset($user->username); + unset($user->email); + unset($user->password); + unset($user->activated); + } + $user->update($this->sanitizeItemForUpdating($user)); + + // Why do we have to do this twice? Update should $user->save(); // Update the location of any assets checked out to this user @@ -110,28 +128,32 @@ class UserImporter extends ItemImporter // This needs to be applied after the update logic, otherwise we'll overwrite user passwords // Issue #5408 - $this->item['password'] = bcrypt($this->tempPassword); + $this->item['password'] = $this->tempPassword; $this->log('No matching user, creating one'); $user = new User(); $user->created_by = auth()->id(); + $user->fill($this->sanitizeItemForStoring($user)); + // TODO - check for gate here I guess + + if ($user->save()) { $this->log('User '.$this->item['name'].' was created'); if (($user->email) && ($user->activated == '1')) { - $data = [ - 'email' => $user->email, - 'username' => $user->username, - 'first_name' => $user->first_name, - 'last_name' => $user->last_name, - 'password' => $this->tempPassword, - ]; if ($this->send_welcome) { - $user->notify(new WelcomeNotification($data)); + + try { + $user->notify(new WelcomeNotification($user)); + } catch (\Exception $e) { + Log::warning('Could not send welcome notification for user: ' . $e->getMessage()); + } + } + } $user = null; $this->item = null; @@ -140,9 +162,9 @@ class UserImporter extends ItemImporter } $this->logError($user, 'User'); - return; } + /** * Fetch an existing department, or create new if it doesn't exist * diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 2bf93afc94..9674bd63a3 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -4,14 +4,17 @@ namespace App\Listeners; use App\Events\CheckoutableCheckedOut; use App\Mail\CheckinAccessoryMail; +use App\Mail\CheckinComponentMail; use App\Mail\CheckinLicenseMail; use App\Mail\CheckoutAccessoryMail; use App\Mail\CheckoutAssetMail; use App\Mail\CheckinAssetMail; +use App\Mail\CheckoutComponentMail; use App\Mail\CheckoutConsumableMail; use App\Mail\CheckoutLicenseMail; use App\Models\Accessory; use App\Models\Asset; +use App\Models\Category; use App\Models\CheckoutAcceptance; use App\Models\Component; use App\Models\Consumable; @@ -21,12 +24,15 @@ use App\Models\Setting; use App\Models\User; use App\Notifications\CheckinAccessoryNotification; use App\Notifications\CheckinAssetNotification; +use App\Notifications\CheckinComponentNotification; use App\Notifications\CheckinLicenseSeatNotification; use App\Notifications\CheckoutAccessoryNotification; use App\Notifications\CheckoutAssetNotification; +use App\Notifications\CheckoutComponentNotification; use App\Notifications\CheckoutConsumableNotification; use App\Notifications\CheckoutLicenseSeatNotification; use GuzzleHttp\Exception\ClientException; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Notification; use Exception; @@ -37,9 +43,27 @@ use Osama\LaravelTeamsNotification\TeamsNotification; class CheckoutableListener { private array $skipNotificationsFor = [ - Component::class, +// Component::class, ]; + /** + * Register the listeners for the subscriber. + * + * @param Illuminate\Events\Dispatcher $events + */ + public function subscribe($events) + { + $events->listen( + \App\Events\CheckoutableCheckedIn::class, + 'App\Listeners\CheckoutableListener@onCheckedIn' + ); + + $events->listen( + \App\Events\CheckoutableCheckedOut::class, + 'App\Listeners\CheckoutableListener@onCheckedOut' + ); + } + /** * Notify the user and post to webhook about the checked out checkoutable * and add a record to the checkout_requests table. @@ -50,93 +74,71 @@ class CheckoutableListener return; } - /** - * Make a checkout acceptance and attach it in the notification - */ - $settings = Setting::getSettings(); $acceptance = $this->getCheckoutAcceptance($event); - $adminCcEmailsArray = []; - if ($settings->admin_cc_email !== '') { - $adminCcEmail = $settings->admin_cc_email; - $adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail)); + $shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable); + $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance); + $shouldSendWebhookNotification = $this->shouldSendWebhookNotification(); + + if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) { + return; } - $ccEmails = array_filter($adminCcEmailsArray); - $mailable = $this->getCheckoutMailType($event, $acceptance); - $notifiable = $this->getNotifiableUsers($event); + if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) { + $mailable = $this->getCheckoutMailType($event, $acceptance); + $notifiable = $this->getNotifiableUser($event); - // Send email notifications - try { - /** - * Send an email if any of the following conditions are met: - * 1. The asset requires acceptance - * 2. The item has a EULA - * 3. The item should send an email at check-in/check-out - * 4. If the admin CC email is set, even if the item being checked out doesn't have an email address (location, etc) - */ + $notifiableHasEmail = $notifiable instanceof User && $notifiable->email; - if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() || - $this->checkoutableShouldSendEmail($event)) { + $shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail; + [$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable); - // Send a checkout email to the admin CC addresses, even if the target has no email - if (!empty($ccEmails)) { - Mail::to($ccEmails)->send($mailable); - Log::info('Checkout Mail sent to CC addresses'); - } - - // Send a checkout email to the target if it has an email - if (!empty($notifiable->email)) { - Mail::to($notifiable)->send($mailable); + if (!empty($to)) { + try { + Mail::to(array_flatten($to))->send($mailable->locale($notifiable->locale)); + Mail::to(array_flatten($cc))->send($mailable->locale(Setting::getSettings()->locale)); Log::info('Checkout Mail sent to checkout target'); + } catch (ClientException $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); } - } - } catch (ClientException $e) { - Log::debug("Exception caught during checkout email: " . $e->getMessage()); - } catch (Exception $e) { - Log::debug("Exception caught during checkout email: " . $e->getMessage()); } - // Send notification - try { - if ($this->shouldSendWebhookNotification()) { + if ($shouldSendWebhookNotification) { + try { if ($this->newMicrosoftTeamsWebhookEnabled()) { $message = $this->getCheckoutNotification($event)->toMicrosoftTeams(); $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); $notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams } else { - Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint) ->notify($this->getCheckoutNotification($event, $acceptance)); } + } catch (ClientException $e) { + if (strpos($e->getMessage(), 'channel_not_found') !== false) { + Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found')); + } else { + Log::error("ClientException caught during checkin notification: " . $e->getMessage()); + } + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); + } catch (Exception $e) { + Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ + 'error' => $e->getMessage(), + 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, + 'event' => $event, + ]); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); } - } catch (ClientException $e) { - if (strpos($e->getMessage(), 'channel_not_found') !== false) { - Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage()); - return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') ); - } - else { - Log::error("ClientException caught during checkin notification: " . $e->getMessage()); - } - return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail') ); - } catch (Exception $e) { - Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ - 'error' => $e->getMessage(), - 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, - 'event' => $event, - ]); - return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); } } - - - /** * Notify the user and post to webhook about the checked in checkoutable - */ + */ public function onCheckedIn($event) { Log::debug('onCheckedIn in the Checkoutable listener fired'); @@ -145,61 +147,54 @@ class CheckoutableListener return; } - /** - * Send the appropriate notification - */ - if ($event->checkedOutTo && $event->checkoutable){ - $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) - ->where('assigned_to_id', $event->checkedOutTo->id) - ->get(); + $shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable); + $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress(); + $shouldSendWebhookNotification = $this->shouldSendWebhookNotification(); + if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) { + return; + } - foreach($acceptances as $acceptance){ - if($acceptance->isPending()){ - $acceptance->delete(); + if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) { + /** + * Send the appropriate notification + */ + if ($event->checkedOutTo && $event->checkoutable) { + $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) + ->where('assigned_to_id', $event->checkedOutTo->id) + ->get(); + + foreach ($acceptances as $acceptance) { + if ($acceptance->isPending()) { + $acceptance->delete(); + } } } - } - $settings = Setting::getSettings(); - $adminCcEmailsArray = []; - if($settings->admin_cc_email !== '') { - $adminCcEmail = $settings->admin_cc_email; - $adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail)); - } - $ccEmails = array_filter($adminCcEmailsArray); - $mailable = $this->getCheckinMailType($event); - $notifiable = $this->getNotifiableUsers($event); + $mailable = $this->getCheckinMailType($event); + $notifiable = $this->getNotifiableUser($event); - // Send email notifications - try { - /** - * Send an email if any of the following conditions are met: - * 1. The asset requires acceptance - * 2. The item has a EULA - * 3. The item should send an email at check-in/check-out - * 4. If the admin CC email is set, even if the item being checked in doesn't have an email address (location, etc) - */ + $notifiableHasEmail = $notifiable instanceof User && $notifiable->email; - // Send a checkout email to the admin's CC addresses, even if the target has no email - if (!empty($ccEmails)) { - Mail::to($ccEmails)->send($mailable); - Log::info('Checkin Mail sent to CC addresses'); + $shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail; + + [$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable); + + try { + if (!empty($to)) { + Mail::to(array_flatten($to))->send($mailable->locale($notifiable->locale)); + Mail::to(array_flatten($cc))->send($mailable->locale(Setting::getSettings()->locale)); + Log::info('Checkin Mail sent to CC addresses'); + } + } catch (ClientException $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); } - - // Send a checkout email to the target if it has an email - if (!empty($notifiable->email)) { - Mail::to($notifiable)->send($mailable); - Log::info('Checkin Mail sent to checkout target'); - } - } catch (ClientException $e) { - Log::debug("Exception caught during checkin email: " . $e->getMessage()); - } catch (Exception $e) { - Log::debug("Exception caught during checkin email: " . $e->getMessage()); } - // Send Webhook notification - try { - if ($this->shouldSendWebhookNotification()) { + if ($shouldSendWebhookNotification) { + // Send Webhook notification + try { if ($this->newMicrosoftTeamsWebhookEnabled()) { $message = $this->getCheckinNotification($event)->toMicrosoftTeams(); $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); @@ -208,25 +203,24 @@ class CheckoutableListener Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint) ->notify($this->getCheckinNotification($event)); } - } - } catch (ClientException $e) { - if (strpos($e->getMessage(), 'channel_not_found') !== false) { - Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage()); - return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') ); - } - else { - Log::error("ClientException caught during checkin notification: " . $e->getMessage()); + } catch (ClientException $e) { + if (strpos($e->getMessage(), 'channel_not_found') !== false) { + Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found')); + } else { + Log::error("ClientException caught during checkin notification: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); + } + } catch (Exception $e) { + Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ + 'error' => $e->getMessage(), + 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, + 'event' => $event, + ]); return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); } - } catch (Exception $e) { - Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ - 'error' => $e->getMessage(), - 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, - 'event' => $event, - ]); - return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail')); } - } + } /** * Generates a checkout acceptance @@ -239,6 +233,7 @@ class CheckoutableListener if ($checkedOutToType != "App\Models\User") { return null; } + if (!$event->checkoutable->requireAcceptance()) { return null; } @@ -246,15 +241,22 @@ class CheckoutableListener $acceptance = new CheckoutAcceptance; $acceptance->checkoutable()->associate($event->checkoutable); $acceptance->assignedTo()->associate($event->checkedOutTo); + + $category = $this->getCategoryFromCheckoutable($event->checkoutable); + + if ($category?->alert_on_response) { + $acceptance->alert_on_response_id = auth()->id(); + } + $acceptance->save(); - return $acceptance; + return $acceptance; } /** * Get the appropriate notification for the event - * - * @param CheckoutableCheckedIn $event + * + * @param CheckoutableCheckedIn $event * @return Notification */ private function getCheckinNotification($event) @@ -268,17 +270,19 @@ class CheckoutableListener break; case Asset::class: $notificationClass = CheckinAssetNotification::class; - break; + break; case LicenseSeat::class: $notificationClass = CheckinLicenseSeatNotification::class; break; + case Component::class: + $notificationClass = CheckinComponentNotification::class; + break; } Log::debug('Notification class: '.$notificationClass); - return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); + return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); } - /** * Get the appropriate notification for the event * @@ -303,6 +307,9 @@ class CheckoutableListener case LicenseSeat::class: $notificationClass = CheckoutLicenseSeatNotification::class; break; + case Component::class: + $notificationClass = CheckoutComponentNotification::class; + break; } @@ -314,19 +321,21 @@ class CheckoutableListener Asset::class => CheckoutAssetMail::class, LicenseSeat::class => CheckoutLicenseMail::class, Consumable::class => CheckoutConsumableMail::class, + Component::class => CheckoutComponentMail::class, ]; $mailable= $lookup[get_class($event->checkoutable)]; return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note); } + private function getCheckinMailType($event){ $lookup = [ Accessory::class => CheckinAccessoryMail::class, Asset::class => CheckinAssetMail::class, LicenseSeat::class => CheckinLicenseMail::class, + Component::class => CheckinComponentMail::class, ]; - $mailable= $lookup[get_class($event->checkoutable)]; return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); @@ -341,7 +350,8 @@ class CheckoutableListener * @param $event * @return mixed */ - private function getNotifiableUsers($event){ + private function getNotifiableUser($event) + { // If it's assigned to an asset, get that asset's assignedTo object if ($event->checkedOutTo instanceof Asset){ @@ -357,6 +367,7 @@ class CheckoutableListener return $event->checkedOutTo; } } + private function webhookSelected(){ if(Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general'){ return 'slack'; @@ -365,60 +376,114 @@ class CheckoutableListener return Setting::getSettings()->webhook_selected; } - /** - * Register the listeners for the subscriber. - * - * @param Illuminate\Events\Dispatcher $events - */ - public function subscribe($events) - { - $events->listen( - \App\Events\CheckoutableCheckedIn::class, - 'App\Listeners\CheckoutableListener@onCheckedIn' - ); - - $events->listen( - \App\Events\CheckoutableCheckedOut::class, - 'App\Listeners\CheckoutableListener@onCheckedOut' - ); - } - private function shouldNotSendAnyNotifications($checkoutable): bool { - if(in_array(get_class($checkoutable), $this->skipNotificationsFor)) { - return true; - } - //runs a check if the category wants to send checkin/checkout emails to users - $category = match (true) { - $checkoutable instanceof Asset => $checkoutable->model->category, - $checkoutable instanceof Accessory, - $checkoutable instanceof Consumable => $checkoutable->category, - $checkoutable instanceof LicenseSeat => $checkoutable->license->category, - default => null, - }; - - if (!$category->checkin_email) { - return true; - } - return false; + return in_array(get_class($checkoutable), $this->skipNotificationsFor); } - private function shouldSendWebhookNotification(): bool { return Setting::getSettings() && Setting::getSettings()->webhook_endpoint; } - private function checkoutableShouldSendEmail($event): bool + private function checkoutableCategoryShouldSendEmail(Model $checkoutable): bool { - if($event->checkoutable instanceof LicenseSeat){ - return $event->checkoutable->license->checkin_email(); + if ($checkoutable instanceof LicenseSeat) { + return $checkoutable->license->checkin_email(); } - return (method_exists($event->checkoutable, 'checkin_email') && $event->checkoutable->checkin_email()); + return (method_exists($checkoutable, 'checkin_email') && $checkoutable->checkin_email()); } private function newMicrosoftTeamsWebhookEnabled(): bool { return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'); } + + private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool + { + /** + * Send an email if any of the following conditions are met: + * 1. The asset requires acceptance + * 2. The item has a EULA + * 3. The item should send an email at check-in/check-out + */ + + if ($checkoutable->requireAcceptance()) { + return true; + } + + if ($checkoutable->getEula()) { + return true; + } + + if ($this->checkoutableCategoryShouldSendEmail($checkoutable)) { + return true; + } + + return false; + } + + private function shouldSendEmailToAlertAddress($acceptance = null): bool + { + $setting = Setting::getSettings(); + + if (!$setting) { + return false; + } + + if (is_null($acceptance) && !$setting->admin_cc_always) { + return false; + } + + return (bool) $setting->admin_cc_email; + } + + private function getFormattedAlertAddresses(): array + { + $alertAddresses = Setting::getSettings()->admin_cc_email; + + if ($alertAddresses !== '') { + return array_filter(array_map('trim', explode(',', $alertAddresses))); + } + + return []; + } + + private function generateEmailRecipients( + bool $shouldSendEmailToUser, + bool $shouldSendEmailToAlertAddress, + mixed $notifiable + ): array { + $to = []; + $cc = []; + + // if user && cc: to user, cc admin + if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) { + $to[] = $notifiable; + $cc[] = $this->getFormattedAlertAddresses(); + } + + // if user && no cc: to user + if ($shouldSendEmailToUser && !$shouldSendEmailToAlertAddress) { + $to[] = $notifiable; + } + + // if no user && cc: to admin + if (!$shouldSendEmailToUser && $shouldSendEmailToAlertAddress) { + $to[] = $this->getFormattedAlertAddresses(); + } + + return array($to, $cc); + } + + private function getCategoryFromCheckoutable(Model $checkoutable): ?Category + { + return match (true) { + $checkoutable instanceof Asset => $checkoutable->model->category, + $checkoutable instanceof Accessory, + $checkoutable instanceof Consumable, + $checkoutable instanceof Component => $checkoutable->category, + $checkoutable instanceof LicenseSeat => $checkoutable->license->category, + }; + } } diff --git a/app/Livewire/CategoryEditForm.php b/app/Livewire/CategoryEditForm.php index fd8bef6489..98e505c8df 100644 --- a/app/Livewire/CategoryEditForm.php +++ b/app/Livewire/CategoryEditForm.php @@ -6,6 +6,8 @@ use Livewire\Component; class CategoryEditForm extends Component { + public bool $alertOnResponse; + public $defaultEulaText; public $eulaText; diff --git a/app/Livewire/Importer.php b/app/Livewire/Importer.php index 5f30dca7be..58da61a3bf 100644 --- a/app/Livewire/Importer.php +++ b/app/Livewire/Importer.php @@ -35,10 +35,14 @@ class Importer extends Component public $accessories_fields; public $assets_fields; public $users_fields; + public $assetmodels_fields; + public $suppliers_fields; public $licenses_fields; public $locations_fields; public $consumables_fields; public $components_fields; + public $manufacturers_fields; + public $categories_fields; public $aliases_fields; protected $rules = [ @@ -85,9 +89,6 @@ class Importer extends Component case 'component': $results = $this->components_fields; break; - case 'consumable': - $results = $this->consumables_fields; - break; case 'license': $results = $this->licenses_fields; break; @@ -97,8 +98,14 @@ class Importer extends Component case 'location': $results = $this->locations_fields; break; - case 'user': - $results = $this->users_fields; + case 'supplier': + $results = $this->suppliers_fields; + break; + case 'manufacturer': + $results = $this->manufacturers_fields; + break; + case 'category': + $results = $this->categories_fields; break; default: $results = []; @@ -128,7 +135,7 @@ class Importer extends Component //yes, this key *is* valid. Continue on to the next field. continue; } else { - //no, this key is *INVALID* for this import type. Better set it to null + //no, this key is *INVALID* for this import type. Better set it to null, // and we'll hope that the $aliases_fields or something else picks it up. $this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh. } // TODO - strictly speaking, this isn't necessary here I don't think. @@ -149,7 +156,7 @@ class Importer extends Component // in "Accessories"!) if (array_key_exists($key, $this->columnOptions[$type])) { $this->field_map[$i] = $key; - continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header + continue 3; // bust out of both of these loops and the surrounding one - e.g. move on to the next header } } } @@ -171,6 +178,9 @@ class Importer extends Component 'license' => trans('general.licenses'), 'location' => trans('general.locations'), 'user' => trans('general.users'), + 'supplier' => trans('general.suppliers'), + 'manufacturer' => trans('general.manufacturers'), + 'category' => trans('general.categories'), ]; /** @@ -193,6 +203,7 @@ class Importer extends Component ]; $this->assets_fields = [ + 'id' => trans('general.id'), 'asset_eol_date' => trans('admin/hardware/form.eol_date'), 'asset_model' => trans('general.model_name'), 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]), @@ -319,12 +330,16 @@ class Importer extends Component 'location' => trans('general.location'), 'manager_first_name' => trans('general.importer.manager_first_name'), 'manager_last_name' => trans('general.importer.manager_last_name'), + 'manager_employee_num' => trans('general.importer.manager_employee_num'), + 'manager_username' => trans('general.importer.manager_username'), 'notes' => trans('general.notes'), 'phone_number' => trans('admin/users/table.phone'), + 'mobile_number' => trans('admin/users/table.mobile'), 'remote' => trans('admin/users/general.remote'), 'start_date' => trans('general.start_date'), 'state' => trans('general.state'), 'username' => trans('admin/users/table.username'), + 'display_name' => trans('admin/users/table.display_name'), 'vip' => trans('general.importer.vip'), 'website' => trans('general.website'), 'zip' => trans('general.zip'), @@ -332,6 +347,7 @@ class Importer extends Component $this->locations_fields = [ 'id' => trans('general.id'), + 'name' => trans('general.name'), 'address' => trans('general.address'), 'address2' => trans('general.importer.address2'), 'city' => trans('general.city'), @@ -340,13 +356,52 @@ class Importer extends Component 'ldap_ou' => trans('admin/locations/table.ldap_ou'), 'manager' => trans('general.importer.manager_full_name'), 'manager_username' => trans('general.importer.manager_username'), - 'name' => trans('general.item_name_var', ['item' => trans('general.location')]), 'notes' => trans('general.notes'), 'parent_location' => trans('admin/locations/table.parent'), 'state' => trans('general.state'), 'zip' => trans('general.zip'), ]; + $this->suppliers_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'address' => trans('general.address'), + 'address2' => trans('general.importer.address2'), + 'city' => trans('general.city'), + 'notes' => trans('general.notes'), + 'state' => trans('general.state'), + 'zip' => trans('general.zip'), + 'phone' => trans('general.phone'), + 'fax' => trans('general.fax'), + 'url' => trans('general.url'), + 'contact' => trans('general.contact'), + 'email' => trans('general.email'), + ]; + + $this->manufacturers_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'notes' => trans('general.notes'), + 'support_phone' => trans('admin/manufacturers/table.support_phone'), + 'support_url' => trans('admin/manufacturers/table.support_url'), + 'support_email' => trans('admin/manufacturers/table.support_email'), + 'warranty_lookup_url' => trans('admin/manufacturers/table.warranty_lookup_url'), + 'url' => trans('general.url'), + ]; + + $this->categories_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'notes' => trans('general.notes'), + 'category_type' => trans('admin/categories/general.import_category_type'), + 'eula_text' => trans('admin/categories/general.import_eula_text'), + 'use_default_eula' => trans('admin/categories/general.use_default_eula_column'), + 'require_acceptance' => trans('admin/categories/general.import_require_acceptance'), + 'checkin_email' => trans('admin/categories/general.import_checkin_email'), + ]; + + + $this->assetmodels_fields = [ 'category' => trans('general.category'), 'eol' => trans('general.eol'), @@ -357,6 +412,7 @@ class Importer extends Component 'model_number' => trans('general.model_no'), 'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), 'requestable' => trans('admin/models/general.requestable'), + 'require_serial' => trans('admin/hardware/general.require_serial'), ]; @@ -371,6 +427,8 @@ class Importer extends Component 'consumable name', 'component name', 'name', + 'supplier name', + 'location name', ], 'item_no' => [ 'item number', @@ -429,6 +487,13 @@ class Importer extends Component 'username', trans('general.importer.checked_out_to_username'), ], + 'display_name' => + [ + 'display name', + 'displayName', + 'display', + trans('admin/users/table.display_name'), + ], 'first_name' => [ 'first name', @@ -455,6 +520,13 @@ class Importer extends Component 'telephone', 'tel.', ], + 'mobile_number' => + [ + 'mobile', + 'mobile number', + 'cell', + 'cellphone', + ], 'serial' => [ @@ -464,6 +536,10 @@ class Importer extends Component 'product key', 'key', ], + 'require_serial' => + [ + 'serial required', + ], 'model_number' => [ 'model', diff --git a/app/Livewire/LocationScopeCheck.php b/app/Livewire/LocationScopeCheck.php new file mode 100644 index 0000000000..24beb9413f --- /dev/null +++ b/app/Livewire/LocationScopeCheck.php @@ -0,0 +1,29 @@ +mismatched = Helper::test_locations_fmcs(false); + $this->is_tested = true; + } + + public function mount() { + $this->setting = Setting::getSettings(); + } + + public function render() + { + return view('livewire.location-scope-check'); + } +} diff --git a/app/Livewire/OauthClients.php b/app/Livewire/OauthClients.php index 017e789060..e114c2278e 100644 --- a/app/Livewire/OauthClients.php +++ b/app/Livewire/OauthClients.php @@ -47,7 +47,7 @@ class OauthClients extends Component { // test for safety // ->delete must be of type Client - thus the model binding - if ($clientId->created_by == auth()->id()) { + if ($clientId->user_id == auth()->id()) { app(ClientRepository::class)->delete($clientId); } else { Log::warning('User ' . auth()->id() . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->created_by); diff --git a/app/Livewire/SlackSettingsForm.php b/app/Livewire/SlackSettingsForm.php index 7487f30961..535a83413f 100644 --- a/app/Livewire/SlackSettingsForm.php +++ b/app/Livewire/SlackSettingsForm.php @@ -71,12 +71,12 @@ class SlackSettingsForm extends Component $this->setting = Setting::getSettings(); $this->save_button = trans('general.save'); - $this->webhook_selected = $this->setting->webhook_selected; - $this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"]; - $this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"]; - $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"]; - $this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"]; - $this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"]; + $this->webhook_selected = ($this->setting->webhook_selected !== '') ? $this->setting->webhook_selected : 'slack'; + $this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"]; + $this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"]; + $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"]; + $this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"] ?? $this->webhook_text['slack']["link"]; + $this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"] ?? $this->webhook_text['slack']["test"]; $this->webhook_endpoint = $this->setting->webhook_endpoint; $this->webhook_channel = $this->setting->webhook_channel; $this->webhook_botname = $this->setting->webhook_botname; @@ -90,7 +90,7 @@ class SlackSettingsForm extends Component $this->isDisabled= ''; } if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) { - session()->flash('warning', 'The selected Microsoft Teams webhook URL will be deprecated Jan 31st, 2025. Please use a workflow URL. Microsofts Documentation on creating a workflow can be found here.'); + session()->flash('warning', trans('admin/settings/message.webhook.ms_teams_deprecation')); } } public function updated($field) { diff --git a/app/Mail/CheckinAssetMail.php b/app/Mail/CheckinAssetMail.php index 355c2f9f13..72f129dd6d 100644 --- a/app/Mail/CheckinAssetMail.php +++ b/app/Mail/CheckinAssetMail.php @@ -47,7 +47,7 @@ class CheckinAssetMail extends Mailable return new Envelope( from: $from, - subject: trans('mail.Asset_Checkin_Notification'), + subject: trans('mail.Asset_Checkin_Notification', ['tag' => $this->item->asset_tag]), ); } diff --git a/app/Mail/CheckinComponentMail.php b/app/Mail/CheckinComponentMail.php new file mode 100644 index 0000000000..5b62a2c4b8 --- /dev/null +++ b/app/Mail/CheckinComponentMail.php @@ -0,0 +1,71 @@ +item = $component; + $this->target = $checkedOutTo; + $this->admin = $checkedInby; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_component_checkin'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-component', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutAcceptanceResponseMail.php b/app/Mail/CheckoutAcceptanceResponseMail.php new file mode 100644 index 0000000000..fba6dd3de2 --- /dev/null +++ b/app/Mail/CheckoutAcceptanceResponseMail.php @@ -0,0 +1,79 @@ +acceptance = $acceptance; + $this->recipient = $recipient; + $this->wasAccepted = $wasAccepted; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $subject = $this->wasAccepted + ? trans('mail.initiated_accepted') + : trans('mail.initiated_declined'); + + return new Envelope( + subject: $subject, + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkout-acceptance-response', + with: [ + 'assignedTo' => $this->acceptance->assignedTo, + 'introduction' => $this->introduction(), + 'item' => $this->acceptance->checkoutable, + 'note' => $this->acceptance->note, + 'recipient' => $this->recipient, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } + + private function introduction(): string + { + return $this->wasAccepted + ? trans('mail.following_accepted') + : trans('mail.following_declined'); + } +} diff --git a/app/Mail/CheckoutAccessoryMail.php b/app/Mail/CheckoutAccessoryMail.php index 64c02e31ed..92ce84a2d6 100644 --- a/app/Mail/CheckoutAccessoryMail.php +++ b/app/Mail/CheckoutAccessoryMail.php @@ -3,6 +3,8 @@ namespace App\Mail; use App\Models\Accessory; +use App\Models\Asset; +use App\Models\Location; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; @@ -41,7 +43,7 @@ class CheckoutAccessoryMail extends Mailable return new Envelope( from: $from, - subject: (trans('mail.Accessory_Checkout_Notification')), + subject: trans('mail.Accessory_Checkout_Notification'), ); } @@ -54,6 +56,17 @@ class CheckoutAccessoryMail extends Mailable $eula = $this->item->getEula(); $req_accept = $this->item->requireAcceptance(); $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + $name = null; + + if($this->target instanceof User){ + $name = $this->target->display_name; + } + else if($this->target instanceof Asset){ + $name = $this->target->assignedto?->display_name; + } + else if($this->target instanceof Location){ + $name = $this->target->manager->name; + } return new Content( markdown: 'mail.markdown.checkout-accessory', @@ -61,14 +74,35 @@ class CheckoutAccessoryMail extends Mailable 'item' => $this->item, 'admin' => $this->admin, 'note' => $this->note, - 'target' => $this->target, + 'target' => $name, 'eula' => $eula, 'req_accept' => $req_accept, 'accept_url' => $accept_url, 'checkout_qty' => $this->checkout_qty, + 'introduction_line' => $this->introductionLine(), ], ); } + private function introductionLine(): string + { + if ($this->target instanceof Location) { + return trans('mail.new_item_checked_location', ['location' => $this->target->name ]); + } + if ($this->requiresAcceptance()) { + return trans('mail.new_item_checked_with_acceptance'); + } + + if (!$this->requiresAcceptance()) { + return trans('mail.new_item_checked'); + } + + // we shouldn't get here but let's send a default message just in case + return trans('new_item_checked'); + } + private function requiresAcceptance(): int|bool + { + return method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; + } /** * Get the attachments for the message. diff --git a/app/Mail/CheckoutAssetMail.php b/app/Mail/CheckoutAssetMail.php index 7ac20861ed..0dcd568bab 100644 --- a/app/Mail/CheckoutAssetMail.php +++ b/app/Mail/CheckoutAssetMail.php @@ -4,6 +4,7 @@ namespace App\Mail; use App\Helpers\Helper; use App\Models\Asset; +use App\Models\Location; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; @@ -24,16 +25,17 @@ class CheckoutAssetMail extends Mailable /** * Create a new message instance. + * @throws \Exception */ public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note, bool $firstTimeSending = true) { $this->item = $asset; $this->admin = $checkedOutBy; $this->note = $note; - $this->target = $checkedOutTo; $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); + $this->target = $checkedOutTo; $this->last_checkout = ''; $this->expected_checkin = ''; @@ -76,6 +78,17 @@ class CheckoutAssetMail extends Mailable $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; $req_accept = $this->requiresAcceptance(); $fields = []; + $name = null; + + if($this->target instanceof User){ + $name = $this->target->display_name; + } + else if($this->target instanceof Asset){ + $name = $this->target->assignedto?->display_name; + } + else if($this->target instanceof Location){ + $name = $this->target->manager->name; + } // Check if the item has custom fields associated with it if (($this->item->model) && ($this->item->model->fieldset)) { @@ -91,7 +104,7 @@ class CheckoutAssetMail extends Mailable 'admin' => $this->admin, 'status' => $this->item->assetstatus?->name, 'note' => $this->note, - 'target' => $this->target, + 'target' => $name, 'fields' => $fields, 'eula' => $eula, 'req_accept' => $req_accept, @@ -116,7 +129,7 @@ class CheckoutAssetMail extends Mailable private function getSubject(): string { if ($this->firstTimeSending) { - return trans('mail.Asset_Checkout_Notification'); + return trans('mail.Asset_Checkout_Notification', ['tag' => $this->item->asset_tag]); } return trans('mail.unaccepted_asset_reminder'); @@ -124,6 +137,9 @@ class CheckoutAssetMail extends Mailable private function introductionLine(): string { + if ($this->firstTimeSending && $this->target instanceof Location) { + return trans('mail.new_item_checked_location', ['location' => $this->target->name ]); + } if ($this->firstTimeSending && $this->requiresAcceptance()) { return trans('mail.new_item_checked_with_acceptance'); } diff --git a/app/Mail/CheckoutComponentMail.php b/app/Mail/CheckoutComponentMail.php new file mode 100644 index 0000000000..e914d14196 --- /dev/null +++ b/app/Mail/CheckoutComponentMail.php @@ -0,0 +1,82 @@ +item = $component; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $component->assets->first()?->pivot?->assigned_qty; + + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_component_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-component', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'qty' => $this->qty, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutLicenseMail.php b/app/Mail/CheckoutLicenseMail.php index 9462c6c332..f3688bae5a 100644 --- a/app/Mail/CheckoutLicenseMail.php +++ b/app/Mail/CheckoutLicenseMail.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\Asset; use App\Models\LicenseSeat; use App\Models\Setting; use App\Models\User; @@ -25,9 +26,16 @@ class CheckoutLicenseMail extends Mailable $this->item = $licenseSeat; $this->admin = $checkedOutBy; $this->note = $note; - $this->target = $checkedOutTo; $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); + $this->target = $checkedOutTo; + + if($this->target instanceof User){ + $this->target = $this->target->display_name; + } + elseif($this->target instanceof Asset){ + $this->target = $this->target->display_name; + } } /** diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index 039f8692f6..33a1c6a570 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -4,6 +4,8 @@ namespace App\Models; use App\Helpers\Helper; use App\Models\Traits\Acceptable; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -14,7 +16,7 @@ use Watson\Validating\ValidatingTrait; /** * Model for Accessories. * - * @version v1.0 + * @version v1.0 */ class Accessory extends SnipeModel { @@ -22,6 +24,7 @@ class Accessory extends SnipeModel protected $presenter = \App\Presenters\AccessoryPresenter::class; use CompanyableTrait; + use HasUploads; use Loggable, Presentable; use SoftDeletes; @@ -54,8 +57,8 @@ class Accessory extends SnipeModel ]; /** - * Accessory validation rules - */ + * Accessory validation rules + */ public $rules = [ 'name' => 'required|min:3|max:255', 'qty' => 'required|integer|min:1', @@ -63,18 +66,18 @@ class Accessory extends SnipeModel 'company_id' => 'integer|nullable', 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; /** - * Whether the model should inject it's identifier to the unique - * validation rules before attempting validation. If this property - * is not set in the model it will default to true. - * + * Whether the model should inject it's identifier to the unique + * validation rules before attempting validation. If this property + * is not set in the model it will default to true. + * * @var bool - */ + */ protected $injectUniqueIdentifier = true; use ValidatingTrait; @@ -102,29 +105,11 @@ class Accessory extends SnipeModel ]; - - /** - * Establishes the accessories -> action logs -> uploads relationship - * - * @author A. Gianotto - * @since [v6.1.13] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') - ->where('item_type', '=', self::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } - - /** * Establishes the accessory -> supplier relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function supplier() @@ -137,7 +122,7 @@ class Accessory extends SnipeModel * Sets the requestable attribute on the accessory * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return void */ public function setRequestableAttribute($value) @@ -152,7 +137,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> company relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -164,7 +149,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> location relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -176,7 +161,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> category relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function category() @@ -188,7 +173,7 @@ class Accessory extends SnipeModel * Returns the action logs associated with the accessory * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() @@ -217,8 +202,8 @@ class Accessory extends SnipeModel * * It's super-mega-assy, but it's the best I could do for now. * - * @author A. Gianotto - * @since v5.0.0 + * @author A. Gianotto + * @since v5.0.0 * * @see \App\Http\Controllers\Api\AccessoriesController\checkedout() */ @@ -235,7 +220,7 @@ class Accessory extends SnipeModel * presenter or service provider * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string */ public function getImageUrl() @@ -251,7 +236,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> users relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function checkouts() @@ -264,7 +249,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> admin user relationship * * @author A. Gianotto - * @since [v7.0.13] + * @since [v7.0.13] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -276,7 +261,7 @@ class Accessory extends SnipeModel * Checks whether or not the accessory has users * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return int */ public function hasUsers() @@ -290,7 +275,7 @@ class Accessory extends SnipeModel * Establishes the accessory -> manufacturer relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manufacturer() @@ -303,12 +288,12 @@ class Accessory extends SnipeModel * accessory based on the category it belongs to. * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return bool */ public function checkin_email() { - return $this->category->checkin_email; + return $this->category?->checkin_email; } /** @@ -316,7 +301,7 @@ class Accessory extends SnipeModel * accept it via email. * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return bool */ public function requireAcceptance() @@ -329,7 +314,7 @@ class Accessory extends SnipeModel * checks for a settings level EULA * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string */ public function getEula() @@ -349,7 +334,7 @@ class Accessory extends SnipeModel * Check how many items within an accessory are checked out * * @author [A. Gianotto] [] - * @since [v5.0] + * @since [v5.0] * @return int */ public function numCheckedOut() @@ -366,7 +351,7 @@ class Accessory extends SnipeModel * bad things happen. * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return int */ public function numRemaining() @@ -381,8 +366,8 @@ class Accessory extends SnipeModel /** * Run after the checkout acceptance was declined by the user * - * @param User $acceptedBy - * @param string $signature + * @param User $acceptedBy + * @param string $signature */ public function declinedCheckout(User $declinedBy, $signature) { @@ -408,8 +393,8 @@ class Accessory extends SnipeModel * This simply checks that there is a value for quantity, and if there isn't, set it to 0. * * @author A. Gianotto - * @since v6.3.4 - * @param $value + * @since v6.3.4 + * @param $value * @return void */ public function setQtyAttribute($value) @@ -426,7 +411,6 @@ class Accessory extends SnipeModel /** * Query builder scope to order on created_by name - * */ public function scopeOrderByCreatedByName($query, $order) { @@ -434,68 +418,68 @@ class Accessory extends SnipeModel } /** - * Query builder scope to order on company - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on company + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderCompany($query, $order) { return $query->leftJoin('companies', 'accessories.company_id', '=', 'companies.id') - ->orderBy('companies.name', $order); + ->orderBy('companies.name', $order); } /** - * Query builder scope to order on category - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on category + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderCategory($query, $order) { return $query->leftJoin('categories', 'accessories.category_id', '=', 'categories.id') - ->orderBy('categories.name', $order); + ->orderBy('categories.name', $order); } /** - * Query builder scope to order on location - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on location + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderLocation($query, $order) { return $query->leftJoin('locations', 'accessories.location_id', '=', 'locations.id') - ->orderBy('locations.name', $order); + ->orderBy('locations.name', $order); } /** - * Query builder scope to order on manufacturer - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on manufacturer + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderManufacturer($query, $order) { return $query->leftJoin('manufacturers', 'accessories.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order); } /** - * Query builder scope to order on supplier - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on supplier + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderSupplier($query, $order) { return $query->leftJoin('suppliers', 'accessories.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); diff --git a/app/Models/AccessoryCheckout.php b/app/Models/AccessoryCheckout.php index 61ffcd08e5..9f49354389 100755 --- a/app/Models/AccessoryCheckout.php +++ b/app/Models/AccessoryCheckout.php @@ -16,7 +16,7 @@ use Watson\Validating\ValidatingTrait; /** * Model for Accessories. * - * @version v1.0 + * @version v1.0 */ class AccessoryCheckout extends Model { @@ -36,7 +36,7 @@ class AccessoryCheckout extends Model * Establishes the accessory checkout -> accessory relationship * * @author [A. Kroeger] - * @since [v7.0.9] + * @since [v7.0.9] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessory() @@ -52,7 +52,7 @@ class AccessoryCheckout extends Model * Establishes the accessory checkout -> user relationship * * @author [A. Kroeger] - * @since [v7.0.9] + * @since [v7.0.9] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -64,7 +64,7 @@ class AccessoryCheckout extends Model * Get the target this asset is checked out to * * @author [A. Kroeger] - * @since [v7.0] + * @since [v7.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedTo() @@ -76,7 +76,7 @@ class AccessoryCheckout extends Model * Gets the lowercased name of the type of target the asset is assigned to * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string */ public function assignedType() @@ -91,7 +91,7 @@ class AccessoryCheckout extends Model * this method is an easy way of seeing if we are checked out to a user. * * @author [A. Kroeger] - * @since [v7.0] + * @since [v7.0] */ public function checkedOutToUser(): bool { @@ -127,50 +127,64 @@ class AccessoryCheckout extends Model * Run additional, advanced searches. * * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $terms The search terms + * @param array $terms The search terms * @return \Illuminate\Database\Eloquent\Builder */ public function advancedTextSearch(Builder $query, array $terms) { - $userQuery = User::where(function ($query) use ($terms) { - foreach ($terms as $term) { - $search_str = '%' . $term . '%'; - $query->where('first_name', 'like', $search_str) - ->orWhere('last_name', 'like', $search_str) - ->orWhere('note', 'like', $search_str); + $userQuery = User::where( + function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('first_name', 'like', $search_str) + ->orWhere('last_name', 'like', $search_str) + ->orWhere('note', 'like', $search_str); + } } - })->select('id'); + )->select('id'); - $locationQuery = Location::where(function ($query) use ($terms) { - foreach ($terms as $term) { - $search_str = '%' . $term . '%'; - $query->where('name', 'like', $search_str); + $locationQuery = Location::where( + function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } } - })->select('id'); + )->select('id'); - $assetQuery = Asset::where(function ($query) use ($terms) { - foreach ($terms as $term) { - $search_str = '%' . $term . '%'; - $query->where('name', 'like', $search_str); + $assetQuery = Asset::where( + function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } } - })->select('id'); + )->select('id'); - $query->where(function ($query) use ($userQuery) { - $query->where('assigned_type', User::class) - ->whereIn('assigned_to', $userQuery); - })->orWhere(function($query) use ($locationQuery) { - $query->where('assigned_type', Location::class) - ->whereIn('assigned_to', $locationQuery); - })->orWhere(function($query) use ($assetQuery) { - $query->where('assigned_type', Asset::class) - ->whereIn('assigned_to', $assetQuery); - })->orWhere(function($query) use ($terms) { - foreach ($terms as $term) { - $search_str = '%' . $term . '%'; - $query->where('note', 'like', $search_str); + $query->where( + function ($query) use ($userQuery) { + $query->where('assigned_type', User::class) + ->whereIn('assigned_to', $userQuery); } - }); + )->orWhere( + function ($query) use ($locationQuery) { + $query->where('assigned_type', Location::class) + ->whereIn('assigned_to', $locationQuery); + } + )->orWhere( + function ($query) use ($assetQuery) { + $query->where('assigned_type', Asset::class) + ->whereIn('assigned_to', $assetQuery); + } + )->orWhere( + function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('note', 'like', $search_str); + } + } + ); return $query; } diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index dd86ae25c6..59e7b8244a 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -2,17 +2,19 @@ namespace App\Models; +use App\Models\Traits\CompanyableTrait; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Str; /** * Model for the Actionlog (the table that keeps a historical log of * checkouts, checkins, and updates). * - * @version v1.0 + * @version v1.0 */ class Actionlog extends SnipeModel { @@ -69,11 +71,13 @@ class Actionlog extends SnipeModel */ protected $searchableRelations = [ 'company' => ['name'], - 'adminuser' => ['first_name','last_name','username', 'email'], - 'user' => ['first_name','last_name','username', 'email'], + 'adminuser' => ['first_name','last_name','username', 'email', 'employee_num'], + 'user' => ['first_name','last_name','username', 'email', 'employee_num'], 'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'], 'assets.model' => ['name', 'model_number', 'eol', 'notes'], 'assets.model.category' => ['name', 'notes'], + 'assets.location' => ['name'], + 'assets.defaultLoc' => ['name'], 'assets.model.manufacturer' => ['name', 'notes'], 'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'], 'licenses.category' => ['name', 'notes'], @@ -96,24 +100,31 @@ class Actionlog extends SnipeModel * Override from Builder to automatically add the company * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public static function boot() { parent::boot(); - static::creating(function (self $actionlog) { - // If the admin is a superadmin, let's see if the target instead has a company. - if (auth()->user() && auth()->user()->isSuperUser()) { - if ($actionlog->target) { - $actionlog->company_id = $actionlog->target->company_id; - } elseif ($actionlog->item) { - $actionlog->company_id = $actionlog->item->company_id; + static::creating( + function (self $actionlog) { + // If the admin is a superadmin, let's see if the target instead has a company. + if (auth()->user() && auth()->user()->isSuperUser()) { + if ($actionlog->target) { + $actionlog->company_id = $actionlog->target->company_id; + } elseif ($actionlog->item) { + $actionlog->company_id = $actionlog->item->company_id; + } + } elseif (auth()->user() && auth()->user()->company) { + $actionlog->company_id = auth()->user()->company_id; } - } elseif (auth()->user() && auth()->user()->company) { - $actionlog->company_id = auth()->user()->company_id; + + if ($actionlog->action_date == '') { + $actionlog->action_date = Carbon::now(); + } + } - }); + ); } @@ -121,7 +132,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> item relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function item() @@ -133,7 +144,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> company relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -146,7 +157,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> asset relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -158,7 +169,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> license relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -170,7 +181,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> consumable relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() @@ -182,7 +193,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> consumable relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() @@ -194,7 +205,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> components relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function components() @@ -206,7 +217,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> item type relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function itemType() @@ -222,7 +233,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> target type relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function targetType() @@ -235,25 +246,12 @@ class Actionlog extends SnipeModel } - /** - * Establishes the actionlog -> uploads relationship - * - * @author [A. Gianotto] [] - * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->morphTo('item') - ->where('action_type', '=', 'uploaded') - ->withTrashed(); - } /** * Establishes the actionlog -> userlog relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function userlog() @@ -265,20 +263,20 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> admin user relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() { return $this->belongsTo(User::class, 'created_by') - ->withTrashed(); + ->withTrashed(); } /** * Establishes the actionlog -> user relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function user() @@ -291,7 +289,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> target relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function target() @@ -303,7 +301,7 @@ class Actionlog extends SnipeModel * Establishes the actionlog -> location relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -316,7 +314,7 @@ class Actionlog extends SnipeModel * Check if the file exists, and if it does, force a download * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string | false */ public function get_src($type = 'assets', $fieldname = 'filename') @@ -334,7 +332,7 @@ class Actionlog extends SnipeModel * Saves the log record with the action type * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return bool */ public function logaction($actiontype) @@ -355,7 +353,7 @@ class Actionlog extends SnipeModel * Calculate the number of days until the next audit * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return int */ public function daysUntilNextAudit($monthInterval = 12, $asset = null) @@ -376,7 +374,7 @@ class Actionlog extends SnipeModel if ($this->created_at > $override_default_next) { $next_audit_days = '-'.$next_audit_days; } - + return $next_audit_days; } @@ -384,7 +382,7 @@ class Actionlog extends SnipeModel * Calculate the date of the next audit * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Datetime */ public function calcNextAuditDate($monthInterval = 12, $asset = null) @@ -401,24 +399,24 @@ class Actionlog extends SnipeModel /** * Gets action logs in chronological order, excluding uploads * - * @author Vincent Sposato - * @since v1.0 + * @author Vincent Sposato + * @since v1.0 * @return \Illuminate\Database\Eloquent\Collection */ public function getListingOfActionLogsChronologicalOrder() { return $this->all() - ->where('action_type', '!=', 'uploaded') - ->orderBy('item_id', 'asc') - ->orderBy('created_at', 'asc') - ->get(); + ->where('action_type', '!=', 'uploaded') + ->orderBy('item_id', 'asc') + ->orderBy('created_at', 'asc') + ->get(); } /** * Determines what the type of request is so we can log it to the action_log * * @author A. Gianotto - * @since v6.3.0 + * @since v6.3.0 * @return string */ public function determineActionSource(): string @@ -430,11 +428,12 @@ class Actionlog extends SnipeModel // This is an API call if (((request()->header('content-type') && (request()->header('accept'))=='application/json')) - && (starts_with(request()->header('authorization'), 'Bearer '))) { + && (starts_with(request()->header('authorization'), 'Bearer ')) + ) { return 'api'; } - // This is probably NOT an API call + // This is probably NOT an API call if (request()->filled('_token')) { return 'gui'; } @@ -444,6 +443,81 @@ class Actionlog extends SnipeModel } + + /** + * @author Godfrey Martinez + * @since [v8.0.4] + * @return \App\Models\Actionlog + */ + public function logUploadDelete($object, $filename) + { + $log = new Actionlog; + $log->item_type = $object instanceof SnipeModel ? get_class($object) : $object; + $log->item_id = $object->id; + $log->created_by = auth()->id(); + $log->target_id = null; + $log->filename = $filename; + $log->created_at = date('Y-m-d H:i:s'); + $log->logaction('upload deleted'); + + return $log; + } + + public function uploads_file_url() + { + + + + if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) { + return route('log.storedeula.download', ['filename' => $this->filename]); + } + + $object = Str::snake(str_plural(str_replace("App\Models\\", '', $this->item_type))); + + if ($object == 'asset_models') { + $object = 'models'; + } + + return route('ui.files.show', [ + 'object_type' => $object, + 'id' => $this->item_id, + 'file_id' => $this->id, + ]); + + } + + public function uploads_file_path() + { + + if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) { + return 'private_uploads/eula-pdfs/'.$this->filename; + } + + switch ($this->item_type) { + case Accessory::class: + return 'private_uploads/accessories/'.$this->filename; + case Asset::class: + return 'private_uploads/assets/'.$this->filename; + case AssetModel::class: + return 'private_uploads/models/'.$this->filename; + case Consumable::class: + return 'private_uploads/consumables/'.$this->filename; + case Component::class: + return 'private_uploads/components/'.$this->filename; + case License::class: + return 'private_uploads/licenses/'.$this->filename; + case Location::class: + return 'private_uploads/locations/'.$this->filename; + case Maintenance::class: + return 'private_uploads/maintenances/'.$this->filename; + case User::class: + return 'private_uploads/users/'.$this->filename; + default: + return null; + } + } + + // Manually sets $this->source for determineActionSource() public function setActionSource($source = null): void { @@ -454,4 +528,4 @@ class Actionlog extends SnipeModel { return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } -} +} \ No newline at end of file diff --git a/app/Models/Asset.php b/app/Models/Asset.php index ac4fecac34..d5b640e40a 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -7,24 +7,25 @@ use App\Exceptions\CheckoutNotAllowed; use App\Helpers\Helper; use App\Http\Traits\UniqueUndeletedTrait; use App\Models\Traits\Acceptable; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; -use App\Presenters\Presentable; use App\Presenters\AssetPresenter; -use Illuminate\Support\Facades\Auth; +use App\Presenters\Presentable; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; -use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Model; /** * Model for Assets. * - * @version v1.0 + * @version v1.0 */ class Asset extends Depreciable { @@ -33,6 +34,7 @@ class Asset extends Depreciable protected $with = ['model', 'adminuser']; use CompanyableTrait; + use HasUploads; use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait; public const LOCATION = 'location'; @@ -44,22 +46,22 @@ class Asset extends Depreciable /** * Run after the checkout acceptance was declined by the user * - * @param User $acceptedBy - * @param string $signature + * @param User $acceptedBy + * @param string $signature */ public function declinedCheckout(User $declinedBy, $signature) { - $this->assigned_to = null; - $this->assigned_type = null; - $this->accepted = null; - $this->save(); + $this->assigned_to = null; + $this->assigned_type = null; + $this->accepted = null; + $this->save(); } /** - * The database table used by the model. - * - * @var string - */ + * The database table used by the model. + * + * @var string + */ protected $table = 'assets'; /** @@ -69,12 +71,12 @@ class Asset extends Depreciable // protected $with = ['model']; /** - * Whether the model should inject it's identifier to the unique - * validation rules before attempting validation. If this property - * is not set in the model it will default to true. - * + * Whether the model should inject it's identifier to the unique + * validation rules before attempting validation. If this property + * is not set in the model it will default to true. + * * @var bool - */ + */ protected $injectUniqueIdentifier = true; protected $casts = [ @@ -111,27 +113,28 @@ class Asset extends Depreciable 'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], - 'serial' => ['nullable', 'unique_undeleted:assets,serial'], - 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'], + 'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'], + 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:99999999999999999.99'], 'supplier_id' => ['nullable', 'exists:suppliers,id'], 'asset_eol_date' => ['nullable', 'date'], 'eol_explicit' => ['nullable', 'boolean'], 'byod' => ['nullable', 'boolean'], 'order_number' => ['nullable', 'string', 'max:191'], 'notes' => ['nullable', 'string', 'max:65535'], - 'assigned_to' => ['nullable', 'integer'], + 'assigned_to' => ['nullable', 'integer', 'required_with:assigned_type'], + 'assigned_type' => ['nullable', 'required_with:assigned_to', 'in:'.User::class.",".Location::class.",".Asset::class], 'requestable' => ['nullable', 'boolean'], - 'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'], - 'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'], - 'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL'] + 'assigned_user' => ['integer', 'nullable', 'exists:users,id,deleted_at,NULL'], + 'assigned_location' => ['integer', 'nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'], + 'assigned_asset' => ['integer', 'nullable', 'exists:assets,id,deleted_at,NULL'] ]; /** - * The attributes that are mass assignable. - * - * @var array - */ + * The attributes that are mass assignable. + * + * @var array + */ protected $fillable = [ 'asset_tag', 'assigned_to', @@ -204,6 +207,17 @@ class Asset extends Depreciable 'model.manufacturer' => ['name'], ]; + protected static function booted(): void + { + static::forceDeleted(function (Asset $asset) { + $asset->requests()->forceDelete(); + }); + + static::softDeleted(function (Asset $asset) { + $asset->requests()->delete(); + }); + } + // To properly set the expected checkin as Y-m-d public function setExpectedCheckinAttribute($value) { @@ -220,18 +234,22 @@ class Asset extends Depreciable $customFieldValidationRules = []; - if (($this->model) && ($this->model->fieldset)) { + if (($this->model) && ($this->model->fieldset)) { - foreach ($this->model->fieldset->fields as $field) { + foreach ($this->model->fieldset->fields as $field) { - if ($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); - } + // this just casts booleans that may come through as strings to an actual boolean type + // adding !$field->field_encrypted because when the encrypted value comes through it + // screws things up for the encrypted validation rules (and the encrypted string + // is not a valid boolean type) + if ($field->format == 'BOOLEAN' && !$field->field_encrypted) { + $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); } - - $customFieldValidationRules += $this->model->fieldset->validation_rules(); } + $customFieldValidationRules += $this->model->fieldset->validation_rules(); + } + return $customFieldValidationRules; } @@ -257,6 +275,7 @@ class Asset extends Depreciable /** * Returns the warranty expiration date as Carbon object + * * @return \Carbon\Carbon|null */ public function getWarrantyExpiresAttribute() @@ -280,7 +299,7 @@ class Asset extends Depreciable * Establishes the asset -> company relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -295,7 +314,7 @@ class Asset extends Depreciable * that the status is deployable * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return bool */ public function availableForCheckout() @@ -306,8 +325,8 @@ class Asset extends Depreciable // The asset status is not archived and is deployable if (($this->assetstatus) && ($this->assetstatus->archived == '0') - && ($this->assetstatus->deployable == '1')) - { + && ($this->assetstatus->deployable == '1') + ) { return true; } @@ -322,14 +341,14 @@ class Asset extends Depreciable * @todo The admin parameter is never used. Can probably be removed. * * @author [A. Gianotto] [] - * @param User $user - * @param User $admin - * @param Carbon $checkout_at - * @param Carbon $expected_checkin - * @param string $note - * @param null $name + * @param User $user + * @param User $admin + * @param Carbon $checkout_at + * @param Carbon $expected_checkin + * @param string $note + * @param null $name * @return bool - * @since [v3.0] + * @since [v3.0] * @return bool */ public function checkOut($target, $admin = null, $checkout_at = null, $expected_checkin = null, $note = null, $name = null, $location = null) @@ -390,7 +409,7 @@ class Asset extends Depreciable * Sets the detailedNameAttribute * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string */ public function getDetailedNameAttribute() @@ -408,7 +427,7 @@ class Asset extends Depreciable * Pulls in the validation rules * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return array */ public function validationRules() @@ -416,15 +435,38 @@ class Asset extends Depreciable return $this->rules; } - public function customFieldsForCheckinCheckout($checkin_checkout) { + public function customFieldsForCheckinCheckout($checkin_checkout) + { // Check to see if any of the custom fields were included on the form and if they have any values if (($this->model) && ($this->model->fieldset) && ($this->model->fieldset->fields)) { + foreach ($this->model->fieldset->fields as $field) { - if (($field->{$checkin_checkout} == 1) && (request()->has($field->db_column))){ - $this->{$field->db_column} = request()->get($field->db_column); + + if (($field->{$checkin_checkout} == 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))) { + $this->{$field->db_column} = Crypt::encrypt(implode(', ', request()->input($field->db_column))); + } else { + $this->{$field->db_column} = Crypt::encrypt(request()->get($field->db_column)); + } + } + + } else { + + if (is_array(request()->input($field->db_column))) { + $this->{$field->db_column} = implode(', ', request()->input($field->db_column)); + } else { + $this->{$field->db_column} = request()->input($field->db_column); + } + + } } } } + } @@ -432,12 +474,12 @@ class Asset extends Depreciable * Establishes the asset -> depreciation relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function depreciation() { - return $this->hasOneThrough(\App\Models\Depreciation::class,\App\Models\AssetModel::class,'id','id','model_id','depreciation_id'); + return $this->hasOneThrough(\App\Models\Depreciation::class, \App\Models\AssetModel::class, 'id', 'id', 'model_id', 'depreciation_id'); } @@ -445,7 +487,7 @@ class Asset extends Depreciable * Get components assigned to this asset * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function components() @@ -460,7 +502,7 @@ class Asset extends Depreciable * @todo Is this still needed? * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function get_depreciation() @@ -471,51 +513,35 @@ class Asset extends Depreciable } - /** - * Get uploads for this asset - * - * @author [A. Gianotto] [] - * @since [v4.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany('\App\Models\Actionlog', 'item_id') - ->where('item_type', '=', Asset::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } - /** * Determines whether the asset is checked out to a user * - * Even though we allow allow for checkout to things beyond users + * Even though we allow for checkout to things beyond users * this method is an easy way of seeing if we are checked out to a user. * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] */ public function checkedOutToUser(): bool { - return $this->assignedType() === self::USER; + return $this->assignedType() === self::USER; } public function checkedOutToLocation(): bool { - return $this->assignedType() === self::LOCATION; + return $this->assignedType() === self::LOCATION; } public function checkedOutToAsset(): bool { - return $this->assignedType() === self::ASSET; + return $this->assignedType() === self::ASSET; } /** * Get the target this asset is checked out to * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedTo() @@ -529,7 +555,7 @@ class Asset extends Depreciable * Sigh. * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedAssets() @@ -541,7 +567,7 @@ class Asset extends Depreciable * Establishes the accessory -> asset assignment relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedAccessories() @@ -556,7 +582,7 @@ class Asset extends Depreciable * @todo Refactor this if possible. It's awful. * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \ArrayObject */ public function assetLoc($iterations = 1, $first_asset = null) @@ -597,7 +623,7 @@ class Asset extends Depreciable * Gets the lowercased name of the type of target the asset is assigned to * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string */ public function assignedType() @@ -609,10 +635,11 @@ class Asset extends Depreciable /** * This is annoying, but because we don't say "assets" in our route names, we have to make an exception here + * * @todo - normalize the route names - API endpoint URLS can stay the same * * @author [A. Gianotto] [] - * @since [v6.1.0] + * @since [v6.1.0] * @return string */ public function targetShowRoute() @@ -631,7 +658,7 @@ class Asset extends Depreciable * Get the asset's location based on default RTD location * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function defaultLoc() @@ -646,7 +673,7 @@ class Asset extends Depreciable * and if not, check for an image uploaded to the asset model. * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return string | false */ public function getImageUrl() @@ -655,6 +682,8 @@ class Asset extends Depreciable return Storage::disk('public')->url(app('assets_upload_path').e($this->image)); } elseif ($this->model && ! empty($this->model->image)) { return Storage::disk('public')->url(app('models_upload_path').e($this->model->image)); + } elseif ($this->model?->category && ! empty($this->model->category->image)) { + return Storage::disk('public')->url(app('categories_upload_path').e($this->model->category->image)); } return false; @@ -665,22 +694,22 @@ class Asset extends Depreciable * Get the asset's logs * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() { return $this->hasMany(\App\Models\Actionlog::class, 'item_id') - ->where('item_type', '=', self::class) - ->orderBy('created_at', 'desc') - ->withTrashed(); + ->where('item_type', '=', self::class) + ->orderBy('created_at', 'desc') + ->withTrashed(); } /** * Get the list of checkouts for this asset * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function checkouts() @@ -695,7 +724,7 @@ class Asset extends Depreciable * Get the list of audits for this asset * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function audits() @@ -709,7 +738,7 @@ class Asset extends Depreciable * Get the list of checkins for this asset * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function checkins() @@ -724,7 +753,7 @@ class Asset extends Depreciable * Get the asset's user requests * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function userRequests() @@ -739,21 +768,21 @@ class Asset extends Depreciable /** * Get maintenances for this asset * - * @author Vincent Sposato - * @since 1.0 + * @author Vincent Sposato + * @since 1.0 * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function assetmaintenances() + public function maintenances() { - return $this->hasMany(\App\Models\AssetMaintenance::class, 'asset_id') - ->orderBy('created_at', 'desc'); + return $this->hasMany(\App\Models\Maintenance::class, 'asset_id') + ->orderBy('created_at', 'desc'); } /** * Get user who created the item * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -767,7 +796,7 @@ class Asset extends Depreciable * Establishes the asset -> status relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetstatus() @@ -779,7 +808,7 @@ class Asset extends Depreciable * Establishes the asset -> model relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function model() @@ -790,9 +819,9 @@ class Asset extends Depreciable /** * Return the assets with a warranty expiring within x days * - * @param $days + * @param $days * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return mixed */ public static function getExpiringWarrantee($days = 30) @@ -803,9 +832,12 @@ class Asset extends Depreciable ->whereNotNull('warranty_months') ->whereNotNull('purchase_date') ->whereNull('deleted_at') - ->whereRaw('DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL ' + ->NotArchived() + ->whereRaw( + 'DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL ' . $days - . ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()') + . ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()' + ) ->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)') ->get(); } @@ -815,7 +847,7 @@ class Asset extends Depreciable * Establishes the asset -> assigned licenses relationship * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -827,7 +859,7 @@ class Asset extends Depreciable * Establishes the asset -> license seats relationship * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenseseats() @@ -839,7 +871,7 @@ class Asset extends Depreciable * Establishes the asset -> aupplier relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function supplier() @@ -851,7 +883,7 @@ class Asset extends Depreciable * Establishes the asset -> location relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -864,7 +896,7 @@ class Asset extends Depreciable * Get the next autoincremented asset tag * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string | false */ public static function autoincrement_asset(int $additional_increment = 0) @@ -890,7 +922,7 @@ class Asset extends Depreciable * We'll add the zerofill and prefixes on the fly as we generate the number. * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return int */ public static function nextAutoIncrement($assets) @@ -901,12 +933,10 @@ class Asset extends Depreciable foreach ($assets as $asset) { $results = preg_match("/\d+$/", $asset['asset_tag'], $matches); - if ($results) - { + if ($results) { $number = $matches[0]; - if ($number > $max) - { + if ($number > $max) { $max = $number; } } @@ -923,7 +953,7 @@ class Asset extends Depreciable * We'll add the zerofill and prefixes on the fly as we generate the number. * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string */ public static function zerofill($num, $zerofill = 3) @@ -936,7 +966,7 @@ class Asset extends Depreciable * asset model category * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return bool */ public function checkin_email() @@ -950,7 +980,7 @@ class Asset extends Depreciable * Determine whether this asset requires acceptance by the assigned user * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return bool */ public function requireAcceptance() @@ -959,6 +989,7 @@ class Asset extends Depreciable return $this->model->category->require_acceptance; } + return false; } @@ -966,7 +997,7 @@ class Asset extends Depreciable * Determine whether this asset's next audit date is before the last audit date * * @return bool - * @since [v6.4.1] + * @since [v6.4.1] * @author [A. Gianotto] [] * */ public function checkInvalidNextAuditDate() @@ -994,16 +1025,16 @@ class Asset extends Depreciable * checks for a settings level EULA * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string | false */ public function getEula() { if (($this->model) && ($this->model->category)) { - if (($this->model->category->eula_text) && ($this->model->category->use_default_eula === 0)) { + if (($this->model->category->eula_text) && ($this->model->category->use_default_eula == 0)) { return Helper::parseEscapedMarkedown($this->model->category->eula_text); - } elseif ($this->model->category->use_default_eula === 1) { + } elseif ($this->model->category->use_default_eula == 1) { return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text); } else { @@ -1013,7 +1044,8 @@ class Asset extends Depreciable return false; } - public function getComponentCost(){ + public function getComponentCost() + { $cost = 0; foreach($this->components as $component) { $cost += $component->pivot->assigned_qty*$component->purchase_cost; @@ -1033,7 +1065,7 @@ class Asset extends Depreciable * This is kind of dumb and confusing, since we already cast it that way AND it's a date field * in the database, but here we are. * - * @param $value + * @param $value * @return void */ @@ -1083,7 +1115,7 @@ class Asset extends Depreciable * * This will also correctly parse a 1/0 if "true"/"false" is passed. * - * @param $value + * @param $value * @return void */ @@ -1097,16 +1129,16 @@ class Asset extends Depreciable /** - * ----------------------------------------------- - * BEGIN QUERY SCOPES - * ----------------------------------------------- - **/ + * ----------------------------------------------- + * BEGIN QUERY SCOPES + * ----------------------------------------------- + **/ /** * Run additional, advanced searches. * * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $terms The search terms + * @param array $terms The search terms * @return \Illuminate\Database\Eloquent\Builder */ public function advancedTextSearch(Builder $query, array $terms) @@ -1115,31 +1147,38 @@ class Asset extends Depreciable /** * Assigned user */ - $query = $query->leftJoin('users as assets_users', function ($leftJoin) { - $leftJoin->on('assets_users.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', User::class); - }); + $query = $query->leftJoin( + 'users as assets_users', function ($leftJoin) { + $leftJoin->on('assets_users.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', User::class); + } + ); foreach ($terms as $term) { $query = $query ->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%') + ->orWhere('assets_users.jobtitle', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.username', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%') - ->orWhereMultipleColumns([ + ->orWhereMultipleColumns( + [ 'assets_users.first_name', 'assets_users.last_name', - ], $term); + ], $term + ); } /** * Assigned location */ - $query = $query->leftJoin('locations as assets_locations', function ($leftJoin) { - $leftJoin->on('assets_locations.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', Location::class); - }); + $query = $query->leftJoin( + 'locations as assets_locations', function ($leftJoin) { + $leftJoin->on('assets_locations.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', Location::class); + } + ); foreach ($terms as $term) { @@ -1149,10 +1188,12 @@ class Asset extends Depreciable /** * Assigned assets */ - $query = $query->leftJoin('assets as assigned_assets', function ($leftJoin) { - $leftJoin->on('assigned_assets.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', self::class); - }); + $query = $query->leftJoin( + 'assets as assigned_assets', function ($leftJoin) { + $leftJoin->on('assigned_assets.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', self::class); + } + ); foreach ($terms as $term) { $query = $query->orWhere('assigned_assets.name', 'LIKE', '%'.$term.'%'); @@ -1164,12 +1205,12 @@ class Asset extends Depreciable /** - * Query builder scope for hardware - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope for hardware + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeHardware($query) { @@ -1177,101 +1218,121 @@ class Asset extends Depreciable } /** - * Query builder scope for pending assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope for pending assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopePending($query) { - return $query->whereHas('assetstatus', function ($query) { - $query->where('deployable', '=', 0) - ->where('pending', '=', 1) - ->where('archived', '=', 0); - }); + return $query->whereHas( + 'assetstatus', function ($query) { + $query->where('deployable', '=', 0) + ->where('pending', '=', 1) + ->where('archived', '=', 0); + } + ); } /** - * Query builder scope for searching location - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope for searching location + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeAssetsByLocation($query, $location) { - return $query->where(function ($query) use ($location) { - $query->whereHas('assignedTo', function ($query) use ($location) { - $query->where([ - ['users.location_id', '=', $location->id], - ['assets.assigned_type', '=', User::class], - ])->orWhere([ - ['locations.id', '=', $location->id], - ['assets.assigned_type', '=', Location::class], - ])->orWhere([ - ['assets.rtd_location_id', '=', $location->id], - ['assets.assigned_type', '=', self::class], - ]); - })->orWhere(function ($query) use ($location) { - $query->where('assets.rtd_location_id', '=', $location->id); - $query->whereNull('assets.assigned_to'); - }); - }); + return $query->where( + function ($query) use ($location) { + $query->whereHas( + 'assignedTo', function ($query) use ($location) { + $query->where( + [ + ['users.location_id', '=', $location->id], + ['assets.assigned_type', '=', User::class], + ] + )->orWhere( + [ + ['locations.id', '=', $location->id], + ['assets.assigned_type', '=', Location::class], + ] + )->orWhere( + [ + ['assets.rtd_location_id', '=', $location->id], + ['assets.assigned_type', '=', self::class], + ] + ); + } + )->orWhere( + function ($query) use ($location) { + $query->where('assets.rtd_location_id', '=', $location->id); + $query->whereNull('assets.assigned_to'); + } + ); + } + ); } /** - * Query builder scope for RTD assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope for RTD assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeRTD($query) { return $query->whereNull('assets.assigned_to') - ->whereHas('assetstatus', function ($query) { - $query->where('deployable', '=', 1) - ->where('pending', '=', 0) - ->where('archived', '=', 0); - }); + ->whereHas( + 'assetstatus', function ($query) { + $query->where('deployable', '=', 1) + ->where('pending', '=', 0) + ->where('archived', '=', 0); + } + ); } - /** - * Query builder scope for Undeployable assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope for Undeployable assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeUndeployable($query) { - return $query->whereHas('assetstatus', function ($query) { - $query->where('deployable', '=', 0) - ->where('pending', '=', 0) - ->where('archived', '=', 0); - }); + return $query->whereHas( + 'assetstatus', function ($query) { + $query->where('deployable', '=', 0) + ->where('pending', '=', 0) + ->where('archived', '=', 0); + } + ); } /** * Query builder scope for non-Archived assets * - * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param \Illuminate\Database\Query\Builder $query Query builder instance * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeNotArchived($query) { - return $query->whereHas('assetstatus', function ($query) { - $query->where('archived', '=', 0); - }); + return $query->whereHas( + 'assetstatus', function ($query) { + $query->where('archived', '=', 0); + } + ); } /** @@ -1291,15 +1352,15 @@ class Asset extends Depreciable * now = May 4, 2019 * * @author A. Gianotto - * @since v4.6.16 - * @param Setting $settings + * @since v4.6.16 + * @param Setting $settings * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeDueForAudit($query, $settings) { - $interval = $settings->audit_warning_days ?? 0; + $interval = (int) $settings->audit_warning_days ?? 0; $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); @@ -1317,8 +1378,8 @@ class Asset extends Depreciable * for an upcoming API call for retrieving a report on overdue assets. * * @author A. Gianotto - * @since v4.6.16 - * @param Setting $settings + * @since v4.6.16 + * @param Setting $settings * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1339,8 +1400,8 @@ class Asset extends Depreciable * for an upcoming API call for retrieving a report on assets that will need to be audited. * * @author A. Gianotto - * @since v4.6.16 - * @param Setting $settings + * @since v4.6.16 + * @param Setting $settings * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1348,11 +1409,15 @@ class Asset extends Depreciable public function scopeDueOrOverdueForAudit($query, $settings) { - return $query->where(function ($query) { - $query->OverdueForAudit(); - })->orWhere(function ($query) use ($settings) { - $query->DueForAudit($settings); - }); + return $query->where( + function ($query) { + $query->OverdueForAudit(); + } + )->orWhere( + function ($query) use ($settings) { + $query->DueForAudit($settings); + } + ); } @@ -1361,13 +1426,13 @@ class Asset extends Depreciable * and settings.audit_warning_days. It checks to see if assets.expected_checkin is now * * @author A. Gianotto - * @since v6.4.0 + * @since v6.4.0 * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeDueForCheckin($query, $settings) { - $interval = $settings->due_checkin_days ?? 0; + $interval = (int) $settings->due_checkin_days ?? 0; $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); @@ -1382,7 +1447,7 @@ class Asset extends Depreciable * Query builder scope for Assets that are overdue for checkin OR overdue * * @author A. Gianotto - * @since v6.4.0 + * @since v6.4.0 * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOverdueForCheckin($query) @@ -1398,16 +1463,20 @@ class Asset extends Depreciable * Query builder scope for Assets that are due for checkin OR overdue * * @author A. Gianotto - * @since v6.4.0 + * @since v6.4.0 * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeDueOrOverdueForCheckin($query, $settings) { - return $query->where(function ($query) { - $query->OverdueForCheckin(); - })->orWhere(function ($query) use ($settings) { - $query->DueForCheckin($settings); - }); + return $query->where( + function ($query) { + $query->OverdueForCheckin(); + } + )->orWhere( + function ($query) use ($settings) { + $query->DueForCheckin($settings); + } + ); } @@ -1418,7 +1487,7 @@ class Asset extends Depreciable * has chosen to not display archived assets in their regular lists * and views, it will return the correct number. * - * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param \Illuminate\Database\Query\Builder $query Query builder instance * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1427,115 +1496,123 @@ class Asset extends Depreciable { if (Setting::getSettings()->show_archived_in_list!=1) { - return $query->whereHas('assetstatus', function ($query) { - $query->where('archived', '=', 0); - }); + return $query->whereHas( + 'assetstatus', function ($query) { + $query->where('archived', '=', 0); + } + ); } else { return $query; } } - /** - * Query builder scope for Archived assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope for Archived assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeArchived($query) { - return $query->whereHas('assetstatus', function ($query) { - $query->where('deployable', '=', 0) - ->where('pending', '=', 0) - ->where('archived', '=', 1); - }); + return $query->whereHas( + 'assetstatus', function ($query) { + $query->where('deployable', '=', 0) + ->where('pending', '=', 0) + ->where('archived', '=', 1); + } + ); } - /** - * Query builder scope for Deployed assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope for Deployed assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeDeployed($query) { return $query->where('assigned_to', '>', '0'); } - /** - * Query builder scope for Requestable assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope for Requestable assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeRequestableAssets($query): Builder { $table = $query->getModel()->getTable(); return Company::scopeCompanyables($query->where($table.'.requestable', '=', 1)) - ->whereHas('assetstatus', function ($query) { - $query->where(function ($query) { - $query->where('deployable', '=', 1) - ->where('archived', '=', 0); // you definitely can't request something that's archived - })->orWhere('pending', '=', 1); // we've decided that even though an asset may be 'pending', you can still request it - }); + ->whereHas( + 'assetstatus', function ($query) { + $query->where( + function ($query) { + $query->where('deployable', '=', 1) + ->where('archived', '=', 0); // you definitely can't request something that's archived + } + )->orWhere('pending', '=', 1); // we've decided that even though an asset may be 'pending', you can still request it + } + ); } /** - * scopeInModelList - * Get all assets in the provided listing of model ids - * - * @param $query - * @param array $modelIdListing - * - * @return mixed - * @author Vincent Sposato - * @version v1.0 - */ + * scopeInModelList + * Get all assets in the provided listing of model ids + * + * @param $query + * @param array $modelIdListing + * + * @return mixed + * @author Vincent Sposato + * @version v1.0 + */ public function scopeInModelList($query, array $modelIdListing) { return $query->whereIn('assets.model_id', $modelIdListing); } - /** - * Query builder scope to get not-yet-accepted assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope to get not-yet-accepted assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeNotYetAccepted($query) { return $query->where('accepted', '=', 'pending'); } - /** - * Query builder scope to get rejected assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope to get rejected assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeRejected($query) { return $query->where('accepted', '=', 'rejected'); } - /** - * Query builder scope to get accepted assets - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope to get accepted assets + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeAccepted($query) { return $query->where('accepted', '=', 'accepted'); @@ -1544,8 +1621,8 @@ class Asset extends Depreciable /** * Query builder scope to search on text for complex Bootstrap Tables API. * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $search Search term + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $search Search term * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1553,69 +1630,99 @@ class Asset extends Depreciable { $search = explode(' OR ', $search); - return $query->leftJoin('users as assets_users', function ($leftJoin) { - $leftJoin->on('assets_users.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', User::class); - })->leftJoin('locations as assets_locations', function ($leftJoin) { - $leftJoin->on('assets_locations.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', Location::class); - })->leftJoin('assets as assigned_assets', function ($leftJoin) { - $leftJoin->on('assigned_assets.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', self::class); - })->where(function ($query) use ($search) { - foreach ($search as $search) { - $query->whereHas('model', function ($query) use ($search) { - $query->whereHas('category', function ($query) use ($search) { - $query->where(function ($query) use ($search) { - $query->where('categories.name', 'LIKE', '%'.$search.'%') - ->orWhere('models.name', 'LIKE', '%'.$search.'%') - ->orWhere('models.model_number', 'LIKE', '%'.$search.'%'); - }); - }); - })->orWhereHas('model', function ($query) use ($search) { - $query->whereHas('manufacturer', function ($query) use ($search) { - $query->where(function ($query) use ($search) { - $query->where('manufacturers.name', 'LIKE', '%'.$search.'%'); - }); - }); - })->orWhere(function ($query) use ($search) { - $query->where('assets_users.first_name', 'LIKE', '%'.$search.'%') - ->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%') - ->orWhereMultipleColumns([ - 'assets_users.first_name', - 'assets_users.last_name', - ], $search) - ->orWhere('assets_users.username', 'LIKE', '%'.$search.'%') - ->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%') - ->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%'); - })->orWhere('assets.name', 'LIKE', '%'.$search.'%') - ->orWhere('assets.asset_tag', 'LIKE', '%'.$search.'%') - ->orWhere('assets.serial', 'LIKE', '%'.$search.'%') - ->orWhere('assets.order_number', 'LIKE', '%'.$search.'%') - ->orWhere('assets.notes', 'LIKE', '%'.$search.'%'); + return $query->leftJoin( + 'users as assets_users', function ($leftJoin) { + $leftJoin->on('assets_users.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', User::class); } + )->leftJoin( + 'locations as assets_locations', function ($leftJoin) { + $leftJoin->on('assets_locations.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', Location::class); + } + )->leftJoin( + 'assets as assigned_assets', function ($leftJoin) { + $leftJoin->on('assigned_assets.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', self::class); + } + )->where( + function ($query) use ($search) { + foreach ($search as $search) { + $query->whereHas( + 'model', function ($query) use ($search) { + $query->whereHas( + 'category', function ($query) use ($search) { + $query->where( + function ($query) use ($search) { + $query->where('categories.name', 'LIKE', '%'.$search.'%') + ->orWhere('models.name', 'LIKE', '%'.$search.'%') + ->orWhere('models.model_number', 'LIKE', '%'.$search.'%'); + } + ); + } + ); + } + )->orWhereHas( + 'model', function ($query) use ($search) { + $query->whereHas( + 'manufacturer', function ($query) use ($search) { + $query->where( + function ($query) use ($search) { + $query->where('manufacturers.name', 'LIKE', '%'.$search.'%'); + } + ); + } + ); + } + )->orWhere( + function ($query) use ($search) { + $query->where('assets_users.first_name', 'LIKE', '%'.$search.'%') + ->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%') + ->orWhere('assets_users.username', 'LIKE', '%'.$search.'%') + ->orWhere('assets_users.jobtitle', 'LIKE', '%'.$search.'%') + ->orWhereMultipleColumns( + [ + 'assets_users.first_name', + 'assets_users.last_name', + 'assets_users.jobtitle', + ], $search + ) + ->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%') + ->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%'); + } + )->orWhere('assets.name', 'LIKE', '%'.$search.'%') + ->orWhere('assets.asset_tag', 'LIKE', '%'.$search.'%') + ->orWhere('assets.serial', 'LIKE', '%'.$search.'%') + ->orWhere('assets.order_number', 'LIKE', '%'.$search.'%') + ->orWhere('assets.notes', 'LIKE', '%'.$search.'%'); + } - })->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug + } + )->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug } /** * Query builder scope to search the department ID of users assigned to assets * * @author [A. Gianotto] [] - * @since [v5.0] + * @since [v5.0] * @return string | false * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeCheckedOutToTargetInDepartment($query, $search) { - return $query->leftJoin('users as assets_dept_users', function ($leftJoin) { - $leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to') - ->where('assets.assigned_type', '=', User::class); - })->where(function ($query) use ($search) { + return $query->leftJoin( + 'users as assets_dept_users', function ($leftJoin) { + $leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to') + ->where('assets.assigned_type', '=', User::class); + } + )->where( + function ($query) use ($search) { $query->whereIn('assets_dept_users.department_id', $search); - })->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug + } + )->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug } @@ -1623,189 +1730,229 @@ class Asset extends Depreciable /** * Query builder scope to search on text filters for complex Bootstrap Tables API * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $filter JSON array of search keys and terms + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $filter JSON array of search keys and terms * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeByFilter($query, $filter) { - return $query->where(function ($query) use ($filter) { - foreach ($filter as $key => $search_val) { + return $query->where( + function ($query) use ($filter) { + foreach ($filter as $key => $search_val) { + + $fieldname = str_replace('custom_fields.', '', $key); + + if ($fieldname == 'asset_tag') { + $query->where('assets.asset_tag', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'name') { + $query->where('assets.name', 'LIKE', '%'.$search_val.'%'); + } + + + if ($fieldname =='serial') { + $query->where('assets.serial', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'purchase_date') { + $query->where('assets.purchase_date', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'purchase_cost') { + $query->where('assets.purchase_cost', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'notes') { + $query->where('assets.notes', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'order_number') { + $query->where('assets.order_number', 'LIKE', '%'.$search_val.'%'); + } + + if ($fieldname == 'status_label') { + $query->whereHas( + 'assetstatus', function ($query) use ($search_val) { + $query->where('status_labels.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + + if ($fieldname == 'location') { + $query->whereHas( + 'location', function ($query) use ($search_val) { + $query->where('locations.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + + if ($fieldname == 'rtd_location') { + $query->whereHas( + 'defaultLoc', function ($query) use ($search_val) { + $query->where('locations.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + + if ($fieldname =='assigned_to') { + $query->whereHasMorph( + 'assignedTo', [User::class], function ($query) use ($search_val) { + $query->where( + function ($query) use ($search_val) { + $query->where('users.first_name', 'LIKE', '%'.$search_val.'%') + ->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + + + if ($fieldname == 'manufacturer') { + $query->whereHas( + 'model', function ($query) use ($search_val) { + $query->whereHas( + 'manufacturer', function ($query) use ($search_val) { + $query->where( + function ($query) use ($search_val) { + $query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + ); + } + + if ($fieldname == 'category') { + $query->whereHas( + 'model', function ($query) use ($search_val) { + $query->whereHas( + 'category', function ($query) use ($search_val) { + $query->where( + function ($query) use ($search_val) { + $query->where('categories.name', 'LIKE', '%'.$search_val.'%') + ->orWhere('models.name', 'LIKE', '%'.$search_val.'%') + ->orWhere('models.model_number', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + ); + } + + if ($fieldname == 'model') { + $query->where( + function ($query) use ($search_val) { + $query->whereHas( + 'model', function ($query) use ($search_val) { + $query->where('models.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + + if ($fieldname == 'model_number') { + $query->where( + function ($query) use ($search_val) { + $query->whereHas( + 'model', function ($query) use ($search_val) { + $query->where('models.model_number', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + + + if ($fieldname == 'company') { + $query->where( + function ($query) use ($search_val) { + $query->whereHas( + 'company', function ($query) use ($search_val) { + $query->where('companies.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + + if ($fieldname == 'supplier') { + $query->where( + function ($query) use ($search_val) { + $query->whereHas( + 'supplier', function ($query) use ($search_val) { + $query->where('suppliers.name', 'LIKE', '%'.$search_val.'%'); + } + ); + } + ); + } + + + /** + * THIS CLUNKY BIT IS VERY IMPORTANT + * + * Although inelegant, this section matters a lot when querying against fields that do not + * exist on the asset table. There's probably a better way to do this moving forward, for + * example using the Schema:: methods to determine whether or not a column actually exists, + * or even just using the $searchableRelations variable earlier in this file. + * + * In short, this set of statements tells the query builder to ONLY query against an + * actual field that's being passed if it doesn't meet known relational fields. This + * allows us to query custom fields directly in the assets table + * (regardless of their name) and *skip* any fields that we already know can only be + * searched through relational searches that we do earlier in this method. + * + * For example, we do not store "location" as a field on the assets table, we store + * that relationship through location_id on the assets table, therefore querying + * assets.location would fail, as that field doesn't exist -- plus we're already searching + * against those relationships earlier in this method. + * + * - snipe + */ + + if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier') + && ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer') + ) { + $query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%'); + } - $fieldname = str_replace('custom_fields.', '', $key); - if ($fieldname == 'asset_tag') { - $query->where('assets.asset_tag', 'LIKE', '%'.$search_val.'%'); } - if ($fieldname == 'name') { - $query->where('assets.name', 'LIKE', '%'.$search_val.'%'); - } - - - if ($fieldname =='serial') { - $query->where('assets.serial', 'LIKE', '%'.$search_val.'%'); - } - - if ($fieldname == 'purchase_date') { - $query->where('assets.purchase_date', 'LIKE', '%'.$search_val.'%'); - } - - if ($fieldname == 'purchase_cost') { - $query->where('assets.purchase_cost', 'LIKE', '%'.$search_val.'%'); - } - - if ($fieldname == 'notes') { - $query->where('assets.notes', 'LIKE', '%'.$search_val.'%'); - } - - if ($fieldname == 'order_number') { - $query->where('assets.order_number', 'LIKE', '%'.$search_val.'%'); - } - - if ($fieldname == 'status_label') { - $query->whereHas('assetstatus', function ($query) use ($search_val) { - $query->where('status_labels.name', 'LIKE', '%'.$search_val.'%'); - }); - } - - if ($fieldname == 'location') { - $query->whereHas('location', function ($query) use ($search_val) { - $query->where('locations.name', 'LIKE', '%'.$search_val.'%'); - }); - } - - if ($fieldname == 'rtd_location') { - $query->whereHas('defaultLoc', function ($query) use ($search_val) { - $query->where('locations.name', 'LIKE', '%'.$search_val.'%'); - }); - } - - if ($fieldname =='assigned_to') { - $query->whereHasMorph('assignedTo', [User::class], function ($query) use ($search_val) { - $query->where(function ($query) use ($search_val) { - $query->where('users.first_name', 'LIKE', '%'.$search_val.'%') - ->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%'); - }); - }); - } - - - if ($fieldname == 'manufacturer') { - $query->whereHas('model', function ($query) use ($search_val) { - $query->whereHas('manufacturer', function ($query) use ($search_val) { - $query->where(function ($query) use ($search_val) { - $query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%'); - }); - }); - }); - } - - if ($fieldname == 'category') { - $query->whereHas('model', function ($query) use ($search_val) { - $query->whereHas('category', function ($query) use ($search_val) { - $query->where(function ($query) use ($search_val) { - $query->where('categories.name', 'LIKE', '%'.$search_val.'%') - ->orWhere('models.name', 'LIKE', '%'.$search_val.'%') - ->orWhere('models.model_number', 'LIKE', '%'.$search_val.'%'); - }); - }); - }); - } - - if ($fieldname == 'model') { - $query->where(function ($query) use ($search_val) { - $query->whereHas('model', function ($query) use ($search_val) { - $query->where('models.name', 'LIKE', '%'.$search_val.'%'); - }); - }); - } - - if ($fieldname == 'model_number') { - $query->where(function ($query) use ($search_val) { - $query->whereHas('model', function ($query) use ($search_val) { - $query->where('models.model_number', 'LIKE', '%'.$search_val.'%'); - }); - }); - } - - - if ($fieldname == 'company') { - $query->where(function ($query) use ($search_val) { - $query->whereHas('company', function ($query) use ($search_val) { - $query->where('companies.name', 'LIKE', '%'.$search_val.'%'); - }); - }); - } - - if ($fieldname == 'supplier') { - $query->where(function ($query) use ($search_val) { - $query->whereHas('supplier', function ($query) use ($search_val) { - $query->where('suppliers.name', 'LIKE', '%'.$search_val.'%'); - }); - }); - } - - - /** - * THIS CLUNKY BIT IS VERY IMPORTANT - * - * Although inelegant, this section matters a lot when querying against fields that do not - * exist on the asset table. There's probably a better way to do this moving forward, for - * example using the Schema:: methods to determine whether or not a column actually exists, - * or even just using the $searchableRelations variable earlier in this file. - * - * In short, this set of statements tells the query builder to ONLY query against an - * actual field that's being passed if it doesn't meet known relational fields. This - * allows us to query custom fields directly in the assets table - * (regardless of their name) and *skip* any fields that we already know can only be - * searched through relational searches that we do earlier in this method. - * - * For example, we do not store "location" as a field on the assets table, we store - * that relationship through location_id on the assets table, therefore querying - * assets.location would fail, as that field doesn't exist -- plus we're already searching - * against those relationships earlier in this method. - * - * - snipe - * - */ - - if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier') - && ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) { - $query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%'); - } - } - - - }); + ); } /** - * Query builder scope to order on model - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on model + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderModels($query, $order) { return $query->join('models as asset_models', 'assets.model_id', '=', 'asset_models.id')->orderBy('asset_models.name', $order); } /** - * Query builder scope to order on model number - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on model number + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderModelNumber($query, $order) { return $query->leftJoin('models as model_number_sort', 'assets.model_id', '=', 'model_number_sort.id')->orderBy('model_number_sort.model_number', $order); @@ -1815,8 +1962,8 @@ class Asset extends Depreciable /** * Query builder scope to order on created_by name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1827,39 +1974,39 @@ class Asset extends Depreciable /** - * Query builder scope to order on assigned user - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on assigned user + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderAssigned($query, $order) { return $query->leftJoin('users as users_sort', 'assets.assigned_to', '=', 'users_sort.id')->select('assets.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order); } /** - * Query builder scope to order on status - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on status + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderStatus($query, $order) { return $query->join('status_labels as status_sort', 'assets.status_id', '=', 'status_sort.id')->orderBy('status_sort.name', $order); } /** - * Query builder scope to order on company - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on company + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderCompany($query, $order) { return $query->leftJoin('companies as company_sort', 'assets.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order); @@ -1869,8 +2016,8 @@ class Asset extends Depreciable /** * Query builder scope to return results of a category * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1878,34 +2025,34 @@ class Asset extends Depreciable { return $query->join('models as category_models', 'assets.model_id', '=', 'category_models.id') ->join('categories', 'category_models.category_id', '=', 'categories.id') - ->whereIn('category_models.category_id', (!is_array($category_id) ? explode(',',$category_id): $category_id)); + ->whereIn('category_models.category_id', (!is_array($category_id) ? explode(',', $category_id): $category_id)); //->whereIn('category_models.category_id', $category_id); } /** * Query builder scope to return results of a manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeByManufacturer($query, $manufacturer_id) { return $query->join('models', 'assets.model_id', '=', 'models.id') - ->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->whereIn('models.manufacturer_id', (!is_array($manufacturer_id) ? explode(',',$manufacturer_id): $manufacturer_id)); + ->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->whereIn('models.manufacturer_id', (!is_array($manufacturer_id) ? explode(',', $manufacturer_id): $manufacturer_id)); } /** - * Query builder scope to order on category - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + * Query builder scope to order on category + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderCategory($query, $order) { return $query->join('models as order_model_category', 'assets.model_id', '=', 'order_model_category.id') @@ -1917,8 +2064,8 @@ class Asset extends Depreciable /** * Query builder scope to order on manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1929,14 +2076,14 @@ class Asset extends Depreciable ->orderBy('manufacturer_order.name', $order); } - /** - * Query builder scope to order on location - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope to order on location + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderLocation($query, $order) { return $query->leftJoin('locations as asset_locations', 'asset_locations.id', '=', 'assets.location_id')->orderBy('asset_locations.name', $order); @@ -1944,8 +2091,9 @@ class Asset extends Depreciable /** * Query builder scope to order on default - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1958,8 +2106,8 @@ class Asset extends Depreciable /** * Query builder scope to order on supplier name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -1968,29 +2116,47 @@ class Asset extends Depreciable return $query->leftJoin('suppliers as suppliers_assets', 'assets.supplier_id', '=', 'suppliers_assets.id')->orderBy('suppliers_assets.name', $order); } + /** + * Query builder scope to order on supplier name + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderByJobTitle($query, $order) + { + return $query->leftJoin('users as users_sort', 'assets.assigned_to', '=', 'users_sort.id')->select('assets.*')->orderBy('users_sort.jobtitle', $order); + } + /** * Query builder scope to search on location ID * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $search Search term + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $search Search term * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeByLocationId($query, $search) { - return $query->where(function ($query) use ($search) { - $query->whereHas('location', function ($query) use ($search) { - $query->where('locations.id', '=', $search); - }); - }); + return $query->where( + function ($query) use ($search) { + $query->whereHas( + 'location', function ($query) use ($search) { + $query->where('locations.id', '=', $search); + } + ); + } + ); } /** * Query builder scope to search on depreciation name - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $search Search term + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $search Search term * * @return \Illuminate\Database\Query\Builder Modified query builder */ diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index 8d60474b96..29308fdab3 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -16,7 +17,7 @@ use App\Http\Traits\TwoColumnUniqueUndeletedTrait; * Model for Asset Models. Asset Models contain higher level * attributes that are common among the same type of asset. * - * @version v1.0 + * @version v1.0 */ class AssetModel extends SnipeModel { @@ -24,6 +25,7 @@ class AssetModel extends SnipeModel use SoftDeletes; use Loggable, Requestable, Presentable; use TwoColumnUniqueUndeletedTrait; + use HasUploads; /** * Whether the model should inject its identifier to the unique @@ -69,6 +71,7 @@ class AssetModel extends SnipeModel 'name', 'notes', 'requestable', + 'require_serial' ]; use Searchable; @@ -96,14 +99,22 @@ class AssetModel extends SnipeModel 'manufacturer' => ['name'], ]; + protected static function booted(): void + { + static::forceDeleted(function (AssetModel $assetModel) { + $assetModel->requests()->forceDelete(); + }); - + static::softDeleted(function (AssetModel $assetModel) { + $assetModel->requests()->delete(); + }); + } /** * Establishes the model -> assets relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -115,7 +126,7 @@ class AssetModel extends SnipeModel * Establishes the model -> category relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function category() @@ -127,7 +138,7 @@ class AssetModel extends SnipeModel * Establishes the model -> depreciation relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function depreciation() @@ -139,7 +150,7 @@ class AssetModel extends SnipeModel * Establishes the model -> manufacturer relationship * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manufacturer() @@ -151,7 +162,7 @@ class AssetModel extends SnipeModel * Establishes the model -> fieldset relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function fieldset() @@ -161,14 +172,14 @@ class AssetModel extends SnipeModel public function customFields() { - return $this->fieldset()->first()->fields(); + return $this->fieldset()->first()->fields(); } /** * Establishes the model -> custom field default values relationship * * @author hannah tinkler - * @since [v4.3] + * @since [v4.3] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function defaultValues() @@ -182,7 +193,7 @@ class AssetModel extends SnipeModel * @todo this should probably be moved * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function getImageUrl() @@ -199,7 +210,7 @@ class AssetModel extends SnipeModel * Checks if the model is deletable * * @author A. Gianotto - * @since [v6.3.4] + * @since [v6.3.4] * @return bool */ public function isDeletable() @@ -209,27 +220,12 @@ class AssetModel extends SnipeModel && ($this->deleted_at == ''); } - /** - * Get uploads for this model - * - * @author [A. Gianotto] [] - * @since [v4.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany('\App\Models\Actionlog', 'item_id') - ->where('item_type', '=', AssetModel::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } /** * Get user who created the item * * @author [A. Gianotto] [] - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -248,10 +244,10 @@ class AssetModel extends SnipeModel * scopeInCategory * Get all models that are in the array of category ids * - * @param $query + * @param $query * @param array $categoryIdListing * - * @return mixed + * @return mixed * @author Vincent Sposato * @version v1.0 */ @@ -264,9 +260,9 @@ class AssetModel extends SnipeModel * scopeRequestable * Get all models that are requestable by a user. * - * @param $query + * @param $query * - * @return $query + * @return $query * @author Daniel Meltzer * @version v3.5 */ @@ -278,8 +274,8 @@ class AssetModel extends SnipeModel /** * Query builder scope to search on text, including catgeory and manufacturer name * - * @param Illuminate\Database\Query\Builder $query Query builder instance - * @param text $search Search term + * @param Illuminate\Database\Query\Builder $query Query builder instance + * @param text $search Search term * * @return Illuminate\Database\Query\Builder Modified query builder */ @@ -287,23 +283,31 @@ class AssetModel extends SnipeModel { return $query->where('models.name', 'LIKE', "%$search%") ->orWhere('model_number', 'LIKE', "%$search%") - ->orWhere(function ($query) use ($search) { - $query->whereHas('category', function ($query) use ($search) { - $query->where('categories.name', 'LIKE', '%'.$search.'%'); - }); - }) - ->orWhere(function ($query) use ($search) { - $query->whereHas('manufacturer', function ($query) use ($search) { - $query->where('manufacturers.name', 'LIKE', '%'.$search.'%'); - }); - }); + ->orWhere( + function ($query) use ($search) { + $query->whereHas( + 'category', function ($query) use ($search) { + $query->where('categories.name', 'LIKE', '%'.$search.'%'); + } + ); + } + ) + ->orWhere( + function ($query) use ($search) { + $query->whereHas( + 'manufacturer', function ($query) use ($search) { + $query->where('manufacturers.name', 'LIKE', '%'.$search.'%'); + } + ); + } + ); } /** * Query builder scope to order on manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -315,8 +319,8 @@ class AssetModel extends SnipeModel /** * Query builder scope to order on category name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -332,7 +336,6 @@ class AssetModel extends SnipeModel /** * Query builder scope to order on created_by name - * */ public function scopeOrderByCreatedByName($query, $order) { diff --git a/app/Models/Category.php b/app/Models/Category.php index cfa83328ab..7d9d7a14ae 100755 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -18,7 +18,7 @@ use Illuminate\Support\Str; * to require acceptance from the user, whether or not to * send a EULA to the user, etc. * - * @version v1.0 + * @version v1.0 */ class Category extends SnipeModel { @@ -32,6 +32,7 @@ class Category extends SnipeModel protected $hidden = ['created_by', 'deleted_at']; protected $casts = [ + 'alert_on_response' => 'boolean', 'created_by' => 'integer', ]; @@ -69,6 +70,7 @@ class Category extends SnipeModel 'eula_text', 'name', 'require_acceptance', + 'alert_on_response', 'use_default_eula', 'created_by', 'notes', @@ -94,12 +96,20 @@ class Category extends SnipeModel * Checks if category can be deleted * * @author [Dan Meltzer] [] - * @since [v5.0] + * @since [v5.0] * @return bool */ public function isDeletable() { + // We have to check for models as well if the category type is asset + if ($this->category_type == 'asset') { + return Gate::allows('delete', $this) + && ($this->itemCount() == 0) + && ($this->models_count == 0) + && ($this->deleted_at == ''); + } + return Gate::allows('delete', $this) && ($this->itemCount() == 0) && ($this->deleted_at == ''); @@ -109,7 +119,7 @@ class Category extends SnipeModel * Establishes the category -> accessories relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() @@ -121,7 +131,7 @@ class Category extends SnipeModel * Establishes the category -> licenses relationship * * @author [A. Gianotto] [] - * @since [v4.3] + * @since [v4.3] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -133,7 +143,7 @@ class Category extends SnipeModel * Establishes the category -> consumables relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() @@ -145,7 +155,7 @@ class Category extends SnipeModel * Establishes the category -> consumables relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function components() @@ -160,7 +170,7 @@ class Category extends SnipeModel * It should only be used in a single category context. * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return int */ public function itemCount() @@ -171,18 +181,18 @@ class Category extends SnipeModel } switch ($this->category_type) { - case 'asset': - return $this->assets->count(); - case 'accessory': - return $this->accessories->count(); - case 'component': - return $this->components->count(); - case 'consumable': - return $this->consumables->count(); - case 'license': - return $this->licenses->count(); - default: - return 0; + case 'asset': + return $this->assets->count(); + case 'accessory': + return $this->accessories->count(); + case 'component': + return $this->components->count(); + case 'consumable': + return $this->consumables->count(); + case 'license': + return $this->licenses->count(); + default: + return 0; } } @@ -191,7 +201,7 @@ class Category extends SnipeModel * Establishes the category -> assets relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -208,8 +218,8 @@ class Category extends SnipeModel * by their category. * * @author [A. Gianotto] [] - * @since [v6.1.0] - * @see \App\Models\Asset::scopeAssetsForShow() + * @since [v6.1.0] + * @see \App\Models\Asset::scopeAssetsForShow() * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function showableAssets() @@ -221,7 +231,7 @@ class Category extends SnipeModel * Establishes the category -> models relationship * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function models() @@ -239,7 +249,7 @@ class Category extends SnipeModel * checks for a settings level EULA * * @author [A. Gianotto] [] - * @since [v2.0] + * @since [v2.0] * @return string | null */ public function getEula() @@ -266,7 +276,7 @@ class Category extends SnipeModel * * This will also correctly parse a 1/0 if "true"/"false" is passed. * - * @param $value + * @param $value * @return void */ public function setCheckinEmailAttribute($value) @@ -283,9 +293,9 @@ class Category extends SnipeModel /** * Query builder scope for whether or not the category requires acceptance * - * @author Vincent Sposato + * @author Vincent Sposato * - * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param \Illuminate\Database\Query\Builder $query Query builder instance * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeRequiresAcceptance($query) diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php index e44a330ebc..f65fb219a8 100644 --- a/app/Models/CheckoutAcceptance.php +++ b/app/Models/CheckoutAcceptance.php @@ -15,6 +15,7 @@ class CheckoutAcceptance extends Model protected $casts = [ 'accepted_at' => 'datetime', 'declined_at' => 'datetime', + 'alert_on_response_id' => 'integer', ]; /** @@ -31,7 +32,19 @@ class CheckoutAcceptance extends Model return array_filter($recipients); } + public function getCheckoutableItemTypeAttribute(): string + { + $type = $this->checkoutable_type; + return match ($type) { + Asset::class => trans('general.asset'), + LicenseSeat::class => trans('general.license'), + Accessory::class => trans('general.accessory'), + Component::class => trans('general.component'), + Consumable::class => trans('general.consumable'), + default => class_basename($type), + }; + } /** * The resource that was is out * @@ -65,7 +78,7 @@ class CheckoutAcceptance extends Model /** * Was the checkoutable checked out to this user? * - * @param User $user + * @param User $user * @return bool */ public function isCheckedOutTo(User $user) @@ -78,7 +91,7 @@ class CheckoutAcceptance extends Model * Do not add stuff here that doesn't have a corresponding column in the * checkout_acceptances table or you'll get an error. * - * @param string $signature_filename + * @param string $signature_filename */ public function accept($signature_filename, $eula = null, $filename = null, $note = null) { @@ -98,7 +111,7 @@ class CheckoutAcceptance extends Model /** * Decline the checkout acceptance * - * @param string $signature_filename + * @param string $signature_filename */ public function decline($signature_filename, $note = null) { @@ -115,8 +128,9 @@ class CheckoutAcceptance extends Model /** * Filter checkout acceptences by the user + * * @param Illuminate\Database\Eloquent\Builder $query - * @param User $user + * @param User $user * @return \Illuminate\Database\Eloquent\Builder */ public function scopeForUser(Builder $query, User $user) @@ -126,6 +140,7 @@ class CheckoutAcceptance extends Model /** * Filter to only get pending acceptances + * * @param Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ diff --git a/app/Models/CheckoutRequest.php b/app/Models/CheckoutRequest.php index d6a85f2972..42512d8fda 100644 --- a/app/Models/CheckoutRequest.php +++ b/app/Models/CheckoutRequest.php @@ -2,11 +2,13 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class CheckoutRequest extends Model { + use HasFactory; use SoftDeletes; protected $fillable = ['user_id']; protected $table = 'checkout_requests'; diff --git a/app/Models/Company.php b/app/Models/Company.php index 43fd396069..72a12aebc6 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -2,22 +2,26 @@ namespace App\Models; +use App\Models\Traits\CompanyableTrait; use App\Models\Traits\Searchable; use App\Presenters\Presentable; -use Illuminate\Support\Facades\Auth; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; -use Watson\Validating\ValidatingTrait; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; +use Watson\Validating\ValidatingTrait; + /** * Model for Companies. * - * @version v1.8 + * @version v1.8 */ final class Company extends SnipeModel { use HasFactory; + use CompanyableTrait; + protected $table = 'companies'; @@ -26,19 +30,19 @@ final class Company extends SnipeModel 'name' => 'required|min:1|max:255|unique:companies,name', 'fax' => 'min:7|max:35|nullable', 'phone' => 'min:7|max:35|nullable', - 'email' => 'email|max:150|nullable', + 'email' => 'email|max:150|nullable', ]; protected $presenter = \App\Presenters\CompanyPresenter::class; use Presentable; /** - * Whether the model should inject it's identifier to the unique - * validation rules before attempting validation. If this property - * is not set in the model it will default to true. - * + * Whether the model should inject it's identifier to the unique + * validation rules before attempting validation. If this property + * is not set in the model it will default to true. + * * @var bool - */ + */ protected $injectUniqueIdentifier = true; use ValidatingTrait; use Searchable; @@ -100,7 +104,7 @@ final class Company extends SnipeModel * account the full multiple company support setting * and if the current user is a super user. * - * @param $unescaped_input + * @param $unescaped_input * @return int|mixed|string|null */ public static function getIdForCurrentUser($unescaped_input) @@ -127,7 +131,7 @@ final class Company extends SnipeModel * Check to see if the current user should have access to the model. * I hate this method and I think it should be refactored. * - * @param $companyable + * @param $companyable * @return bool|void */ public static function isCurrentUserHasAccess($companyable) @@ -146,10 +150,10 @@ final class Company extends SnipeModel if (!is_string($companyable)) { $company_table = $companyable->getModel()->getTable(); try { - // This is primary for the gate:allows-check in location->isDeletable() + // This is primarily for the gate:allows-check in location->isDeletable() // Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled // because this function is called by SnipePermissionsPolicy->before() - if (!$companyable instanceof Company && !Schema::hasColumn($company_table, 'company_id')) { + if (!Schema::hasColumn($company_table, 'company_id')) { return true; } @@ -160,12 +164,19 @@ final class Company extends SnipeModel if (auth()->user()) { - Log::warning('Companyable is '.$companyable); + // Log::warning('Companyable is '.$companyable); $current_user_company_id = auth()->user()->company_id; $companyable_company_id = $companyable->company_id; - return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || auth()->user()->isSuperUser(); + + // Set this to check companyable on company + if ($companyable instanceof Company) { + $companyable_company_id = $companyable->id; + } + return ($current_user_company_id == null) || ($current_user_company_id == $companyable_company_id) || auth()->user()->isSuperUser(); } + return false; + } public static function isCurrentUserAuthorized() @@ -183,7 +194,7 @@ final class Company extends SnipeModel * Checks if company can be deleted * * @author [Dan Meltzer] [] - * @since [v5.0] + * @since [v5.0] * @return bool */ public function isDeletable() @@ -200,7 +211,7 @@ final class Company extends SnipeModel } /** - * @param $unescaped_input + * @param $unescaped_input * @return int|mixed|string|null */ public static function getIdForUser($unescaped_input) @@ -256,14 +267,14 @@ final class Company extends SnipeModel * @todo - refactor that trait to handle the user's model as well. * * @author [A. Gianotto] - * @param $query - * @param $column - * @param $table_name + * @param $query + * @param $column + * @param $table_name * @return mixed */ public static function scopeCompanyables($query, $column = 'company_id', $table_name = null) { - // If not logged in and hitting this, assume we are on the command line and don't scope?' + // If not logged in and hitting this, assume we are on the command line and don't scope? if (! static::isFullMultipleCompanySupportEnabled() || (Auth::hasUser() && auth()->user()->isSuperUser()) || (! Auth::hasUser())) { return $query; } else { @@ -280,11 +291,16 @@ final class Company extends SnipeModel private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null) { + $company_id = null; // Get the company ID of the logged-in user, or set it to null if there is no company associated with the user if (Auth::hasUser()) { $company_id = auth()->user()->company_id; - } else { - $company_id = null; + } + + + // If we are scoping the companies table itself, look for the company.id + if ($query->getModel()->getTable() == 'companies') { + return $query->where('companies.id', '=', $company_id); } @@ -297,6 +313,8 @@ final class Company extends SnipeModel return $query->where($table.$column, '=', $company_id); } + + } public function adminuser() @@ -311,8 +329,8 @@ final class Company extends SnipeModel * This gets invoked by CompanyableChildScope, but I'm not sure what it does. * * @author [A. Gianotto] - * @param array $companyable_names - * @param $query + * @param array $companyable_names + * @param $query * @return mixed */ public static function scopeCompanyableChildren(array $companyable_names, $query) @@ -324,17 +342,18 @@ final class Company extends SnipeModel return $query; } else { $f = function ($q) { - Log::debug('scopeCompanyablesDirectly firing '); static::scopeCompanyablesDirectly($q); }; - $q = $query->where(function ($q) use ($companyable_names, $f) { - $q2 = $q->whereHas($companyable_names[0], $f); + $q = $query->where( + function ($q) use ($companyable_names, $f) { + $q2 = $q->whereHas($companyable_names[0], $f); - for ($i = 1; $i < count($companyable_names); $i++) { - $q2 = $q2->orWhereHas($companyable_names[$i], $f); + for ($i = 1; $i < count($companyable_names); $i++) { + $q2 = $q2->orWhereHas($companyable_names[$i], $f); + } } - }); + ); return $q; } diff --git a/app/Models/CompanyableChildScope.php b/app/Models/CompanyableChildScope.php index 4077ebd596..35f2049e8b 100644 --- a/app/Models/CompanyableChildScope.php +++ b/app/Models/CompanyableChildScope.php @@ -9,15 +9,15 @@ use Illuminate\Database\Eloquent\Scope; /** * Handle query scoping for full company support. * - * @todo Move this to a more Laravel 5.2 esque way - * @version v1.0 + * @todo Move this to a more Laravel 5.2 esque way + * @version v1.0 */ final class CompanyableChildScope implements Scope { /** * Apply the scope to a given Eloquent query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function apply(Builder $builder, Model $model) @@ -31,7 +31,7 @@ final class CompanyableChildScope implements Scope * @todo IMPLEMENT * Remove the scope from the given Eloquent query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function remove(Builder $builder) diff --git a/app/Models/CompanyableScope.php b/app/Models/CompanyableScope.php index 4bbe7d6396..bbc0b5e0e6 100644 --- a/app/Models/CompanyableScope.php +++ b/app/Models/CompanyableScope.php @@ -9,15 +9,15 @@ use Illuminate\Database\Eloquent\Scope; /** * Handle query scoping for full company support. * - * @todo Move this to a more Laravel 5.2 esque way - * @version v1.0 + * @todo Move this to a more Laravel 5.2 esque way + * @version v1.0 */ final class CompanyableScope implements Scope { /** * Apply the scope to a given Eloquent query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function apply(Builder $builder, Model $model) @@ -29,7 +29,7 @@ final class CompanyableScope implements Scope * @todo IMPLEMENT * Remove the scope from the given Eloquent query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ public function remove(Builder $builder) diff --git a/app/Models/Component.php b/app/Models/Component.php index 0208fb9f68..09ca41830e 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Helpers\Helper; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,7 +15,7 @@ use Watson\Validating\ValidatingTrait; /** * Model for Components. * - * @version v1.0 + * @version v1.0 */ class Component extends SnipeModel { @@ -20,6 +23,7 @@ class Component extends SnipeModel protected $presenter = \App\Presenters\ComponentPresenter::class; use CompanyableTrait; + use HasUploads; use Loggable, Presentable; use SoftDeletes; protected $casts = [ @@ -39,7 +43,7 @@ class Component extends SnipeModel 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99', 'manufacturer_id' => 'integer|exists:manufacturers,id|nullable', ]; @@ -113,28 +117,13 @@ class Component extends SnipeModel && ($this->deleted_at == ''); } - /** - * Establishes the components -> action logs -> uploads relationship - * - * @author A. Gianotto - * @since [v6.1.13] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') - ->where('item_type', '=', self::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } /** * Establishes the component -> location relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -146,7 +135,7 @@ class Component extends SnipeModel * Establishes the component -> assets relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -160,7 +149,7 @@ class Component extends SnipeModel * @todo this is probably not needed - refactor * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -172,7 +161,7 @@ class Component extends SnipeModel * Establishes the component -> company relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -184,7 +173,7 @@ class Component extends SnipeModel * Establishes the component -> category relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function category() @@ -196,7 +185,7 @@ class Component extends SnipeModel * Establishes the item -> supplier relationship * * @author [A. Gianotto] [] - * @since [v6.1.1] + * @since [v6.1.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function supplier() @@ -209,19 +198,49 @@ class Component extends SnipeModel * Establishes the item -> manufacturer relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manufacturer() { return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); } + /** + * Determine whether this asset requires acceptance by the assigned user + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return bool + */ + public function requireAcceptance() + { + return $this->category->require_acceptance; + } + + /** + * Checks for a category-specific EULA, and if that doesn't exist, + * checks for a settings level EULA + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return string | false + */ + public function getEula() + { + if ($this->category->eula_text) { + return Helper::parseEscapedMarkedown($this->category->eula_text); + } elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) { + return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text); + } else { + return null; + } + } /** * Establishes the component -> action logs relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() @@ -233,7 +252,7 @@ class Component extends SnipeModel * Check how many items within a component are checked out * * @author [A. Gianotto] [] - * @since [v5.0] + * @since [v5.0] * @return int */ public function numCheckedOut() @@ -252,20 +271,34 @@ class Component extends SnipeModel * * This allows us to get the assets with assigned components without the company restriction */ - public function uncontrainedAssets() { + public function uncontrainedAssets() + { return $this->belongsToMany(\App\Models\Asset::class, 'components_assets') - ->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note') - ->withoutGlobalScope(new CompanyableScope); + ->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note') + ->withoutGlobalScope(new CompanyableScope); } + /** + * Determine whether to send a checkin/checkout email based on + * asset model category + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return bool + */ + public function checkin_email() + { + return $this->category?->checkin_email; + } + /** * Check how many items within a component are remaining * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return int */ public function numRemaining() @@ -288,8 +321,8 @@ class Component extends SnipeModel * This simply checks that there is a value for quantity, and if there isn't, set it to 0. * * @author A. Gianotto - * @since v6.3.4 - * @param $value + * @since v6.3.4 + * @param $value * @return void */ public function setQtyAttribute($value) @@ -307,8 +340,8 @@ class Component extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -320,8 +353,8 @@ class Component extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -333,8 +366,8 @@ class Component extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -346,8 +379,8 @@ class Component extends SnipeModel /** * Query builder scope to order on supplier * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -359,8 +392,8 @@ class Component extends SnipeModel /** * Query builder scope to order on manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index c83aa6106e..c7cdff0fe3 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -4,21 +4,16 @@ namespace App\Models; use App\Helpers\Helper; use App\Models\Traits\Acceptable; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; +use App\Presenters\ConsumablePresenter; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; -use Illuminate\Database\Eloquent\Relations\Relation; -use App\Presenters\ConsumablePresenter; -use App\Models\Actionlog; -use App\Models\ConsumableAssignment; -use App\Models\User; -use App\Models\Location; -use App\Models\Manufacturer; -use App\Models\Supplier; -use App\Models\Category; class Consumable extends SnipeModel { @@ -29,6 +24,7 @@ class Consumable extends SnipeModel use Loggable, Presentable; use SoftDeletes; use Acceptable; + use HasUploads; protected $table = 'consumables'; protected $casts = [ @@ -51,7 +47,7 @@ class Consumable extends SnipeModel 'company_id' => 'integer|nullable', 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|max:99999|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -111,21 +107,6 @@ class Consumable extends SnipeModel ]; - /** - * Establishes the components -> action logs -> uploads relationship - * - * @author A. Gianotto - * @since [v6.1.13] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany(Actionlog::class, 'item_id') - ->where('item_type', '=', self::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } /** @@ -138,7 +119,7 @@ class Consumable extends SnipeModel * @todo Update this comment once it's been implemented * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function setRequestableAttribute($value) @@ -153,7 +134,7 @@ class Consumable extends SnipeModel * Establishes the consumable -> admin user relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -165,7 +146,7 @@ class Consumable extends SnipeModel * Establishes the component -> assignments relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumableAssignments() @@ -177,7 +158,7 @@ class Consumable extends SnipeModel * Establishes the component -> company relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -189,7 +170,7 @@ class Consumable extends SnipeModel * Establishes the component -> manufacturer relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manufacturer() @@ -201,7 +182,7 @@ class Consumable extends SnipeModel * Establishes the component -> location relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -213,7 +194,7 @@ class Consumable extends SnipeModel * Establishes the component -> category relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function category() @@ -226,7 +207,7 @@ class Consumable extends SnipeModel * Establishes the component -> action logs relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() @@ -238,23 +219,28 @@ class Consumable extends SnipeModel * Gets the full image url for the consumable * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string | false */ public function getImageUrl() { + // If there is a consumable image, use that if ($this->image) { return Storage::disk('public')->url(app('consumables_upload_path').$this->image); - } - return false; + // Otherwise check for a category image + } elseif (($this->category) && ($this->category->image)) { + return Storage::disk('public')->url(app('categories_upload_path').e($this->category->image)); + } + + return false; } /** * Establishes the component -> users relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] */ public function users() : Relation { @@ -265,7 +251,7 @@ class Consumable extends SnipeModel * Establishes the item -> supplier relationship * * @author [A. Gianotto] [] - * @since [v6.1.1] + * @since [v6.1.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function supplier() @@ -279,19 +265,19 @@ class Consumable extends SnipeModel * asset model category * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return bool */ public function checkin_email() { - return $this->category->checkin_email; + return $this->category?->checkin_email; } /** * Determine whether this asset requires acceptance by the assigned user * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return bool */ public function requireAcceptance() @@ -304,7 +290,7 @@ class Consumable extends SnipeModel * checks for a settings level EULA * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string | false */ public function getEula() @@ -322,7 +308,7 @@ class Consumable extends SnipeModel * Check how many items within a consumable are checked out * * @author [A. Gianotto] [] - * @since [v5.0] + * @since [v5.0] * @return int */ public function numCheckedOut() @@ -334,7 +320,7 @@ class Consumable extends SnipeModel * Checks the number of available consumables * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return int */ public function numRemaining() @@ -360,8 +346,8 @@ class Consumable extends SnipeModel * This simply checks that there is a value for quantity, and if there isn't, set it to 0. * * @author A. Gianotto - * @since v6.3.4 - * @param $value + * @since v6.3.4 + * @param $value * @return void */ public function setQtyAttribute($value) @@ -378,8 +364,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -391,8 +377,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on location * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -404,8 +390,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -417,8 +403,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -430,8 +416,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on remaining * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -444,8 +430,8 @@ class Consumable extends SnipeModel /** * Query builder scope to order on supplier * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ diff --git a/app/Models/ConsumableAssignment.php b/app/Models/ConsumableAssignment.php index 4c9a19703e..0e634f580f 100644 --- a/app/Models/ConsumableAssignment.php +++ b/app/Models/ConsumableAssignment.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Traits\CompanyableTrait; use Illuminate\Database\Eloquent\Model; use Watson\Validating\ValidatingTrait; diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index 4954e2f17b..0e8845cfb3 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -16,6 +16,7 @@ class CustomField extends Model UniqueUndeletedTrait; /** + * * Custom field predfined formats * * @var array @@ -79,6 +80,9 @@ class CustomField extends Model 'auto_add_to_fieldsets', 'show_in_listview', 'show_in_email', + 'display_checkout', + 'display_checkin', + 'display_audit', 'show_in_requestable_list', ]; @@ -89,7 +93,7 @@ class CustomField extends Model * table instead of the assets table. * * @author [Brady Wetherington] [] - * @since [v3.0] + * @since [v3.0] */ public static $table_name = 'assets'; @@ -100,7 +104,7 @@ class CustomField extends Model * do with previously existing values. - @snipe * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return string */ public static function name_to_db_name($name) @@ -117,83 +121,132 @@ class CustomField extends Model * to do it in the controllers. * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return bool */ public static function boot() { parent::boot(); - self::created(function ($custom_field) { + self::created( + function ($custom_field) { - // Column already exists on the assets table - nothing to do here. - // This *shouldn't* happen in the wild. - if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) { - return false; + // Column already exists on the assets table - nothing to do here. + // This *shouldn't* happen in the wild. + if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) { + return false; + } + + // Update the column name in the assets table + Schema::table( + self::$table_name, function ($table) use ($custom_field) { + $table->text($custom_field->convertUnicodeDbSlug())->nullable(); + } + ); + + // Update the db_column property in the custom fields table + $custom_field->db_column = $custom_field->convertUnicodeDbSlug(); + $custom_field->save(); } + ); - // Update the column name in the assets table - Schema::table(self::$table_name, function ($table) use ($custom_field) { - $table->text($custom_field->convertUnicodeDbSlug())->nullable(); - }); + self::updating( + function ($custom_field) { - // Update the db_column property in the custom fields table - $custom_field->db_column = $custom_field->convertUnicodeDbSlug(); - $custom_field->save(); - }); + // Column already exists on the assets table - nothing to do here. + if ($custom_field->isDirty('name')) { + if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) { + return true; + } - self::updating(function ($custom_field) { + // Rename the field if the name has changed + Schema::table( + self::$table_name, function ($table) use ($custom_field) { + $table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug()); + } + ); + + // Save the updated column name to the custom fields table + $custom_field->db_column = $custom_field->convertUnicodeDbSlug(); + $custom_field->save(); - // Column already exists on the assets table - nothing to do here. - if ($custom_field->isDirty('name')) { - if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) { return true; } - // Rename the field if the name has changed - Schema::table(self::$table_name, function ($table) use ($custom_field) { - $table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug()); - }); - - // Save the updated column name to the custom fields table - $custom_field->db_column = $custom_field->convertUnicodeDbSlug(); - $custom_field->save(); - return true; } - - return true; - }); + ); // Drop the assets column if we've deleted it from custom fields - self::deleting(function ($custom_field) { - return Schema::table(self::$table_name, function ($table) use ($custom_field) { - $table->dropColumn($custom_field->db_column); - }); - }); + self::deleting( + function ($custom_field) { + return Schema::table( + self::$table_name, function ($table) use ($custom_field) { + $table->dropColumn($custom_field->db_column); + } + ); + } + ); } /** * Establishes the customfield -> fieldset relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function fieldset() { return $this->belongsToMany(\App\Models\CustomFieldset::class); } - + + public function displayFieldInCheckinForm() + { + if ($this->display_checkin == '1') { + return true; + } + return false; + } + + public function displayFieldInCheckoutForm() + { + if ($this->display_checkout == '1') { + return true; + } + return false; + } + + public function displayFieldInAuditForm() + { + if ($this->display_audit == '1') { + return true; + } + return false; + } + + public function displayFieldInCurrentForm($form_type = null) + { + switch ($form_type) { + case 'audit': + return $this->displayFieldInAuditForm(); + case 'checkin': + return $this->displayFieldInCheckinForm(); + case 'checkout': + return $this->displayFieldInCheckoutForm(); + } + } + + public function assetModels() { - return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id'); + return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id'); } /** * Establishes the customfield -> admin user relationship * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function user() @@ -205,7 +258,7 @@ class CustomField extends Model * Establishes the customfield -> default values relationship * * @author Hannah Tinkler - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function defaultValues() @@ -222,19 +275,23 @@ class CustomField extends Model */ public function defaultValue($modelId) { - return $this->defaultValues->filter(function ($item) use ($modelId) { - return $item->pivot->asset_model_id == $modelId; - })->map(function ($item) { - return $item->pivot->default_value; - })->first(); + return $this->defaultValues->filter( + function ($item) use ($modelId) { + return $item->pivot->asset_model_id == $modelId; + } + )->map( + function ($item) { + return $item->pivot->default_value; + } + )->first(); } /** * Checks the format of the attribute * * @author [A. Gianotto] [] - * @param $value string - * @since [v3.0] + * @param $value string + * @since [v3.0] * @return bool */ public function check_format($value) @@ -246,7 +303,7 @@ class CustomField extends Model * Gets the DB column name. * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return string */ public function db_column_name() @@ -262,7 +319,7 @@ class CustomField extends Model * user-friendly text in the dropdowns, and in the custom fields display. * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return string */ public function getFormatAttribute($value) @@ -280,7 +337,7 @@ class CustomField extends Model * Format a value string as an array for select boxes and checkboxes. * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return array */ public function setFormatAttribute($value) @@ -296,7 +353,7 @@ class CustomField extends Model * Format a value string as an array for select boxes and checkboxes. * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return array */ public function formatFieldValuesAsArray() @@ -326,7 +383,7 @@ class CustomField extends Model * Check whether the field is encrypted * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return bool */ public function isFieldDecryptable($string) @@ -343,7 +400,7 @@ class CustomField extends Model * won't break the database. * * @author [A. Gianotto] [] - * @since [v3.4] + * @since [v3.4] * @return string */ public function convertUnicodeDbSlug($original = null) @@ -352,7 +409,7 @@ class CustomField extends Model $id = $this->id ? $this->id : 'xx'; if (! function_exists('transliterator_transliterate')) { - $long_slug = '_snipeit_'.str_slug(mb_convert_encoding(trim($name),"UTF-8"), '_'); + $long_slug = '_snipeit_'.str_slug(mb_convert_encoding(trim($name), "UTF-8"), '_'); } else { $long_slug = '_snipeit_'.Utf8Slugger::slugify($name, '_'); } @@ -362,9 +419,10 @@ class CustomField extends Model /** * Get validation rules for custom fields to use with Validator + * * @author [V. Cordes] [] - * @param int $id - * @since [v4.1.10] + * @param int $id + * @since [v4.1.10] * @return array */ public function validationRules($regex_format = null) @@ -378,6 +436,7 @@ class CustomField extends Model /** * Check to see if there is a custom regex format type + * * @see https://github.com/grokability/snipe-it/issues/5896 * * @author Wes Hulette diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index d6bd7a1bef..f27b838647 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -3,12 +3,19 @@ namespace App\Models; use App\Rules\AlphaEncrypted; +use App\Rules\BooleanEncrypted; +use App\Rules\DateEncrypted; +use App\Rules\EmailEncrypted; +use App\Rules\IPEncrypted; +use App\Rules\IPv4Encrypted; +use App\Rules\IPv6Encrypted; +use App\Rules\MacEncrypted; use App\Rules\NumericEncrypted; +use App\Rules\RegexEncrypted; +use App\Rules\UrlEncrypted; use Gate; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Log; -use Illuminate\Validation\Rule; use Watson\Validating\ValidatingTrait; class CustomFieldset extends Model @@ -20,6 +27,7 @@ class CustomFieldset extends Model /** * Validation rules + * * @var array */ public $rules = [ @@ -39,7 +47,7 @@ class CustomFieldset extends Model * Establishes the fieldset -> field relationship * * @author [Brady Wetherington] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function fields() @@ -51,7 +59,7 @@ class CustomFieldset extends Model * Establishes the fieldset -> models relationship * * @author [Brady Wetherington] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function models() @@ -63,7 +71,7 @@ class CustomFieldset extends Model * Establishes the fieldset -> admin user relationship * * @author [Brady Wetherington] [] - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function user() @@ -71,22 +79,42 @@ class CustomFieldset extends Model return $this->belongsTo(\App\Models\User::class); //WARNING - not all CustomFieldsets have a User!! } + public function displayAnyFieldsInForm($form_type = null) + { + if ($this->fields) { + + switch ($form_type) { + case 'audit': + return $this->fields->where('display_audit', '1')->count() > 0; + case 'checkin': + return $this->fields->where('display_checkin', '1')->count() > 0; + case 'checkout': + return $this->fields->where('display_checkout', '1')->count() > 0; + default: + return true; + } + } + + return false; + } + /** * Determine the validation rules we should apply based on the * custom field format * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return array */ - public function validation_rules() + public function validation_rules(): array { $rules = []; foreach ($this->fields as $field) { $rule = []; - if (($field->field_encrypted != '1') || - (($field->field_encrypted == '1') && (Gate::allows('admin')))) { + if (($field->field_encrypted != '1') + || (($field->field_encrypted == '1') && (Gate::allows('admin'))) + ) { $rule[] = ($field->pivot->required == '1') ? 'required' : 'nullable'; } @@ -94,21 +122,71 @@ class CustomFieldset extends Model $rule[] = 'unique_undeleted'; } - array_push($rule, $field->attributes['format']); + if ($field->attributes['format']!='') { + array_push($rule, $field->attributes['format']); + } + $rules[$field->db_column_name()] = $rule; - - // these are to replace the standard 'numeric' and 'alpha' rules if the custom field is also encrypted. - // the values need to be decrypted first, because encrypted strings are alphanumeric - if ($field->format === 'NUMERIC' && $field->field_encrypted) { + // this is to switch the rules to rules specially made for encrypted custom fields that decrypt the value before validating + if ($field->field_encrypted) { $numericKey = array_search('numeric', $rules[$field->db_column_name()]); - $rules[$field->db_column_name()][$numericKey] = new NumericEncrypted; + $alphaKey = array_search('alpha', $rules[$field->db_column_name()]); + $emailKey = array_search('email', $rules[$field->db_column_name()]); + $dateKey = array_search('date', $rules[$field->db_column_name()]); + $urlKey = array_search('url', $rules[$field->db_column_name()]); + $ipKey = array_search('ip', $rules[$field->db_column_name()]); + $ipv4Key = array_search('ipv4', $rules[$field->db_column_name()]); + $ipv6Key = array_search('ipv6', $rules[$field->db_column_name()]); + $macKey = array_search('regex:/^[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}$/', $rules[$field->db_column_name()]); + $booleanKey = array_search('boolean', $rules[$field->db_column_name()]); + // find objects in array that start with "regex:" + // collect because i couldn't figure how to do this + // with array filter and get keys out of it + $regexCollect = collect($rules[$field->db_column_name()]); + $regexKeys = $regexCollect->filter(function ($value, $key) { + return starts_with($value, 'regex:'); + })->keys()->values()->toArray(); + + switch ($field->format) { + case 'NUMERIC': + $rules[$field->db_column_name()][$numericKey] = new NumericEncrypted; + break; + case 'ALPHA': + $rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted; + break; + case 'EMAIL': + $rules[$field->db_column_name()][$emailKey] = new EmailEncrypted; + break; + case 'DATE': + $rules[$field->db_column_name()][$dateKey] = new DateEncrypted; + break; + case 'URL': + $rules[$field->db_column_name()][$urlKey] = new UrlEncrypted; + break; + case 'IP': + $rules[$field->db_column_name()][$ipKey] = new IPEncrypted; + break; + case 'IPV4': + $rules[$field->db_column_name()][$ipv4Key] = new IPv4Encrypted; + break; + case 'IPV6': + $rules[$field->db_column_name()][$ipv6Key] = new IPv6Encrypted; + break; + case 'MAC': + $rules[$field->db_column_name()][$macKey] = new MacEncrypted; + break; + case 'BOOLEAN': + $rules[$field->db_column_name()][$booleanKey] = new BooleanEncrypted; + break; + case starts_with($field->format, 'regex'): + foreach ($regexKeys as $regexKey) { + $rules[$field->db_column_name()][$regexKey] = new RegexEncrypted; + } + break; + } } - if ($field->format === 'ALPHA' && $field->field_encrypted) { - $alphaKey = array_search('alpha', $rules[$field->db_column_name()]); - $rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted; - } // add not_array to rules for all fields but checkboxes if ($field->element != 'checkbox') { diff --git a/app/Models/Department.php b/app/Models/Department.php index 592fd840b1..1569081fdd 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Http\Traits\UniqueUndeletedTrait; +use App\Models\Traits\CompanyableTrait; use App\Models\Traits\Searchable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Watson\Validating\ValidatingTrait; @@ -72,7 +73,7 @@ class Department extends SnipeModel * Establishes the department -> company relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -84,7 +85,7 @@ class Department extends SnipeModel * Establishes the department -> users relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function users() @@ -96,7 +97,7 @@ class Department extends SnipeModel * Establishes the department -> manager relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manager() @@ -108,7 +109,7 @@ class Department extends SnipeModel * Establishes the department -> location relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -119,8 +120,8 @@ class Department extends SnipeModel /** * Query builder scope to order on location name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -132,8 +133,8 @@ class Department extends SnipeModel /** * Query builder scope to order on manager name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -145,8 +146,8 @@ class Department extends SnipeModel /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ diff --git a/app/Models/Depreciable.php b/app/Models/Depreciable.php index 0c513a3d33..b4cd40e1d1 100644 --- a/app/Models/Depreciable.php +++ b/app/Models/Depreciable.php @@ -48,15 +48,15 @@ class Depreciable extends SnipeModel $depreciation = 0; $setting = Setting::getSettings(); switch ($setting->depreciation_method) { - case 'half_1': + case 'half_1': $depreciation = $this->getHalfYearDepreciatedValue(true); break; - case 'half_2': + case 'half_2': $depreciation = $this->getHalfYearDepreciatedValue(false); break; - default: + default: $depreciation = $this->getLinearDepreciatedValue(); } @@ -74,7 +74,7 @@ class Depreciable extends SnipeModel return null; } - if ($months_passed >= $this->get_depreciation()->months){ + if ($months_passed >= $this->get_depreciation()->months) { //if there is a floor use it if($this->get_depreciation()->depreciation_min) { @@ -93,14 +93,15 @@ class Depreciable extends SnipeModel return $current_value; } - public function getMonthlyDepreciation(){ + public function getMonthlyDepreciation() + { return ($this->purchase_cost-$this->calculateDepreciation())/$this->get_depreciation()->months; } /** - * @param onlyHalfFirstYear Boolean always applied only second half of the first year + * @param onlyHalfFirstYear Boolean always applied only second half of the first year * @return float|int */ public function getHalfYearDepreciatedValue($onlyHalfFirstYear = false) @@ -131,7 +132,7 @@ class Depreciable extends SnipeModel } /** - * @param \DateTime $date + * @param \DateTime $date * @return int */ protected function get_fiscal_year($date) @@ -146,7 +147,7 @@ class Depreciable extends SnipeModel } /** - * @param \DateTime $date + * @param \DateTime $date * @return bool */ protected function is_first_half_of_year($date) diff --git a/app/Models/Depreciation.php b/app/Models/Depreciation.php index 11ee82c16a..6e01c6d782 100755 --- a/app/Models/Depreciation.php +++ b/app/Models/Depreciation.php @@ -16,7 +16,7 @@ class Depreciation extends SnipeModel // Declare the rules for the form validation protected $rules = [ 'name' => 'required|min:3|max:255|unique:depreciations,name', - 'months' => 'required|max:3600|integer|gt:0', + 'months' => 'required|max:3600|integer', ]; /** @@ -56,7 +56,7 @@ class Depreciation extends SnipeModel * Establishes the depreciation -> models relationship * * @author A. Gianotto - * @since [v5.0] + * @since [v5.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function models() @@ -68,7 +68,7 @@ class Depreciation extends SnipeModel * Establishes the depreciation -> licenses relationship * * @author A. Gianotto - * @since [v5.0] + * @since [v5.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -80,7 +80,7 @@ class Depreciation extends SnipeModel * Establishes the depreciation -> assets relationship * * @author A. Gianotto - * @since [v5.0] + * @since [v5.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -92,7 +92,7 @@ class Depreciation extends SnipeModel * Get the user that created the depreciation * * @author A. Gianotto - * @since [v7.0.13] + * @since [v7.0.13] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() diff --git a/app/Models/Group.php b/app/Models/Group.php index 253d47fbb9..9f4f2e2e56 100755 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -51,7 +51,7 @@ class Group extends SnipeModel * Establishes the groups -> users relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function users() @@ -63,7 +63,7 @@ class Group extends SnipeModel * Get the user that created the group * * @author A. Gianotto - * @since [v6.3.0] + * @since [v6.3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -75,29 +75,33 @@ class Group extends SnipeModel * Decode JSON permissions into array * * @author A. Gianotto - * @since [v1.0] - * @return array + * @since [v1.0] + * @return array | \stdClass */ public function decodePermissions() { - // Set default to empty JSON if the value is null + // If the permissions are an array, convert it to JSON if (is_array($this->permissions)) { $this->permissions = json_encode($this->permissions); } + $permissions = json_decode($this->permissions ?? '{}', JSON_OBJECT_AS_ARRAY); - // If there are no permissions, return an empty array - if (!$permissions) { - return []; - } - // Otherwise, loop through the permissions and cast the values as integers - foreach ($permissions as $permission => $value) { - $permissions[$permission] = (int) $value; + if ((is_array($permissions)) && ($permissions)) { + foreach ($permissions as $permission => $value) { + + if (!is_integer($permission)) { + $permissions[$permission] = (int) $value; + } else { + \Log::info('Weird data here - skipping it'); + unset($permissions[$permission]); + } + } + return $permissions ?: new \stdClass; } + return new \stdClass; - - return $permissions; } /** diff --git a/app/Models/Import.php b/app/Models/Import.php index d824a3840c..4ea259ba05 100644 --- a/app/Models/Import.php +++ b/app/Models/Import.php @@ -19,7 +19,7 @@ class Import extends Model * Establishes the license -> admin user relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php index 9f7059bcd5..fa60dafab3 100644 --- a/app/Models/Labels/DefaultLabel.php +++ b/app/Models/Labels/DefaultLabel.php @@ -38,13 +38,14 @@ class DefaultLabel extends RectangleSheet private int $rows; - public function __construct() { + public function __construct() + { $settings = Setting::getSettings(); $this->textSize = Helper::convertUnit($settings->labels_fontsize, 'pt', 'in'); - $this->labelWidth = $settings->labels_width; - $this->labelHeight = $settings->labels_height; + $this->labelWidth = $this->setLabelWidth($settings); + $this->labelHeight = $this->setLabelHeight($settings); $this->labelSpacingH = $settings->labels_display_sgutter; $this->labelSpacingV = $settings->labels_display_bgutter; @@ -74,41 +75,116 @@ class DefaultLabel extends RectangleSheet } - public function getUnit() { return 'in'; } + public function getUnit() + { + return 'in'; + } - public function getPageWidth() { return $this->pageWidth; } - public function getPageHeight() { return $this->pageHeight; } + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } - public function getPageMarginTop() { return $this->pageMarginTop; } - public function getPageMarginBottom() { return $this->pageMarginBottom; } - public function getPageMarginLeft() { return $this->pageMarginLeft; } - public function getPageMarginRight() { return $this->pageMarginRight; } + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginBottom; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginRight; + } - public function getColumns() { return $this->columns; } - public function getRows() { return $this->rows; } - public function getLabelBorder() { return 0; } + public function getColumns() + { + return $this->columns; + } + public function getRows() + { + return $this->rows; + } + public function getLabelBorder() + { + return 0; + } - public function getLabelWidth() { return $this->labelWidth; } - public function getLabelHeight() { return $this->labelHeight; } + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } - public function getLabelMarginTop() { return 0; } - public function getLabelMarginBottom() { return 0; } - public function getLabelMarginLeft() { return 0; } - public function getLabelMarginRight() { return 0; } + public function getLabelMarginTop() + { + return 0; + } + public function getLabelMarginBottom() + { + return 0; + } + public function getLabelMarginLeft() + { + return 0; + } + public function getLabelMarginRight() + { + return 0; + } - public function getLabelColumnSpacing() { return $this->labelSpacingH; } - public function getLabelRowSpacing() { return $this->labelSpacingV; } + public function getLabelColumnSpacing() + { + return $this->labelSpacingH; + } + public function getLabelRowSpacing() + { + return $this->labelSpacingV; + } - public function getSupportAssetTag() { return false; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 4; } - public function getSupportTitle() { return true; } - public function getSupportLogo() { return true; } + public function getSupportAssetTag() + { + return false; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 4; + } + public function getSupportTitle() + { + return true; + } + public function getSupportLogo() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $asset = $record->get('asset'); $settings = Setting::getSettings(); @@ -181,6 +257,25 @@ class DefaultLabel extends RectangleSheet } } -} + private function setLabelWidth(Setting $settings) + { + $labelWidth = $settings->labels_width; -?> \ No newline at end of file + if ($labelWidth == 0) { + $labelWidth = 0.1; + } + + return $labelWidth; + } + + private function setLabelHeight(?Setting $settings) + { + $labelHeight = $settings->labels_height; + + if ($labelHeight == 0) { + $labelHeight = 0.1; + } + + return $labelHeight; + } +} diff --git a/app/Models/Labels/Field.php b/app/Models/Labels/Field.php index c023f54175..cdd40cac49 100644 --- a/app/Models/Labels/Field.php +++ b/app/Models/Labels/Field.php @@ -5,21 +5,30 @@ namespace App\Models\Labels; use App\Models\Asset; use Illuminate\Support\Collection; -class Field { +class Field +{ protected Collection $options; - public function getOptions() { return $this->options; } - public function setOptions($options) { + public function getOptions() + { + return $this->options; + } + public function setOptions($options) + { $tempCollect = collect($options); if (!$tempCollect->contains(fn($o) => !is_subclass_of($o, FieldOption::class))) { $this->options = $options; } } - public function toArray(Asset $asset) { return Field::makeArray($this, $asset); } + public function toArray(Asset $asset) + { + return Field::makeArray($this, $asset); + } /* Statics */ - public static function makeArray(Field $field, Asset $asset) { + public static function makeArray(Field $field, Asset $asset) + { return $field->getOptions() // filter out any FieldOptions that are accidentally null ->filter() @@ -27,11 +36,13 @@ class Field { ->filter(fn($result) => $result['value'] != null); } - public static function makeString(Field $option) { + public static function makeString(Field $option) + { return implode('|', $option->getOptions()); } - public static function fromString(string $theString) { + public static function fromString(string $theString) + { $field = new Field(); $field->options = collect(explode('|', $theString)) ->filter(fn($optionString) => !empty($optionString)) diff --git a/app/Models/Labels/FieldOption.php b/app/Models/Labels/FieldOption.php index 94394eda23..cf65af57ba 100644 --- a/app/Models/Labels/FieldOption.php +++ b/app/Models/Labels/FieldOption.php @@ -5,14 +5,22 @@ namespace App\Models\Labels; use App\Models\Asset; use Illuminate\Support\Collection; -class FieldOption { +class FieldOption +{ protected string $label; - public function getLabel() { return $this->label; } + public function getLabel() + { + return $this->label; + } protected string $dataSource; - public function getDataSource() { return $this->dataSource; } + public function getDataSource() + { + return $this->dataSource; + } - public function getValue(Asset $asset) { + public function getValue(Asset $asset) + { $dataPath = collect(explode('.', $this->dataSource)); // assignedTo directly on the asset is a special case where @@ -22,10 +30,10 @@ class FieldOption { if ($asset->relationLoaded('assignedTo')) { // If the "assignedTo" relationship was eager loaded then the way to get the // relationship changes from $asset->assignedTo to $asset->assigned. - return $asset->assigned ? $asset->assigned->present()->fullName() : null; + return $asset->assigned ? $asset->assigned->display_name : null; } - return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null; + return $asset->assignedTo ? $asset->assignedTo->display_name : null; } // Handle Laravel's stupid Carbon datetime casting @@ -33,18 +41,29 @@ class FieldOption { return $asset->purchase_date ? $asset->purchase_date->format('Y-m-d') : null; } - return $dataPath->reduce(function ($myValue, $path) { - try { return $myValue ? $myValue->{$path} : ${$myValue}; } - catch (\Exception $e) { return $myValue; } - }, $asset); + return $dataPath->reduce( + function ($myValue, $path) { + try { return $myValue ? $myValue->{$path} : ${$myValue}; + } + catch (\Exception $e) { return $myValue; + } + }, $asset + ); } - public function toArray(Asset $asset=null) { return FieldOption::makeArray($this, $asset); } - public function toString() { return FieldOption::makeString($this); } + public function toArray(Asset $asset=null) + { + return FieldOption::makeArray($this, $asset); + } + public function toString() + { + return FieldOption::makeString($this); + } /* Statics */ - public static function makeArray(FieldOption $option, Asset $asset=null) { + public static function makeArray(FieldOption $option, Asset $asset=null) + { return [ 'label' => $option->getLabel(), 'dataSource' => $option->getDataSource(), @@ -52,11 +71,13 @@ class FieldOption { ]; } - public static function makeString(FieldOption $option) { + public static function makeString(FieldOption $option) + { return $option->getLabel() . '=' . $option->getDataSource(); } - public static function fromString(string $theString) { + public static function fromString(string $theString) + { $parts = explode('=', $theString); if (count($parts) == 2) { $option = new FieldOption(); diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php index 2405da5401..cff859f359 100644 --- a/app/Models/Labels/Label.php +++ b/app/Models/Labels/Label.php @@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Log; /** * Model for Labels. * - * @version v1.0 + * @version v1.0 */ abstract class Label { @@ -32,7 +32,8 @@ abstract class Label * * @return int */ - public function getRotation() { + public function getRotation() + { return 0; } @@ -123,29 +124,32 @@ abstract class Label /** * Make changes to the PDF properties here. OPTIONAL. * - * @param TCPDF $pdf The TCPDF instance + * @param TCPDF $pdf The TCPDF instance */ public abstract function preparePDF(TCPDF $pdf); /** * Write single data record as content here. * - * @param TCPDF $pdf The TCPDF instance - * @param Collection $record A data record + * @param TCPDF $pdf The TCPDF instance + * @param Collection $record A data record */ public abstract function write(TCPDF $pdf, Collection $record); /** * Handle the data here. Override for multiple-per-page handling * - * @param TCPDF $pdf The TCPDF instance - * @param Collection $data The data + * @param TCPDF $pdf The TCPDF instance + * @param Collection $data The data */ - public function writeAll(TCPDF $pdf, Collection $data) { - $data->each(function ($record, $index) use ($pdf) { - $pdf->AddPage(); - $this->write($pdf, $record); - }); + public function writeAll(TCPDF $pdf, Collection $data) + { + $data->each( + function ($record, $index) use ($pdf) { + $pdf->AddPage(); + $this->write($pdf, $record); + } + ); } /** @@ -153,7 +157,8 @@ abstract class Label * * @return string */ - public final function getName() { + public final function getName() + { $refClass = new \ReflectionClass(Label::class); return str_replace($refClass->getNamespaceName() . '\\', '', get_class($this)); } @@ -165,7 +170,8 @@ abstract class Label * * @return string */ - public final function getOrientation() { + public final function getOrientation() + { return ($this->getWidth() >= $this->getHeight()) ? 'L' : 'P'; } @@ -174,7 +180,8 @@ abstract class Label * * @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ] */ - public final function getPrintableArea() { + public final function getPrintableArea() + { return (object)[ 'x1' => $this->getMarginLeft(), 'y1' => $this->getMarginTop(), @@ -188,21 +195,22 @@ abstract class Label /** * Write a text cell. * - * @param TCPDF $pdf The TCPDF instance - * @param string $text The text to write. Supports 'some **bold** text'. - * @param float $x X position of top-left - * @param float $y Y position of top-left - * @param string $font The font family - * @param string $style The font style - * @param int $size The font size in getUnit() units - * @param string $align Align text in the box. 'L' left, 'R' right, 'C' center. - * @param float $width Force text box width. NULL to auto-fit. - * @param float $height Force text box height. NULL to auto-fit. - * @param bool $squash Squash text if it's too big - * @param int $border Thickness of border. Default = 0. - * @param int $spacing Letter spacing. Default = 0. + * @param TCPDF $pdf The TCPDF instance + * @param string $text The text to write. Supports 'some **bold** text'. + * @param float $x X position of top-left + * @param float $y Y position of top-left + * @param string $font The font family + * @param string $style The font style + * @param int $size The font size in getUnit() units + * @param string $align Align text in the box. 'L' left, 'R' right, 'C' center. + * @param float $width Force text box width. NULL to auto-fit. + * @param float $height Force text box height. NULL to auto-fit. + * @param bool $squash Squash text if it's too big + * @param int $border Thickness of border. Default = 0. + * @param int $spacing Letter spacing. Default = 0. */ - public final function writeText(TCPDF $pdf, $text, $x, $y, $font=null, $style=null, $size=null, $align='L', $width=null, $height=null, $squash=false, $border=0, $spacing=0) { + public final function writeText(TCPDF $pdf, $text, $x, $y, $font=null, $style=null, $size=null, $align='L', $width=null, $height=null, $squash=false, $border=0, $spacing=0) + { $prevFamily = $pdf->getFontFamily(); $prevStyle = $pdf->getFontStyle(); $prevSizePt = $pdf->getFontSizePt(); @@ -211,33 +219,42 @@ abstract class Label $fontFamily = !empty($font) ? $font : $prevFamily; $fontStyle = !empty($style) ? $style : $prevStyle; - if ($size) $fontSizePt = Helper::convertUnit($size, $this->getUnit(), 'pt', true); - else $fontSizePt = $prevSizePt; + if ($size) { $fontSizePt = Helper::convertUnit($size, $this->getUnit(), 'pt', true); + } else { $fontSizePt = $prevSizePt; + } $pdf->SetFontSpacing($spacing); $parts = collect(explode('**', $text)) - ->map(function ($part, $index) use ($pdf, $fontFamily, $fontStyle, $fontSizePt) { - $modStyle = ($index % 2 == 1) ? 'B' : $fontStyle; - $pdf->setFont($fontFamily, $modStyle, $fontSizePt); - return [ + ->map( + function ($part, $index) use ($pdf, $fontFamily, $fontStyle, $fontSizePt) { + $modStyle = ($index % 2 == 1) ? 'B' : $fontStyle; + $pdf->setFont($fontFamily, $modStyle, $fontSizePt); + return [ 'text' => $part, 'text_width' => $pdf->GetStringWidth($part), 'font_family' => $fontFamily, 'font_style' => $modStyle, 'font_size' => $fontSizePt, - ]; - }); + ]; + } + ); - $textWidth = $parts->reduce(function ($carry, $part) { return $carry += $part['text_width']; }); + $textWidth = $parts->reduce( + function ($carry, $part) { + return $carry += $part['text_width']; + } + ); $cellWidth = !empty($width) ? $width : $textWidth; if ($squash && ($textWidth > 0)) { $scaleFactor = min(1.0, $cellWidth / $textWidth); - $parts = $parts->map(function ($part, $index) use ($scaleFactor) { - $part['text_width'] = $part['text_width'] * $scaleFactor; - return $part; - }); + $parts = $parts->map( + function ($part, $index) use ($scaleFactor) { + $part['text_width'] = $part['text_width'] * $scaleFactor; + return $part; + } + ); } $cellHeight = !empty($height) ? $height : Helper::convertUnit($fontSizePt, 'pt', $this->getUnit()); @@ -249,18 +266,23 @@ abstract class Label } switch($align) { - case 'R': $startX = ($x + $cellWidth) - min($cellWidth, $textWidth); break; - case 'C': $startX = ($x + ($cellWidth / 2)) - (min($cellWidth, $textWidth) / 2); break; - case 'L': - default: $startX = $x; break; + case 'R': $startX = ($x + $cellWidth) - min($cellWidth, $textWidth); + break; + case 'C': $startX = ($x + ($cellWidth / 2)) - (min($cellWidth, $textWidth) / 2); + break; + case 'L': + default: $startX = $x; + break; } - $parts->reduce(function ($currentX, $part) use ($pdf, $y, $cellHeight) { - $pdf->SetXY($currentX, $y); - $pdf->setFont($part['font_family'], $part['font_style'], $part['font_size']); - $pdf->Cell($part['text_width'], $cellHeight, $part['text'], 0, 0, '', false, '', 1, true); - return $currentX += $part['text_width']; - }, $startX); + $parts->reduce( + function ($currentX, $part) use ($pdf, $y, $cellHeight) { + $pdf->SetXY($currentX, $y); + $pdf->setFont($part['font_family'], $part['font_style'], $part['font_size']); + $pdf->Cell($part['text_width'], $cellHeight, $part['text'], 0, 0, '', false, '', 1, true); + return $currentX += $part['text_width']; + }, $startX + ); $pdf->SetFont($prevFamily, $prevStyle, $prevSizePt); $pdf->SetFontSpacing(0); @@ -269,27 +291,30 @@ abstract class Label /** * Write an image. * - * @param TCPDF $pdf The TCPDF instance - * @param string $image The image to write - * @param float $x X position of top-left - * @param float $y Y position of top-left - * @param float $width The container width - * @param float $height The container height - * @param string $halign Align text in the box. 'L' left, 'R' right, 'C' center. Default 'L'. - * @param string $valign Align text in the box. 'T' top, 'B' bottom, 'C' center. Default 'T'. - * @param int $dpi Pixels per inch - * @param bool $resize Resize to fit container - * @param bool $stretch Stretch (vs Scale) to fit container - * @param int $border Thickness of border. Default = 0. + * @param TCPDF $pdf The TCPDF instance + * @param string $image The image to write + * @param float $x X position of top-left + * @param float $y Y position of top-left + * @param float $width The container width + * @param float $height The container height + * @param string $halign Align text in the box. 'L' left, 'R' right, 'C' center. Default 'L'. + * @param string $valign Align text in the box. 'T' top, 'B' bottom, 'C' center. Default 'T'. + * @param int $dpi Pixels per inch + * @param bool $resize Resize to fit container + * @param bool $stretch Stretch (vs Scale) to fit container + * @param int $border Thickness of border. Default = 0. * * @return array Returns the final calculated size [w,h] */ - public final function writeImage(TCPDF $pdf, $image, $x, $y, $width=null, $height=null, $halign='L', $valign='L', $dpi=300, $resize=false, $stretch=false, $border=0) { + public final function writeImage(TCPDF $pdf, $image, $x, $y, $width=null, $height=null, $halign='L', $valign='L', $dpi=300, $resize=false, $stretch=false, $border=0) + { - if (empty($image)) return [0,0]; + if (empty($image)) { return [0,0]; + } $imageInfo = getimagesize($image); - if (!$imageInfo) return [0,0]; // TODO: SVG or other + if (!$imageInfo) { return [0,0]; // TODO: SVG or other + } $imageWidthPx = $imageInfo[0]; $imageHeightPx = $imageInfo[1]; @@ -342,18 +367,24 @@ abstract class Label // Horizontal Position switch ($halign) { - case 'R': $originX = ($x + $containerWidth) - $outputWidth; break; - case 'C': $originX = ($x + ($containerWidth / 2)) - ($outputWidth / 2); break; - case 'L': - default: $originX = $x; break; + case 'R': $originX = ($x + $containerWidth) - $outputWidth; + break; + case 'C': $originX = ($x + ($containerWidth / 2)) - ($outputWidth / 2); + break; + case 'L': + default: $originX = $x; + break; } // Vertical Position switch ($valign) { - case 'B': $originY = ($y + $containerHeight) - $outputHeight; break; - case 'C': $originY = ($y + ($containerHeight / 2)) - ($outputHeight / 2); break; - case 'T': - default: $originY = $y; break; + case 'B': $originY = ($y + $containerHeight) - $outputHeight; + break; + case 'C': $originY = ($y + ($containerHeight / 2)) - ($outputHeight / 2); + break; + case 'T': + default: $originY = $y; + break; } // Actual Image @@ -373,16 +404,18 @@ abstract class Label /** * Write a 1D barcode. * - * @param TCPDF $pdf The TCPDF instance - * @param string $value The barcode content - * @param string $type The barcode type - * @param float $x X position of top-left - * @param float $y Y position of top-left - * @param float $width The container width - * @param float $height The container height + * @param TCPDF $pdf The TCPDF instance + * @param string $value The barcode content + * @param string $type The barcode type + * @param float $x X position of top-left + * @param float $y Y position of top-left + * @param float $width The container width + * @param float $height The container height */ - public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) { - if (empty($value)) return; + public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) + { + if (empty($value)) { return; + } try { $pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]); } catch (\Exception|TypeError $e) { @@ -393,16 +426,18 @@ abstract class Label /** * Write a 2D barcode. * - * @param TCPDF $pdf The TCPDF instance - * @param string $value The barcode content - * @param string $type The barcode type - * @param float $x X position of top-left - * @param float $y Y position of top-left - * @param float $width The container width - * @param float $height The container height + * @param TCPDF $pdf The TCPDF instance + * @param string $value The barcode content + * @param string $type The barcode type + * @param float $x X position of top-left + * @param float $y Y position of top-left + * @param float $width The container width + * @param float $height The container height */ - public final function write2DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) { - if (empty($value)) return; + public final function write2DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) + { + if (empty($value)) { return; + } $pdf->write2DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]); } @@ -411,127 +446,180 @@ abstract class Label /** * Checks the template is internally valid */ - public final function validate() : void { + public final function validate() : void + { $this->validateUnits(); $this->validateSize(); $this->validateMargins(); $this->validateSupport(); } - private function validateUnits() : void { + private function validateUnits() : void + { $validUnits = [ 'pt', 'mm', 'cm', 'in' ]; $unit = $this->getUnit(); if (!in_array(strtolower($unit), $validUnits)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_value', [ - 'name' => 'getUnit()', - 'expected' => '[ \''.implode('\', \'', $validUnits).'\' ]', - 'actual' => '\''.$unit.'\'' - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_value', [ + 'name' => 'getUnit()', + 'expected' => '[ \''.implode('\', \'', $validUnits).'\' ]', + 'actual' => '\''.$unit.'\'' + ] + ) + ); } } - private function validateSize() : void { + private function validateSize() : void + { $width = $this->getWidth(); if (!is_numeric($width) || is_string($width)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getWidth()', - 'expected' => 'float', - 'actual' => gettype($width) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getWidth()', + 'expected' => 'float', + 'actual' => gettype($width) + ] + ) + ); } $height = $this->getHeight(); if (!is_numeric($height) || is_string($height)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getHeight()', - 'expected' => 'float', - 'actual' => gettype($height) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getHeight()', + 'expected' => 'float', + 'actual' => gettype($height) + ] + ) + ); } } - private function validateMargins() : void { + private function validateMargins() : void + { $marginTop = $this->getMarginTop(); if (!is_numeric($marginTop) || is_string($marginTop)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getMarginTop()', - 'expected' => 'float', - 'actual' => gettype($marginTop) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getMarginTop()', + 'expected' => 'float', + 'actual' => gettype($marginTop) + ] + ) + ); } $marginBottom = $this->getMarginBottom(); if (!is_numeric($marginBottom) || is_string($marginBottom)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getMarginBottom()', - 'expected' => 'float', - 'actual' => gettype($marginBottom) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getMarginBottom()', + 'expected' => 'float', + 'actual' => gettype($marginBottom) + ] + ) + ); } $marginLeft = $this->getMarginLeft(); if (!is_numeric($marginLeft) || is_string($marginLeft)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getMarginLeft()', - 'expected' => 'float', - 'actual' => gettype($marginLeft) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getMarginLeft()', + 'expected' => 'float', + 'actual' => gettype($marginLeft) + ] + ) + ); } $marginRight = $this->getMarginRight(); if (!is_numeric($marginRight) || is_string($marginRight)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getMarginRight()', - 'expected' => 'float', - 'actual' => gettype($marginRight) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getMarginRight()', + 'expected' => 'float', + 'actual' => gettype($marginRight) + ] + ) + ); } } - private function validateSupport() : void { + private function validateSupport() : void + { $support1D = $this->getSupport1DBarcode(); if (!is_bool($support1D)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getSupport1DBarcode()', - 'expected' => 'boolean', - 'actual' => gettype($support1D) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getSupport1DBarcode()', + 'expected' => 'boolean', + 'actual' => gettype($support1D) + ] + ) + ); } $support2D = $this->getSupport2DBarcode(); if (!is_bool($support2D)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getSupport2DBarcode()', - 'expected' => 'boolean', - 'actual' => gettype($support2D) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getSupport2DBarcode()', + 'expected' => 'boolean', + 'actual' => gettype($support2D) + ] + ) + ); } $supportFields = $this->getSupportFields(); if (!is_int($supportFields)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getSupportFields()', - 'expected' => 'integer', - 'actual' => gettype($supportFields) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getSupportFields()', + 'expected' => 'integer', + 'actual' => gettype($supportFields) + ] + ) + ); } $supportLogo = $this->getSupportLogo(); if (!is_bool($supportLogo)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getSupportLogo()', - 'expected' => 'boolean', - 'actual' => gettype($supportLogo) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getSupportLogo()', + 'expected' => 'boolean', + 'actual' => gettype($supportLogo) + ] + ) + ); } $supportTitle = $this->getSupportTitle(); if (!is_bool($supportTitle)) { - throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ - 'name' => 'getSupportTitle()', - 'expected' => 'boolean', - 'actual' => gettype($supportTitle) - ])); + throw new \UnexpectedValueException( + trans( + 'admin/labels/message.invalid_return_type', [ + 'name' => 'getSupportTitle()', + 'expected' => 'boolean', + 'actual' => gettype($supportTitle) + ] + ) + ); } } @@ -539,23 +627,26 @@ abstract class Label /** * Public Static Functions - */ + */ /** * Find size of a page by its format. * - * @param string $format Format name (eg: 'A4', 'LETTER', etc.) - * @param string $orientation 'L' for Landscape, 'P' for Portrait ('L' default) - * @param string $unit Unit of measure to return in ('mm' default) + * @param string $format Format name (eg: 'A4', 'LETTER', etc.) + * @param string $orientation 'L' for Landscape, 'P' for Portrait ('L' default) + * @param string $unit Unit of measure to return in ('mm' default) * * @return object (object)[ 'width' => (float)123.4, 'height' => (float)123.4 ] */ - public static function fromFormat($format, $orientation='L', $unit='mm', $round=false) { + public static function fromFormat($format, $orientation='L', $unit='mm', $round=false) + { $size = collect(TCPDF_STATIC::getPageSizeFromFormat(strtoupper($format))) ->sort() - ->map(function ($value) use ($unit) { - return Helper::convertUnit($value, 'pt', $unit); - }) + ->map( + function ($value) use ($unit) { + return Helper::convertUnit($value, 'pt', $unit); + } + ) ->toArray(); $width = ($orientation == 'L') ? $size[1] : $size[0]; $height = ($orientation == 'L') ? $size[0] : $size[1]; @@ -571,16 +662,19 @@ abstract class Label * Unlike most Models, these are defined by their existence as non- * abstract classes stored in Models\Labels. * - * @param string|Arrayable|array|null $path Label path[s] + * @param string|Arrayable|array|null $path Label path[s] * @return Collection|Label|null */ - public static function find($name=null) { + public static function find($name=null) + { // Find many if (is_array($name) || $name instanceof Arrayable) { $labels = collect($name) - ->map(function ($thisname) { - return static::find($thisname); - }) + ->map( + function ($thisname) { + return static::find($thisname); + } + ) ->whereNotNull(); return ($labels->count() > 0) ? $labels : null; } @@ -588,26 +682,36 @@ abstract class Label // Find one if ($name !== null) { return static::find() - ->sole(function ($label) use ($name) { - return $label->getName() == $name; - }); + ->sole( + function ($label) use ($name) { + return $label->getName() == $name; + } + ); } // Find all return collect(File::allFiles(__DIR__)) - ->map(function ($file) { - preg_match_all('/\/*(.+?)(?:\/|\.)/', $file->getRelativePathName(), $matches); - return __NAMESPACE__ . '\\' . implode('\\', $matches[1]); - }) - ->filter(function ($name) { - if (!class_exists($name)) return false; - $refClass = new \ReflectionClass($name); - if ($refClass->isAbstract()) return false; - return $refClass->isSubclassOf(Label::class); - }) - ->map(function ($name) { - return new $name(); - }); + ->map( + function ($file) { + preg_match_all('/\/*(.+?)(?:\/|\.)/', $file->getRelativePathName(), $matches); + return __NAMESPACE__ . '\\' . implode('\\', $matches[1]); + } + ) + ->filter( + function ($name) { + if (!class_exists($name)) { return false; + } + $refClass = new \ReflectionClass($name); + if ($refClass->isAbstract()) { return false; + } + return $refClass->isSubclassOf(Label::class); + } + ) + ->map( + function ($name) { + return new $name(); + } + ); } diff --git a/app/Models/Labels/RectangleSheet.php b/app/Models/Labels/RectangleSheet.php index f5fe5cda98..e9c6f007d9 100644 --- a/app/Models/Labels/RectangleSheet.php +++ b/app/Models/Labels/RectangleSheet.php @@ -33,9 +33,13 @@ abstract class RectangleSheet extends Sheet public abstract function getLabelRowSpacing(); - public function getLabelsPerPage() { return $this->getColumns() * $this->getRows(); } + public function getLabelsPerPage() + { + return $this->getColumns() * $this->getRows(); + } - public function getLabelPosition($index) { + public function getLabelPosition($index) + { $printIndex = $index + $this->getLabelIndexOffset(); $row = (int)($printIndex / $this->getColumns()); $col = $printIndex - ($row * $this->getColumns()); diff --git a/app/Models/Labels/Sheet.php b/app/Models/Labels/Sheet.php index 83e363591a..63f9da173a 100644 --- a/app/Models/Labels/Sheet.php +++ b/app/Models/Labels/Sheet.php @@ -6,12 +6,30 @@ abstract class Sheet extends Label { protected int $indexOffset = 0; - public function getWidth() { return $this->getPageWidth(); } - public function getHeight() { return $this->getPageHeight(); } - public function getMarginTop() { return $this->getPageMarginTop(); } - public function getMarginBottom() { return $this->getPageMarginBottom(); } - public function getMarginLeft() { return $this->getPageMarginLeft(); } - public function getMarginRight() { return $this->getPageMarginRight(); } + public function getWidth() + { + return $this->getPageWidth(); + } + public function getHeight() + { + return $this->getPageHeight(); + } + public function getMarginTop() + { + return $this->getPageMarginTop(); + } + public function getMarginBottom() + { + return $this->getPageMarginBottom(); + } + public function getMarginLeft() + { + return $this->getPageMarginLeft(); + } + public function getMarginRight() + { + return $this->getPageMarginRight(); + } /** * Returns the page width in getUnit() units @@ -107,7 +125,7 @@ abstract class Sheet extends Label /** * Returns label position based on its index * - * @param int $index + * @param int $index * * @return array [x,y] */ @@ -123,10 +141,11 @@ abstract class Sheet extends Label /** * Handle the data here. Override for multiple-per-page handling * - * @param TCPDF $pdf The TCPDF instance - * @param Collection $data The data + * @param TCPDF $pdf The TCPDF instance + * @param Collection $data The data */ - public function writeAll($pdf, $data) { + public function writeAll($pdf, $data) + { $prevPageNumber = -1; foreach ($data->toArray() as $recordIndex => $record) { @@ -170,7 +189,8 @@ abstract class Sheet extends Label * * @return string */ - public final function getLabelOrientation() { + public final function getLabelOrientation() + { return ($this->getLabelWidth() >= $this->getLabelHeight()) ? 'L' : 'P'; } @@ -179,7 +199,8 @@ abstract class Sheet extends Label * * @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ] */ - public final function getLabelPrintableArea() { + public final function getLabelPrintableArea() + { return (object)[ 'x1' => $this->getLabelMarginLeft(), 'y1' => $this->getLabelMarginTop(), @@ -195,15 +216,20 @@ abstract class Sheet extends Label * * @return int */ - public function getLabelIndexOffset() { return $this->indexOffset; } + public function getLabelIndexOffset() + { + return $this->indexOffset; + } /** * Sets label index offset (skip positions) * - * @param int $offset - * + * @param int $offset */ - public function setLabelIndexOffset(int $offset) { $this->indexOffset = $offset; } + public function setLabelIndexOffset(int $offset) + { + $this->indexOffset = $offset; + } } ?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/L6009.php b/app/Models/Labels/Sheets/Avery/L6009.php new file mode 100644 index 0000000000..11d6cfffca --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/L6009.php @@ -0,0 +1,110 @@ +getUnit(), 0); + $this->pageWidth = $paperSize->width; + $this->pageHeight = $paperSize->height; + + $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit()); + $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit()); + + $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W; + $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit()); + $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H; + $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit()); + + $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit()); + $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); + } + + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } + + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } + + public function getColumns() + { + return 4; + } + public function getRows() + { + return 12; + } + + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } + + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } + + public function getLabelBorder() + { + return 0; + } +} + +?> diff --git a/app/Models/Labels/Sheets/Avery/L6009_A.php b/app/Models/Labels/Sheets/Avery/L6009_A.php new file mode 100644 index 0000000000..09f3aa8358 --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/L6009_A.php @@ -0,0 +1,121 @@ +getLabelPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', self::TITLE_SIZE, 'C', + $pa->w, self::TITLE_SIZE, true, 0 + ); + + } + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + $usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN; + $barcodeSize = $usableHeight; + if ($record->has('barcode2d')) { + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + self::BARCODE_MARGIN; + $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.01 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> diff --git a/app/Models/Labels/Sheets/Avery/L7162.php b/app/Models/Labels/Sheets/Avery/L7162.php index e1097db9b2..d0e6c673c7 100644 --- a/app/Models/Labels/Sheets/Avery/L7162.php +++ b/app/Models/Labels/Sheets/Avery/L7162.php @@ -31,7 +31,8 @@ abstract class L7162 extends RectangleSheet private float $labelWidth; private float $labelHeight; - public function __construct() { + public function __construct() + { $paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0); $this->pageWidth = $paperSize->width; $this->pageHeight = $paperSize->height; @@ -48,24 +49,63 @@ abstract class L7162 extends RectangleSheet $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); } - public function getPageWidth() { return $this->pageWidth; } - public function getPageHeight() { return $this->pageHeight; } + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } - public function getPageMarginTop() { return $this->pageMarginTop; } - public function getPageMarginBottom() { return $this->pageMarginTop; } - public function getPageMarginLeft() { return $this->pageMarginLeft; } - public function getPageMarginRight() { return $this->pageMarginLeft; } + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } - public function getColumns() { return 2; } - public function getRows() { return 8; } + public function getColumns() + { + return 2; + } + public function getRows() + { + return 8; + } - public function getLabelColumnSpacing() { return $this->columnSpacing; } - public function getLabelRowSpacing() { return $this->rowSpacing; } + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } - public function getLabelWidth() { return $this->labelWidth; } - public function getLabelHeight() { return $this->labelHeight; } + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } - public function getLabelBorder() { return 0; } + public function getLabelBorder() + { + return 0; + } } ?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/L7162_A.php b/app/Models/Labels/Sheets/Avery/L7162_A.php index 0b3312ba7c..adfe7f382a 100644 --- a/app/Models/Labels/Sheets/Avery/L7162_A.php +++ b/app/Models/Labels/Sheets/Avery/L7162_A.php @@ -14,23 +14,59 @@ class L7162_A extends L7162 private const FIELD_SIZE = 4.60; private const FIELD_MARGIN = 0.30; - public function getUnit() { return 'mm'; } + public function getUnit() + { + return 'mm'; + } - public function getLabelMarginTop() { return 1.0; } - public function getLabelMarginBottom() { return 1.0; } - public function getLabelMarginLeft() { return 1.0; } - public function getLabelMarginRight() { return 1.0; } + public function getLabelMarginTop() + { + return 1.0; + } + public function getLabelMarginBottom() + { + return 1.0; + } + public function getLabelMarginLeft() + { + return 1.0; + } + public function getLabelMarginRight() + { + return 1.0; + } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 4; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 4; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); $usableWidth = $pa->w; diff --git a/app/Models/Labels/Sheets/Avery/L7162_B.php b/app/Models/Labels/Sheets/Avery/L7162_B.php index 268754e04f..833653cabf 100644 --- a/app/Models/Labels/Sheets/Avery/L7162_B.php +++ b/app/Models/Labels/Sheets/Avery/L7162_B.php @@ -17,23 +17,59 @@ class L7162_B extends L7162 private const FIELD_SIZE = 4.20; private const FIELD_MARGIN = 0.30; - public function getUnit() { return 'mm'; } + public function getUnit() + { + return 'mm'; + } - public function getLabelMarginTop() { return 1.0; } - public function getLabelMarginBottom() { return 0; } - public function getLabelMarginLeft() { return 1.0; } - public function getLabelMarginRight() { return 1.0; } + public function getLabelMarginTop() + { + return 1.0; + } + public function getLabelMarginBottom() + { + return 0; + } + public function getLabelMarginLeft() + { + return 1.0; + } + public function getLabelMarginRight() + { + return 1.0; + } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return true; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return false; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return true; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); $usableWidth = $pa->w; @@ -55,7 +91,7 @@ class L7162_B extends L7162 $pdf, $record->get('logo'), $pa->x1, $pa->y1, self::LOGO_MAX_WIDTH, $usableHeight, - 'L', 'T', 300, true, false, 0.1 + 'L', 'T', 300, true, false, 0 ); $currentX += $logoSize[0] + self::LOGO_MARGIN; $usableWidth -= $logoSize[0] + self::LOGO_MARGIN; @@ -100,4 +136,4 @@ class L7162_B extends L7162 } -?> \ No newline at end of file +?> diff --git a/app/Models/Labels/Sheets/Avery/L7163.php b/app/Models/Labels/Sheets/Avery/L7163.php index f143260336..01fe9a78e0 100644 --- a/app/Models/Labels/Sheets/Avery/L7163.php +++ b/app/Models/Labels/Sheets/Avery/L7163.php @@ -31,7 +31,8 @@ abstract class L7163 extends RectangleSheet private float $labelWidth; private float $labelHeight; - public function __construct() { + public function __construct() + { $paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0); $this->pageWidth = $paperSize->width; $this->pageHeight = $paperSize->height; @@ -48,24 +49,63 @@ abstract class L7163 extends RectangleSheet $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); } - public function getPageWidth() { return $this->pageWidth; } - public function getPageHeight() { return $this->pageHeight; } + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } - public function getPageMarginTop() { return $this->pageMarginTop; } - public function getPageMarginBottom() { return $this->pageMarginTop; } - public function getPageMarginLeft() { return $this->pageMarginLeft; } - public function getPageMarginRight() { return $this->pageMarginLeft; } + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } - public function getColumns() { return 2; } - public function getRows() { return 7; } + public function getColumns() + { + return 2; + } + public function getRows() + { + return 7; + } - public function getLabelColumnSpacing() { return $this->columnSpacing; } - public function getLabelRowSpacing() { return $this->rowSpacing; } + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } - public function getLabelWidth() { return $this->labelWidth; } - public function getLabelHeight() { return $this->labelHeight; } + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } - public function getLabelBorder() { return 0; } + public function getLabelBorder() + { + return 0; + } } ?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/L7163_A.php b/app/Models/Labels/Sheets/Avery/L7163_A.php index 6dc33f64dd..47a44d75db 100644 --- a/app/Models/Labels/Sheets/Avery/L7163_A.php +++ b/app/Models/Labels/Sheets/Avery/L7163_A.php @@ -14,23 +14,59 @@ class L7163_A extends L7163 private const FIELD_SIZE = 4.80; private const FIELD_MARGIN = 0.30; - public function getUnit() { return 'mm'; } + public function getUnit() + { + return 'mm'; + } - public function getLabelMarginTop() { return 1.0; } - public function getLabelMarginBottom() { return 1.0; } - public function getLabelMarginLeft() { return 1.0; } - public function getLabelMarginRight() { return 1.0; } + public function getLabelMarginTop() + { + return 1.0; + } + public function getLabelMarginBottom() + { + return 1.0; + } + public function getLabelMarginLeft() + { + return 1.0; + } + public function getLabelMarginRight() + { + return 1.0; + } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 4; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 4; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); $usableWidth = $pa->w; diff --git a/app/Models/Labels/Sheets/Avery/_3490.php b/app/Models/Labels/Sheets/Avery/_3490.php new file mode 100644 index 0000000000..c81c980e11 --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/_3490.php @@ -0,0 +1,111 @@ +getUnit(), 2); + $this->pageWidth = $paperSize->width; + $this->pageHeight = $paperSize->height; + + $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit()); + $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit()); + + $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W; + $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit()); + $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H; + $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit()); + + $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit()); + $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); + } + + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } + + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } + + public function getColumns() + { + return 3; + } + public function getRows() + { + return 10; + } + + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } + + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } + + public function getLabelBorder() + { + return 0; + } +} + +?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_3490_A.php b/app/Models/Labels/Sheets/Avery/_3490_A.php new file mode 100644 index 0000000000..591f8318aa --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/_3490_A.php @@ -0,0 +1,121 @@ +getLabelPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', self::TITLE_SIZE, 'C', + $pa->w, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + $usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN; + } + + $barcodeSize = $usableHeight; + if ($record->has('barcode2d')) { + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + self::BARCODE_MARGIN; + $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.01 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_5267.php b/app/Models/Labels/Sheets/Avery/_5267.php index f5f2f13557..882c862691 100644 --- a/app/Models/Labels/Sheets/Avery/_5267.php +++ b/app/Models/Labels/Sheets/Avery/_5267.php @@ -31,7 +31,8 @@ abstract class _5267 extends RectangleSheet private float $labelWidth; private float $labelHeight; - public function __construct() { + public function __construct() + { $paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 2); $this->pageWidth = $paperSize->width; $this->pageHeight = $paperSize->height; @@ -48,24 +49,63 @@ abstract class _5267 extends RectangleSheet $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); } - public function getPageWidth() { return $this->pageWidth; } - public function getPageHeight() { return $this->pageHeight; } + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } - public function getPageMarginTop() { return $this->pageMarginTop; } - public function getPageMarginBottom() { return $this->pageMarginTop; } - public function getPageMarginLeft() { return $this->pageMarginLeft; } - public function getPageMarginRight() { return $this->pageMarginLeft; } + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } - public function getColumns() { return 4; } - public function getRows() { return 20; } + public function getColumns() + { + return 4; + } + public function getRows() + { + return 20; + } - public function getLabelColumnSpacing() { return $this->columnSpacing; } - public function getLabelRowSpacing() { return $this->rowSpacing; } + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } - public function getLabelWidth() { return $this->labelWidth; } - public function getLabelHeight() { return $this->labelHeight; } + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } - public function getLabelBorder() { return 0; } + public function getLabelBorder() + { + return 0; + } } ?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_5267_A.php b/app/Models/Labels/Sheets/Avery/_5267_A.php index efe0855d5e..1ed6f557f0 100644 --- a/app/Models/Labels/Sheets/Avery/_5267_A.php +++ b/app/Models/Labels/Sheets/Avery/_5267_A.php @@ -12,23 +12,59 @@ class _5267_A extends _5267 private const FIELD_SIZE = 0.150; private const FIELD_MARGIN = 0.012; - public function getUnit() { return 'in'; } + public function getUnit() + { + return 'in'; + } - public function getLabelMarginTop() { return 0.02; } - public function getLabelMarginBottom() { return 0.00; } - public function getLabelMarginLeft() { return 0.04; } - public function getLabelMarginRight() { return 0.04; } + public function getLabelMarginTop() + { + return 0.02; + } + public function getLabelMarginBottom() + { + return 0.00; + } + public function getLabelMarginLeft() + { + return 0.04; + } + public function getLabelMarginRight() + { + return 0.04; + } - public function getSupportAssetTag() { return false; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 1; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return false; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return false; + } + public function getSupportFields() + { + return 1; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); if ($record->has('barcode1d')) { diff --git a/app/Models/Labels/Sheets/Avery/_5520.php b/app/Models/Labels/Sheets/Avery/_5520.php index 00cb0e0687..ae9ff0b039 100644 --- a/app/Models/Labels/Sheets/Avery/_5520.php +++ b/app/Models/Labels/Sheets/Avery/_5520.php @@ -31,7 +31,8 @@ abstract class _5520 extends RectangleSheet private float $labelWidth; private float $labelHeight; - public function __construct() { + public function __construct() + { $paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 2); $this->pageWidth = $paperSize->width; $this->pageHeight = $paperSize->height; @@ -48,24 +49,63 @@ abstract class _5520 extends RectangleSheet $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); } - public function getPageWidth() { return $this->pageWidth; } - public function getPageHeight() { return $this->pageHeight; } + public function getPageWidth() + { + return $this->pageWidth; + } + public function getPageHeight() + { + return $this->pageHeight; + } - public function getPageMarginTop() { return $this->pageMarginTop; } - public function getPageMarginBottom() { return $this->pageMarginTop; } - public function getPageMarginLeft() { return $this->pageMarginLeft; } - public function getPageMarginRight() { return $this->pageMarginLeft; } + public function getPageMarginTop() + { + return $this->pageMarginTop; + } + public function getPageMarginBottom() + { + return $this->pageMarginTop; + } + public function getPageMarginLeft() + { + return $this->pageMarginLeft; + } + public function getPageMarginRight() + { + return $this->pageMarginLeft; + } - public function getColumns() { return 3; } - public function getRows() { return 10; } + public function getColumns() + { + return 3; + } + public function getRows() + { + return 10; + } - public function getLabelColumnSpacing() { return $this->columnSpacing; } - public function getLabelRowSpacing() { return $this->rowSpacing; } + public function getLabelColumnSpacing() + { + return $this->columnSpacing; + } + public function getLabelRowSpacing() + { + return $this->rowSpacing; + } - public function getLabelWidth() { return $this->labelWidth; } - public function getLabelHeight() { return $this->labelHeight; } + public function getLabelWidth() + { + return $this->labelWidth; + } + public function getLabelHeight() + { + return $this->labelHeight; + } - public function getLabelBorder() { return 0; } + public function getLabelBorder() + { + return 0; + } } ?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_5520_A.php b/app/Models/Labels/Sheets/Avery/_5520_A.php index 199566d248..9d2c91502d 100644 --- a/app/Models/Labels/Sheets/Avery/_5520_A.php +++ b/app/Models/Labels/Sheets/Avery/_5520_A.php @@ -14,23 +14,59 @@ class _5520_A extends _5520 private const FIELD_SIZE = 0.150; private const FIELD_MARGIN = 0.012; - public function getUnit() { return 'in'; } + public function getUnit() + { + return 'in'; + } - public function getLabelMarginTop() { return 0.06; } - public function getLabelMarginBottom() { return 0.06; } - public function getLabelMarginLeft() { return 0.06; } - public function getLabelMarginRight() { return 0.06; } + public function getLabelMarginTop() + { + return 0.06; + } + public function getLabelMarginBottom() + { + return 0.06; + } + public function getLabelMarginLeft() + { + return 0.06; + } + public function getLabelMarginRight() + { + return 0.06; + } - public function getSupportAssetTag() { return false; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return false; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Sheets/Avery/_5520_B.php b/app/Models/Labels/Sheets/Avery/_5520_B.php index eb6494e0aa..9c44e26bf4 100644 --- a/app/Models/Labels/Sheets/Avery/_5520_B.php +++ b/app/Models/Labels/Sheets/Avery/_5520_B.php @@ -15,23 +15,59 @@ class _5520_B extends _5520 private const FIELD_SIZE = 0.150; private const FIELD_MARGIN = 0.012; - public function getUnit() { return 'in'; } + public function getUnit() + { + return 'in'; + } - public function getLabelMarginTop() { return 0.06; } - public function getLabelMarginBottom() { return 0.06; } - public function getLabelMarginLeft() { return 0.06; } - public function getLabelMarginRight() { return 0.06; } + public function getLabelMarginTop() + { + return 0.06; + } + public function getLabelMarginBottom() + { + return 0.06; + } + public function getLabelMarginLeft() + { + return 0.06; + } + public function getLabelMarginRight() + { + return 0.06; + } - public function getSupportAssetTag() { return false; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 2; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getSupportAssetTag() + { + return false; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return false; + } + public function getSupportFields() + { + return 2; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getLabelPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm.php b/app/Models/Labels/Tapes/Brother/TZe_12mm.php index f9196847ce..3cf1f54081 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_12mm.php +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm.php @@ -11,9 +11,24 @@ abstract class TZe_12mm extends Label private const MARGIN_SIDES = 3.20; private const MARGIN_ENDS = 3.20; - public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); } - public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } - public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} - public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } - public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getHeight() + { + return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); + } + public function getMarginTop() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginBottom() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginLeft() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } + public function getMarginRight() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } } \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php index f89cfc5d47..5ff9fcd474 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php @@ -8,18 +8,45 @@ class TZe_12mm_A extends TZe_12mm private const BARCODE_MARGIN = 0.30; private const TEXT_SIZE_MOD = 1.00; - public function getUnit() { return 'mm'; } - public function getWidth() { return 50.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 1; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return false; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 50.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return false; + } + public function getSupportFields() + { + return 1; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return false; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); if ($record->has('barcode1d')) { diff --git a/app/Models/Labels/Tapes/Brother/TZe_18mm.php b/app/Models/Labels/Tapes/Brother/TZe_18mm.php index 38c14c7aa4..efcc405bc4 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_18mm.php +++ b/app/Models/Labels/Tapes/Brother/TZe_18mm.php @@ -11,9 +11,24 @@ abstract class TZe_18mm extends Label private const MARGIN_SIDES = 3.20; private const MARGIN_ENDS = 3.20; - public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); } - public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } - public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} - public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } - public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getHeight() + { + return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); + } + public function getMarginTop() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginBottom() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginLeft() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } + public function getMarginRight() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } } \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php index 32156f5ee6..fd27a80421 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_18mm_A.php @@ -8,18 +8,45 @@ class TZe_18mm_A extends TZe_18mm private const BARCODE_MARGIN = 0.30; private const TEXT_SIZE_MOD = 1.00; - public function getUnit() { return 'mm'; } - public function getWidth() { return 50.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 1; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return false; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 50.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return false; + } + public function getSupportFields() + { + return 1; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return false; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); if ($record->has('barcode1d')) { diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm.php b/app/Models/Labels/Tapes/Brother/TZe_24mm.php index 3c67bc1614..c9ee228beb 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm.php @@ -11,9 +11,24 @@ abstract class TZe_24mm extends Label private const MARGIN_SIDES = 3.20; private const MARGIN_ENDS = 3.20; - public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); } - public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } - public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} - public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } - public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getHeight() + { + return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); + } + public function getMarginTop() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginBottom() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginLeft() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } + public function getMarginRight() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } } \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php index ea4c6c9dfb..4210bb2709 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php @@ -13,18 +13,45 @@ class TZe_24mm_A extends TZe_24mm private const FIELD_SIZE = 3.20; private const FIELD_MARGIN = 0.15; - public function getUnit() { return 'mm'; } - public function getWidth() { return 65.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 65.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_B.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_B.php index cedf5e0cbd..2ea5697316 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_B.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_B.php @@ -15,18 +15,45 @@ class TZe_24mm_B extends TZe_24mm private const FIELD_SIZE = 3.20; private const FIELD_MARGIN = 0.15; - public function getUnit() { return 'mm'; } - public function getWidth() { return 73.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return true; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 73.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return true; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_C.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_C.php index 65b3676bfc..e100dfa81b 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_C.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_C.php @@ -15,18 +15,45 @@ class TZe_24mm_C extends TZe_24mm private const FIELD_SIZE = 3.20; private const FIELD_MARGIN = 0.15; - public function getUnit() { return 'mm'; } - public function getWidth() { return 34.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return false; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 0; } - public function getSupportLogo() { return true; } - public function getSupportTitle() { return false; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 34.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return false; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 0; + } + public function getSupportLogo() + { + return true; + } + public function getSupportTitle() + { + return false; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php index 5ecd86c977..c88e84d246 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php @@ -14,18 +14,45 @@ class TZe_24mm_D extends TZe_24mm private const FIELD_MARGIN = 0.35; private const BARCODE1D_SIZE = 3.00; // Size for the C128 barcode at bottom - public function getUnit() { return 'mm'; } - public function getWidth() { return 65.0; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 65.0; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; @@ -70,27 +97,38 @@ class TZe_24mm_D extends TZe_24mm } foreach ($record->get('fields') as $field) { - // Write label and value on the same line - // Calculate label width with proportional character spacing - $labelWidth = $pdf->GetStringWidth($field['label'], 'freemono', '', self::LABEL_SIZE); - $charCount = strlen($field['label']); - $spacingPerChar = 0.5; - $totalSpacing = $charCount * $spacingPerChar; - $adjustedWidth = $labelWidth + $totalSpacing; + if (!empty($field['label']) && $field['label'] !== "\u{200B}") { + // Write label and value on the same line + // Calculate label width with proportional character spacing + $labelWidth = $pdf->GetStringWidth($field['label'], 'freemono', '', self::LABEL_SIZE); + $charCount = strlen($field['label']); + $spacingPerChar = 0.5; + $totalSpacing = $charCount * $spacingPerChar; + $adjustedWidth = $labelWidth + $totalSpacing; - static::writeText( - $pdf, $field['label'], - $currentX, $currentY, - 'freemono', 'B', self::LABEL_SIZE, 'L', - $adjustedWidth, self::LABEL_SIZE, true, 0, $spacingPerChar - ); + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freemono', 'B', self::LABEL_SIZE, 'L', + $adjustedWidth, self::LABEL_SIZE, true, 0, $spacingPerChar + ); - static::writeText( - $pdf, $field['value'], - $currentX + $adjustedWidth + 2, $currentY, - 'freemono', 'B', self::FIELD_SIZE, 'L', - $usableWidth - $adjustedWidth - 2, self::FIELD_SIZE, true, 0, 0.3 - ); + static::writeText( + $pdf, $field['value'], + $currentX + $adjustedWidth + 2, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth - $adjustedWidth - 2, self::FIELD_SIZE, true, 0, 0.3 + ); + } else { + + // Label is empty, so write value only. + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, // No offset + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.3 + ); + } $currentY += max(self::LABEL_SIZE, self::FIELD_SIZE) + self::FIELD_MARGIN; } diff --git a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php index 2069927a34..1f5ee1df3d 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php +++ b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php @@ -14,10 +14,28 @@ abstract class TZe_62mm_Landscape extends Label private const MARGIN_SIDES = 1.50; private const MARGIN_ENDS = 1.50; - public function getWidth() { return Helper::convertUnit(self::WIDTH, 'mm', $this->getUnit()); } - public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } - public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} - public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } - public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } - public function getRotation() { return 90; } + public function getWidth() + { + return Helper::convertUnit(self::WIDTH, 'mm', $this->getUnit()); + } + public function getMarginTop() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginBottom() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); + } + public function getMarginLeft() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } + public function getMarginRight() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); + } + public function getRotation() + { + return 90; + } } diff --git a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php index 3a4d6da2c7..6cf53f8e18 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php @@ -4,14 +4,38 @@ namespace App\Models\Labels\Tapes\Brother; class TZe_62mm_Landscape_A extends TZe_62mm_Landscape { - public function getUnit() { return 'mm'; } - public function getHeight() { return 31.50; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 2; } - public function getSupportLogo() { return true; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getHeight() + { + return 31.50; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 2; + } + public function getSupportLogo() + { + return true; + } + public function getSupportTitle() + { + return true; + } private const BARCODE1D_HEIGHT = 3.00; private const BARCODE1D_MARGIN = 3.00; @@ -27,9 +51,12 @@ class TZe_62mm_Landscape_A extends TZe_62mm_Landscape private const FIELD_SIZE = 3.00; private const FIELD_MARGIN = 0.10; - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter.php b/app/Models/Labels/Tapes/Dymo/LabelWriter.php index fa427fd213..8d9e37555d 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter.php @@ -11,9 +11,24 @@ abstract class LabelWriter extends Label private const MARGIN_SIDES = 0.1; private const MARGIN_ENDS = 0.1; - public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'in', $this->getUnit()); } - public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); } - public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit());} - public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); } - public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); } + public function getHeight() + { + return Helper::convertUnit(self::HEIGHT, 'in', $this->getUnit()); + } + public function getMarginTop() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); + } + public function getMarginBottom() + { + return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); + } + public function getMarginLeft() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); + } + public function getMarginRight() + { + return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); + } } \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_11354.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_11354.php new file mode 100644 index 0000000000..08f2fb6d27 --- /dev/null +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_11354.php @@ -0,0 +1,116 @@ +getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + // Wide 1D barcode on top + if ($record->has('barcode1d')) { + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $currentX, $currentY, $usableWidth, self::BARCODE1D_HEIGHT + ); + $currentY += self::BARCODE1D_HEIGHT + self::BARCODE_MARGIN; + $usableHeight -= self::BARCODE1D_HEIGHT + self::BARCODE_MARGIN; + } + + // 2D Barcode in left column + if ($record->has('barcode2d')) { + $barcodeSize = $usableHeight - self::TAG_SIZE; + + static::writeText( + $pdf, $record->get('tag'), + $currentX, $pa->y2 - self::TAG_SIZE, + 'freesans', 'b', self::TAG_SIZE, 'C', + $barcodeSize, self::TAG_SIZE, true, 0 + ); + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + self::BARCODE_MARGIN; + $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; + } + + // Right column + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $currentX, $currentY, + 'freesans', 'b', self::TITLE_SIZE, 'L', + $usableWidth, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'], + $currentX, $currentY, + 'freesans', '', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.3 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + } + +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php index 9f5fa735e4..63c270e865 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php @@ -14,19 +14,49 @@ class LabelWriter_1933081 extends LabelWriter private const FIELD_SIZE = 2.80; private const FIELD_MARGIN = 0.15; - public function getUnit() { return 'mm'; } - public function getWidth() { return 89; } - public function getHeight() { return 25; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 5; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 89; + } + public function getHeight() + { + return 25; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 5; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php index 117486a8e5..63701d31a1 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php @@ -14,19 +14,49 @@ class LabelWriter_2112283 extends LabelWriter private const FIELD_SIZE = 2.80; private const FIELD_MARGIN = 0.15; - public function getUnit() { return 'mm'; } - public function getWidth() { return 54; } - public function getHeight() { return 25; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 5; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 54; + } + public function getHeight() + { + return 25; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 5; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php index ed8074547b..8c3f050242 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php @@ -16,18 +16,45 @@ class LabelWriter_30252 extends LabelWriter - public function getUnit() { return 'mm'; } - public function getWidth() { return 96.52; } - public function getSupportAssetTag() { return true; } - public function getSupport1DBarcode() { return true; } - public function getSupport2DBarcode() { return true; } - public function getSupportFields() { return 3; } - public function getSupportLogo() { return false; } - public function getSupportTitle() { return true; } + public function getUnit() + { + return 'mm'; + } + public function getWidth() + { + return 96.52; + } + public function getSupportAssetTag() + { + return true; + } + public function getSupport1DBarcode() + { + return true; + } + public function getSupport2DBarcode() + { + return true; + } + public function getSupportFields() + { + return 3; + } + public function getSupportLogo() + { + return false; + } + public function getSupportTitle() + { + return true; + } - public function preparePDF($pdf) {} + public function preparePDF($pdf) + { + } - public function write($pdf, $record) { + public function write($pdf, $record) + { $pa = $this->getPrintableArea(); $currentX = $pa->x1; diff --git a/app/Models/Labels/Tapes/Generic/Continuous_53mm.php b/app/Models/Labels/Tapes/Generic/Continuous_53mm.php new file mode 100644 index 0000000000..18b12fbc99 --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Continuous_53mm.php @@ -0,0 +1,110 @@ +tapeHeight = $height; + } + + public function getBarcodeRatio() + { + return 0.9; // Barcode should use 90% of available width + } + + /** + * Calculate the required height for the content + * + * @param $record The record to calculate height for + * @return float The calculated height in mm + */ + protected function calculateRequiredHeight($record) + { + $height = $this->marginTop + $this->marginBottom; + + // Add title height if present + if ($record->has('title') && $this->getSupportTitle()) { + $height += $this->titleSize + $this->titleMargin; + } + + // Add barcode height if present + if (($record->has('barcode2d') && $this->getSupport2DBarcode()) + || ($record->has('barcode') && $this->getSupport1DBarcode()) + ) { + $pa = $this->getPrintableArea(); + $usableWidth = $pa->w; + $barcodeSize = $usableWidth * $this->getBarcodeRatio(); + $height += $barcodeSize + $this->barcodeMargin; + } + + // Add fields height if present + if ($record->has('fields') && $this->getSupportFields() > 0) { + foreach ($record->get('fields') as $field) { + $height += $this->labelSize + $this->labelMargin; + $height += $this->fieldSize + $this->fieldMargin; + } + } + + // Add a small buffer to ensure everything fits + $height += 2.0; + + // Ensure minimum height + return max($this->minHeight, $height); + } + + /** + * Override the writeAll method to support dynamic page sizes for continuous tapes + */ + public function writeAll($pdf, $data) + { + // Use auto-sizing for continuous tapes, fixed height for die-cut tapes + if ($this->continuous) { + $data->each( + function ($record, $index) use ($pdf) { + // Calculate the required height for this record + $requiredHeight = $this->calculateRequiredHeight($record); + + // Temporarily update the height property + $originalHeight = $this->height; + $this->height = $requiredHeight; + + // Add a new page with the calculated dimensions + $pdf->AddPage( + $this->getOrientation(), + [$this->getWidth(), $requiredHeight], + false, // Don't reset page number + false // Don't reset object ID + ); + + // Write the content + $this->write($pdf, $record); + + // Restore the original height + $this->height = $originalHeight; + } + ); + } else { + // Use the default implementation for non-continuous (die-cut) tapes + parent::writeAll($pdf, $data); + } + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/Continuous_53mm_A.php b/app/Models/Labels/Tapes/Generic/Continuous_53mm_A.php new file mode 100644 index 0000000000..4f40f64627 --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Continuous_53mm_A.php @@ -0,0 +1,96 @@ +SetAutoPageBreak(false); + } + + public function write($pdf, $record) + { + $pa = $this->getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', $this->titleSize, 'C', + $pa->w, $this->titleSize, true, 0 + ); + $currentY += $this->titleSize + $this->titleMargin; + $usableHeight -= $this->titleSize + $this->titleMargin; + } + + // Make the barcode as large as possible while still leaving room for fields + $barcodeSize = min($usableHeight * 0.8, $usableWidth * $this->getBarcodeRatio()); + + if ($record->has('barcode2d')) { + $barcodeX = $pa->x1 + ($usableWidth - $barcodeSize) / 2; + + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $barcodeX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentY += $barcodeSize + $this->barcodeMargin; + } + + if ($record->has('fields')) { + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', $this->labelSize, 'L', + $usableWidth, $this->labelSize, true, 0 + ); + $currentY += $this->labelSize + $this->labelMargin; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', $this->fieldSize, 'L', + $usableWidth, $this->fieldSize, true, 0, 0.01 + ); + $currentY += $this->fieldSize + $this->fieldMargin; + } + } + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in.php b/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in.php new file mode 100644 index 0000000000..47856823bc --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in.php @@ -0,0 +1,178 @@ +tapeHeight = $length; + + $this->marginTop = 0.1; + $this->marginBottom = 0.1; + // Keep small horizontal margins + $this->marginLeft = self::TAPE_WIDTH * 0.2; + // $this->marginRight = self::TAPE_WIDTH * 0.1; + + // Override font sizes to make them larger + // Calculate a larger base font size (3x the default) + $baseFontSize = self::TAPE_WIDTH * 0.16; // 3x the default 0.07 + + // Recalculate all element sizing based on the larger base font size + $this->titleSize = $baseFontSize; // Same as base font size + $this->titleMargin = $baseFontSize * 0.3; // 30% of base font size + $this->fieldSize = $baseFontSize * 1.1; // 110% of base font size + $this->fieldMargin = $baseFontSize * 0.1; // 10% of base font size + $this->labelSize = $baseFontSize * 0.7; // 70% of base font size + $this->labelMargin = $baseFontSize * -0.1; // -10% of base font size + $this->barcodeMargin = $baseFontSize * 0.9; // 20% of base font size + $this->tagSize = $baseFontSize * 0.8; // 80% of base font size + } + + public function getBarcodeRatio() + { + return 1.0; // Barcode should use 100% of available height + } + + /** + * Calculate the required length for the content + * + * @param $record The record to calculate length for + * @return float The calculated length in inches + */ + protected function calculateRequiredLength($record) + { + + // Calculate length needed for barcode and fields side by side + $requiredLength = 0; + + // Add barcode length if present + if (($record->has('barcode2d') && $this->getSupport2DBarcode()) + || ($record->has('barcode') && $this->getSupport1DBarcode()) + ) { + // Use full tape width for barcode size + $barcodeSize = self::TAPE_WIDTH; + $requiredLength += $barcodeSize + $this->barcodeMargin * 0.3; // Minimal margin + } + + // Add fields length if present - calculate based on actual content + if ($record->has('fields') && $this->getSupportFields() > 0) { + $fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields()); + + // Base width for field area + $fieldsWidth = self::TAPE_WIDTH; + + // Calculate additional width based on text length + foreach ($fields as $field) { + // Get label and value text + $labelText = $field['label'] ?? ''; + $valueText = $field['value'] ?? ''; + + // Calculate approximate width needed based on text length + // Increase character width to ensure enough space (0.15 inches per character) + $labelWidth = strlen($labelText) * 0.09; + $valueWidth = strlen($valueText) * 0.09; + + // Use the longer of the two + $textWidth = max($labelWidth, $valueWidth); + + // Ensure minimum width and add to total + $fieldsWidth = max($fieldsWidth, $textWidth); + } + + // Add the calculated width for fields + $requiredLength += $fieldsWidth; + + // Add minimal extra space for field padding + // Reduce padding to eliminate extraneous space on right edge + // $requiredLength += self::TAPE_WIDTH * 0.1; + } + + // Ensure minimum length + return max($this->minHeight, $requiredLength); + } + + /** + * Calculate text width accurately using the PDF object + * + * @param $pdf The PDF object + * @param string $text The text to measure + * @param string $font The font to use + * @param string $style The font style + * @param float $size The font size + * @return float The calculated width + */ + protected function calculateTextWidth($pdf, $text, $font, $style, $size) + { + $originalFont = $pdf->getFontFamily(); + $originalStyle = $pdf->getFontStyle(); + $originalSize = $pdf->getFontSizePt(); + + $pdf->SetFont($font, $style, Helper::convertUnit($size, $this->getUnit(), 'pt', true)); + $width = $pdf->GetStringWidth($text); + + // Restore original font settings + $pdf->SetFont($originalFont, $originalStyle, $originalSize); + + return $width; + } + + /** + * Override the writeAll method to support dynamic page sizes for continuous tapes + */ + public function writeAll($pdf, $data) + { + // Use auto-sizing for continuous tapes, fixed height for die-cut tapes + if ($this->continuous) { + $data->each( + function ($record, $index) use ($pdf) { + // Calculate the required length by calling write with calculateOnly=true + $requiredLength = $this->write($pdf, $record); + + // If write didn't return a length (old implementation), fall back to calculateRequiredLength + if ($requiredLength === null) { + $requiredLength = $this->calculateRequiredLength($record); + } + + // Temporarily update the height property + $originalHeight = $this->height; + $this->height = self::TAPE_WIDTH; // Keep height fixed at tape width + + // Add a new page with the calculated dimensions + // Keep height fixed at TAPE_WIDTH, use calculated length for width + $pdf->AddPage( + $this->getOrientation(), + [$requiredLength, self::TAPE_WIDTH], + false, // Don't reset page number + false // Don't reset object ID + ); + + // Write the content + $this->write($pdf, $record); + + // Restore the original height + $this->height = $originalHeight; + } + ); + } else { + // Use the default implementation for non-continuous (die-cut) tapes + parent::writeAll($pdf, $data); + } + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in_A.php b/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in_A.php new file mode 100644 index 0000000000..23325aa837 --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Continuous_Landscape_0_59in_A.php @@ -0,0 +1,180 @@ +SetAutoPageBreak(false); + } + + public function write($pdf, $record, $calculateOnly = false) + { + $pa = $this->getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + // Calculate required length based on content + $requiredLength = 0; + + // Use full usable height for barcode + $barcodeSize = $usableHeight; + + // Add barcode width to required length + if ($record->has('barcode2d') && $this->getSupport2DBarcode()) { + $requiredLength += $barcodeSize; + // Add gap between barcode and fields + $requiredLength += $this->barcodeMargin; + } + + // Calculate fields width using accurate text measurement + if ($record->has('fields') && $this->getSupportFields() > 0) { + $fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields()); + $fieldsWidth = 0; + + foreach ($fields as $field) { + $labelText = $field['label'] ?? ''; + $valueText = $field['value'] ?? ''; + + // Calculate accurate width using the PDF object + $labelWidth = $this->calculateTextWidth($pdf, $labelText, 'freesans', 'B', $this->labelSize * 1.2); + $valueWidth = $this->calculateTextWidth($pdf, $valueText, 'freemono', 'B', $this->fieldSize * 1.3); + + // Use the longer of the two + $textWidth = max($labelWidth, $valueWidth); + $fieldsWidth = max($fieldsWidth, $textWidth); + } + + $requiredLength += $fieldsWidth; + } + + // Add more padding to prevent text from being cut off + // $requiredLength += self::TAPE_WIDTH * 0.8; + + // Ensure minimum length + $requiredLength = max($this->minHeight, $requiredLength); + + // If we're just calculating, return the length + if ($calculateOnly) { + return $requiredLength; + } + + // Otherwise, render the content + // Position barcode on the left side + if ($record->has('barcode2d') && $this->getSupport2DBarcode()) { + // Position at top of usable area + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + $this->barcodeMargin; + $usableWidth -= $barcodeSize + $this->barcodeMargin; + } + + // Position fields to the right of the barcode + if ($record->has('fields') && $this->getSupportFields() > 0) { + // Limit to the number of supported fields + $fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields()); + + // Calculate total height needed for fields + $totalFieldsHeight = 0; + foreach ($fields as $field) { + $totalFieldsHeight += $this->labelSize * 1.2 + $this->labelMargin; // Increased label size by 20% + $totalFieldsHeight += $this->fieldSize * 1.3 + $this->fieldMargin * 2; // Increased field size by 30% and margin + } + + // Start position - respect top margin + $fieldY = $currentY; // $currentY already includes the top margin + $fieldWidth = $usableWidth; + + // Calculate available height for fields (respecting margins) + $availableHeight = $usableHeight; + + // If fields don't fill available height, adjust spacing proportionally + // but don't exceed the available height + $scaleFactor = 1.0; // Default scale factor + if ($totalFieldsHeight < $availableHeight && count($fields) > 0) { + // Scale up to fill available height, but not too much + $scaleFactor = min(1.5, $availableHeight / $totalFieldsHeight); + } else if ($totalFieldsHeight > $availableHeight && count($fields) > 0) { + // Scale down to fit within available height + $scaleFactor = $availableHeight / $totalFieldsHeight; + } + + foreach ($fields as $field) { + // Calculate scaled spacing + $labelHeight = $this->labelSize * 1.2 * $scaleFactor; + $labelSpacing = $this->labelMargin * $scaleFactor; + $fieldHeight = $this->fieldSize * 1.3 * $scaleFactor; + $fieldSpacing = $this->fieldMargin * 2 * $scaleFactor; + + // Check if label is empty or null + $labelText = $field['label'] ?? ''; + $valueText = $field['value'] ?? ''; + + if (empty(trim($labelText))) { + // If label is empty, just render the value at the current Y position + static::writeText( + $pdf, $valueText, + $currentX, $fieldY, + 'freemono', 'B', $this->fieldSize * 1.3, 'L', // Increased field size by 30% + $fieldWidth, $fieldHeight, false, 0, 0.00 + ); + $fieldY += ($fieldHeight + $fieldSpacing) + 0.02; // Increased spacing after value + } else { + // If label has content, render both label and value + static::writeText( + $pdf, $labelText, + $currentX, $fieldY, + 'freesans', 'B', $this->labelSize * 1.2, 'L', // Increased label size by 20% and made bold + $labelWidth, $labelHeight, false, 0, + ); + $fieldY += ($labelHeight + $labelSpacing) + 0.01; + + // Value + static::writeText( + $pdf, $valueText, + $currentX, $fieldY, // Position value directly below label + 'freemono', 'B', $this->fieldSize * 1.3, 'L', // Increased field size by 30% + $fieldWidth, $fieldHeight, false, 0, 0.00 + ); + $fieldY += ($fieldHeight + $fieldSpacing) + 0.02; // Increased spacing after value + } + } + } + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/GenericTape.php b/app/Models/Labels/Tapes/Generic/GenericTape.php new file mode 100644 index 0000000000..1c8426a79a --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/GenericTape.php @@ -0,0 +1,127 @@ +width = $width; + $this->height = $height; + $this->continuous = $continuous; + $this->spacing = $spacing; + + // Calculate base font size (7% of tape width) + $baseFontSize = static::TAPE_WIDTH * 0.07; + + // Calculate margin (4% of tape width) + $margin = static::TAPE_WIDTH * 0.04; + + // Set margins + $this->marginTop = $margin; + $this->marginBottom = $margin; + $this->marginLeft = $margin; + $this->marginRight = $margin; + + // Calculate and set element sizing based on base font size + $this->titleSize = $baseFontSize; // Same as base font size + $this->titleMargin = $baseFontSize * 0.3; // 30% of base font size + $this->fieldSize = $baseFontSize * 1.1; // 110% of base font size + $this->fieldMargin = $baseFontSize * 0.1; // 10% of base font size + $this->labelSize = $baseFontSize * 0.7; // 70% of base font size + $this->labelMargin = $baseFontSize * -0.1; // -10% of base font size + $this->barcodeMargin = $baseFontSize * 0.5; // 50% of base font size + $this->tagSize = $baseFontSize * 0.8; // 80% of base font size + } + + // Unit of measurement + public function getUnit() + { + return 'mm'; + } + + // Label dimensions + public function getWidth() + { + return $this->width; + } + public function getHeight() + { + return $this->height; + } + + // Margins + public function getMarginTop() + { + return $this->marginTop; + } + public function getMarginBottom() + { + return $this->marginBottom; + } + public function getMarginLeft() + { + return $this->marginLeft; + } + public function getMarginRight() + { + return $this->marginRight; + } + + + /** + * Check if this is a continuous tape + * + * @return bool + */ + public function isContinuous() + { + return $this->continuous; + } + + /** + * Get spacing between labels (for die-cut tapes) + * + * @return float + */ + public function getSpacing() + { + return $this->spacing; + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/Tape_53mm.php b/app/Models/Labels/Tapes/Generic/Tape_53mm.php new file mode 100644 index 0000000000..679380f221 --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Tape_53mm.php @@ -0,0 +1,34 @@ +tapeHeight = $height; + } + + /** + * Get the barcode size ratio for calculations + * + * @return float + */ + public function getBarcodeRatio() + { + return 0.9; // Barcode should use 90% of available width + } +} \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Generic/Tape_53mm_A.php b/app/Models/Labels/Tapes/Generic/Tape_53mm_A.php new file mode 100644 index 0000000000..8b7a79dda0 --- /dev/null +++ b/app/Models/Labels/Tapes/Generic/Tape_53mm_A.php @@ -0,0 +1,101 @@ +SetAutoPageBreak(false); + } + + public function write($pdf, $record) + { + $pa = $this->getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', $this->titleSize, 'C', + $pa->w, $this->titleSize, true, 0 + ); + $currentY += $this->titleSize + $this->titleMargin; + $usableHeight -= $this->titleSize + $this->titleMargin; + } + + // Make the barcode as large as possible while still leaving room for fields + $barcodeSize = min($usableHeight * 0.8, $usableWidth * $this->getBarcodeRatio()); + + if ($record->has('barcode2d')) { + $barcodeX = $pa->x1 + ($usableWidth - $barcodeSize) / 2; + + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $barcodeX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentY += $barcodeSize + $this->barcodeMargin; + } + + if ($record->has('fields')) { + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', $this->labelSize, 'L', + $usableWidth, $this->labelSize, true, 0 + ); + $currentY += $this->labelSize + $this->labelMargin; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', $this->fieldSize, 'L', + $usableWidth, $this->fieldSize, true, 0, 0.01 + ); + $currentY += $this->fieldSize + $this->fieldMargin; + } + } + } +} \ No newline at end of file diff --git a/app/Models/Ldap.php b/app/Models/Ldap.php index f71f926a93..c40ad60f16 100644 --- a/app/Models/Ldap.php +++ b/app/Models/Ldap.php @@ -27,11 +27,40 @@ use Illuminate\Support\Facades\Crypt; class Ldap extends Model { + public static function ignoreCertificates(bool $ignore_cert = true) + { + if (defined('LDAP_OPT_X_TLS_REQUIRE_CERT') && defined('LDAP_OPT_X_TLS_NEVER')) { + // TODO - we are currently, as a 'safety', doing *both* the following 'new-style' ldap_set_option calls, + // as well as "falling-through" to the 'old-style' putenv() calls. + // + // I *suspect* we can eventually remove the putenv() calls, but I'm just a little nervous about that. + // According to the PHP docs, the LDAP_OPT_X_TLS_REQUIRE_CERT constant has been available since PHP 7.0. + // We're currently using PHP versions way, way later than that (v8.2-v8.4 as of this writing). So it's + // unlikely that these constants wouldn't be defined - unless you didn't have LDAP support in the first + // place. But if that were to happen, I would hope we would've detected that long, long ago, rather than at + // this point. + if ($ignore_cert) { + if (ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER)) { + //return true; + } + } else { + if (ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) { + //return true; + } + } + } + if ($ignore_cert) { + return putenv('LDAPTLS_REQCERT=never'); + } else { + return putenv('LDAPTLS_REQCERT'); + } + } + /** * Makes a connection to LDAP using the settings in Admin > Settings. * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * @return connection */ public static function connectToLdap() @@ -43,15 +72,12 @@ class Ldap extends Model // If we are ignoring the SSL cert we need to setup the environment variable // before we create the connection - if ($ldap_server_cert_ignore == '1') { - putenv('LDAPTLS_REQCERT=never'); - } + self::ignoreCertificates((bool)$ldap_server_cert_ignore); // If the user specifies where CA Certs are, make sure to use them if (env('LDAPTLS_CACERT')) { putenv('LDAPTLS_CACERT='.env('LDAPTLS_CACERT')); } - $connection = @ldap_connect($ldap_host); if (! $connection) { @@ -81,10 +107,10 @@ class Ldap extends Model * Binds/authenticates the user to LDAP, and returns their attributes. * * @author [A. Gianotto] [] - * @since [v3.0] - * @param $username - * @param $password - * @param bool|false $user + * @since [v3.0] + * @param $username + * @param $password + * @param bool|false $user * @return bool true if the username and/or password provided are valid * false if the username and/or password provided are invalid * array of ldap_attributes if $user is true @@ -160,8 +186,8 @@ class Ldap extends Model * Here we also return a better error if the app key is donked. * * @author [A. Gianotto] [] - * @since [v3.0] - * @param bool|false $user + * @since [v3.0] + * @param bool|false $user * @return bool true if the username and/or password provided are valid * false if the username and/or password provided are invalid */ @@ -169,37 +195,37 @@ class Ldap extends Model { $ldap_username = Setting::getSettings()->ldap_uname; - if ( $ldap_username ) { - // Lets return some nicer messages for users who donked their app key, and disable LDAP - try { - $ldap_pass = Crypt::decrypt(Setting::getSettings()->ldap_pword); - } catch (Exception $e) { - throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.'); - } + if ($ldap_username ) { + // Lets return some nicer messages for users who donked their app key, and disable LDAP + try { + $ldap_pass = Crypt::decrypt(Setting::getSettings()->ldap_pword); + } catch (Exception $e) { + throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.'); + } - if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) { - throw new Exception('Could not bind to LDAP: '.ldap_error($connection)); - } - // TODO - this just "falls off the end" but the function states that it should return true or false - // unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire - // so I don't want to fix this right now. - // this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined) - // at the next refactor, this should be appropriately modified to be more consistent. - } else { - // LDAP should also work with anonymous bind (no dn, no password available) - if (! $ldapbind = @ldap_bind($connection )) { - throw new Exception('Could not bind to LDAP: '.ldap_error($connection)); - } - } - } + if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) { + throw new Exception('Could not bind to LDAP: '.ldap_error($connection)); + } + // TODO - this just "falls off the end" but the function states that it should return true or false + // unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire + // so I don't want to fix this right now. + // this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined) + // at the next refactor, this should be appropriately modified to be more consistent. + } else { + // LDAP should also work with anonymous bind (no dn, no password available) + if (! $ldapbind = @ldap_bind($connection)) { + throw new Exception('Could not bind to LDAP: '.ldap_error($connection)); + } + } + } /** * Parse and map LDAP attributes based on settings * * @author [A. Gianotto] [] - * @since [v3.0] + * @since [v3.0] * - * @param $ldapatttibutes + * @param $ldapatttibutes * @return array|bool */ public static function parseAndMapLdapAttributes($ldapattributes) @@ -238,8 +264,8 @@ class Ldap extends Model * Create user from LDAP attributes * * @author [A. Gianotto] [] - * @since [v3.0] - * @param $ldapatttibutes + * @since [v3.0] + * @param $ldapatttibutes * @return User | bool */ public static function createUserFromLdap($ldapatttibutes, $password) @@ -279,11 +305,11 @@ class Ldap extends Model * Searches LDAP * * @author [A. Gianotto] [] - * @since [v3.0] - * @param $base_dn - * @param $count - * @param $filter - * @param $attributes + * @since [v3.0] + * @param $base_dn + * @param $count + * @param $filter + * @param $attributes * @return array|bool */ public static function findLdapUsers($base_dn = null, $count = -1, $filter = null, $attributes = []) @@ -331,7 +357,7 @@ class Ldap extends Model $errmsg = null; $referrals = null; $controls = []; - ldap_parse_result($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, $controls); + ldap_parse_result($ldapconn, $search_results, $errcode, $matcheddn, $errmsg, $referrals, $controls); if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) { // You need to pass the cookie from the last call to the next one $cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie']; diff --git a/app/Models/License.php b/app/Models/License.php index 490a8401a4..ecd1b003e3 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -3,13 +3,14 @@ namespace App\Models; use App\Helpers\Helper; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Session; use Watson\Validating\ValidatingTrait; @@ -21,6 +22,7 @@ class License extends Depreciable use SoftDeletes; use CompanyableTrait; + use HasUploads; use Loggable, Presentable; protected $injectUniqueIdentifier = true; use ValidatingTrait; @@ -43,13 +45,13 @@ class License extends Depreciable protected $rules = [ 'name' => 'required|string|min:3|max:255', - 'seats' => 'required|min:1|integer', + 'seats' => 'required|min:1|integer|limit_change:10000', // limit_change is a "pseudo-rule" that translates into 'between', see prepareLimitChangeRule() below 'license_email' => 'email|nullable|max:120', 'license_name' => 'string|nullable|max:100', 'notes' => 'string|nullable', 'category_id' => 'required|exists:categories,id', 'company_id' => 'integer|nullable', - 'purchase_cost'=> 'numeric|nullable|gte:0', + 'purchase_cost' => 'numeric|nullable|gte:0|max:99999999999999999.99', 'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id', 'expiration_date' => 'date_format:Y-m-d|nullable|max:10', 'termination_date' => 'date_format:Y-m-d|nullable|max:10', @@ -113,6 +115,7 @@ class License extends Depreciable 'company' => ['name'], 'category' => ['name'], 'depreciation' => ['name'], + 'supplier' => ['name'], ]; protected $appends = ['free_seat_count']; @@ -120,39 +123,51 @@ class License extends Depreciable * Update seat counts when the license is updated * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] */ public static function boot() { parent::boot(); // We need to listen for created for the initial setup so that we have a license ID. - static::created(function ($license) { - $newSeatCount = $license->getAttributes()['seats']; + static::created( + function ($license) { + $newSeatCount = $license->getAttributes()['seats']; - return static::adjustSeatCount($license, 0, $newSeatCount); - }); + return static::adjustSeatCount($license, 0, $newSeatCount); + } + ); // However, we listen for updating to be able to prevent the edit if we cannot delete enough seats. - static::updating(function ($license) { - $newSeatCount = $license->getAttributes()['seats']; - //$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0; - /* - That previous method *did* mostly work, but if you ever managed to get your $license->seats value out of whack - with your actual count of license_seats *records*, you would never manage to get back 'into whack'. - The below method actually grabs a count of existing license_seats records, so it will be more accurate. - This means that if your license_seats are out of whack, you can change the quantity and hit 'save' and it - will manage to 'true up' and make your counts line up correctly. - */ - $oldSeatCount = $license->license_seats_count; + static::updating( + function ($license) { + $newSeatCount = $license->getAttributes()['seats']; + //$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0; + /* + That previous method *did* mostly work, but if you ever managed to get your $license->seats value out of whack + with your actual count of license_seats *records*, you would never manage to get back 'into whack'. + The below method actually grabs a count of existing license_seats records, so it will be more accurate. + This means that if your license_seats are out of whack, you can change the quantity and hit 'save' and it + will manage to 'true up' and make your counts line up correctly. + */ + $oldSeatCount = $license->license_seats_count; - return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount); - }); + return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount); + } + ); + } + + public function prepareLimitChangeRule($parameters, $field) + { + $actual_seat_count = $this->licenseseats()->count(); //we use the *actual* seat count here, in case your license has gone wonky + $lower_bound = $actual_seat_count - $parameters[0]; + $upper_bound = $actual_seat_count + $parameters[0]; + return ["between", ($lower_bound <= 0 ? 1 : $lower_bound), $upper_bound]; } /** * Balance seat counts * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public static function adjustSeatCount($license, $oldSeats, $newSeats) @@ -164,21 +179,17 @@ class License extends Depreciable // On Create, we just make one for each of the seats. $change = abs($oldSeats - $newSeats); if ($oldSeats > $newSeats) { - $license->load('licenseseats.user'); // Need to delete seats... lets see if if we have enough. - $seatsAvailableForDelete = $license->licenseseats->reject(function ($seat) { - return ((bool) $seat->assigned_to) || ((bool) $seat->asset_id); - }); + $seatsAvailableForDelete = $license->licenseseats()->whereNull('assigned_to')->whereNull('asset_id')->limit($change); if ($change > $seatsAvailableForDelete->count()) { Session::flash('error', trans('admin/licenses/message.assoc_users')); return false; } - for ($i = 1; $i <= $change; $i++) { - $seatsAvailableForDelete->pop()->delete(); - } + $seatsAvailableForDelete->delete(); + // Log Deletion of seats. $logAction = new Actionlog; $logAction->item_type = self::class; @@ -203,11 +214,15 @@ class License extends Depreciable } //Chunk and use DB transactions to prevent timeouts. - collect($licenseInsert)->chunk(1000)->each(function ($chunk) { - DB::transaction(function () use ($chunk) { - LicenseSeat::insert($chunk->toArray()); - }); - }); + collect($licenseInsert)->chunk(1000)->each( + function ($chunk) { + DB::transaction( + function () use ($chunk) { + LicenseSeat::insert($chunk->toArray()); + } + ); + } + ); // On initial create, we shouldn't log the addition of seats. if ($license->id) { @@ -228,7 +243,7 @@ class License extends Depreciable * Sets the attribute for whether or not the license is maintained * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return mixed */ public function setMaintainedAttribute($value) @@ -240,7 +255,7 @@ class License extends Depreciable * Sets the reassignable attribute * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return mixed */ public function setReassignableAttribute($value) @@ -252,7 +267,7 @@ class License extends Depreciable * Sets expiration date attribute * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return mixed */ public function setExpirationDateAttribute($value) @@ -269,7 +284,7 @@ class License extends Depreciable * Sets termination date attribute * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return mixed */ public function setTerminationDateAttribute($value) @@ -285,10 +300,11 @@ class License extends Depreciable * Sets free_seat_count attribute * * @author G. Martinez - * @since [v6.3] + * @since [v6.3] * @return mixed */ - public function getFreeSeatCountAttribute(){ + public function getFreeSeatCountAttribute() + { return $this->attributes['free_seat_count'] = $this->remaincount(); } @@ -296,7 +312,7 @@ class License extends Depreciable * Establishes the license -> company relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -308,7 +324,7 @@ class License extends Depreciable * Establishes the license -> category relationship * * @author A. Gianotto - * @since [v4.4.0] + * @since [v4.4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function category() @@ -320,7 +336,7 @@ class License extends Depreciable * Establishes the license -> manufacturer relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manufacturer() @@ -332,7 +348,7 @@ class License extends Depreciable * Determine whether the user should be emailed on checkin/checkout * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return bool */ public function checkin_email() @@ -347,7 +363,7 @@ class License extends Depreciable * Determine whether the user should be required to accept the license * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return bool */ public function requireAcceptance() @@ -364,12 +380,12 @@ class License extends Depreciable * checks for a settings level EULA * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * @return string | false */ public function getEula() { - if ($this->category){ + if ($this->category) { if ($this->category->eula_text) { return Helper::parseEscapedMarkedown($this->category->eula_text); } elseif ($this->category->use_default_eula == '1') { @@ -385,7 +401,7 @@ class License extends Depreciable * Establishes the license -> assigned user relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedusers() @@ -397,7 +413,7 @@ class License extends Depreciable * Establishes the license -> action logs relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() @@ -407,28 +423,13 @@ class License extends Depreciable ->orderBy('created_at', 'desc'); } - /** - * Establishes the license -> action logs -> uploads relationship - * - * @author A. Gianotto - * @since [v2.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') - ->where('item_type', '=', self::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } /** * Establishes the license -> admin user relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function adminuser() @@ -442,7 +443,7 @@ class License extends Depreciable * @todo this can probably be refactored at some point. We don't need counting methods. * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return int */ public static function assetcount() @@ -458,7 +459,7 @@ class License extends Depreciable * @todo this can also probably be refactored at some point. * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function totalSeatsByLicenseID() @@ -475,7 +476,7 @@ class License extends Depreciable * Otherwise calling "count()" on each model results in n+1 sadness. * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenseSeatsRelation() @@ -487,7 +488,7 @@ class License extends Depreciable * Sets the license seat count attribute * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return int */ public function getLicenseSeatsCountAttribute() @@ -503,7 +504,7 @@ class License extends Depreciable * Returns the number of total available seats across all licenses * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return int */ public static function availassetcount() @@ -517,7 +518,7 @@ class License extends Depreciable * Returns the available seats remaining * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return int */ @@ -525,7 +526,7 @@ class License extends Depreciable * Returns the number of total available seats for this license * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function availCount() @@ -541,7 +542,7 @@ class License extends Depreciable * Sets the available seats attribute * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return mixed */ public function getAvailSeatsCountAttribute() @@ -557,22 +558,24 @@ class License extends Depreciable * Retuns the number of assigned seats for this asset * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedCount() { - return $this->licenseSeatsRelation()->where(function ($query) { - $query->whereNotNull('assigned_to') - ->orWhereNotNull('asset_id'); - }); + return $this->licenseSeatsRelation()->where( + function ($query) { + $query->whereNotNull('assigned_to') + ->orWhereNotNull('asset_id'); + } + ); } /** * Sets the assigned seats attribute * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return int */ public function getAssignedSeatsCountAttribute() @@ -603,7 +606,7 @@ class License extends Depreciable * Calculates the number of remaining seats * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return int */ public function remaincount() : int @@ -620,7 +623,7 @@ class License extends Depreciable * Returns the total number of seats for this license * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return int */ public function totalcount() @@ -636,7 +639,7 @@ class License extends Depreciable * Establishes the license -> seats relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenseseats() @@ -648,7 +651,7 @@ class License extends Depreciable * Establishes the license -> supplier relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function supplier() @@ -662,7 +665,7 @@ class License extends Depreciable * the API to populate next_seat * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return mixed */ public function freeSeat() @@ -683,7 +686,7 @@ class License extends Depreciable * Establishes the license -> free seats relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function freeSeats() @@ -697,7 +700,7 @@ class License extends Depreciable * @todo should refactor. I don't like get() in model methods * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public static function getExpiringLicenses($days = 60) @@ -715,8 +718,8 @@ class License extends Depreciable /** * Query builder scope to order on manufacturer * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -729,8 +732,8 @@ class License extends Depreciable /** * Query builder scope to order on supplier * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -743,8 +746,8 @@ class License extends Depreciable /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php index 449e0f6ab3..9ddd3fb431 100755 --- a/app/Models/LicenseSeat.php +++ b/app/Models/LicenseSeat.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Models\Traits\Acceptable; +use App\Models\Traits\CompanyableChildTrait; use App\Notifications\CheckinLicenseNotification; use App\Notifications\CheckoutLicenseNotification; use App\Presenters\Presentable; @@ -47,7 +48,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild * Determine whether the user should be required to accept the license * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return bool */ public function requireAcceptance() @@ -67,7 +68,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild * Establishes the seat -> license relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function license() @@ -79,7 +80,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild * Establishes the seat -> assignee relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function user() @@ -91,7 +92,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild * Establishes the seat -> asset relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function asset() @@ -104,7 +105,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild * or asset its assigned to * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return string */ public function location() @@ -121,8 +122,8 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild /** * Query builder scope to order on department * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -133,4 +134,22 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild ->whereNotNull('license_seats.assigned_to') ->orderBy('license_user_dept.name', $order); } + + + public function scopeByAssigned($query) + { + + return $query->where( + function ($query) { + $query->whereNotNull('assigned_to') + ->orWhere( + function ($query) { + $query->whereNotNull('asset_id'); + } + ); + } + ); + + } + } diff --git a/app/Models/Location.php b/app/Models/Location.php index 9b1ed02f43..352ffd1201 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -3,15 +3,11 @@ namespace App\Models; use App\Http\Traits\UniqueUndeletedTrait; -use App\Models\Asset; -use App\Models\Setting; -use App\Models\SnipeModel; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; -use App\Models\User; use App\Presenters\Presentable; -use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Gate; use Watson\Validating\ValidatingTrait; @@ -25,6 +21,7 @@ class Location extends SnipeModel protected $presenter = \App\Presenters\LocationPresenter::class; use Presentable; use SoftDeletes; + use HasUploads; protected $table = 'locations'; protected $rules = [ @@ -109,7 +106,7 @@ class Location extends SnipeModel * it can be deleted. It's tempting to load those here, but that increases the query load considerably. * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return bool */ public function isDeletable() @@ -127,7 +124,7 @@ class Location extends SnipeModel * Establishes the user -> location relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function users() @@ -135,21 +132,34 @@ class Location extends SnipeModel return $this->hasMany(\App\Models\User::class, 'location_id'); } + /** + * Establishes the location -> admin user relationship + * + * @author A. Gianotto + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Find assets with this location as their location_id * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() { return $this->hasMany(\App\Models\Asset::class, 'location_id') - ->whereHas('assetstatus', function ($query) { - $query->where('status_labels.deployable', '=', 1) + ->whereHas( + 'assetstatus', function ($query) { + $query->where('status_labels.deployable', '=', 1) ->orWhere('status_labels.pending', '=', 1) ->orWhere('status_labels.archived', '=', 0); - }); + } + ); } @@ -157,7 +167,7 @@ class Location extends SnipeModel * Establishes the asset -> rtd_location relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function rtd_assets() @@ -177,7 +187,7 @@ class Location extends SnipeModel * Establishes the consumable -> location relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() @@ -189,7 +199,7 @@ class Location extends SnipeModel * Establishes the component -> location relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function components() @@ -201,7 +211,7 @@ class Location extends SnipeModel * Establishes the component -> accessory relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() @@ -213,7 +223,7 @@ class Location extends SnipeModel * Find the parent of a location * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function parent() @@ -223,12 +233,12 @@ class Location extends SnipeModel } /** - * Establishes the locations -> company relationship - * - * @author [T. Regnery] [] - * @since [v7.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ + * Establishes the locations -> company relationship + * + * @author [T. Regnery] [] + * @since [v7.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ public function company() { return $this->belongsTo(\App\Models\Company::class, 'company_id'); @@ -238,7 +248,7 @@ class Location extends SnipeModel * Find the manager of a location * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manager() @@ -251,7 +261,7 @@ class Location extends SnipeModel * Find children of a location * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function children() @@ -264,7 +274,7 @@ class Location extends SnipeModel * Establishes the asset -> location assignment relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedAssets() @@ -276,7 +286,7 @@ class Location extends SnipeModel * Establishes the accessory -> location assignment relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assignedAccessories() @@ -289,28 +299,12 @@ class Location extends SnipeModel return $this->attributes['ldap_ou'] = empty($ldap_ou) ? null : $ldap_ou; } - /** - * Get uploads for this location - * - * @author [A. Gianotto] [] - * @since [v4.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany('\App\Models\Actionlog', 'item_id') - ->where('item_type', '=', Location::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } - /** * Query builder scope to order on parent * - * @param Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return Illuminate\Database\Query\Builder Modified query builder */ @@ -338,8 +332,8 @@ class Location extends SnipeModel /** * Query builder scope to order on parent * - * @param Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return Illuminate\Database\Query\Builder Modified query builder */ @@ -352,8 +346,8 @@ class Location extends SnipeModel /** * Query builder scope to order on manager name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -362,16 +356,25 @@ class Location extends SnipeModel return $query->leftJoin('users as location_user', 'locations.manager_id', '=', 'location_user.id')->orderBy('location_user.first_name', $order)->orderBy('location_user.last_name', $order); } - /** - * Query builder scope to order on company - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ + /** + * Query builder scope to order on company + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ public function scopeOrderCompany($query, $order) { return $query->leftJoin('companies as company_sort', 'locations.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order); } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'locations.created_by', '=', 'admin_sort.id')->select('locations.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php index 506eb116b9..f912d15927 100644 --- a/app/Models/Loggable.php +++ b/app/Models/Loggable.php @@ -15,8 +15,8 @@ trait Loggable public ?bool $imported = false; /** - * @author Daniel Meltzer - * @since [v3.4] + * @author Daniel Meltzer + * @since [v3.4] * @return \App\Models\Actionlog */ public function log() @@ -30,8 +30,8 @@ trait Loggable } /** - * @author Daniel Meltzer - * @since [v3.4] + * @author Daniel Meltzer + * @since [v3.4] * @return \App\Models\Actionlog */ public function logCheckout($note, $target, $action_date = null, $originalValues = []) @@ -89,27 +89,26 @@ trait Loggable $log->note = $note; $log->action_date = $action_date; - if (! $log->action_date) { - $log->action_date = date('Y-m-d H:i:s'); - } $changed = []; $array_to_flip = array_keys($fields_array); - $array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']); + $array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']); $originalValues = array_intersect_key($originalValues, array_flip($array_to_flip)); foreach ($originalValues as $key => $value) { + // TODO - action_date isn't a valid attribute of any first-class object, so we might want to remove this? if ($key == 'action_date' && $value != $action_date) { $changed[$key]['old'] = $value; $changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s'); - } elseif ($value != $this->getAttributes()[$key]) { + } elseif (array_key_exists($key, $this->getAttributes()) && $value != $this->getAttributes()[$key]) { $changed[$key]['old'] = $value; $changed[$key]['new'] = $this->getAttributes()[$key]; } + // NOTE - if the attribute exists in $originalValues, but *not* in ->getAttributes(), it isn't added to $changed } - if (!empty($changed)){ + if (!empty($changed)) { $log->log_meta = json_encode($changed); } @@ -136,8 +135,8 @@ trait Loggable } /** - * @author Daniel Meltzer - * @since [v3.4] + * @author Daniel Meltzer + * @since [v3.4] * @return \App\Models\Actionlog */ public function logCheckin($target, $note, $action_date = null, $originalValues = []) @@ -146,7 +145,7 @@ trait Loggable $fields_array = []; - if($target != null){ + if($target != null) { $log->target_type = get_class($target); $log->target_id = $target->id; @@ -180,7 +179,7 @@ trait Loggable $log->note = $note; $log->action_date = $action_date; - if (! $log->action_date) { + if (!$action_date) { $log->action_date = date('Y-m-d H:i:s'); } @@ -191,7 +190,7 @@ trait Loggable $changed = []; $array_to_flip = array_keys($fields_array); - $array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']); + $array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']); $originalValues = array_intersect_key($originalValues, array_flip($array_to_flip)); @@ -206,7 +205,7 @@ trait Loggable } } - if (!empty($changed)){ + if (!empty($changed)) { $log->log_meta = json_encode($changed); } @@ -216,8 +215,8 @@ trait Loggable } /** - * @author A. Gianotto - * @since [v4.0] + * @author A. Gianotto + * @since [v4.0] * @return \App\Models\Actionlog */ public function logAudit($note, $location_id, $filename = null, $originalValues = []) @@ -250,7 +249,7 @@ trait Loggable } } - if (!empty($changed)){ + if (!empty($changed)) { $log->log_meta = json_encode($changed); } @@ -277,7 +276,7 @@ trait Loggable 'location' => ($location) ? $location->name : '', 'note' => $note, ]; - if(Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')){ + if(Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { $message = AuditNotification::toMicrosoftTeams($params); $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); $notification->success()->sendMessage($message[0], $message[1]); @@ -290,8 +289,8 @@ trait Loggable } /** - * @author Daniel Meltzer - * @since [v3.5] + * @author Daniel Meltzer + * @since [v3.5] * @return \App\Models\Actionlog */ public function logCreate($note = null) @@ -319,8 +318,8 @@ trait Loggable } /** - * @author Daniel Meltzer - * @since [v3.4] + * @author Daniel Meltzer + * @since [v3.4] * @return \App\Models\Actionlog */ public function logUpload($filename, $note) @@ -351,7 +350,7 @@ trait Loggable * Returns the latest acceptance ActionLog that contains a signature * from $user or null if there is none * - * @param User $user + * @param User $user * @return null|Actionlog **/ public function getLatestSignedAcceptance(User $user) diff --git a/app/Models/AssetMaintenance.php b/app/Models/Maintenance.php similarity index 56% rename from app/Models/AssetMaintenance.php rename to app/Models/Maintenance.php index 246220f5c7..942fdb122a 100644 --- a/app/Models/AssetMaintenance.php +++ b/app/Models/Maintenance.php @@ -3,37 +3,41 @@ namespace App\Models; use App\Helpers\Helper; +use App\Models\Traits\CompanyableChildTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; +use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; /** * Model for Asset Maintenances. * - * @version v1.0 + * @version v1.0 */ -class AssetMaintenance extends Model implements ICompanyableChild +class Maintenance extends SnipeModel implements ICompanyableChild { use HasFactory; + use HasUploads; use SoftDeletes; use CompanyableChildTrait; use ValidatingTrait; + use Loggable, Presentable; - protected $table = 'asset_maintenances'; + protected $table = 'maintenances'; protected $rules = [ 'asset_id' => 'required|integer', - 'supplier_id' => 'required|integer', + 'supplier_id' => 'nullable|integer', 'asset_maintenance_type' => 'required', - 'title' => 'required|max:100', + 'name' => 'required|max:100', 'is_warranty' => 'boolean', 'start_date' => 'required|date_format:Y-m-d', - 'completion_date' => 'date_format:Y-m-d|nullable', + 'completion_date' => 'date_format:Y-m-d|nullable|after_or_equal:start_date', 'notes' => 'string|nullable', - 'cost' => 'numeric|nullable', + 'cost' => 'numeric|nullable|gte:0|max:99999999999999999.99', ]; @@ -43,7 +47,7 @@ class AssetMaintenance extends Model implements ICompanyableChild * @var array */ protected $fillable = [ - 'title', + 'name', 'asset_id', 'supplier_id', 'asset_maintenance_type', @@ -64,7 +68,7 @@ class AssetMaintenance extends Model implements ICompanyableChild */ protected $searchableAttributes = [ - 'title', + 'name', 'notes', 'asset_maintenance_type', 'cost', @@ -93,21 +97,21 @@ class AssetMaintenance extends Model implements ICompanyableChild /** * getImprovementOptions * - * @return array + * @return array * @author Vincent Sposato * @version v1.0 */ public static function getImprovementOptions() { return [ - trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'), - trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'), - trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'), - trans('admin/asset_maintenances/general.pat_test') => trans('admin/asset_maintenances/general.pat_test'), - trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'), - trans('admin/asset_maintenances/general.software_support') => trans('admin/asset_maintenances/general.software_support'), - trans('admin/asset_maintenances/general.hardware_support') => trans('admin/asset_maintenances/general.hardware_support'), - trans('admin/asset_maintenances/general.configuration_change') => trans('admin/asset_maintenances/general.configuration_change'), + trans('admin/maintenances/general.maintenance') => trans('admin/maintenances/general.maintenance'), + trans('admin/maintenances/general.repair') => trans('admin/maintenances/general.repair'), + trans('admin/maintenances/general.upgrade') => trans('admin/maintenances/general.upgrade'), + trans('admin/maintenances/general.pat_test') => trans('admin/maintenances/general.pat_test'), + trans('admin/maintenances/general.calibration') => trans('admin/maintenances/general.calibration'), + trans('admin/maintenances/general.software_support') => trans('admin/maintenances/general.software_support'), + trans('admin/maintenances/general.hardware_support') => trans('admin/maintenances/general.hardware_support'), + trans('admin/maintenances/general.configuration_change') => trans('admin/maintenances/general.configuration_change'), ]; } @@ -157,20 +161,36 @@ class AssetMaintenance extends Model implements ICompanyableChild * asset * Get asset for this improvement * - * @return mixed + * @return mixed * @author Vincent Sposato * @version v1.0 */ public function asset() { return $this->belongsTo(\App\Models\Asset::class, 'asset_id') - ->withTrashed(); + ->withTrashed(); } + /** + * Get the maintenance logs + * + * @author [A. Gianotto] [] + * @since [v8.2.2] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assetlog() + { + return $this->hasMany(\App\Models\Actionlog::class, 'item_id') + ->where('item_type', '=', self::class) + ->orderBy('created_at', 'desc') + ->withTrashed(); + } + + /** * Get the admin who created the maintenance * - * @return mixed + * @return mixed * @author A. Gianotto * @version v3.0 */ @@ -183,7 +203,12 @@ class AssetMaintenance extends Model implements ICompanyableChild public function supplier() { return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id') - ->withTrashed(); + ->withTrashed(); + } + + public function getDisplayNameAttribute() + { + return $this->name; } /** @@ -195,14 +220,14 @@ class AssetMaintenance extends Model implements ICompanyableChild /** * Query builder scope to order on a supplier * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOrderBySupplier($query, $order) { - return $query->leftJoin('suppliers as suppliers_maintenances', 'asset_maintenances.supplier_id', '=', 'suppliers_maintenances.id') + return $query->leftJoin('suppliers as suppliers_maintenances', 'maintenances.supplier_id', '=', 'suppliers_maintenances.id') ->orderBy('suppliers_maintenances.name', $order); } @@ -211,65 +236,80 @@ class AssetMaintenance extends Model implements ICompanyableChild /** * Query builder scope to order on asset tag * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOrderByTag($query, $order) { - return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id') + return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id') ->orderBy('assets.asset_tag', $order); } /** * Query builder scope to order on asset tag * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOrderByAssetName($query, $order) { - return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id') + return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id') ->orderBy('assets.name', $order); } /** * Query builder scope to order on serial * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOrderByAssetSerial($query, $order) { - return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id') + return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id') ->orderBy('assets.serial', $order); } /** * Query builder scope to order on status label name * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ public function scopeOrderStatusName($query, $order) { - return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id') + return $query->join('assets as maintained_asset', 'maintenances.asset_id', '=', 'maintained_asset.id') ->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id') ->orderBy('maintained_asset_status.name', $order); } + /** + * Query builder scope to order on status label name + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderLocationName($query, $order) + { + return $query->join('assets as maintained_asset', 'maintenances.asset_id', '=', 'maintained_asset.id') + ->leftjoin('locations as maintained_asset_location', 'maintained_asset_location.id', '=', 'maintained_asset.location_id') + ->orderBy('maintained_asset_location.name', $order); + } + /** * Query builder scope to order on the user that created it */ public function scopeOrderByCreatedBy($query, $order) { - return $query->leftJoin('users as admin_sort', 'asset_maintenances.created_by', '=', 'admin_sort.id')->select('asset_maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + return $query->leftJoin('users as admin_sort', 'maintenances.created_by', '=', 'admin_sort.id')->select('maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } } diff --git a/app/Models/PredefinedKit.php b/app/Models/PredefinedKit.php index 36790a1fc7..2d0c87066e 100644 --- a/app/Models/PredefinedKit.php +++ b/app/Models/PredefinedKit.php @@ -11,8 +11,8 @@ use Watson\Validating\ValidatingTrait; /** * Model for predefined kits. * - * @author [D. Minaev.] [] - * @version v1.0 + * @author [D. Minaev.] [] + * @version v1.0 */ class PredefinedKit extends SnipeModel { @@ -39,8 +39,9 @@ class PredefinedKit extends SnipeModel /** * this rules use in edit an attached asset model form * see PredefinedKit::_makeRuleHelper function for details - * @param int $model_id - * @param bool $new = true if append a new element to kit + * + * @param int $model_id + * @param bool $new = true if append a new element to kit */ public function makeModelRules($model_id, $new = false) { @@ -50,8 +51,9 @@ class PredefinedKit extends SnipeModel /** * this rules use in edit an attached license form * see PredefinedKit::_makeRuleHelper function for details - * @param int $license_id - * @param bool $new = true if append a new element to kit + * + * @param int $license_id + * @param bool $new = true if append a new element to kit */ public function makeLicenseRules($license_id, $new = false) { @@ -61,8 +63,9 @@ class PredefinedKit extends SnipeModel /** * this rules use in edit an attached accessory form * see PredefinedKit::_makeRuleHelper function for details - * @param int $accessoriy_id - * @param bool $new = true if append a new element to kit + * + * @param int $accessoriy_id + * @param bool $new = true if append a new element to kit */ public function makeAccessoryRules($accessory_id, $new = false) { @@ -72,8 +75,9 @@ class PredefinedKit extends SnipeModel /** * this rules use in edit an attached consumable form * see PredefinedKit::_makeRuleHelper function for details - * @param int $consumable_id - * @param bool $new = true if append a new element to kit + * + * @param int $consumable_id + * @param bool $new = true if append a new element to kit */ public function makeConsumableRules($consumable_id, $new = false) { @@ -86,11 +90,12 @@ class PredefinedKit extends SnipeModel * uniqueness of the record in table for this kit * existence of record in table * and simple types check - * @param string $table element table name - * @param string $pivot_table kit+element table name - * @param string $pivot_elem_key element key name inside pivot table - * @param int $element_id - * @param bool $new = true if append a new element to kit + * + * @param string $table element table name + * @param string $pivot_table kit+element table name + * @param string $pivot_elem_key element key name inside pivot table + * @param int $element_id + * @param bool $new = true if append a new element to kit * @return array */ protected function _makeRuleHelper($table, $pivot_table, $pivot_elem_key, $element_id, $new) @@ -144,6 +149,7 @@ class PredefinedKit extends SnipeModel /** * Establishes the kits -> models relationship + * * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function models() @@ -158,6 +164,7 @@ class PredefinedKit extends SnipeModel /** * Establishes the kits -> licenses relationship + * * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -167,6 +174,7 @@ class PredefinedKit extends SnipeModel /** * Establishes the kits -> licenses relationship + * * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() @@ -176,6 +184,7 @@ class PredefinedKit extends SnipeModel /** * Establishes the kits -> licenses relationship + * * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() diff --git a/app/Models/Recipients/AdminRecipient.php b/app/Models/Recipients/AdminRecipient.php index 90e39d4ee5..f74997abd2 100644 --- a/app/Models/Recipients/AdminRecipient.php +++ b/app/Models/Recipients/AdminRecipient.php @@ -14,7 +14,8 @@ class AdminRecipient extends Recipient $this->email = trim($settings->admin_cc_email); } - public function getEmail(){ + public function getEmail() + { return $this->email; } } diff --git a/app/Models/ReportTemplate.php b/app/Models/ReportTemplate.php index cdd05b6f50..452bd029d7 100644 --- a/app/Models/ReportTemplate.php +++ b/app/Models/ReportTemplate.php @@ -39,16 +39,17 @@ class ReportTemplate extends Model protected static function booted() { // Scope to current user - static::addGlobalScope('current_user', function (Builder $builder) { - if (auth()->check()) { - $builder->where('created_by', auth()->id()); + static::addGlobalScope( + 'current_user', function (Builder $builder) { + if (auth()->check()) { + $builder->where('created_by', auth()->id()); + } } - }); + ); } /** * Establishes the report template -> creator relationship. - * */ public function creator(): BelongsTo { @@ -60,7 +61,6 @@ class ReportTemplate extends Model * * @param string $fieldName * @param string $fallbackValue The value to return if the report template is not saved yet. - * */ public function checkmarkValue(string $fieldName, string $fallbackValue = '1'): string { @@ -83,9 +83,8 @@ class ReportTemplate extends Model * Get the value of a radio field for the given field name. * * @param string $fieldName - * @param string $value The value to check against. - * @param bool $isDefault Whether the radio input being checked is the default. - * + * @param string $value The value to check against. + * @param bool $isDefault Whether the radio input being checked is the default. */ public function radioValue(string $fieldName, string $value, bool $isDefault = false): bool { @@ -109,11 +108,10 @@ class ReportTemplate extends Model /** * Get the value of a select field for the given field name. * - * @param string $fieldName - * @param string|null $model The Eloquent model to check against. + * @param string $fieldName + * @param string|null $model The Eloquent model to check against. * * @return mixed|null - * */ public function selectValue(string $fieldName, string $model = null) { @@ -146,11 +144,10 @@ class ReportTemplate extends Model /** * Get the values of a multi-select field for the given field name. * - * @param string $fieldName - * @param string|null $model The Eloquent model to check against. + * @param string $fieldName + * @param string|null $model The Eloquent model to check against. * * @return iterable - * */ public function selectValues(string $fieldName, string $model = null): iterable { @@ -178,8 +175,8 @@ class ReportTemplate extends Model /** * Get the value of a text field for the given field name. * - * @param string $fieldName - * @param string|null $fallbackValue + * @param string $fieldName + * @param string|null $fallbackValue * * @return string */ diff --git a/app/Models/Requestable.php b/app/Models/Requestable.php index 4dead82bb3..016c1e1692 100644 --- a/app/Models/Requestable.php +++ b/app/Models/Requestable.php @@ -21,9 +21,11 @@ trait Requestable public function scopeRequestedBy($query, User $user) { - return $query->whereHas('requests', function ($query) use ($user) { - $query->where('user_id', $user->id); - }); + return $query->whereHas( + 'requests', function ($query) use ($user) { + $query->where('user_id', $user->id); + } + ); } public function request($qty = 1) @@ -40,7 +42,7 @@ trait Requestable public function cancelRequest($user_id = null) { - if (!$user_id){ + if (!$user_id) { $user_id = auth()->id(); } diff --git a/app/Models/SCIMUser.php b/app/Models/SCIMUser.php index fcf34c0d5d..f261fea8b6 100644 --- a/app/Models/SCIMUser.php +++ b/app/Models/SCIMUser.php @@ -8,7 +8,8 @@ class SCIMUser extends User protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false - public function __construct(array $attributes = []) { + public function __construct(array $attributes = []) + { $attributes['password'] = $this->noPassword(); parent::__construct($attributes); } diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 199aee33dc..4b96230027 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -25,6 +25,7 @@ class Setting extends Model /** * The cache property so that multiple invocations of this will only load the Settings record from disk only once + * * @var self */ public static ?self $_cache = null; @@ -67,11 +68,13 @@ class Setting extends Model 'google_login', 'google_client_id', 'google_client_secret', + 'manager_view_enabled', ]; protected $casts = [ 'label2_asset_logo' => 'boolean', 'require_checkinout_notes' => 'boolean', + 'manager_view_enabled' => 'boolean', ]; /** @@ -304,7 +307,8 @@ class Setting extends Model */ public static function getLdapSettings(): Collection { - $ldapSettings = self::select([ + $ldapSettings = self::select( + [ 'ldap_enabled', 'ldap_server', 'ldap_uname', @@ -335,7 +339,8 @@ class Setting extends Model 'ldap_manager', 'ldap_country', 'ldap_location', - ])->first()->getAttributes(); + ] + )->first()->getAttributes(); return collect($ldapSettings); } diff --git a/app/Models/SnipeModel.php b/app/Models/SnipeModel.php index f26946d22a..f5a5a51cc0 100644 --- a/app/Models/SnipeModel.php +++ b/app/Models/SnipeModel.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Helpers\Helper; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; class SnipeModel extends Model @@ -21,7 +22,7 @@ class SnipeModel extends Model */ public function setPurchaseCostAttribute($value) { - if (is_float($value)) { + if (is_numeric($value)) { //value is *already* a floating-point number. Just assign it directly $this->attributes['purchase_cost'] = $value; return; @@ -155,9 +156,13 @@ class SnipeModel extends Model $this->attributes['status_id'] = $value; } - // - public function getDisplayNameAttribute() + + protected function displayName(): Attribute { - return $this->name; + return Attribute:: make( + get: fn(mixed $value) => $this->name, + ); } + + } diff --git a/app/Models/SnipeSCIMConfig.php b/app/Models/SnipeSCIMConfig.php index f17b5cfb61..7387569e10 100644 --- a/app/Models/SnipeSCIMConfig.php +++ b/app/Models/SnipeSCIMConfig.php @@ -34,6 +34,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig 'validations' => [ $user_prefix . 'userName' => 'required', + $user_prefix . 'displayName' => 'nullable|string', $user_prefix . 'name.givenName' => 'required', $user_prefix . 'name.familyName' => 'nullable|string', $user_prefix . 'externalId' => 'nullable|string', @@ -64,7 +65,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig //eager loading 'withRelations' => [], 'map_unmapped' => false, -// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped', + // 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped', 'description' => 'User Account', // Map a SCIM attribute to an attribute of the object. @@ -121,7 +122,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig 'honorificSuffix' => null ], - 'displayName' => null, + 'displayName' => AttributeMapping::eloquent("display_name"), 'nickName' => null, 'profileUrl' => null, 'title' => AttributeMapping::eloquent('jobtitle'), diff --git a/app/Models/Statuslabel.php b/app/Models/Statuslabel.php index c1bcc3042d..bd981d867a 100755 --- a/app/Models/Statuslabel.php +++ b/app/Models/Statuslabel.php @@ -56,7 +56,7 @@ class Statuslabel extends SnipeModel * Establishes the status label -> assets relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -73,7 +73,7 @@ class Statuslabel extends SnipeModel * Gets the status label type * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return string */ public function getStatuslabelType() @@ -97,8 +97,8 @@ class Statuslabel extends SnipeModel public function scopePending() { return $this->where('pending', '=', 1) - ->where('archived', '=', 0) - ->where('deployable', '=', 0); + ->where('archived', '=', 0) + ->where('deployable', '=', 0); } /** @@ -141,7 +141,7 @@ class Statuslabel extends SnipeModel * Helper function to determine type attributes * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return string */ public static function getStatuslabelTypesForDB($type) diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index e198d10c10..2c99330604 100755 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -7,7 +7,7 @@ use App\Models\Traits\Searchable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; - +use \Illuminate\Database\Eloquent\Relations\Relation; class Supplier extends SnipeModel { use HasFactory; @@ -48,7 +48,7 @@ class Supplier extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name']; + protected $searchableAttributes = ['name', 'notes', 'phone', 'fax', 'url', 'email', 'contact', 'address', 'address2', 'city', 'state', 'country', 'zip']; /** * The relations and their attributes that should be included when searching the model. @@ -71,7 +71,7 @@ class Supplier extends SnipeModel * Otherwise calling "count()" on each model results in n+1. * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetsRelation() @@ -84,7 +84,7 @@ class Supplier extends SnipeModel * Establishes the supplier -> assets relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -96,7 +96,7 @@ class Supplier extends SnipeModel * Establishes the supplier -> accessories relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() @@ -108,7 +108,7 @@ class Supplier extends SnipeModel * Establishes the supplier -> component relationship * * @author A. Gianotto - * @since [v6.1.1] + * @since [v6.1.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function components() @@ -120,7 +120,7 @@ class Supplier extends SnipeModel * Establishes the supplier -> component relationship * * @author A. Gianotto - * @since [v6.1.1] + * @since [v6.1.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() @@ -128,23 +128,35 @@ class Supplier extends SnipeModel return $this->hasMany(\App\Models\Consumable::class, 'supplier_id'); } + + /** + * Establishes the supplier -> admin user relationship + * + * @author A. Gianotto + * @return Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Establishes the supplier -> asset maintenances relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function asset_maintenances() + public function maintenances(): Relation { - return $this->hasMany(\App\Models\AssetMaintenance::class, 'supplier_id'); + return $this->hasMany(\App\Models\Maintenance::class, 'supplier_id'); } /** * Return the number of assets by supplier * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return int */ public function num_assets() @@ -160,7 +172,7 @@ class Supplier extends SnipeModel * Establishes the supplier -> license relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -172,7 +184,7 @@ class Supplier extends SnipeModel * Return the number of licenses by supplier * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return int */ public function num_licenses() @@ -186,7 +198,7 @@ class Supplier extends SnipeModel * @todo this should be handled via validation, no? * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function addhttp($url) @@ -197,4 +209,13 @@ class Supplier extends SnipeModel return $url; } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'suppliers.created_by', '=', 'admin_sort.id')->select('suppliers.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Traits/Acceptable.php b/app/Models/Traits/Acceptable.php index f0d1442ed3..6621cdb4fb 100644 --- a/app/Models/Traits/Acceptable.php +++ b/app/Models/Traits/Acceptable.php @@ -14,8 +14,8 @@ trait Acceptable /** * Run after the checkout acceptance was accepted by the user * - * @param User $acceptedBy - * @param string $signature + * @param User $acceptedBy + * @param string $signature */ public function acceptedCheckout(User $acceptedBy, $signature, $filename = null) { @@ -25,8 +25,8 @@ trait Acceptable /** * Run after the checkout acceptance was declined by the user * - * @param User $acceptedBy - * @param string $signature + * @param User $acceptedBy + * @param string $signature */ public function declinedCheckout(User $declinedBy, $signature) { diff --git a/app/Models/CompanyableChildTrait.php b/app/Models/Traits/CompanyableChildTrait.php similarity index 79% rename from app/Models/CompanyableChildTrait.php rename to app/Models/Traits/CompanyableChildTrait.php index 1158c70e98..36e19cffa8 100644 --- a/app/Models/CompanyableChildTrait.php +++ b/app/Models/Traits/CompanyableChildTrait.php @@ -1,6 +1,8 @@ hasMany(Actionlog::class, 'item_id') + ->where('item_type', self::class) + ->where('action_type', '=', 'uploaded') + ->whereNotNull('filename') + ->whereNotIn('filename', function ($query) { + $query->select('filename') + ->from('action_logs') + ->where('item_type', '=', self::class) + ->where('action_type', '=', 'upload deleted') + ->where('item_id', $this->id); + }); + } + + +} \ No newline at end of file diff --git a/app/Models/Traits/Searchable.php b/app/Models/Traits/Searchable.php index 1430ce649a..5ccae9a585 100644 --- a/app/Models/Traits/Searchable.php +++ b/app/Models/Traits/Searchable.php @@ -18,8 +18,8 @@ trait Searchable /** * Performs a search on the model, using the provided search terms * - * @param \Illuminate\Database\Eloquent\Builder $query The query to start the search on - * @param string $search + * @param \Illuminate\Database\Eloquent\Builder $query The query to start the search on + * @param string $search * @return \Illuminate\Database\Eloquent\Builder A query with added "where" clauses */ public function scopeTextSearch($query, $search) @@ -51,6 +51,7 @@ trait Searchable /** * Prepares the search term, splitting and cleaning it up + * * @param string $search The search term * @return array An array of search terms */ @@ -63,7 +64,7 @@ trait Searchable * Searches the models attributes for the search terms * * @param Illuminate\Database\Eloquent\Builder $query - * @param array $terms + * @param array $terms * @return Illuminate\Database\Eloquent\Builder */ private function searchAttributes(Builder $query, array $terms) @@ -87,7 +88,7 @@ trait Searchable * We need to form the query properly, starting with a "where", * otherwise the generated select is wrong. * - * @todo This does the job, but is inelegant and fragile + * @todo This does the job, but is inelegant and fragile */ if (! $firstConditionAdded) { $query = $query->where($table.'.'.$column, 'LIKE', '%'.$term.'%'); @@ -107,7 +108,7 @@ trait Searchable * Searches the models custom fields for the search terms * * @param Illuminate\Database\Eloquent\Builder $query - * @param array $terms + * @param array $terms * @return Illuminate\Database\Eloquent\Builder */ private function searchCustomFields(Builder $query, array $terms) @@ -135,45 +136,49 @@ trait Searchable * Searches the models relations for the search terms * * @param Illuminate\Database\Eloquent\Builder $query - * @param array $terms + * @param array $terms * @return Illuminate\Database\Eloquent\Builder */ private function searchRelations(Builder $query, array $terms) { foreach ($this->getSearchableRelations() as $relation => $columns) { - $query = $query->orWhereHas($relation, function ($query) use ($relation, $columns, $terms) { - $table = $this->getRelationTable($relation); + $query = $query->orWhereHas( + $relation, function ($query) use ($relation, $columns, $terms) { + $table = $this->getRelationTable($relation); - /** - * We need to form the query properly, starting with a "where", - * otherwise the generated nested select is wrong. - * - * @todo This does the job, but is inelegant and fragile - */ - $firstConditionAdded = false; + /** + * We need to form the query properly, starting with a "where", + * otherwise the generated nested select is wrong. + * + * @todo This does the job, but is inelegant and fragile + */ + $firstConditionAdded = false; - foreach ($columns as $column) { - foreach ($terms as $term) { - if (! $firstConditionAdded) { - $query->where($table.'.'.$column, 'LIKE', '%'.$term.'%'); - $firstConditionAdded = true; - continue; + foreach ($columns as $column) { + foreach ($terms as $term) { + if (! $firstConditionAdded) { + $query->where($table.'.'.$column, 'LIKE', '%'.$term.'%'); + $firstConditionAdded = true; + continue; + } + + $query->orWhere($table.'.'.$column, 'LIKE', '%'.$term.'%'); } - - $query->orWhere($table.'.'.$column, 'LIKE', '%'.$term.'%'); } - } - // I put this here because I only want to add the concat one time in the end of the user relation search - if($relation == 'user') { - $query->orWhereRaw( - $this->buildMultipleColumnSearch([ + // I put this here because I only want to add the concat one time in the end of the user relation search + if(($relation == 'adminuser') || ($relation == 'user')) { + $query->orWhereRaw( + $this->buildMultipleColumnSearch( + [ 'users.first_name', 'users.last_name', - ]), + ] + ), ["%{$term}%"] ); + } } - }); + ); } return $query; @@ -185,7 +190,7 @@ trait Searchable * This is a noop in this trait, but can be overridden in the implementing model, to allow more advanced searches * * @param Illuminate\Database\Eloquent\Builder $query - * @param array $terms The search terms + * @param array $terms The search terms * @return Illuminate\Database\Eloquent\Builder * * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -268,7 +273,7 @@ trait Searchable /** * Builds a search string for either MySQL or sqlite by separating the provided columns with a space. * - * @param array $columns Columns to include in search string. + * @param array $columns Columns to include in search string. * @return string */ private function buildMultipleColumnSearch(array $columns): string @@ -288,9 +293,9 @@ trait Searchable /** * Search a string across multiple columns separated with a space. * - * @param Builder $query - * @param array $columns - Columns to include in search string. - * @param $term + * @param Builder $query + * @param array $columns - Columns to include in search string. + * @param $term * @return Builder */ public function scopeOrWhereMultipleColumns($query, array $columns, $term) diff --git a/app/Models/User.php b/app/Models/User.php index bd03367d1a..dd3bbfec57 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,8 +3,11 @@ namespace App\Models; use App\Http\Traits\UniqueUndeletedTrait; +use App\Models\Traits\CompanyableTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; +use App\Presenters\UserPresenter; use Illuminate\Auth\Authenticatable; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; @@ -12,30 +15,41 @@ use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Str; use Laravel\Passport\HasApiTokens; use Watson\Validating\ValidatingTrait; -use Illuminate\Database\Eloquent\Casts\Attribute; class User extends SnipeModel implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, HasLocalePreference { use HasFactory; use CompanyableTrait; + use HasUploads; - protected $presenter = \App\Presenters\UserPresenter::class; - use SoftDeletes, ValidatingTrait; + protected $presenter = UserPresenter::class; + use SoftDeletes, ValidatingTrait, Loggable; use Authenticatable, Authorizable, CanResetPassword, HasApiTokens; use UniqueUndeletedTrait; use Notifiable; use Presentable; use Searchable; - protected $hidden = ['password', 'remember_token', 'permissions', 'reset_password_code', 'persist_code']; + protected $hidden = [ + 'password', + 'remember_token', + 'permissions', + 'reset_password_code', + 'persist_code', + 'two_factor_secret', + 'activation_code', + ]; + protected $table = 'users'; protected $injectUniqueIdentifier = true; @@ -51,12 +65,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'first_name', 'jobtitle', 'last_name', + 'display_name', 'ldap_import', 'locale', 'location_id', 'manager_id', 'password', 'phone', + 'mobile', 'notes', 'state', 'username', @@ -89,6 +105,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo protected $rules = [ 'first_name' => 'required|string|min:1|max:191', + 'last_name' => 'nullable|string|max:191', + 'display_name' => 'nullable|string|max:191', 'username' => 'required|string|min:1|unique_undeleted|max:191', 'email' => 'email|nullable|max:191', 'password' => 'required|min:8', @@ -99,9 +117,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'start_date' => 'nullable|date_format:Y-m-d', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date', 'autoassign_licenses' => 'boolean', - 'address' => 'max:191|nullable', - 'city' => 'max:191|nullable', - 'state' => 'min:2|max:191|nullable', + 'address' => 'nullable|string|max:191', + 'city' => 'nullable|string|max:191', + 'state' => 'nullable|string|max:191', 'country' => 'min:2|max:191|nullable', 'zip' => 'max:10|nullable', 'vip' => 'boolean', @@ -115,16 +133,23 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * @var array */ protected $searchableAttributes = [ - 'first_name', - 'last_name', + 'address', + 'city', + 'country', + 'display_name', 'email', - 'username', + 'employee_num', + 'first_name', + 'jobtitle', + 'last_name', + 'locale', + 'mobile', 'notes', 'phone', - 'jobtitle', - 'employee_num', + 'state', + 'username', 'website', - 'locale', + 'zip', ]; /** @@ -133,11 +158,11 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * @var array */ protected $searchableRelations = [ - 'userloc' => ['name'], + 'userloc' => ['name', 'address', 'address2', 'city', 'state', 'zip'], 'department' => ['name'], 'groups' => ['name'], 'company' => ['name'], - 'manager' => ['first_name', 'last_name', 'username'], + 'manager' => ['first_name', 'last_name', 'username', 'display_name'], ]; @@ -148,6 +173,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * * We only have to do this on the User model and no other models because other * first-class objects have a name field. + * * @return void */ @@ -157,11 +183,46 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo { parent::boot(); - static::retrieved(function($user){ - $user->name = $user->getFullNameAttribute(); + static::retrieved( + function ($user) { + $user->name = $user->getFullNameAttribute(); + } + ); + } + + protected static function booted(): void + { + static::forceDeleted(function (User $user) { + CheckoutRequest::where(['user_id' => $user->id])->forceDelete(); + }); + + static::softDeleted(function (User $user) { + CheckoutRequest::where(['user_id' => $user->id])->delete(); }); } + /** + * This overrides the SnipeModel displayName accessor to return the full name if display_name is not set + * @see SnipeModel::displayName() + * @return Attribute + */ + + protected function displayName(): Attribute + { + return Attribute:: make( + get: fn(mixed $value) => $value ?? $this->getFullNameAttribute(), + ); + } + + public function isAvatarExternal() : bool + { + // Check if it's a google avatar or some external avatar + if (Str::startsWith($this->avatar, ['http://', 'https://'])) { + return true; + } + + return false; + } /** * Internally check the user permission for the given section @@ -172,11 +233,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo { $user_groups = $this->groups; if (($this->permissions == '') && (count($user_groups) == 0)) { - return false; } - $user_permissions = json_decode($this->permissions, true); + $user_permissions = $this->permissions; + + if (is_object($this->permissions)) { + $user_permissions = json_decode(json_encode($this->permissions), true); + } + + if (is_string($this->permissions)) { + $user_permissions = json_decode($this->permissions, true); + } + $is_user_section_permissions_set = ($user_permissions != '') && array_key_exists($section, $user_permissions); //If the user is explicitly granted, return true @@ -206,7 +275,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * is authorized to do the thing * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return bool */ public function hasAccess($section) @@ -222,7 +291,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Checks if the user is a SuperUser * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return bool */ public function isSuperUser() @@ -230,15 +299,28 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->checkPermissionSection('superuser'); } - /** - * Checks if the can edit their own profile + * Checks if the user is an admin * * @author A. Gianotto - * @since [v6.3.4] + * @since [v8.1.18] * @return bool */ - public function canEditProfile() : bool { + public function isAdmin() + { + return $this->checkPermissionSection('admin'); + } + + + /** + * Checks if the user can edit their own profile + * + * @author A. Gianotto + * @since [v6.3.4] + * @return bool + */ + public function canEditProfile() : bool + { $setting = Setting::getSettings(); if ($setting->profile_edit == 1) { @@ -251,18 +333,20 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Checks if the user is deletable * * @author A. Gianotto - * @since [v6.3.4] + * @since [v6.3.4] * @return bool */ public function isDeletable() { + return Gate::allows('delete', $this) - && ($this->assets->count() === 0) - && ($this->licenses->count() === 0) - && ($this->consumables->count() === 0) - && ($this->accessories->count() === 0) - && ($this->managedLocations->count() === 0) - && ($this->managesUsers->count() === 0) + && (($this->assets_count ?? $this->assets()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->licenses_count ?? $this->licenses()->count()) === 0) + && (($this->consumables_count ?? $this->consumables()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->manages_users_count ?? $this->managesUsers()->count()) === 0) + && (($this->manages_locations_count ?? $this->managedLocations()->count()) === 0) && ($this->deleted_at == ''); } @@ -271,7 +355,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> company relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function company() @@ -283,7 +367,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> department relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function department() @@ -295,7 +379,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Checks activated status * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return bool */ public function isActivated() @@ -308,14 +392,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Returns the full name attribute * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return string */ public function getFullNameAttribute() { $setting = Setting::getSettings(); - if ($setting->name_display_format=='last_first') { + if ($setting?->name_display_format == 'last_first') { return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name; } return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name; @@ -326,7 +410,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> assets relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assets() @@ -341,19 +425,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * created. * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function assetmaintenances() + public function maintenances() { - return $this->hasMany(\App\Models\AssetMaintenance::class, 'user_id')->withTrashed(); + return $this->hasMany(\App\Models\Maintenance::class, 'user_id')->withTrashed(); } /** * Establishes the user -> accessories relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function accessories() @@ -367,19 +451,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> consumables relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function consumables() { - return $this->belongsToMany(\App\Models\Consumable::class, 'consumables_users', 'assigned_to', 'consumable_id')->withPivot('id','created_at','note')->withTrashed(); + return $this->belongsToMany(\App\Models\Consumable::class, 'consumables_users', 'assigned_to', 'consumable_id')->withPivot('id', 'created_at', 'note')->withTrashed(); } /** * Establishes the user -> license seats relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function licenses() @@ -389,7 +473,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Establishes the user -> reportTemplates relationship - * */ public function reportTemplates(): HasMany { @@ -400,10 +483,11 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes a count of all items assigned * * @author J. Vinsmoke - * @since [v6.1] + * @since [v6.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - Public function allAssignedCount() { + Public function allAssignedCount() + { $assetsCount = $this->assets()->count(); $licensesCount = $this->licenses()->count(); $accessoriesCount = $this->accessories()->count(); @@ -412,13 +496,13 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo $totalCount = $assetsCount + $licensesCount + $accessoriesCount + $consumablesCount; return (int) $totalCount; - } + } /** * Establishes the user -> actionlogs relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function userlog() @@ -434,7 +518,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * @todo - this should be removed once we're sure we've switched it to location() * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function userloc() @@ -446,7 +530,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> location relationship * * @author A. Gianotto - * @since [v3.0] + * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function location() @@ -458,7 +542,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> manager relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function manager() @@ -470,7 +554,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> managed users relationship * * @author A. Gianotto - * @since [v6.4.1] + * @since [v6.4.1] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function managesUsers() @@ -483,7 +567,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> managed locations relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function managedLocations() @@ -495,7 +579,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> groups relationship * * @author A. Gianotto - * @since [v1.0] + * @since [v1.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function groups() @@ -507,7 +591,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Establishes the user -> assets relationship * * @author A. Gianotto - * @since [v4.0] + * @since [v4.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function assetlog() @@ -515,27 +599,13 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->hasMany(\App\Models\Asset::class, 'id')->withTrashed(); } - /** - * Establishes the user -> uploads relationship - * - * @author A. Gianotto - * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') - ->where('item_type', self::class) - ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename') - ->orderBy('created_at', 'desc'); - } + /** * Establishes the user -> acceptances relationship * * @author A. Gianotto - * @since [v7.0.7] + * @since [v7.0.7] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function acceptances() @@ -546,11 +616,30 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo ->orderBy('created_at', 'desc'); } + /** + * Establishes the user -> eula relationship + * + * @return \Illuminate\Database\Eloquent\Relations\Relation + * @since [v8.1.16] + * @author [Godfrey Martinez] [] + */ + public function eulas() + { + return $this->hasMany(Actionlog::class, 'target_id') + ->with('item') + ->select(['id', 'target_id', 'target_type', 'action_type', 'filename', 'accept_signature', 'created_at', 'note', 'item_id', 'item_type']) + ->where('target_type', self::class) + ->where('action_type', 'accepted') + ->whereNotNull('filename') + ->whereNotNull('accept_signature') + ->orderBy('created_at', 'desc'); + } + /** * Establishes the user -> requested assets relationship * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function checkoutRequests() @@ -566,7 +655,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * - CSV import where no password was provided * * @author A. Gianotto - * @since [v6.2.0] + * @since [v6.2.0] * @return string */ public function noPassword() @@ -577,8 +666,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Query builder scope to return NOT-deleted users + * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * * @param string $query * @return \Illuminate\Database\Query\Builder @@ -592,7 +682,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Query builder scope to return users by email or username * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * * @param string $query * @param string $user_username @@ -610,7 +700,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Generate email from full name * * @author A. Gianotto - * @since [v2.0] + * @since [v2.0] * * @param string $query * @return string @@ -680,7 +770,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * 2 = 2FA universally required * * @author [A. Gianotto] [] - * @since [v4.0] + * @since [v4.0] * * @return bool */ @@ -709,7 +799,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * 2 = 2FA universally required * * @author [A. Gianotto] [] - * @since [v4.6.14] + * @since [v4.6.14] * * @return bool */ @@ -732,7 +822,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Get the admin user who created this user * * @author [A. Gianotto] [] - * @since [v6.0.5] + * @since [v6.0.5] * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function createdBy() @@ -741,24 +831,36 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo } - + /** + * Decode JSON permissions into array + * + * @author A. Gianotto + * @since [v1.0] + * @return array | \stdClass + */ public function decodePermissions() { - // Set default to empty JSON if the value is null + // If the permissions are an array, convert it to JSON + if (is_array($this->permissions)) { + $this->permissions = json_encode($this->permissions); + } + $permissions = json_decode($this->permissions ?? '{}', JSON_OBJECT_AS_ARRAY); - // If there are no permissions, return an empty array - if (!$permissions) { - return []; - } - // Otherwise, loop through the permissions and cast the values as integers - foreach ($permissions as $permission => $value) { - $permissions[$permission] = (int) $value; + if ((is_array($permissions)) && ($permissions)) { + foreach ($permissions as $permission => $value) { + + if (!is_integer($permission)) { + $permissions[$permission] = (int) $value; + } else { + \Log::info('Weird data here - skipping it'); + unset($permissions[$permission]); + } + } + return $permissions ?: new \stdClass; } - - - return $permissions; + return new \stdClass; } /** @@ -767,32 +869,38 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * all of the relations as well, which is more than what we need. * * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param array $terms The search terms + * @param array $terms The search terms * @return \Illuminate\Database\Query\Builder */ public function scopeSimpleNameSearch($query, $search) { return $query->where('first_name', 'LIKE', '%' . $search . '%') ->orWhere('last_name', 'LIKE', '%' . $search . '%') - ->orWhereMultipleColumns([ + ->orWhere('display_name', 'LIKE', '%' . $search . '%') + ->orWhereMultipleColumns( + [ 'users.first_name', 'users.last_name', - ], $search); + ], $search + ); } /** * Run additional, advanced searches. * * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param array $terms The search terms + * @param array $terms The search terms * @return \Illuminate\Database\Eloquent\Builder */ - public function advancedTextSearch(Builder $query, array $terms) { + public function advancedTextSearch(Builder $query, array $terms) + { foreach($terms as $term) { - $query->orWhereMultipleColumns([ + $query->orWhereMultipleColumns( + [ 'users.first_name', 'users.last_name', - ], $term); + ], $term + ); } return $query; @@ -802,22 +910,67 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * Query builder scope to return users by group * * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param int $id + * @param int $id * @return \Illuminate\Database\Query\Builder */ public function scopeByGroup($query, $id) { - return $query->whereHas('groups', function ($query) use ($id) { - $query->where('permission_groups.id', '=', $id); - }); + return $query->whereHas( + 'groups', function ($query) use ($id) { + $query->where('permission_groups.id', '=', $id); + } + ); } + /** + * Return only admins and superusers + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + */ + public function scopeOnlySuperAdmins($query) + { + + return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%') + ->orWhere('users.permissions', 'LIKE', '%"superuser":1%') + ->orWhereHas( + 'groups', function ($query) { + $query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%') + ->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%'); + } + ); + + } + + /** + * Return only admins and superusers + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + */ + public function scopeOnlyAdminsAndSuperAdmins($query) + { + + return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%') + ->orWhere('users.permissions', 'LIKE', '%"superuser":1%') + ->orWhere('users.permissions', 'LIKE', '%"admin":1%') + ->orWhere('users.permissions', 'LIKE', '%"admin":"1"%') + ->orWhereHas( + 'groups', function ($query) { + $query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%') + ->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%') + ->orWhere('permission_groups.permissions', 'LIKE', '%"admin":1%') + ->orWhere('permission_groups.permissions', 'LIKE', '%"admin":"1"%'); + } + ); + + } + + /** * Query builder scope to order on manager * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -830,8 +983,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Query builder scope to order on company * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -843,8 +996,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Query builder scope to order on department * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -856,8 +1009,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Query builder scope to order on admin user * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order * * @return \Illuminate\Database\Query\Builder Modified query builder */ @@ -873,8 +1026,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Query builder scope to order on company * - * @param Illuminate\Database\Query\Builder $query Query builder instance - * @param text $order Order + * @param Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order * * @return Illuminate\Database\Query\Builder Modified query builder */ @@ -885,21 +1038,20 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo - /** * Get the preferred locale for the user. * * This uses the HasLocalePreference contract to determine the user's preferred locale, * used by Laravel's mail system to determine the locale for sending emails. * https://laravel.com/docs/11.x/mail#user-preferred-locales - * */ public function preferredLocale(): string { return $this->locale ?? Setting::getSettings()->locale ?? config('app.locale'); } - public function getUserTotalCost(){ + public function getUserTotalCost() + { $asset_cost= 0; $license_cost= 0; $accessory_cost= 0; @@ -921,10 +1073,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this; } - public function scopeUserLocation($query, $location, $search){ + public function scopeUserLocation($query, $location, $search) + { - - return $query->where('location_id','=', $location) + return $query->where('location_id', '=', $location) ->where('users.first_name', 'LIKE', '%' . $search . '%') ->orWhere('users.email', 'LIKE', '%' . $search . '%') ->orWhere('users.last_name', 'LIKE', '%' . $search . '%') @@ -934,10 +1086,78 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo ->orWhere('users.jobtitle', 'LIKE', '%' . $search . '%') ->orWhere('users.employee_num', 'LIKE', '%' . $search . '%') ->orWhere('users.username', 'LIKE', '%' . $search . '%') + ->orWhere('users.display_name', 'LIKE', '%' . $search . '%') ->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\''); + } + /** + * Get all direct and indirect subordinates for this user. + * + * @return \Illuminate\Support\Collection + */ + public function getAllSubordinates() + { + $subordinates = collect(); + $this->fetchSubordinatesRecursive($this, $subordinates); + return $subordinates->unique('id'); + } + /** + * Get all direct and indirect subordinates for this user, including self. + * + * @return \Illuminate\Support\Collection + */ + public function getAllSubordinatesIncludingSelf() + { + $subordinates = collect([$this]); + $this->fetchSubordinatesRecursive($this, $subordinates); + return $subordinates->unique('id'); + } + /** + * Recursive helper function to fetch subordinates. + * + * @param User $manager + * @param \Illuminate\Support\Collection $subs + */ + protected function fetchSubordinatesRecursive(User $manager, \Illuminate\Support\Collection &$subs) + { + // Eager load 'managesUsers' to prevent N+1 queries in recursion + $directSubordinates = $manager->managesUsers()->with('managesUsers')->get(); + + foreach ($directSubordinates as $directSubordinate) { + // Add subordinate if not already in the collection + if (!$subs->contains('id', $directSubordinate->id)) { + $subs->push($directSubordinate); + // Recursive call for this subordinate's subordinates + $this->fetchSubordinatesRecursive($directSubordinate, $subs); + } + } + } + + /** + * Check if the current user is a direct or indirect manager of the given user. + * + * @param User $userToCheck + * @return bool + */ + public function isManagerOf(User $userToCheck): bool + { + // Optimization: If it's the same user, they are not their own manager + if ($this->id === $userToCheck->id) { + return false; + } + + // Eager load manager relationship to potentially reduce queries in the loop + $manager = $userToCheck->load('manager')->manager; + while ($manager) { + if ($manager->id === $this->id) { + return true; + } + // Move up the hierarchy (load relationship if not already loaded) + $manager = $manager->load('manager')->manager; + } + return false; } } diff --git a/app/Notifications/AcceptanceAssetAcceptedNotification.php b/app/Notifications/AcceptanceAssetAcceptedNotification.php index 7798dbc0d5..6c4e2e99e0 100644 --- a/app/Notifications/AcceptanceAssetAcceptedNotification.php +++ b/app/Notifications/AcceptanceAssetAcceptedNotification.php @@ -29,6 +29,7 @@ class AcceptanceAssetAcceptedNotification extends Notification $this->assigned_to = $params['assigned_to']; $this->note = $params['note']; $this->company_name = $params['company_name']; + $this->admin = $params['admin'] ?? null; $this->settings = Setting::getSettings(); } @@ -72,6 +73,7 @@ class AcceptanceAssetAcceptedNotification extends Notification 'assigned_to' => $this->assigned_to, 'company_name' => $this->company_name, 'intro_text' => trans('mail.acceptance_asset_accepted'), + 'admin' => $this->admin, ]) ->subject(trans('mail.acceptance_asset_accepted')); diff --git a/app/Notifications/AcceptanceAssetAcceptedToUserNotification.php b/app/Notifications/AcceptanceAssetAcceptedToUserNotification.php new file mode 100644 index 0000000000..22a97b709f --- /dev/null +++ b/app/Notifications/AcceptanceAssetAcceptedToUserNotification.php @@ -0,0 +1,79 @@ +item_tag = $params['item_tag']; + $this->item_model = $params['item_model']; + $this->item_serial = $params['item_serial']; + $this->item_status = $params['item_status']; + $this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false); + $this->assigned_to = $params['assigned_to']; + $this->note = $params['note']; + $this->company_name = $params['company_name']; + $this->settings = Setting::getSettings(); + $this->file = $params['file'] ?? null; + + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via() + { + + $notifyBy = ['mail']; + + return $notifyBy; + + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail() + { + $pdf_path = storage_path('private_uploads/eula-pdfs/'.$this->file); + $message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance', + [ + 'item_tag' => $this->item_tag, + 'item_model' => $this->item_model, + 'item_serial' => $this->item_serial, + 'item_status' => $this->item_status, + 'note' => $this->note, + 'accepted_date' => $this->accepted_date, + 'assigned_to' => $this->assigned_to, + 'company_name' => $this->company_name, + 'intro_text' => trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->company_name ?? $this->settings->site_name]), + ]) + ->attach($pdf_path) + ->subject(trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->settings->site_name])); + + return $message; + } + + + +} diff --git a/app/Notifications/AuditNotification.php b/app/Notifications/AuditNotification.php index 4f4638e6cd..3acce861b4 100644 --- a/app/Notifications/AuditNotification.php +++ b/app/Notifications/AuditNotification.php @@ -2,6 +2,7 @@ namespace App\Notifications; +use AllowDynamicProperties; use App\Models\Setting; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Channels\SlackWebhookChannel; @@ -12,7 +13,7 @@ use Illuminate\Support\Str; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage; -class AuditNotification extends Notification +#[AllowDynamicProperties] class AuditNotification extends Notification { use Queueable; /** @@ -56,14 +57,14 @@ class AuditNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; return (new SlackMessage) ->success() - ->content(class_basename(get_class($this->params['item'])).' Audited') + ->content(class_basename(get_class($this->params['item'])).' '.trans('general.audited')) ->from(($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot') ->to($channel) ->attachment(function ($attachment) { $item = $this->params['item']; $admin_user = $this->params['admin']; $fields = [ - 'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>', + 'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->display_name.'>', ]; array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note']; array_key_exists('location', $this->params) && $fields['Location'] = $this->params['location']; @@ -75,24 +76,24 @@ class AuditNotification extends Notification public static function toMicrosoftTeams($params) { - $item = $params['item']; - $admin_user = $params['admin']; - $note = $params['note']; - $location = $params['location']; + $item = $params['item'] ?? null; + $admin_user = $params['admin'] ?? null; + $note = $params['note'] ?? ''; + $location = $params['location'] ?? ''; $setting = Setting::getSettings(); if(!Str::contains($setting->webhook_endpoint, 'workflows')) { return MicrosoftTeamsMessage::create() ->to($setting->webhook_endpoint) ->type('success') - ->title(class_basename(get_class($params['item'])) . ' Audited') + ->title(class_basename(get_class($params['item'])) .' '.trans('general.audited')) ->addStartGroupToSection('activityText') ->fact(trans('mail.asset'), $item) - ->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->present()->fullName()); + ->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->display_name); } - $message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->present()->fullName(); + $message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->display_name; $details = [ - trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.asset') => htmlspecialchars_decode($item->display_name), trans('mail.notes') => $note ?: '', trans('general.location') => $location ?: '', ]; diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 5354775d7e..45c6fa617d 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -73,8 +73,8 @@ class CheckinAccessoryNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -90,7 +90,7 @@ class CheckinAccessoryNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -107,18 +107,18 @@ class CheckinAccessoryNotification extends Notification ->addStartGroupToSection('activityTitle') ->title(trans('Accessory_Checkin_Notification')) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityTitle') ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->display_name) ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) ->fact(trans('mail.notes'), $note ?: ''); } $message = trans('mail.Accessory_Checkin_Notification'); $details = [ - trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('mail.accessory_name') => htmlspecialchars_decode($item->display_name), trans('mail.checked_into') => $item->location->name ? $item->location->name : '', - trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->present()->fullName(), + trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->display_name, trans('admin/consumables/general.remaining')=> $item->numRemaining(), trans('mail.notes') => $note ?: '', ]; @@ -135,7 +135,7 @@ class CheckinAccessoryNotification extends Notification Card::create() ->header( ''.trans('mail.Accessory_Checkin_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php index 5de3ff1be8..12f45884c9 100644 --- a/app/Notifications/CheckinAssetNotification.php +++ b/app/Notifications/CheckinAssetNotification.php @@ -78,7 +78,7 @@ class CheckinAssetNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', trans('general.status') => $item->assetstatus?->name, trans('general.location') => ($item->location) ? $item->location->name : '', ]; @@ -93,11 +93,11 @@ class CheckinAssetNotification extends Notification return (new SlackMessage) - ->content(':arrow_down: :computer: '.trans('mail.Asset_Checkin_Notification')) + ->content(':arrow_down: :computer: '.trans('mail.Asset_Checkin_Notification', ['tag' => ''])) ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -112,21 +112,21 @@ class CheckinAssetNotification extends Notification return MicrosoftTeamsMessage::create() ->to($this->settings->webhook_endpoint) ->type('success') - ->title(trans('mail.Asset_Checkin_Notification')) + ->title(trans('mail.Asset_Checkin_Notification', ['tag' => ''])) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityText') ->fact(trans('mail.checked_into'), ($item->location) ? $item->location->name : '') - ->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('general.administrator'), $admin->display_name) ->fact(trans('admin/hardware/form.status'), $item->assetstatus?->name) ->fact(trans('mail.notes'), $note ?: ''); } - $message = trans('mail.Asset_Checkin_Notification'); + $message = trans('mail.Asset_Checkin_Notification', ['tag' => '']); $details = [ - trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.asset') => htmlspecialchars_decode($item->display_name), trans('mail.checked_into') => ($item->location) ? $item->location->name : '', - trans('mail.Asset_Checkin_Notification')." by " => $admin->present()->fullName(), + trans('general.administrator') => $admin->display_name, trans('admin/hardware/form.status') => $item->assetstatus?->name, trans('mail.notes') => $note ?: '', ]; @@ -144,8 +144,8 @@ class CheckinAssetNotification extends Notification ->card( Card::create() ->header( - ''.trans('mail.Asset_Checkin_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + ''.trans('mail.Asset_Checkin_Notification', ['tag' =>'']).'' ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( diff --git a/app/Notifications/CheckinComponentNotification.php b/app/Notifications/CheckinComponentNotification.php new file mode 100644 index 0000000000..b302ee8979 --- /dev/null +++ b/app/Notifications/CheckinComponentNotification.php @@ -0,0 +1,165 @@ +target = $checkedOutTo; + $this->item = $component; + $this->admin = $checkedInBy; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via() + { + $notifyBy = []; + + if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = GoogleChatChannel::class; + } + if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = MicrosoftTeamsChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + $notifyBy[] = SlackWebhookChannel::class; + } + + return $notifyBy; + } + + public function toSlack() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; + + if ($admin) { + $fields = [ + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', + ]; + + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + } else { + $fields = [ + 'To' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + 'By' => 'CLI tool', + ]; + } + + return (new SlackMessage) + ->content(':arrow_down: :package: '.trans('mail.Component_checkin_notification')) + ->from($botname) + ->to($channel) + ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + public function toMicrosoftTeams() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Component_checkin_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->display_name), '', 'header') + ->fact(trans('mail.Component_checkin_notification')." by ", $admin->display_name ?: 'CLI tool') + ->fact(trans('mail.checkedin_from'), $target->display_name) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Component_checkin_notification'); + $details = [ + trans('mail.checkedin_from')=> $target->display_name, + trans('mail.Component_checkin_notification')." by " => $admin->display_name ?: 'CLI tool', + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); + } + public function toGoogleChat() + { + $target = $this->target; + $item = $this->item; + $note = $this->note; + + return GoogleChatMessage::create() + ->to($this->settings->webhook_endpoint) + ->card( + Card::create() + ->header( + ''.trans('mail.Component_checkin_notification').'' ?: '', + htmlspecialchars_decode($item->display_name) ?: '', + ) + ->section( + Section::create( + KeyValue::create( + trans('mail.checkedin_from') ?: '', + $target->display_name ?: '', + trans('admin/consumables/general.remaining').': '.$item->numRemaining(), + ) + ->onClick(route('components.show', $item->id)) + ) + ) + ); + + } +} diff --git a/app/Notifications/CheckinLicenseSeatNotification.php b/app/Notifications/CheckinLicenseSeatNotification.php index 07c3308dbc..60eec737d6 100644 --- a/app/Notifications/CheckinLicenseSeatNotification.php +++ b/app/Notifications/CheckinLicenseSeatNotification.php @@ -77,8 +77,8 @@ class CheckinLicenseSeatNotification extends Notification if ($admin) { $fields = [ - trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -91,7 +91,7 @@ class CheckinLicenseSeatNotification extends Notification } else { $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + 'To' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', 'By' => 'CLI tool', ]; } @@ -101,7 +101,7 @@ class CheckinLicenseSeatNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -119,18 +119,18 @@ class CheckinLicenseSeatNotification extends Notification ->addStartGroupToSection('activityTitle') ->title(trans('mail.License_Checkin_Notification')) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') - ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool') - ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) + ->fact(htmlspecialchars_decode($item->display_name), '', 'header') + ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->display_name ?: 'CLI tool') + ->fact(trans('mail.checkedin_from'), $target->display_name) ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) ->fact(trans('mail.notes'), $note ?: ''); } $message = trans('mail.License_Checkin_Notification'); $details = [ - trans('mail.checkedin_from')=> $target->present()->fullName(), - trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), - trans('mail.License_Checkin_Notification')." by " => $admin->present()->fullName() ?: 'CLI tool', + trans('mail.checkedin_from')=> $target->display_name, + trans('mail.license_for') => htmlspecialchars_decode($item->display_name), + trans('mail.License_Checkin_Notification')." by " => $admin->display_name ?: 'CLI tool', trans('admin/consumables/general.remaining') => $item->availCount()->count(), trans('mail.notes') => $note ?: '', ]; @@ -149,13 +149,13 @@ class CheckinLicenseSeatNotification extends Notification Card::create() ->header( ''.trans('mail.License_Checkin_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( KeyValue::create( trans('mail.checkedin_from') ?: '', - $target->present()->fullName() ?: '', + $target->display_name ?: '', trans('admin/consumables/general.remaining').': '.$item->availCount()->count(), ) ->onClick(route('licenses.show', $item->id)) diff --git a/app/Notifications/CheckoutAccessoryNotification.php b/app/Notifications/CheckoutAccessoryNotification.php index 016bfc526c..f1a9551318 100644 --- a/app/Notifications/CheckoutAccessoryNotification.php +++ b/app/Notifications/CheckoutAccessoryNotification.php @@ -100,8 +100,8 @@ class CheckoutAccessoryNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -117,7 +117,7 @@ class CheckoutAccessoryNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($this->checkout_qty.' x '.$item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($this->checkout_qty.' x '.$item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -136,11 +136,11 @@ class CheckoutAccessoryNotification extends Notification ->addStartGroupToSection('activityTitle') ->title(trans('mail.Accessory_Checkout_Notification')) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.assigned_to'), $target->present()->name) + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityTitle') + ->fact(trans('mail.assigned_to'), $target->display_name) ->fact(trans('general.qty'), $this->checkout_qty) ->fact(trans('mail.checkedout_from'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->display_name) ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) ->fact(trans('mail.notes'), $note ?: ''); } @@ -148,10 +148,10 @@ class CheckoutAccessoryNotification extends Notification $message = trans('mail.Accessory_Checkout_Notification'); $details = [ trans('mail.assigned_to') => $target->present()->name, - trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('mail.accessory_name') => htmlspecialchars_decode($item->display_name), trans('general.qty') => $this->checkout_qty, trans('mail.checkedout_from') => $item->location->name ? $item->location->name : '', - trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->display_name, trans('admin/consumables/general.remaining')=> $item->numRemaining(), trans('mail.notes') => $note ?: '', ]; @@ -169,7 +169,7 @@ class CheckoutAccessoryNotification extends Notification Card::create() ->header( ''.trans('mail.Accessory_Checkout_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index 8cdfbdd046..213e0f77c6 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -93,8 +93,8 @@ class CheckoutAssetNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -110,11 +110,11 @@ class CheckoutAssetNotification extends Notification } return (new SlackMessage) - ->content(':arrow_up: :computer: '.trans('mail.Asset_Checkout_Notification')) + ->content(':arrow_up: :computer: '.trans('mail.Asset_Checkout_Notification', ['tag' => ''])) ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -131,19 +131,19 @@ class CheckoutAssetNotification extends Notification return MicrosoftTeamsMessage::create() ->to($this->settings->webhook_endpoint) ->type('success') - ->title(trans('mail.Asset_Checkout_Notification')) + ->title(trans('mail.Asset_Checkout_Notification', ['tag' => ''])) ->addStartGroupToSection('activityText') - ->fact(trans('mail.assigned_to'), $target->present()->name) - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') - ->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->display_name) + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityText') + ->fact(trans('general.administrator'), $admin->display_name) ->fact(trans('mail.notes'), $note ?: ''); } - $message = trans('mail.Asset_Checkout_Notification'); + $message = trans('mail.Asset_Checkout_Notification', ['tag' => '']); $details = [ trans('mail.assigned_to') => $target->present()->name, - trans('mail.asset') => htmlspecialchars_decode($item->present()->name), - trans('mail.Asset_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('mail.asset') => htmlspecialchars_decode($item->display_name), + trans('general.administrator') => $admin->display_name, trans('mail.notes') => $note ?: '', ]; return array($message, $details); @@ -159,8 +159,8 @@ public function toGoogleChat() ->card( Card::create() ->header( - ''.trans('mail.Asset_Checkout_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + ''.trans('mail.Asset_Checkout_Notification', ['tag' => '']).'' ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( diff --git a/app/Notifications/CheckoutComponentNotification.php b/app/Notifications/CheckoutComponentNotification.php new file mode 100644 index 0000000000..86ab20fcb4 --- /dev/null +++ b/app/Notifications/CheckoutComponentNotification.php @@ -0,0 +1,164 @@ +item = $component; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $component->checkout_qty; + + $this->settings = Setting::getSettings(); + } + + /**` + * Get the notification's delivery channels. + * + * @return array + */ + public function via() + { + $notifyBy = []; + if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = GoogleChatChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = MicrosoftTeamsChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + $notifyBy[] = SlackWebhookChannel::class; + } + + return $notifyBy; + } + + public function toSlack() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; + + $fields = [ + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', + ]; + + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + return (new SlackMessage) + ->content(':arrow_up: :package: '.trans('mail.Component_checkout_notification')) + ->from($botname) + ->to($channel) + ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + public function toMicrosoftTeams() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Component_checkout_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityTitle') + ->fact(trans('mail.Component_checkout_notification')." by ", $admin->display_name) + ->fact(trans('mail.assigned_to'), $target->display_name) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Component_checkout_notification'); + $details = [ + trans('mail.assigned_to') => $target->display_name, + trans('mail.item') => htmlspecialchars_decode($item->display_name), + trans('mail.Component_checkout_notification').' by' => $admin->display_name, + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); + } + public function toGoogleChat() + { + $target = $this->target; + $item = $this->item; + $note = $this->note; + + return GoogleChatMessage::create() + ->to($this->settings->webhook_endpoint) + ->card( + Card::create() + ->header( + ''.trans('mail.Component_checkout_notification').'' ?: '', + htmlspecialchars_decode($item->display_name) ?: '', + ) + ->section( + Section::create( + KeyValue::create( + trans('mail.assigned_to') ?: '', + $target->display_name ?: '', + trans('admin/consumables/general.remaining').': '.$item->numRemaining(), + ) + ->onClick(route('api.assets.show', $target->id)) + ) + ) + ); + + } +} diff --git a/app/Notifications/CheckoutConsumableNotification.php b/app/Notifications/CheckoutConsumableNotification.php index e8db8b8bd1..96568c4bd6 100644 --- a/app/Notifications/CheckoutConsumableNotification.php +++ b/app/Notifications/CheckoutConsumableNotification.php @@ -80,8 +80,8 @@ class CheckoutConsumableNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -97,7 +97,7 @@ class CheckoutConsumableNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -116,18 +116,18 @@ class CheckoutConsumableNotification extends Notification ->addStartGroupToSection('activityTitle') ->title(trans('mail.Consumable_checkout_notification')) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityTitle') + ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->display_name) + ->fact(trans('mail.assigned_to'), $target->display_name) ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) ->fact(trans('mail.notes'), $note ?: ''); } $message = trans('mail.Consumable_checkout_notification'); $details = [ - trans('mail.assigned_to') => $target->present()->fullName(), - trans('mail.item') => htmlspecialchars_decode($item->present()->name), - trans('mail.Consumable_checkout_notification').' by' => $admin->present()->fullName(), + trans('mail.assigned_to') => $target->display_name, + trans('mail.item') => htmlspecialchars_decode($item->display_name), + trans('mail.Consumable_checkout_notification').' by' => $admin->display_name, trans('admin/consumables/general.remaining') => $item->numRemaining(), trans('mail.notes') => $note ?: '', ]; @@ -146,13 +146,13 @@ class CheckoutConsumableNotification extends Notification Card::create() ->header( ''.trans('mail.Consumable_checkout_notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( KeyValue::create( trans('mail.assigned_to') ?: '', - $target->present()->fullName() ?: '', + $target->display_name ?: '', trans('admin/consumables/general.remaining').': '.$item->numRemaining(), ) ->onClick(route('users.show', $target->id)) diff --git a/app/Notifications/CheckoutLicenseSeatNotification.php b/app/Notifications/CheckoutLicenseSeatNotification.php index fa00421885..43de9f698b 100644 --- a/app/Notifications/CheckoutLicenseSeatNotification.php +++ b/app/Notifications/CheckoutLicenseSeatNotification.php @@ -78,8 +78,8 @@ class CheckoutLicenseSeatNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>', ]; if ($item->location) { @@ -95,7 +95,7 @@ class CheckoutLicenseSeatNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -114,18 +114,18 @@ class CheckoutLicenseSeatNotification extends Notification ->addStartGroupToSection('activityTitle') ->title(trans('mail.License_Checkout_Notification')) ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(htmlspecialchars_decode($item->display_name), '', 'activityTitle') + ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->display_name) + ->fact(trans('mail.assigned_to'), $target->display_name) ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) ->fact(trans('mail.notes'), $note ?: ''); } $message = trans('mail.License_Checkout_Notification'); $details = [ - trans('mail.assigned_to') => $target->present()->fullName(), - trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), - trans('mail.License_Checkout_Notification').' by' => $admin->present()->fullName(), + trans('mail.assigned_to') => $target->display_name, + trans('mail.license_for') => htmlspecialchars_decode($item->display_name), + trans('mail.License_Checkout_Notification').' by' => $admin->display_name, trans('admin/consumables/general.remaining') => $item->availCount()->count(), trans('mail.notes') => $note ?: '', ]; @@ -143,7 +143,7 @@ class CheckoutLicenseSeatNotification extends Notification Card::create() ->header( ''.trans('mail.License_Checkout_Notification').'' ?: '', - htmlspecialchars_decode($item->present()->name) ?: '', + htmlspecialchars_decode($item->display_name) ?: '', ) ->section( Section::create( diff --git a/app/Notifications/RequestAssetCancelation.php b/app/Notifications/RequestAssetCancelation.php index 6c06ede3ca..d6e5f44fd9 100644 --- a/app/Notifications/RequestAssetCancelation.php +++ b/app/Notifications/RequestAssetCancelation.php @@ -79,7 +79,7 @@ class RequestAssetCancelation extends Notification $fields = [ 'QTY' => $qty, - 'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + 'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', ]; if (($this->expected_checkin) && ($this->expected_checkin != '')) { @@ -91,7 +91,7 @@ class RequestAssetCancelation extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); diff --git a/app/Notifications/RequestAssetNotification.php b/app/Notifications/RequestAssetNotification.php index d2001f2e13..85f5aef50c 100644 --- a/app/Notifications/RequestAssetNotification.php +++ b/app/Notifications/RequestAssetNotification.php @@ -78,7 +78,7 @@ class RequestAssetNotification extends Notification $fields = [ 'QTY' => $qty, - 'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + 'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>', ]; return (new SlackMessage) @@ -86,7 +86,7 @@ class RequestAssetNotification extends Notification ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($item->display_name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); diff --git a/app/Notifications/WelcomeNotification.php b/app/Notifications/WelcomeNotification.php index 1e27ca7364..68d852e392 100644 --- a/app/Notifications/WelcomeNotification.php +++ b/app/Notifications/WelcomeNotification.php @@ -5,26 +5,23 @@ namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Facades\Password; +use App\Models\User; class WelcomeNotification extends Notification { use Queueable; - private $_data = []; - + public $expire_date; /** * Create a new notification instance. * * @return void */ - public function __construct(array $content) + public function __construct(public User $user) { - $this->_data['email'] = htmlspecialchars_decode($content['email']); - $this->_data['first_name'] = htmlspecialchars_decode($content['first_name']); - $this->_data['last_name'] = htmlspecialchars_decode($content['last_name']); - $this->_data['username'] = htmlspecialchars_decode($content['username']); - $this->_data['password'] = htmlspecialchars_decode($content['password']); - $this->_data['url'] = config('app.url'); + $this->user->token = Password::broker('invites')->createToken($user); + $this->user->expire_date = now()->addMinutes((int) config('auth.passwords.invites.expire', 2880))->format('F j, Y, g:i a'); } /** @@ -44,8 +41,9 @@ class WelcomeNotification extends Notification */ public function toMail() { + return (new MailMessage()) - ->subject(trans('mail.welcome', ['name' => $this->_data['first_name'].' '.$this->_data['last_name']])) - ->markdown('notifications.Welcome', $this->_data); + ->subject(trans('mail.welcome', ['name' => $this->user->first_name.' '.$this->user->last_name])) + ->markdown('notifications.Welcome', $this->user->toArray()); } } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index d0ff86e53c..6c07a355ff 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -40,8 +40,9 @@ class AssetObserver // If the asset isn't being checked out or audited, log the update. // (Those other actions already create log entries.) - if (($attributes['assigned_to'] == $attributesOriginal['assigned_to']) - && ($same_checkout_counter) && ($same_checkin_counter) + if (array_key_exists('assigned_to', $attributes) && array_key_exists('assigned_to', $attributesOriginal) + && ($attributes['assigned_to'] == $attributesOriginal['assigned_to']) + && ($same_checkout_counter) && ($same_checkin_counter) && ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null)) && ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting)) { @@ -61,6 +62,7 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); @@ -107,6 +109,7 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; // can we instead say $logAction->item = $asset ? $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); if($asset->imported) { @@ -127,6 +130,7 @@ class AssetObserver $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } @@ -142,6 +146,7 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); $logAction->logaction('restore'); diff --git a/app/Observers/ComponentObserver.php b/app/Observers/ComponentObserver.php index cd2c58c367..dfc5091b3d 100644 --- a/app/Observers/ComponentObserver.php +++ b/app/Observers/ComponentObserver.php @@ -20,7 +20,11 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); + if($component->imported) { + $logAction->setActionSource('importer'); + } $logAction->logaction('update'); } @@ -37,6 +41,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); if($component->imported) { $logAction->setActionSource('importer'); @@ -56,6 +61,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } diff --git a/app/Observers/MaintenanceObserver.php b/app/Observers/MaintenanceObserver.php new file mode 100644 index 0000000000..7e59d1dbc0 --- /dev/null +++ b/app/Observers/MaintenanceObserver.php @@ -0,0 +1,74 @@ +item_type = Maintenance::class; + $logAction->item_id = $maintenance->id; + $logAction->target_type = Asset::class; + $logAction->target_id = $maintenance->asset_id; + $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); + $logAction->created_by = auth()->id(); + if($maintenance->imported) { + $logAction->setActionSource('importer'); + } + $logAction->logaction('update'); + } + + /** + * Listen to the Component created event when + * a new component is created. + * + * @param Maintenance $maintenance + * @return void + */ + public function created(Maintenance $maintenance) + { + $logAction = new Actionlog(); + $logAction->item_type = Maintenance::class; + $logAction->item_id = $maintenance->id; + $logAction->target_type = Asset::class; + $logAction->target_id = $maintenance->asset_id; + $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); + $logAction->created_by = auth()->id(); + if($maintenance->imported) { + $logAction->setActionSource('importer'); + } + $logAction->logaction('create'); + } + + /** + * Listen to the Component deleting event. + * + * @param Maintenance $maintenance + * @return void + */ + public function deleting(Maintenance $maintenance) + { + $logAction = new Actionlog(); + $logAction->item_type = Maintenance::class; + $logAction->item_id = $maintenance->id; + $logAction->target_type = Asset::class; + $logAction->target_id = $maintenance->asset_id; + $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->action_date = date('Y-m-d H:i:s'); + $logAction->created_by = auth()->id(); + $logAction->logaction('delete'); + } +} diff --git a/app/Policies/AssetPolicy.php b/app/Policies/AssetPolicy.php index 2384dfd836..6a15f5c77e 100644 --- a/app/Policies/AssetPolicy.php +++ b/app/Policies/AssetPolicy.php @@ -3,6 +3,7 @@ namespace App\Policies; use App\Models\User; +use App\Models\Asset; class AssetPolicy extends CheckoutablePermissionsPolicy { diff --git a/app/Policies/CompanyPolicy.php b/app/Policies/CompanyPolicy.php index 78d1b831b4..948d6c86c2 100644 --- a/app/Policies/CompanyPolicy.php +++ b/app/Policies/CompanyPolicy.php @@ -8,4 +8,5 @@ class CompanyPolicy extends SnipePermissionsPolicy { return 'companies'; } + } diff --git a/app/Policies/SnipePermissionsPolicy.php b/app/Policies/SnipePermissionsPolicy.php index 96c94cd776..59c35394c5 100644 --- a/app/Policies/SnipePermissionsPolicy.php +++ b/app/Policies/SnipePermissionsPolicy.php @@ -53,7 +53,7 @@ abstract class SnipePermissionsPolicy } /** - * If we got here by $this→authorize('something', $actualModel) then we can continue on Il but if we got here + * If we got here by $this→authorize('something', $actualModel) then we can continue on, but if we got here * via $this→authorize('something', Model::class) then calling Company:: isCurrentUserHasAccess($item) gets weird. * Bail out here by returning "nothing" and allow the relevant method lower in this class to be called and handle authorization. */ @@ -85,7 +85,7 @@ abstract class SnipePermissionsPolicy } /** - * Determine whether the user can view the accessory. + * Determine whether the user can view the model. * * @param \App\Models\User $user * @return mixed @@ -101,7 +101,7 @@ abstract class SnipePermissionsPolicy } /** - * Determine whether the user can create accessories. + * Determine whether the user can create model. * * @param \App\Models\User $user * @return mixed @@ -112,7 +112,7 @@ abstract class SnipePermissionsPolicy } /** - * Determine whether the user can update the accessory. + * Determine whether the user can update the model. * * @param \App\Models\User $user * @return mixed @@ -124,7 +124,7 @@ abstract class SnipePermissionsPolicy /** - * Determine whether the user can update the accessory. + * Determine whether the user can update the model. * * @param \App\Models\User $user * @return mixed @@ -135,7 +135,7 @@ abstract class SnipePermissionsPolicy } /** - * Determine whether the user can delete the accessory. + * Determine whether the user can delete the model. * * @param \App\Models\User $user * @return mixed @@ -151,7 +151,7 @@ abstract class SnipePermissionsPolicy } /** - * Determine whether the user can manage the accessory. + * Determine whether the user can manage the model. * * @param \App\Models\User $user * @return mixed diff --git a/app/Presenters/AccessoryPresenter.php b/app/Presenters/AccessoryPresenter.php index 86b4d1bc0f..368ddd3f65 100644 --- a/app/Presenters/AccessoryPresenter.php +++ b/app/Presenters/AccessoryPresenter.php @@ -21,6 +21,7 @@ class AccessoryPresenter extends Presenter 'switchable' => true, 'title' => trans('general.id'), 'visible' => false, + 'printIgnore' => true, ], [ 'field' => 'image', 'searchable' => false, @@ -172,6 +173,7 @@ class AccessoryPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'accessoriesActionsFormatter', + 'printIgnore' => true, ], ]; @@ -240,6 +242,7 @@ class AccessoryPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'accessoriesInOutFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 4b7aefc87a..7ab022138b 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -62,6 +62,10 @@ class ActionlogPresenter extends Presenter return 'fa-solid fa-user-minus'; } + if ($this->action_type == 'upload deleted') { + return 'fa-solid fa-trash'; + } + if ($this->action_type == 'update') { return 'fa-solid fa-user-pen'; } @@ -74,7 +78,7 @@ class ActionlogPresenter extends Presenter return 'fa-solid fa-plus'; } - if ($this->action_type == 'delete') { + if (($this->action_type == 'delete') || ($this->action_type == 'upload deleted')) { return 'fa-solid fa-trash'; } diff --git a/app/Presenters/AssetAuditPresenter.php b/app/Presenters/AssetAuditPresenter.php index a4d208fc0e..e03316ecdf 100644 --- a/app/Presenters/AssetAuditPresenter.php +++ b/app/Presenters/AssetAuditPresenter.php @@ -21,6 +21,7 @@ class AssetAuditPresenter extends Presenter 'field' => 'checkbox', 'checkbox' => true, 'titleTooltip' => trans('general.select_all_none'), + 'printIgnore' => true, ], [ 'field' => 'id', @@ -266,6 +267,7 @@ class AssetAuditPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'hardwareAuditFormatter', + 'printIgnore' => true, ]; return json_encode($layout); diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index 2ed019eaea..04ea717f94 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -16,6 +16,7 @@ class AssetModelPresenter extends Presenter 'field' => 'checkbox', 'checkbox' => true, 'titleTooltip' => trans('general.select_all_none'), + 'printIgnore' => true, ], [ 'field' => 'id', @@ -142,6 +143,14 @@ class AssetModelPresenter extends Presenter 'title' => trans('admin/hardware/general.requestable'), 'formatter' => 'trueFalseFormatter', ], + [ + 'field' => 'require_serial', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('admin/hardware/general.require_serial'), + 'formatter' => 'trueFalseFormatter', + ], [ 'field' => 'notes', 'searchable' => true, @@ -185,6 +194,7 @@ class AssetModelPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'modelsActionsFormatter', + 'printIgnore' => true, ]; return json_encode($layout); diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index 2a4d09d131..6f467644fd 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -22,6 +22,7 @@ class AssetPresenter extends Presenter 'field' => 'checkbox', 'checkbox' => true, 'titleTooltip' => trans('general.select_all_none'), + 'printIgnore' => true, ], [ 'field' => 'id', 'searchable' => false, @@ -108,6 +109,13 @@ class AssetPresenter extends Presenter 'title' => trans('general.employee_number'), 'visible' => false, 'formatter' => 'employeeNumFormatter', + ],[ + 'field' => 'jobtitle', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('admin/users/table.title'), + 'visible' => false, + 'formatter' => 'jobtitleFormatter', ], [ 'field' => 'location', 'searchable' => true, @@ -298,6 +306,7 @@ class AssetPresenter extends Presenter 'sortable' => true, 'visible' => false, 'title' => trans('general.byod'), + 'class' => 'byod', 'formatter' => 'trueFalseFormatter', ], @@ -319,7 +328,7 @@ class AssetPresenter extends Presenter // name can break the listings page. - snipe foreach ($fields as $field) { $layout[] = [ - 'field' => 'custom_fields.'.$field->db_column, + 'field' => $field->db_column, 'searchable' => true, 'sortable' => true, 'switchable' => true, @@ -339,6 +348,7 @@ class AssetPresenter extends Presenter 'title' => trans('general.checkin').'/'.trans('general.checkout'), 'visible' => true, 'formatter' => 'hardwareInOutFormatter', + 'printIgnore' => true, ]; $layout[] = [ @@ -348,6 +358,7 @@ class AssetPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'hardwareActionsFormatter', + 'printIgnore' => true, ]; return json_encode($layout); @@ -415,6 +426,7 @@ class AssetPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'accessoriesInOutFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/CategoryPresenter.php b/app/Presenters/CategoryPresenter.php index d4a9f01a05..e7d31264e9 100644 --- a/app/Presenters/CategoryPresenter.php +++ b/app/Presenters/CategoryPresenter.php @@ -112,7 +112,8 @@ class CategoryPresenter extends Presenter 'sortable' => false, 'switchable' => false, 'title' => trans('table.actions'), - 'formatter' => 'categoriesActionsFormatter', + 'formatter' => 'categoriesActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/CompanyPresenter.php b/app/Presenters/CompanyPresenter.php index fb86166092..1f46b26f71 100644 --- a/app/Presenters/CompanyPresenter.php +++ b/app/Presenters/CompanyPresenter.php @@ -142,6 +142,7 @@ class CompanyPresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'companiesActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/ComponentPresenter.php b/app/Presenters/ComponentPresenter.php index 61f7a0ef4b..32e63ac713 100644 --- a/app/Presenters/ComponentPresenter.php +++ b/app/Presenters/ComponentPresenter.php @@ -169,6 +169,7 @@ class ComponentPresenter extends Presenter 'title' => trans('general.checkin').'/'.trans('general.checkout'), 'visible' => true, 'formatter' => 'componentsInOutFormatter', + 'printIgnore' => true, ]; $layout[] = [ @@ -178,6 +179,7 @@ class ComponentPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'componentsActionsFormatter', + 'printIgnore' => true, ]; return json_encode($layout); diff --git a/app/Presenters/ConsumablePresenter.php b/app/Presenters/ConsumablePresenter.php index fd4cee272c..57f628c5ba 100644 --- a/app/Presenters/ConsumablePresenter.php +++ b/app/Presenters/ConsumablePresenter.php @@ -72,14 +72,6 @@ class ConsumablePresenter extends Presenter 'searchable' => true, 'sortable' => true, 'title' => trans('admin/consumables/general.item_no'), - ], [ - 'field' => 'min_amt', - 'searchable' => false, - 'sortable' => true, - 'title' => trans('general.min_amt'), - 'visible' => true, - 'formatter' => 'minAmtFormatter', - 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'qty', 'searchable' => false, @@ -96,6 +88,14 @@ class ConsumablePresenter extends Presenter 'visible' => true, 'class' => 'text-right text-padding-number-cell', 'footerFormatter' => 'qtySumFormatter', + ], [ + 'field' => 'min_amt', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.min_amt'), + 'visible' => true, + 'formatter' => 'minAmtFormatter', + 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'location', 'searchable' => true, @@ -173,6 +173,7 @@ class ConsumablePresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'consumablesActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/DepreciationPresenter.php b/app/Presenters/DepreciationPresenter.php index 3f240fcc53..a25643e934 100644 --- a/app/Presenters/DepreciationPresenter.php +++ b/app/Presenters/DepreciationPresenter.php @@ -96,6 +96,7 @@ class DepreciationPresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'depreciationsActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/HistoryPresenter.php b/app/Presenters/HistoryPresenter.php new file mode 100644 index 0000000000..9b9968ebd2 --- /dev/null +++ b/app/Presenters/HistoryPresenter.php @@ -0,0 +1,172 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + 'class' => 'hidden-xs', + ], + [ + 'field' => 'icon', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/hardware/table.icon'), + 'visible' => true, + 'class' => 'hidden-xs', + 'formatter' => 'iconFormatter', + ], + [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => true, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'action_date', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.action_date'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'action_type', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.action'), + 'visible' => true, + ], + [ + 'field' => 'item', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.item'), + 'visible' => true, + 'formatter' => 'polymorphicItemFormatter', + ], + ]; + + + if ($serial) { + $extra = [ + [ + 'field' => 'item.serial', + 'title' => trans('admin/hardware/table.serial'), + 'visible' => false, + ] + ]; + } + + $layout_end = [ + [ + 'field' => 'target', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.target'), + 'visible' => true, + 'formatter' => 'polymorphicItemFormatter', + ], + [ + 'field' => 'file', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.file_name'), + 'visible' => true, + 'formatter' => 'fileNameFormatter', + ], + [ + 'field' => 'file_download', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.download'), + 'visible' => true, + 'formatter' => 'fileDownloadButtonsFormatter', + ], + [ + 'field' => 'note', + 'searchable' => true, + 'sortable' => true, + 'visible' => true, + 'title' => trans('general.notes'), + 'formatter' => 'notesFormatter' + ], + [ + 'field' => 'signature_file', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.signature'), + 'visible' => false, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'log_meta', + 'searchable' => false, + 'sortable' => false, + 'visible' => true, + 'title' => trans('admin/hardware/table.changed'), + 'formatter' => 'changeLogFormatter', + ], + [ + 'field' => 'remote_ip', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('admin/settings/general.login_ip'), + ], + [ + 'field' => 'user_agent', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('admin/settings/general.login_user_agent'), + ], + [ + 'field' => 'action_source', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.action_source'), + ], + ]; + + $merged = array_merge($layout_start, $extra, $layout_end); + return json_encode($merged); + } + +} \ No newline at end of file diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 351a9acea4..b0518ef264 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -53,6 +53,7 @@ class LicensePresenter extends Presenter 'searchable' => true, 'sortable' => true, 'title' => trans('admin/licenses/form.to_email'), + 'formatter' => 'emailFormatter', ], [ 'field' => 'license_name', 'searchable' => true, @@ -202,6 +203,7 @@ class LicensePresenter extends Presenter 'title' => trans('general.checkin').'/'.trans('general.checkout'), 'visible' => true, 'formatter' => 'licensesInOutFormatter', + 'printIgnore' => true, ]; $layout[] = [ @@ -211,6 +213,7 @@ class LicensePresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'licensesActionsFormatter', + 'printIgnore' => true, ]; return json_encode($layout); @@ -230,16 +233,7 @@ class LicensePresenter extends Presenter 'switchable' => true, 'title' => trans('general.id'), 'visible' => false, - ], - [ - 'field' => 'name', - 'searchable' => false, - 'sortable' => false, - 'sorter' => 'numericOnly', - 'switchable' => true, - 'title' => trans('admin/licenses/general.seat'), - 'visible' => true, - ], [ + ],[ 'field' => 'assigned_user', 'searchable' => false, 'sortable' => false, @@ -280,6 +274,14 @@ class LicensePresenter extends Presenter 'visible' => true, 'formatter' => 'locationsLinkObjFormatter', ], + [ + 'field' => 'updated_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.updated_at'), + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'notes', 'searchable' => false, diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php index 073918ce1c..31d2f05f77 100644 --- a/app/Presenters/LocationPresenter.php +++ b/app/Presenters/LocationPresenter.php @@ -18,6 +18,8 @@ class LocationPresenter extends Presenter 'field' => 'bulk_selectable', 'checkbox' => true, 'formatter' => 'checkboxEnabledFormatter', + 'titleTooltip' => trans('general.select_all_none'), + 'printIgnore' => true, ], [ 'field' => 'id', 'searchable' => false, @@ -33,7 +35,7 @@ class LocationPresenter extends Presenter 'switchable' => true, 'title' => trans('general.company'), 'visible' => false, - 'formatter' => 'locationCompanyObjFilterFormatter' + 'formatter' => 'companiesLinkObjFormatter' ], [ 'field' => 'name', @@ -65,7 +67,9 @@ class LocationPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/message.current_location'), + 'titleTooltip' => trans('admin/locations/message.current_location'), 'visible' => true, + 'class' => 'css-house-laptop', ], [ 'field' => 'rtd_assets_count', 'searchable' => false, @@ -208,7 +212,16 @@ class LocationPresenter extends Presenter 'title' => trans('general.created_at'), 'visible' => false, 'formatter' => 'dateDisplayFormatter', - ], [ + ], + [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/AssetMaintenancesPresenter.php b/app/Presenters/MaintenancesPresenter.php similarity index 84% rename from app/Presenters/AssetMaintenancesPresenter.php rename to app/Presenters/MaintenancesPresenter.php index 0648613adb..41ff927f67 100644 --- a/app/Presenters/AssetMaintenancesPresenter.php +++ b/app/Presenters/MaintenancesPresenter.php @@ -5,7 +5,7 @@ namespace App\Presenters; /** * Class AssetModelPresenter */ -class AssetMaintenancesPresenter extends Presenter +class MaintenancesPresenter extends Presenter { /** * Json Column Layout for bootstrap table @@ -22,7 +22,7 @@ class AssetMaintenancesPresenter extends Presenter 'title' => trans('general.id'), 'visible' => false, ], [ - 'field' => 'title', + 'field' => 'name', 'searchable' => true, 'sortable' => true, 'switchable' => true, @@ -30,6 +30,15 @@ class AssetMaintenancesPresenter extends Presenter 'visible' => true, 'formatter' => 'maintenancesLinkFormatter', ], + [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], [ 'field' => 'company', 'searchable' => true, @@ -42,7 +51,7 @@ class AssetMaintenancesPresenter extends Presenter 'field' => 'asset_name', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/table.asset_name'), + 'title' => trans('admin/maintenances/table.asset_name'), 'formatter' => 'assetNameLinkFormatter', ], [ 'field' => 'asset_tag', @@ -89,35 +98,35 @@ class AssetMaintenancesPresenter extends Presenter 'field' => 'asset_maintenance_type', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/form.asset_maintenance_type'), + 'title' => trans('admin/maintenances/form.asset_maintenance_type'), ], [ 'field' => 'start_date', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/form.start_date'), + 'title' => trans('admin/maintenances/form.start_date'), 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'completion_date', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/form.completion_date'), + 'title' => trans('admin/maintenances/form.completion_date'), 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'notes', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/form.notes'), + 'title' => trans('admin/maintenances/form.notes'), ], [ 'field' => 'is_warranty', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/table.is_warranty'), + 'title' => trans('admin/maintenances/table.is_warranty'), 'formatter' => 'trueFalseFormatter' ], [ 'field' => 'cost', 'searchable' => true, 'sortable' => true, - 'title' => trans('admin/asset_maintenances/form.cost'), + 'title' => trans('admin/maintenances/form.cost'), 'class' => 'text-right', ], [ 'field' => 'created_by', @@ -150,6 +159,7 @@ class AssetMaintenancesPresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'maintenancesActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/ManufacturerPresenter.php b/app/Presenters/ManufacturerPresenter.php index dfefec2998..5d539641cf 100644 --- a/app/Presenters/ManufacturerPresenter.php +++ b/app/Presenters/ManufacturerPresenter.php @@ -163,6 +163,7 @@ class ManufacturerPresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'manufacturersActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/PredefinedKitPresenter.php b/app/Presenters/PredefinedKitPresenter.php index 7ce7d8c23d..c8b8f1360d 100644 --- a/app/Presenters/PredefinedKitPresenter.php +++ b/app/Presenters/PredefinedKitPresenter.php @@ -61,6 +61,7 @@ class PredefinedKitPresenter extends Presenter 'title' => trans('general.checkin').'/'.trans('general.checkout'), 'visible' => true, 'formatter' => 'kitsInOutFormatter', + 'printIgnore' => true, ]; $layout[] = [ @@ -70,6 +71,7 @@ class PredefinedKitPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'kitsActionsFormatter', + 'printIgnore' => true, ]; return json_encode($layout); @@ -121,6 +123,7 @@ class PredefinedKitPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'kits_modelsActionsFormatter', + 'printIgnore' => true, ], ]; @@ -173,6 +176,7 @@ class PredefinedKitPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'kits_licensesActionsFormatter', + 'printIgnore' => true, ], ]; @@ -225,6 +229,7 @@ class PredefinedKitPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'kits_accessoriesActionsFormatter', + 'printIgnore' => true, ], ]; @@ -277,6 +282,7 @@ class PredefinedKitPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'kits_consumablesActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/Presenter.php b/app/Presenters/Presenter.php index 85fe2338ae..13bae66ecd 100644 --- a/app/Presenters/Presenter.php +++ b/app/Presenters/Presenter.php @@ -3,6 +3,7 @@ namespace App\Presenters; use App\Models\SnipeModel; +use Illuminate\Database\Eloquent\Casts\Attribute; abstract class Presenter { @@ -69,10 +70,30 @@ abstract class Presenter return ''; } - public function name() - { - return $this->model->name; - } +// public function name() +// { +// return $this->model->name; +// } +// +// public function display_name() +// { +// return $this->model->display_name; +// } + + +// protected function displayName(): Attribute +// { +// // This override should only kick in if the model has a display_name prope +// if ($this->getRawOriginal('display_name')) { +// return Attribute:: make ( +// get: fn(mixed $value) => 'Poop:'.$this->display_name +// ); +// } +// +// return Attribute:: make( +// get: fn(mixed $value) => 'Fart: '.$this->name, +// ); +// } public function __get($property) { @@ -80,7 +101,7 @@ abstract class Presenter return $this->{$property}(); } - return e($this->model->{$property}); + return $this->model->{$property}; } public function __call($method, $args) diff --git a/app/Presenters/StatusLabelPresenter.php b/app/Presenters/StatusLabelPresenter.php index 5bc4bd8831..eac73d67ab 100644 --- a/app/Presenters/StatusLabelPresenter.php +++ b/app/Presenters/StatusLabelPresenter.php @@ -105,6 +105,7 @@ class StatusLabelPresenter extends Presenter 'switchable' => false, 'title' => trans('table.actions'), 'formatter' => 'statuslabelsActionsFormatter', + 'printIgnore' => true, ], ]; diff --git a/app/Presenters/SupplierPresenter.php b/app/Presenters/SupplierPresenter.php new file mode 100644 index 0000000000..13f1887621 --- /dev/null +++ b/app/Presenters/SupplierPresenter.php @@ -0,0 +1,227 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'name', + 'searchable' => true, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.name'), + 'visible' => true, + 'formatter' => 'suppliersLinkFormatter', + ], [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'assets_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.assets'), + 'titleTooltip' => trans('general.assets'), + 'visible' => true, + 'class' => 'css-barcode', + ], [ + 'field' => 'accessories_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.accessories'), + 'titleTooltip' => trans('general.accessories'), + 'visible' => true, + 'class' => 'css-accessory', + ], + [ + 'field' => 'licenses_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.licenses'), + 'titleTooltip' => trans('general.licenses'), + 'visible' => true, + 'class' => 'css-license', + ], [ + 'field' => 'components_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.components'), + 'titleTooltip' => trans('general.components'), + 'visible' => true, + 'class' => 'css-component', + ], [ + 'field' => 'consumables_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.consumables'), + 'titleTooltip' => trans('general.consumables'), + 'visible' => true, + 'class' => 'css-consumable', + ], [ + 'field' => 'url', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.url'), + 'visible' => true, + 'formatter' => 'externalLinkFormatter', + ], [ + 'field' => 'address', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.address'), + 'visible' => true, + ], [ + 'field' => 'address2', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.address2'), + 'visible' => false, + ], [ + 'field' => 'city', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.city'), + 'visible' => true, + ], [ + 'field' => 'state', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.state'), + 'visible' => true, + ], [ + 'field' => 'zip', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.zip'), + 'visible' => false, + ], [ + 'field' => 'country', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.country'), + 'visible' => false, + ], [ + 'field' => 'phone', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/users/table.phone'), + 'visible' => false, + 'formatter' => 'phoneFormatter', + ], [ + 'field' => 'fax', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/suppliers/table.fax'), + 'visible' => false, + 'formatter' => 'phoneFormatter', + ], [ + 'field' => 'notes', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.notes'), + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'visible' => true, + 'formatter' => 'suppliersActionsFormatter', + 'printIgnore' => true, + ], + ]; + + return json_encode($layout); + } + + + /** + * Link to this supplier name + * @return string + */ + public function nameUrl() + { + return (string) link_to_route('suppliers.show', $this->name, $this->id); + } + + /** + * Getter for Polymorphism. + * @return mixed + */ + public function name() + { + return $this->model->name; + } + + /** + * Url to view this item. + * @return string + */ + public function viewUrl() + { + return route('suppliers.show', $this->id); + } + + public function glyph() + { + return ''; + } + + public function fullName() + { + return $this->name; + } +} diff --git a/app/Presenters/UploadedFilesPresenter.php b/app/Presenters/UploadedFilesPresenter.php new file mode 100644 index 0000000000..83b528d387 --- /dev/null +++ b/app/Presenters/UploadedFilesPresenter.php @@ -0,0 +1,101 @@ + 'id', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'icon', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('general.type'), + 'visible' => true, + 'formatter' => 'iconFormatter', + ], + [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'filePreviewFormatter', + ], + [ + 'field' => 'filename', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.file_name'), + 'visible' => true, + 'formatter' => 'fileNameFormatter', + ], + [ + 'field' => 'download', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.download'), + 'visible' => true, + 'formatter' => 'fileDownloadButtonsFormatter', + ], + [ + 'field' => 'note', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => true, + ], + [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => true, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'available_actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'visible' => true, + 'formatter' => 'deleteUploadFormatter', + ], + ]; + + return json_encode($layout); + } + +} \ No newline at end of file diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index ff81814818..296fafe568 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -25,6 +25,7 @@ class UserPresenter extends Presenter 'field' => 'checkbox', 'checkbox' => true, 'titleTooltip' => trans('general.select_all_none'), + 'printIgnore' => true, ], [ 'field' => 'id', @@ -78,6 +79,14 @@ class UserPresenter extends Presenter 'visible' => false, 'formatter' => 'usersLinkFormatter', ], + [ + 'field' => 'display_name', + 'searchable' => true, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('admin/users/table.display_name'), + 'visible' => true, + ], [ 'field' => 'jobtitle', 'searchable' => true, @@ -123,6 +132,15 @@ class UserPresenter extends Presenter 'visible' => true, 'formatter' => 'phoneFormatter', ], + [ + 'field' => 'mobile', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/users/table.mobile'), + 'visible' => false, + 'formatter' => 'mobileFormatter', + ], [ 'field' => 'website', 'searchable' => true, @@ -179,8 +197,9 @@ class UserPresenter extends Presenter 'switchable' => false, 'title' => trans('admin/users/table.username'), 'visible' => true, - 'formatter' => 'usersLinkFormatter', + 'formatter' => 'usernameRoleLinkFormatter', ], + [ 'field' => 'employee_num', 'searchable' => true, @@ -206,6 +225,15 @@ class UserPresenter extends Presenter 'visible' => true, 'formatter' => 'departmentsLinkObjFormatter', ], + [ + 'field' => 'department_manager', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/users/general.department_manager'), + 'visible' => true, + 'formatter' => 'usersLinkObjFormatter', + ], [ 'field' => 'location', 'searchable' => true, @@ -406,6 +434,7 @@ class UserPresenter extends Presenter 'title' => trans('table.actions'), 'visible' => true, 'formatter' => 'usersActionsFormatter', + 'printIgnore' => true, ], ]; @@ -427,20 +456,25 @@ class UserPresenter extends Presenter * * @return string */ - public function fullName() - { - return html_entity_decode($this->first_name.' '.$this->last_name, ENT_QUOTES | ENT_XML1, 'UTF-8'); - } +// public function fullName() +// { +// if ($this->display_name) { +// return 'kjdfh'.html_entity_decode($this->display_name, ENT_QUOTES | ENT_XML1, 'UTF-8'); +// } +// return 'roieuoe'.html_entity_decode($this->first_name.' '.$this->last_name, ENT_QUOTES | ENT_XML1, 'UTF-8'); +// } + +// /** +// * Standard accessor. +// * @TODO Remove presenter::fullName() entirely? +// * @return string +// */ +// public function name() +// { +// return $this->fullName(); +// } + - /** - * Standard accessor. - * @TODO Remove presenter::fullName() entirely? - * @return string - */ - public function name() - { - return $this->fullName(); - } /** * Returns the user Gravatar image url. @@ -454,7 +488,7 @@ class UserPresenter extends Presenter if ($this->avatar) { // Check if it's a google avatar or some external avatar - if (Str::startsWith($this->avatar, ['http://', 'https://'])) { + if ($this->isAvatarExternal()) { return $this->avatar; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index aa2604bce5..2ebcb9b035 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,6 +7,7 @@ use App\Models\Asset; use App\Models\Component; use App\Models\Consumable; use App\Models\License; +use App\Models\Maintenance; use App\Models\User; use App\Models\Setting; use App\Models\SnipeSCIMConfig; @@ -17,6 +18,7 @@ use App\Observers\ComponentObserver; use App\Observers\ConsumableObserver; use App\Observers\LicenseObserver; use App\Observers\SettingObserver; +use App\Observers\MaintenanceObserver; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; @@ -67,6 +69,7 @@ class AppServiceProvider extends ServiceProvider Schema::defaultStringLength(191); Asset::observe(AssetObserver::class); + Maintenance::observe(MaintenanceObserver::class); User::observe(UserObserver::class); Accessory::observe(AccessoryObserver::class); Component::observe(ComponentObserver::class); @@ -82,6 +85,12 @@ class AppServiceProvider extends ServiceProvider */ public function register() { + + if ($this->app->environment('local')) { + $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); + $this->app->register(TelescopeServiceProvider::class); + } + // Only load rollbar if there is a rollbar key and the app is in production if (($this->app->environment('production')) && (config('logging.channels.rollbar.access_token'))) { $this->app->register(\Rollbar\Laravel\RollbarServiceProvider::class); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 0d86cbdf6d..114100f288 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -101,13 +101,21 @@ class AuthServiceProvider extends ServiceProvider * This is where we set the superadmin permission to allow superadmins to be able to do everything within the system. * */ - Gate::before(function ($user) { + Gate::before(function ($user, $ability) { + + // Disallow even superadmins to edit non-editable things when in demo mode. + // (We have to do this to prevent jerks from trying to break the demo by editing things they shouldn't.) + if (($ability == 'editableOnDemo') && (config('app.lock_passwords'))) { + return false; + } if ($user->isSuperUser()) { return true; } }); + + /** * GENERAL GATES * @@ -115,6 +123,45 @@ class AuthServiceProvider extends ServiceProvider * use in our controllers to determine if a user has access to a certain area. */ + Gate::define('canEditAuthFields', function ($user, $item) { + + if ($item instanceof User) { + + // if they can only edit users, deny them if the user is admin or superadmin + if (($user->hasAccess('users.edit')) && (!$user->isAdmin()) && (!$user->isAdmin())) { + + if ($item->isAdmin() || $item->isSuperUser()) { + return false; + } + return true; + } + + // if they are an admin, deny them only if the user is a superadmin + if ($user->hasAccess('admin')) { + if ($item->isSuperUser()) { + return false; + } + + return true; + } + + return false; + } + + return false; + }); + + + /** + * Define the demo mode gate so we have an easy way to use @can and Gate::allows() + */ + Gate::define('editableOnDemo', function () { + if (config('app.lock_passwords')) { + return false; + } + return true; + }); + Gate::define('admin', function ($user) { if ($user->hasAccess('admin')) { return true; @@ -168,6 +215,15 @@ class AuthServiceProvider extends ServiceProvider } }); + // ----------------------------------------- + // Activity + // ----------------------------------------- + Gate::define('activity.view', function ($user) { + if (($user->hasAccess('reports.view')) || ($user->hasAccess('admin'))) { + return true; + } + }); + // ----------------------------------------- // Self // ----------------------------------------- @@ -240,5 +296,6 @@ class AuthServiceProvider extends ServiceProvider return $user->canEditProfile(); }); + } } diff --git a/app/Providers/BreadcrumbsServiceProvider.php b/app/Providers/BreadcrumbsServiceProvider.php index 3744ae81d4..a5092b3ded 100644 --- a/app/Providers/BreadcrumbsServiceProvider.php +++ b/app/Providers/BreadcrumbsServiceProvider.php @@ -2,7 +2,7 @@ use App\Models\Accessory; use App\Models\Asset; -use App\Models\AssetMaintenance; +use App\Models\Maintenance; use App\Models\AssetModel; use App\Models\Category; use App\Models\Company; @@ -67,14 +67,20 @@ class BreadcrumbsServiceProvider extends ServiceProvider ->push(trans('general.create'), route('hardware.create')) ); + Breadcrumbs::for('clone/hardware', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('admin/hardware/general.clone'), route('hardware.create')) + ); + Breadcrumbs::for('hardware.show', fn (Trail $trail, Asset $asset) => $trail->parent('hardware.index', route('hardware.index')) - ->push($asset->present()->fullName(), route('hardware.show', $asset)) + ->push($asset->display_name, route('hardware.show', $asset)) ); Breadcrumbs::for('hardware.edit', fn (Trail $trail, Asset $asset) => $trail->parent('hardware.index', route('hardware.index')) - ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $asset->asset_tag]), route('hardware.edit', $asset)) + ->push($asset->display_name, route('hardware.show', $asset)) + ->push(trans('admin/hardware/general.edit')) ); @@ -377,6 +383,12 @@ class BreadcrumbsServiceProvider extends ServiceProvider ->push(trans('general.create'), route('locations.create')) ); + Breadcrumbs::for('clone/location', fn (Trail $trail) => + $trail->parent('locations.index', route('locations.index')) + ->push(trans('admin/locations/table.clone'), route('locations.create')) + ); + + Breadcrumbs::for('locations.show', fn (Trail $trail, Location $location) => $trail->parent('locations.index', route('locations.index')) ->push($location->name, route('locations.show', $location)) @@ -384,6 +396,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider Breadcrumbs::for('locations.edit', fn (Trail $trail, Location $location) => $trail->parent('locations.index', route('locations.index')) + ->push($location->name, route('locations.show', $location)) ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $location->name]), route('locations.edit', $location)) ); @@ -400,14 +413,14 @@ class BreadcrumbsServiceProvider extends ServiceProvider ->push(trans('general.create'), route('maintenances.create')) ); - Breadcrumbs::for('maintenances.show', fn (Trail $trail, AssetMaintenance $maintenance) => - $trail->parent('maintenances.index', route('locations.index')) - ->push($maintenance->title, route('maintenances.show', $maintenance)) + Breadcrumbs::for('maintenances.show', fn (Trail $trail, Maintenance $maintenance) => + $trail->parent('maintenances.index', route('maintenances.index')) + ->push($maintenance->name, route('maintenances.show', $maintenance)) ); - Breadcrumbs::for('manufacturers.edit', fn (Trail $trail, Manufacturer $manufacturer) => - $trail->parent('manufacturers.index', route('manufacturers.index')) - ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $manufacturer->name]), route('manufacturers.edit', $manufacturer)) + Breadcrumbs::for('maintenances.edit', fn (Trail $trail, Maintenance $maintenance) => + $trail->parent('maintenances.index', route('maintenances.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $maintenance->name]), route('maintenances.edit', $maintenance)) ); @@ -526,6 +539,26 @@ class BreadcrumbsServiceProvider extends ServiceProvider ->push(trans('general.users'), route('users.index')) ->push(trans('general.deleted_users'), route('users.index')) ); + } elseif ((request()->is('users*')) && (request()->admins=='true')) { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ->push(trans('general.show_admins'), route('users.index')) + ); + } elseif ((request()->is('users*')) && (request()->superadmins=='true')) { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ->push(trans('general.show_superadmins'), route('users.index')) + ); + } elseif ((request()->is('users*')) && (request()->activated=='0')) { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ->push(trans('general.login_disabled'), route('users.index')) + ); + } elseif ((request()->is('users*')) && (request()->activated=='1')) { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ->push(trans('general.login_enabled'), route('users.index')) + ); } else { Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) ->push(trans('general.users'), route('users.index')) @@ -537,9 +570,16 @@ class BreadcrumbsServiceProvider extends ServiceProvider ->push(trans('general.create'), route('users.create')) ); + Breadcrumbs::for('users.clone.show', fn (Trail $trail) => + $trail->parent('users.index', route('users.index')) + ->push(trans('admin/users/general.clone'), route('users.create')) + ); + + + Breadcrumbs::for('users.show', fn (Trail $trail, User $user) => $trail->parent('users.index', route('users.index')) - ->push($user->getFullNameAttribute() ?? 'Missing Username!', route('users.show', $user)) + ->push($user->display_name ?? 'Missing Username!', route('users.show', $user)) ); Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) => diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 276aaf1e4f..054ffabe3e 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -76,6 +76,8 @@ class RouteServiceProvider extends ServiceProvider /** * Configure the rate limiters for the application. * + * This ONLY fires on 429 responses. + * * https://laravel.com/docs/8.x/routing#rate-limiting * * @return void @@ -83,9 +85,17 @@ class RouteServiceProvider extends ServiceProvider protected function configureRateLimiting() { - // Rate limiter for API calls + // Rate limiter for API calls - this sends the correct API headers to show the user the remaining time they have to wait and gives them the 429 status code if they are throttled RateLimiter::for('api', function (Request $request) { - return Limit::perMinute(config('app.api_throttle_per_minute'))->by(optional($request->user())->id ?: $request->ip()); + return Limit::perMinute(config('app.api_throttle_per_minute'))->by(optional($request->user())->id ?: $request->ip()) + ->response(function ($request, $headers) { + return response()->json([ + 'status' => 'error', + 'messages' => 'Too many requests. Try again in '.$headers['Retry-After'].' seconds.', + 'status_code' => 429, + 'retryAfter' => $headers['Retry-After'] ?? 60, + ], 429, $headers); + }); }); // Rate limiter for forgotten password requests diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 1c89dc2b8c..45e62ac4f9 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -31,7 +31,7 @@ class SettingsServiceProvider extends ServiceProvider // Make sure the limit is actually set, is an integer and does not exceed system limits - \App::singleton('api_limit_value', function () { + app()->singleton('api_limit_value', function () { $limit = config('app.max_results'); $int_limit = intval(request('limit')); @@ -43,7 +43,7 @@ class SettingsServiceProvider extends ServiceProvider }); // Make sure the offset is actually set and is an integer - \App::singleton('api_offset_value', function () { + app()->singleton('api_offset_value', function () { $offset = intval(request('offset')); return $offset; }); @@ -57,113 +57,121 @@ class SettingsServiceProvider extends ServiceProvider // Model paths and URLs - \App::singleton('eula_pdf_path', function () { + app()->singleton('eula_pdf_path', function () { return 'eula_pdf_path/'; }); - \App::singleton('assets_upload_path', function () { + app()->singleton('assets_upload_path', function () { return 'assets/'; }); - \App::singleton('accessories_upload_path', function () { + app()->singleton('maintenances_path', function () { + return 'maintenances/'; + }); + + app()->singleton('audits_upload_path', function () { + return 'audits/'; + }); + + app()->singleton('accessories_upload_path', function () { return 'public/uploads/accessories/'; }); - \App::singleton('models_upload_path', function () { + app()->singleton('models_upload_path', function () { return 'models/'; }); - \App::singleton('models_upload_url', function () { + app()->singleton('models_upload_url', function () { return 'models/'; }); // Categories - \App::singleton('categories_upload_path', function () { + app()->singleton('categories_upload_path', function () { return 'categories/'; }); - \App::singleton('categories_upload_url', function () { + app()->singleton('categories_upload_url', function () { return 'categories/'; }); // Locations - \App::singleton('locations_upload_path', function () { + app()->singleton('locations_upload_path', function () { return 'locations/'; }); - \App::singleton('locations_upload_url', function () { + app()->singleton('locations_upload_url', function () { return 'locations/'; }); // Users - \App::singleton('users_upload_path', function () { + app()->singleton('users_upload_path', function () { return 'avatars/'; }); - \App::singleton('users_upload_url', function () { + app()->singleton('users_upload_url', function () { return 'users/'; }); // Manufacturers - \App::singleton('manufacturers_upload_path', function () { + app()->singleton('manufacturers_upload_path', function () { return 'manufacturers/'; }); - \App::singleton('manufacturers_upload_url', function () { + app()->singleton('manufacturers_upload_url', function () { return 'manufacturers/'; }); // Suppliers - \App::singleton('suppliers_upload_path', function () { + app()->singleton('suppliers_upload_path', function () { return 'suppliers/'; }); - \App::singleton('suppliers_upload_url', function () { + app()->singleton('suppliers_upload_url', function () { return 'suppliers/'; }); // Departments - \App::singleton('departments_upload_path', function () { + app()->singleton('departments_upload_path', function () { return 'departments/'; }); - \App::singleton('departments_upload_url', function () { + app()->singleton('departments_upload_url', function () { return 'departments/'; }); // Company paths and URLs - \App::singleton('companies_upload_path', function () { + app()->singleton('companies_upload_path', function () { return 'companies/'; }); - \App::singleton('companies_upload_url', function () { + app()->singleton('companies_upload_url', function () { return 'companies/'; }); // Accessories paths and URLs - \App::singleton('accessories_upload_path', function () { + app()->singleton('accessories_upload_path', function () { return 'accessories/'; }); - \App::singleton('accessories_upload_url', function () { + app()->singleton('accessories_upload_url', function () { return 'accessories/'; }); // Consumables paths and URLs - \App::singleton('consumables_upload_path', function () { + app()->singleton('consumables_upload_path', function () { return 'consumables/'; }); - \App::singleton('consumables_upload_url', function () { + app()->singleton('consumables_upload_url', function () { return 'consumables/'; }); // Components paths and URLs - \App::singleton('components_upload_path', function () { + app()->singleton('components_upload_path', function () { return 'components/'; }); - \App::singleton('components_upload_url', function () { + app()->singleton('components_upload_url', function () { return 'components/'; }); diff --git a/app/Providers/TelescopeServiceProvider.php b/app/Providers/TelescopeServiceProvider.php new file mode 100644 index 0000000000..414cd2467e --- /dev/null +++ b/app/Providers/TelescopeServiceProvider.php @@ -0,0 +1,66 @@ +hideSensitiveRequestDetails(); + + $isLocal = $this->app->environment('local'); + + Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { + return $isLocal || + $entry->isReportableException() || + $entry->isFailedRequest() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->hasMonitoredTag(); + }); + } + + /** + * Prevent sensitive request details from being logged by Telescope. + */ + protected function hideSensitiveRequestDetails(): void + { + if ($this->app->environment('local')) { + return; + } + + Telescope::hideRequestParameters(['_token']); + + Telescope::hideRequestHeaders([ + 'cookie', + 'x-csrf-token', + 'x-xsrf-token', + ]); + } + + /** + * Register the Telescope gate. + * + * This gate determines who can access Telescope in NON-LOCAL environments. + */ + protected function gate(): void + { + Gate::define('viewTelescope', function ($user) { + if ($user->isSuperUser()) { + return true; + } + + return false; + }); + } +} diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index a08a035fbb..13ee6c5729 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -362,7 +362,7 @@ class ValidationServiceProvider extends ServiceProvider $company_id = array_get($validator->getData(), 'company_id'); $location = Location::find($value); - if ($company_id != $location->company_id) { + if (($location) && ($company_id != $location->company_id)) { return false; } } diff --git a/app/Rules/AlphaEncrypted.php b/app/Rules/AlphaEncrypted.php index f4ed1d6c32..e13f677946 100644 --- a/app/Rules/AlphaEncrypted.php +++ b/app/Rules/AlphaEncrypted.php @@ -5,9 +5,11 @@ namespace App\Rules; use Closure; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Crypt; +use Illuminate\Validation\Concerns\ValidatesAttributes; class AlphaEncrypted implements ValidationRule { + use ValidatesAttributes; /** * Run the validation rule. * @@ -18,7 +20,7 @@ class AlphaEncrypted implements ValidationRule try { $attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute)); $decrypted = Crypt::decrypt($value); - if (!ctype_alpha($decrypted) && !is_null($decrypted)) { + if (!$this->validateAlpha($attributeName, $decrypted, 'ascii') && !is_null($decrypted)) { $fail(trans('validation.alpha', ['attribute' => $attributeName])); } } catch (\Exception $e) { diff --git a/app/Rules/BooleanEncrypted.php b/app/Rules/BooleanEncrypted.php new file mode 100644 index 0000000000..06fd370703 --- /dev/null +++ b/app/Rules/BooleanEncrypted.php @@ -0,0 +1,33 @@ +validateBoolean($attributeName, $decrypted) && !is_null($decrypted)) { + $fail(trans('validation.ipv6', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/DateEncrypted.php b/app/Rules/DateEncrypted.php new file mode 100644 index 0000000000..73d5cd1b3e --- /dev/null +++ b/app/Rules/DateEncrypted.php @@ -0,0 +1,32 @@ +validateDate($attributeName, $decrypted) && !is_null($decrypted)) { + $fail(trans('validation.date', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/EmailEncrypted.php b/app/Rules/EmailEncrypted.php new file mode 100644 index 0000000000..cd0e4ad289 --- /dev/null +++ b/app/Rules/EmailEncrypted.php @@ -0,0 +1,32 @@ +validateEmail($attribute, $decrypted, []) && !is_null($decrypted)) { + $fail(trans('validation.email', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/IPEncrypted.php b/app/Rules/IPEncrypted.php new file mode 100644 index 0000000000..2d64546308 --- /dev/null +++ b/app/Rules/IPEncrypted.php @@ -0,0 +1,32 @@ +validateIp($attributeName, $decrypted) && !is_null($decrypted)) { + $fail(trans('validation.ip', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/IPv4Encrypted.php b/app/Rules/IPv4Encrypted.php new file mode 100644 index 0000000000..57f36da49a --- /dev/null +++ b/app/Rules/IPv4Encrypted.php @@ -0,0 +1,32 @@ +validateIpv4($attributeName, $decrypted) && !is_null($decrypted)) { + $fail(trans('validation.ipv4', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/IPv6Encrypted.php b/app/Rules/IPv6Encrypted.php new file mode 100644 index 0000000000..6bb44fb2d9 --- /dev/null +++ b/app/Rules/IPv6Encrypted.php @@ -0,0 +1,32 @@ +validateIpv6($attributeName, $decrypted) && !is_null($decrypted)) { + $fail(trans('validation.ipv6', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/MacEncrypted.php b/app/Rules/MacEncrypted.php new file mode 100644 index 0000000000..c9b125a900 --- /dev/null +++ b/app/Rules/MacEncrypted.php @@ -0,0 +1,33 @@ +validateRegex($attributeName, $decrypted, ['/^[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}$/']) && !is_null($decrypted)) { + $fail(trans('validation.mac_address', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/NumericEncrypted.php b/app/Rules/NumericEncrypted.php index f3cb3ba76e..ff551e0322 100644 --- a/app/Rules/NumericEncrypted.php +++ b/app/Rules/NumericEncrypted.php @@ -3,12 +3,16 @@ namespace App\Rules; use Closure; +use Egulias\EmailValidator\Validation\RFCValidation; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Crypt; +use Illuminate\Validation\Concerns\ValidatesAttributes; class NumericEncrypted implements ValidationRule { + use ValidatesAttributes; + /** * Run the validation rule. * @@ -16,11 +20,10 @@ class NumericEncrypted implements ValidationRule */ public function validate(string $attribute, mixed $value, Closure $fail): void { - try { $attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute)); $decrypted = Crypt::decrypt($value); - if (!is_numeric($decrypted) && !is_null($decrypted)) { + if (!$this->validateNumeric($attributeName, $decrypted) && !is_null($decrypted)) { $fail(trans('validation.numeric', ['attribute' => $attributeName])); } } catch (\Exception $e) { diff --git a/app/Rules/RegexEncrypted.php b/app/Rules/RegexEncrypted.php new file mode 100644 index 0000000000..9a573f4b53 --- /dev/null +++ b/app/Rules/RegexEncrypted.php @@ -0,0 +1,36 @@ +first(); + $regex = $field->format; + $regex = str_replace('regex:', '', $regex); + try { + $attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute)); + $decrypted = Crypt::decrypt($value); + if (!$this->validateRegex($attributeName, $decrypted, [$regex]) && !is_null($decrypted)) { + $fail(trans('validation.regex', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e->getMessage()); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/UrlEncrypted.php b/app/Rules/UrlEncrypted.php new file mode 100644 index 0000000000..61c5f51c90 --- /dev/null +++ b/app/Rules/UrlEncrypted.php @@ -0,0 +1,32 @@ +validateUrl($attributeName, $decrypted, []) && !is_null($decrypted)) { + $fail(trans('validation.url', ['attribute' => $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php b/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php index a433ee9a28..b31bc91d1b 100644 --- a/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php +++ b/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php @@ -15,7 +15,7 @@ class UserCannotSwitchCompaniesIfItemsAssigned implements ValidationRule */ public function validate(string $attribute, mixed $value, Closure $fail): void { - $user = User::find(request()->route('user')->id); + $user = request()->route('user'); if (($value) && ($user->allAssignedCount() > 0) && (Setting::getSettings()->full_multiple_companies_support=='1')) { diff --git a/app/View/Label.php b/app/View/Label.php index 6dbad39a34..ee9a1ae2fd 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -25,6 +25,16 @@ class Label implements View */ protected $data; + + /** + * TCPDF output destination. + * "I" - inline by default. + * See TCPDF's Output method for details. + * + * @var string + */ + private string $destination = 'I'; + public function __construct() { $this->data = new Collection(); } @@ -140,7 +150,9 @@ class Label implements View $barcode2DTarget = $asset->serial; break; case 'location': - $barcode2DTarget = route('locations.show', $asset->location_id); + $barcode2DTarget = $asset->location_id + ? route('locations.show', $asset->location_id) + : null; break; case 'hardware_id': default: @@ -184,16 +196,34 @@ class Label implements View // We'll set the label to an empty string since we // injected the label into the value field above. $previous['label'] = ''; - return $previous; }); return $toAdd ? $myFields->push($toAdd) : $myFields; }, new Collection()); - $assetData->put('fields', $fields->take($template->getSupportFields())); + $emptyRowsCount = $settings->label2_empty_row_count; + if($emptyRowsCount) { + // Create empty rows + $emptyRows = collect(range(1, $emptyRowsCount))->map(function () { + return [ + 'label' => '', + 'value' => '', + 'dataSource' => null, + ]; + }); + + // Prepend empty rows to the existing fields + $fieldsWithEmpty = $emptyRows->merge($fields); + + $assetData->put('fields', $fieldsWithEmpty->take($template->getSupportFields())); + return $assetData; + } + else{ + $assetData->put('fields', $fields->take($template->getSupportFields())); + return $assetData; + } - return $assetData; }); if ($template instanceof Sheet) { @@ -202,7 +232,7 @@ class Label implements View $template->writeAll($pdf, $data); $filename = $assets->count() > 1 ? 'assets.pdf' : $assets->first()->asset_tag.'.pdf'; - $pdf->Output($filename, 'I'); + $pdf->Output($filename, $this->destination); } /** diff --git a/composer.json b/composer.json index 76d0923e1d..f7b8641756 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,9 @@ "doctrine/dbal": "^3.1", "doctrine/instantiator": "^1.3", "eduardokum/laravel-mail-auto-embed": "^2.0", - "enshrined/svg-sanitize": "^0.15.0", + "enshrined/svg-sanitize": "^0.22.0", "erusev/parsedown": "^1.7", + "fakerphp/faker": "^1.24", "guzzlehttp/guzzle": "^7.0.1", "intervention/image": "^2.5", "javiereguiluz/easyslugger": "^1.0", @@ -82,8 +83,8 @@ "ext-exif": "*" }, "require-dev": { - "fakerphp/faker": "^1.16", "larastan/larastan": "^2.9", + "laravel/telescope": "^5.11", "mockery/mockery": "^1.4", "nunomaduro/phpinsights": "^2.11", "php-mock/php-mock-phpunit": "^2.10", @@ -95,7 +96,8 @@ "extra": { "laravel": { "dont-discover": [ - "rollbar/rollbar-laravel" + "rollbar/rollbar-laravel", + "laravel/telescope" ] } }, diff --git a/composer.lock b/composer.lock index 63617c5bcf..788e74282f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "87742cc73d186f6f47627ad498cf0fe5", + "content-hash": "41f2c8e1296de21aaf82d4b1bfbc1800", "packages": [ { "name": "alek13/slack", @@ -190,16 +190,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.337.2", + "version": "3.351.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "f885dd803a257da9d54e72a4750bba73e1196aee" + "reference": "9506d7fdb3cb84f8d7b175c594db9993264814be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f885dd803a257da9d54e72a4750bba73e1196aee", - "reference": "f885dd803a257da9d54e72a4750bba73e1196aee", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9506d7fdb3cb84f8d7b175c594db9993264814be", + "reference": "9506d7fdb3cb84f8d7b175c594db9993264814be", "shasum": "" }, "require": { @@ -207,31 +207,30 @@ "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", - "mtdowling/jmespath.php": "^2.6", - "php": ">=7.2.5", - "psr/http-message": "^1.0 || ^2.0" + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", - "composer/composer": "^1.10.22", + "composer/composer": "^2.7.8", "dms/phpunit-arraysubset-asserts": "^0.4.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", "ext-pcntl": "*", "ext-sockets": "*", - "nette/neon": "^2.3", - "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", - "sebastian/comparator": "^1.2.3 || ^4.0", - "yoast/phpunit-polyfills": "^1.0" + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "symfony/filesystem": "^v6.4.0 || ^v7.1.0", + "yoast/phpunit-polyfills": "^2.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -280,11 +279,11 @@ "sdk" ], "support": { - "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.337.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.351.7" }, - "time": "2025-01-17T19:10:04+00:00" + "time": "2025-07-25T18:06:34+00:00" }, { "name": "bacon/bacon-qr-code", @@ -342,30 +341,30 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.10", + "version": "v3.16.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "56b9bd235e3fe62e250124804009ce5bab97cc63" + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/56b9bd235e3fe62e250124804009ce5bab97cc63", - "reference": "56b9bd235e3fe62e250124804009ce5bab97cc63", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23", + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23", "shasum": "" }, "require": { - "illuminate/routing": "^9|^10|^11", - "illuminate/session": "^9|^10|^11", - "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.23.0", - "php": "^8.0", + "illuminate/routing": "^9|^10|^11|^12", + "illuminate/session": "^9|^10|^11|^12", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1", + "php-debugbar/php-debugbar": "~2.2.0", "symfony/finder": "^6|^7" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7|^8|^9", - "phpunit/phpunit": "^9.6|^10.5", + "orchestra/testbench-dusk": "^7|^8|^9|^10", + "phpunit/phpunit": "^9.5.10|^10|^11", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", @@ -379,7 +378,7 @@ ] }, "branch-alias": { - "dev-master": "3.14-dev" + "dev-master": "3.16-dev" } }, "autoload": { @@ -404,13 +403,14 @@ "keywords": [ "debug", "debugbar", + "dev", "laravel", "profiler", "webprofiler" ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.10" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0" }, "funding": [ { @@ -422,7 +422,7 @@ "type": "github" } ], - "time": "2024-12-23T10:10:42+00:00" + "time": "2025-07-14T11:56:43+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -503,16 +503,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -521,7 +521,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -551,7 +551,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -559,7 +559,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -923,36 +923,39 @@ }, { "name": "doctrine/dbal", - "version": "3.9.4", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" + "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214", + "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1|^2", "php": "^7.4 || ^8.0", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, + "conflict": { + "doctrine/cache": "< 1.11" + }, "require-dev": { - "doctrine/coding-standard": "12.0.0", + "doctrine/cache": "^1.11|^2.0", + "doctrine/coding-standard": "13.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "2.1.17", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.22", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.2", + "phpunit/phpunit": "9.6.23", + "slevomat/coding-standard": "8.16.2", + "squizlabs/php_codesniffer": "3.13.1", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/console": "^4.4|^5.4|^6.0|^7.0" }, @@ -1014,7 +1017,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.4" + "source": "https://github.com/doctrine/dbal/tree/3.10.0" }, "funding": [ { @@ -1030,30 +1033,33 @@ "type": "tidelift" } ], - "time": "2025-01-16T08:28:55+00:00" + "time": "2025-07-10T21:11:04+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -1073,9 +1079,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/event-manager", @@ -1535,30 +1541,30 @@ }, { "name": "eduardokum/laravel-mail-auto-embed", - "version": "2.11", + "version": "2.12", "source": { "type": "git", "url": "https://github.com/eduardokum/laravel-mail-auto-embed.git", - "reference": "ee17be8f4a221593190ca949a1fb036c6884fc2c" + "reference": "ba5438aff4b4a78c4af101acaf69cad6577f2775" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/eduardokum/laravel-mail-auto-embed/zipball/ee17be8f4a221593190ca949a1fb036c6884fc2c", - "reference": "ee17be8f4a221593190ca949a1fb036c6884fc2c", + "url": "https://api.github.com/repos/eduardokum/laravel-mail-auto-embed/zipball/ba5438aff4b4a78c4af101acaf69cad6577f2775", + "reference": "ba5438aff4b4a78c4af101acaf69cad6577f2775", "shasum": "" }, "require": { "ext-curl": "*", "ext-dom": "*", "ext-fileinfo": "*", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/mail": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/mail": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "masterminds/html5": "^2.7", - "php": "^7.2|^8.0" + "php": "^7.2|^8.0|^8.1|^8.2" }, "require-dev": { - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", "phpunit/phpunit": "^8.5.30|^9.0|^10.0|^11.0", "squizlabs/php_codesniffer": "^3.5" }, @@ -1593,7 +1599,7 @@ ], "support": { "issues": "https://github.com/eduardokum/laravel-mail-auto-embed/issues", - "source": "https://github.com/eduardokum/laravel-mail-auto-embed/tree/2.11" + "source": "https://github.com/eduardokum/laravel-mail-auto-embed/tree/2.12" }, "funding": [ { @@ -1601,20 +1607,20 @@ "type": "github" } ], - "time": "2024-03-12T22:10:05+00:00" + "time": "2025-03-20T16:02:45+00:00" }, { "name": "egulias/email-validator", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b115554301161fa21467629f1e1391c1936de517" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", - "reference": "b115554301161fa21467629f1e1391c1936de517", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { @@ -1660,7 +1666,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -1668,26 +1674,26 @@ "type": "github" } ], - "time": "2024-12-27T00:36:43+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { "name": "enshrined/svg-sanitize", - "version": "0.15.4", + "version": "0.22.0", "source": { "type": "git", "url": "https://github.com/darylldoyle/svg-sanitizer.git", - "reference": "e50b83a2f1f296ca61394fe88fbfe3e896a84cf4" + "reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/e50b83a2f1f296ca61394fe88fbfe3e896a84cf4", - "reference": "e50b83a2f1f296ca61394fe88fbfe3e896a84cf4", + "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/0afa95ea74be155a7bcd6c6fb60c276c39984500", + "reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^6.5 || ^8.5" @@ -1711,9 +1717,9 @@ "description": "An SVG sanitizer for PHP", "support": { "issues": "https://github.com/darylldoyle/svg-sanitizer/issues", - "source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.15.4" + "source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.22.0" }, - "time": "2022-02-21T09:13:59+00:00" + "time": "2025-08-12T10:13:48+00:00" }, { "name": "erusev/parsedown", @@ -1766,17 +1772,80 @@ "time": "2019-12-30T22:54:17+00:00" }, { - "name": "filp/whoops", - "version": "2.16.0", + "name": "fakerphp/faker", + "version": "v1.24.1", "source": { "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", - "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.3", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "59a123a3d459c5a23055802237cb317f609867e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5", + "reference": "59a123a3d459c5a23055802237cb317f609867e5", "shasum": "" }, "require": { @@ -1826,7 +1895,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.16.0" + "source": "https://github.com/filp/whoops/tree/2.18.3" }, "funding": [ { @@ -1834,20 +1903,20 @@ "type": "github" } ], - "time": "2024-09-25T12:00:00+00:00" + "time": "2025-06-16T00:02:10+00:00" }, { "name": "firebase/php-jwt", - "version": "v6.10.2", + "version": "v6.11.1", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b" + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b", - "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "shasum": "" }, "require": { @@ -1895,9 +1964,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.2" + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" }, - "time": "2024-11-24T11:22:49+00:00" + "time": "2025-04-09T20:32:01+00:00" }, { "name": "fruitcake/php-cors", @@ -2034,16 +2103,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { @@ -2140,7 +2209,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -2156,20 +2225,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.4", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { @@ -2223,7 +2292,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" + "source": "https://github.com/guzzle/promises/tree/2.2.0" }, "funding": [ { @@ -2239,20 +2308,20 @@ "type": "tidelift" } ], - "time": "2024-10-17T10:06:22+00:00" + "time": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -2339,7 +2408,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.1" }, "funding": [ { @@ -2355,20 +2424,20 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.3", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c" + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/ecea8feef63bd4fef1f037ecb288386999ecc11c", - "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", "shasum": "" }, "require": { @@ -2425,7 +2494,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.3" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" }, "funding": [ { @@ -2441,7 +2510,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T19:50:20+00:00" + "time": "2025-02-03T10:55:03+00:00" }, { "name": "intervention/image", @@ -2620,27 +2689,27 @@ }, { "name": "laravel-notification-channels/microsoft-teams", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/laravel-notification-channels/microsoft-teams.git", - "reference": "41d054c5f603ca10951144a87eb8e9652307ce1b" + "reference": "e19c029525b54728fe978afa6bead6c8f33a842f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel-notification-channels/microsoft-teams/zipball/41d054c5f603ca10951144a87eb8e9652307ce1b", - "reference": "41d054c5f603ca10951144a87eb8e9652307ce1b", + "url": "https://api.github.com/repos/laravel-notification-channels/microsoft-teams/zipball/e19c029525b54728fe978afa6bead6c8f33a842f", + "reference": "e19c029525b54728fe978afa6bead6c8f33a842f", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^6.3 || ^7.0", - "illuminate/notifications": "~5.5 || ~6.0 || ~7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", - "illuminate/support": "~5.5 || ~6.0 || ~7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/notifications": "~5.5 || ~6.0 || ~7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "illuminate/support": "~5.5 || ~6.0 || ~7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", "php": ">=7.2" }, "require-dev": { "mockery/mockery": "^1.2.3", - "phpunit/phpunit": "^8.0 || ^9.5 || ^10.5" + "phpunit/phpunit": "^8.0 || ^9.5 || ^10.5 || ^11.0 || ^12.0" }, "type": "library", "extra": { @@ -2671,22 +2740,22 @@ "homepage": "https://github.com/laravel-notification-channels/microsoft-teams", "support": { "issues": "https://github.com/laravel-notification-channels/microsoft-teams/issues", - "source": "https://github.com/laravel-notification-channels/microsoft-teams/tree/1.2.0" + "source": "https://github.com/laravel-notification-channels/microsoft-teams/tree/1.3.0" }, - "time": "2024-03-11T15:07:16+00:00" + "time": "2025-02-25T06:41:39+00:00" }, { "name": "laravel/framework", - "version": "v11.44.1", + "version": "v11.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d" + "reference": "b09ba32795b8e71df10856a2694706663984a239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", - "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", + "url": "https://api.github.com/repos/laravel/framework/zipball/b09ba32795b8e71df10856a2694706663984a239", + "reference": "b09ba32795b8e71df10856a2694706663984a239", "shasum": "" }, "require": { @@ -2707,7 +2776,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", @@ -2794,7 +2863,7 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.11.2", + "orchestra/testbench-core": "^9.13.2", "pda/pheanstalk": "^5.0.6", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -2888,24 +2957,24 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-05T15:34:10+00:00" + "time": "2025-06-03T14:01:40+00:00" }, { "name": "laravel/helpers", - "version": "v1.7.1", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35" + "reference": "672d79d5b5f65dc821e57783fa11f22c4d762d70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/f28907033d7edf8a0525cfb781ab30ce6d531c35", - "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35", + "url": "https://api.github.com/repos/laravel/helpers/zipball/672d79d5b5f65dc821e57783fa11f22c4d762d70", + "reference": "672d79d5b5f65dc821e57783fa11f22c4d762d70", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^7.2.0|^8.0" }, "require-dev": { @@ -2943,36 +3012,36 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.7.1" + "source": "https://github.com/laravel/helpers/tree/v1.7.2" }, - "time": "2024-11-26T14:56:25+00:00" + "time": "2025-01-24T15:41:25+00:00" }, { "name": "laravel/passport", - "version": "v12.4.0", + "version": "v12.4.2", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "b06a413cb18d07123ced88ba8caa432d40e3bb8c" + "reference": "65a885607b62d361aedaeb10a946bc6b5a954262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/b06a413cb18d07123ced88ba8caa432d40e3bb8c", - "reference": "b06a413cb18d07123ced88ba8caa432d40e3bb8c", + "url": "https://api.github.com/repos/laravel/passport/zipball/65a885607b62d361aedaeb10a946bc6b5a954262", + "reference": "65a885607b62d361aedaeb10a946bc6b5a954262", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4", - "illuminate/auth": "^9.21|^10.0|^11.0", - "illuminate/console": "^9.21|^10.0|^11.0", - "illuminate/container": "^9.21|^10.0|^11.0", - "illuminate/contracts": "^9.21|^10.0|^11.0", - "illuminate/cookie": "^9.21|^10.0|^11.0", - "illuminate/database": "^9.21|^10.0|^11.0", - "illuminate/encryption": "^9.21|^10.0|^11.0", - "illuminate/http": "^9.21|^10.0|^11.0", - "illuminate/support": "^9.21|^10.0|^11.0", + "illuminate/auth": "^9.21|^10.0|^11.0|^12.0", + "illuminate/console": "^9.21|^10.0|^11.0|^12.0", + "illuminate/container": "^9.21|^10.0|^11.0|^12.0", + "illuminate/contracts": "^9.21|^10.0|^11.0|^12.0", + "illuminate/cookie": "^9.21|^10.0|^11.0|^12.0", + "illuminate/database": "^9.21|^10.0|^11.0|^12.0", + "illuminate/encryption": "^9.21|^10.0|^11.0|^12.0", + "illuminate/http": "^9.21|^10.0|^11.0|^12.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0", "lcobucci/jwt": "^4.3|^5.0", "league/oauth2-server": "^8.5.3", "nyholm/psr7": "^1.5", @@ -2983,9 +3052,9 @@ }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.35|^8.14|^9.0", + "orchestra/testbench": "^7.35|^8.14|^9.0|^10.0", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3|^10.5" + "phpunit/phpunit": "^9.3|^10.5|^11.5" }, "type": "library", "extra": { @@ -3021,20 +3090,20 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2025-01-13T17:40:20+00:00" + "time": "2025-02-12T16:11:33+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.3", + "version": "v0.3.6", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea" + "reference": "86a8b692e8661d0fb308cec64f3d176821323077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", - "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", + "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077", "shasum": "" }, "require": { @@ -3048,7 +3117,7 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0", + "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", @@ -3078,31 +3147,31 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.3" + "source": "https://github.com/laravel/prompts/tree/v0.3.6" }, - "time": "2024-12-30T15:53:31+00:00" + "time": "2025-07-07T14:17:42+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.1", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8" + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8", - "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "illuminate/support": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0|^12.0", "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36", + "pestphp/pest": "^2.36|^3.0", "phpstan/phpstan": "^2.0", "symfony/var-dumper": "^6.2.0|^7.0.0" }, @@ -3141,34 +3210,34 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-12-16T15:26:28+00:00" + "time": "2025-03-19T13:51:03+00:00" }, { "name": "laravel/slack-notification-channel", - "version": "v3.4.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/laravel/slack-notification-channel.git", - "reference": "282d52d70195283eb1b92e59d7bdc2d525361f4b" + "reference": "642677a57490eebccb7e9fb666f5a5379c6e3459" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/282d52d70195283eb1b92e59d7bdc2d525361f4b", - "reference": "282d52d70195283eb1b92e59d7bdc2d525361f4b", + "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/642677a57490eebccb7e9fb666f5a5379c6e3459", + "reference": "642677a57490eebccb7e9fb666f5a5379c6e3459", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.0", - "illuminate/http": "^9.0|^10.0|^11.0", - "illuminate/notifications": "^9.0|^10.0|^11.0", - "illuminate/support": "^9.0|^10.0|^11.0", + "illuminate/http": "^9.0|^10.0|^11.0|^12.0", + "illuminate/notifications": "^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^9.0|^10.0|^11.0|^12.0", "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.0|^8.0|^9.0", + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.0|^10.4" + "phpunit/phpunit": "^9.0|^10.4|^11.5" }, "type": "library", "extra": { @@ -3204,40 +3273,40 @@ ], "support": { "issues": "https://github.com/laravel/slack-notification-channel/issues", - "source": "https://github.com/laravel/slack-notification-channel/tree/v3.4.2" + "source": "https://github.com/laravel/slack-notification-channel/tree/v3.6.0" }, - "time": "2024-11-29T14:59:32+00:00" + "time": "2025-06-26T16:51:38+00:00" }, { "name": "laravel/socialite", - "version": "v5.16.1", + "version": "v5.23.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71" + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", - "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", + "url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.0|^9.3|^10.4" + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", + "phpstan/phpstan": "^1.12.23", + "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5" }, "type": "library", "extra": { @@ -3278,26 +3347,26 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-12-11T16:43:51+00:00" + "time": "2025-07-23T14:16:08+00:00" }, { "name": "laravel/tinker", - "version": "v2.10.0", + "version": "v2.10.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5" + "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5", - "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5", + "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3", + "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.11.1|^0.12.0", "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" @@ -3305,10 +3374,10 @@ "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.5.8|^9.3.3" + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." }, "type": "library", "extra": { @@ -3342,35 +3411,35 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.0" + "source": "https://github.com/laravel/tinker/tree/v2.10.1" }, - "time": "2024-09-23T13:32:56+00:00" + "time": "2025-01-27T14:24:01+00:00" }, { "name": "laravel/ui", - "version": "v4.6.0", + "version": "v4.6.1", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93" + "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/a34609b15ae0c0512a0cf47a21695a2729cb7f93", - "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93", + "url": "https://api.github.com/repos/laravel/ui/zipball/7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", + "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", "shasum": "" }, "require": { - "illuminate/console": "^9.21|^10.0|^11.0", - "illuminate/filesystem": "^9.21|^10.0|^11.0", - "illuminate/support": "^9.21|^10.0|^11.0", - "illuminate/validation": "^9.21|^10.0|^11.0", + "illuminate/console": "^9.21|^10.0|^11.0|^12.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0", + "illuminate/validation": "^9.21|^10.0|^11.0|^12.0", "php": "^8.0", "symfony/console": "^6.0|^7.0" }, "require-dev": { - "orchestra/testbench": "^7.35|^8.15|^9.0", - "phpunit/phpunit": "^9.3|^10.4|^11.0" + "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0", + "phpunit/phpunit": "^9.3|^10.4|^11.5" }, "type": "library", "extra": { @@ -3405,9 +3474,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.6.0" + "source": "https://github.com/laravel/ui/tree/v4.6.1" }, - "time": "2024-11-21T15:06:41+00:00" + "time": "2025-01-28T15:15:29+00:00" }, { "name": "laravelcollective/html", @@ -3484,16 +3553,16 @@ }, { "name": "lcobucci/jwt", - "version": "5.4.2", + "version": "5.5.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712" + "reference": "a835af59b030d3f2967725697cf88300f579088e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/ea1ce71cbf9741e445a5914e2f67cdbb484ff712", - "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", + "reference": "a835af59b030d3f2967725697cf88300f579088e", "shasum": "" }, "require": { @@ -3541,7 +3610,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.4.2" + "source": "https://github.com/lcobucci/jwt/tree/5.5.0" }, "funding": [ { @@ -3553,20 +3622,20 @@ "type": "patreon" } ], - "time": "2024-11-07T12:54:35+00:00" + "time": "2025-01-26T21:29:45+00:00" }, { "name": "league/commonmark", - "version": "2.6.1", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -3595,7 +3664,7 @@ "symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -3603,7 +3672,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -3660,7 +3729,7 @@ "type": "tidelift" } ], - "time": "2024-12-29T14:10:59+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -3746,16 +3815,16 @@ }, { "name": "league/csv", - "version": "9.21.0", + "version": "9.24.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "72196d11ebba22d868954cb39c0c7346207430cc" + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/72196d11ebba22d868954cb39c0c7346207430cc", - "reference": "72196d11ebba22d868954cb39c0c7346207430cc", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/e0221a3f16aa2a823047d59fab5809d552e29bc8", + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8", "shasum": "" }, "require": { @@ -3765,19 +3834,23 @@ "require-dev": { "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.64.0", - "phpbench/phpbench": "^1.3.1", - "phpstan/phpstan": "^1.12.11", + "friendsofphp/php-cs-fixer": "^3.75.0", + "phpbench/phpbench": "^1.4.1", + "phpstan/phpstan": "^1.12.27", "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.1", - "phpstan/phpstan-strict-rules": "^1.6.1", - "phpunit/phpunit": "^10.5.16 || ^11.4.3", - "symfony/var-dumper": "^6.4.8 || ^7.1.8" + "phpstan/phpstan-phpunit": "^1.4.2", + "phpstan/phpstan-strict-rules": "^1.6.2", + "phpunit/phpunit": "^10.5.16 || ^11.5.22", + "symfony/var-dumper": "^6.4.8 || ^7.3.0" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters", - "ext-mbstring": "Needed to ease transcoding CSV using mb stream filters" + "ext-mbstring": "Needed to ease transcoding CSV using mb stream filters", + "ext-mysqli": "Requiered to use the package with the MySQLi extension", + "ext-pdo": "Required to use the package with the PDO extension", + "ext-pgsql": "Requiered to use the package with the PgSQL extension", + "ext-sqlite3": "Required to use the package with the SQLite3 extension" }, "type": "library", "extra": { @@ -3790,7 +3863,7 @@ "src/functions_include.php" ], "psr-4": { - "League\\Csv\\": "src/" + "League\\Csv\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3829,24 +3902,24 @@ "type": "github" } ], - "time": "2025-01-08T19:27:58+00:00" + "time": "2025-06-25T14:53:51+00:00" }, { "name": "league/event", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/event.git", - "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119" + "reference": "062ebb450efbe9a09bc2478e89b7c933875b0935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119", - "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119", + "url": "https://api.github.com/repos/thephpleague/event/zipball/062ebb450efbe9a09bc2478e89b7c933875b0935", + "reference": "062ebb450efbe9a09bc2478e89b7c933875b0935", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.1.0" }, "require-dev": { "henrikbjorn/phpspec-code-coverage": "~1.0.1", @@ -3881,22 +3954,22 @@ ], "support": { "issues": "https://github.com/thephpleague/event/issues", - "source": "https://github.com/thephpleague/event/tree/master" + "source": "https://github.com/thephpleague/event/tree/2.3.0" }, - "time": "2018-11-26T11:52:41+00:00" + "time": "2025-03-14T19:51:10+00:00" }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", "shasum": "" }, "require": { @@ -3920,13 +3993,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -3964,9 +4037,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2025-06-25T13:29:59+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -4025,16 +4098,16 @@ }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", "shasum": "" }, "require": { @@ -4068,9 +4141,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2025-05-21T10:34:19+00:00" }, { "name": "league/mime-type-detection", @@ -4468,23 +4541,23 @@ }, { "name": "livewire/livewire", - "version": "v3.5.18", + "version": "v3.6.4", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "62f0fa6b340a467c25baa590a567d9a134b357da" + "reference": "ef04be759da41b14d2d129e670533180a44987dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/62f0fa6b340a467c25baa590a567d9a134b357da", - "reference": "62f0fa6b340a467c25baa590a567d9a134b357da", + "url": "https://api.github.com/repos/livewire/livewire/zipball/ef04be759da41b14d2d129e670533180a44987dc", + "reference": "ef04be759da41b14d2d129e670533180a44987dc", "shasum": "" }, "require": { - "illuminate/database": "^10.0|^11.0", - "illuminate/routing": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "illuminate/validation": "^10.0|^11.0", + "illuminate/database": "^10.0|^11.0|^12.0", + "illuminate/routing": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/validation": "^10.0|^11.0|^12.0", "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", @@ -4493,11 +4566,11 @@ }, "require-dev": { "calebporzio/sushi": "^2.1", - "laravel/framework": "^10.15.0|^11.0", + "laravel/framework": "^10.15.0|^11.0|^12.0", "mockery/mockery": "^1.3.1", - "orchestra/testbench": "^8.21.0|^9.0", - "orchestra/testbench-dusk": "^8.24|^9.1", - "phpunit/phpunit": "^10.4", + "orchestra/testbench": "^8.21.0|^9.0|^10.0", + "orchestra/testbench-dusk": "^8.24|^9.1|^10.0", + "phpunit/phpunit": "^10.4|^11.5", "psy/psysh": "^0.11.22|^0.12" }, "type": "library", @@ -4532,7 +4605,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.18" + "source": "https://github.com/livewire/livewire/tree/v3.6.4" }, "funding": [ { @@ -4540,20 +4613,20 @@ "type": "github" } ], - "time": "2024-12-23T15:05:02+00:00" + "time": "2025-07-17T05:12:15+00:00" }, { "name": "masterminds/html5", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -4605,77 +4678,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2024-03-31T07:05:07+00:00" - }, - { - "name": "maximebf/debugbar", - "version": "v1.23.5", - "source": { - "type": "git", - "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/eeabd61a1f19ba5dcd5ac4585a477130ee03ce25", - "reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25", - "shasum": "" - }, - "require": { - "php": "^7.2|^8", - "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6|^7" - }, - "require-dev": { - "dbrekelmans/bdi": "^1", - "phpunit/phpunit": "^8|^9", - "symfony/panther": "^1|^2.1", - "twig/twig": "^1.38|^2.7|^3.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.23-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "support": { - "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v1.23.5" - }, - "time": "2024-12-15T19:20:42+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "monolog/monolog", @@ -4889,16 +4894,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.4", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", - "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", "shasum": "" }, "require": { @@ -4906,9 +4911,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -4916,14 +4921,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.57.2", + "friendsofphp/php-cs-fixer": "^3.75.0", "kylekatarnls/multi-tester": "^2.5.3", - "ondrejmirtes/better-reflection": "^6.25.0.4", "phpmd/phpmd": "^2.15.0", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan": "^1.11.2", - "phpunit/phpunit": "^10.5.20", - "squizlabs/php_codesniffer": "^3.9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^10.5.46", + "squizlabs/php_codesniffer": "^3.13.0" }, "bin": [ "bin/carbon" @@ -4974,8 +4978,8 @@ ], "support": { "docs": "https://carbon.nesbot.com/docs", - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { @@ -4991,7 +4995,7 @@ "type": "tidelift" } ], - "time": "2024-12-27T09:25:35+00:00" + "time": "2025-06-21T15:19:35+00:00" }, { "name": "nette/schema", @@ -5057,16 +5061,16 @@ }, { "name": "nette/utils", - "version": "v4.0.5", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", "shasum": "" }, "require": { @@ -5137,31 +5141,33 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.5" + "source": "https://github.com/nette/utils/tree/v4.0.7" }, - "time": "2024-08-07T15:39:19+00:00" + "time": "2025-06-03T04:55:08+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.4", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -5169,7 +5175,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5193,9 +5199,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2024-09-29T15:01:53+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "nunomaduro/collision", @@ -5296,31 +5302,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5363,7 +5369,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -5379,7 +5385,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "nyholm/psr7", @@ -5521,16 +5527,16 @@ }, { "name": "onelogin/php-saml", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "91c1a3b3e2390aba9facc64ba81b407a50962ebb" + "reference": "03bd22f5e028a8aa3b5fec9864bb8984a55df899" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/91c1a3b3e2390aba9facc64ba81b407a50962ebb", - "reference": "91c1a3b3e2390aba9facc64ba81b407a50962ebb", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/03bd22f5e028a8aa3b5fec9864bb8984a55df899", + "reference": "03bd22f5e028a8aa3b5fec9864bb8984a55df899", "shasum": "" }, "require": { @@ -5580,7 +5586,7 @@ "type": "github" } ], - "time": "2024-05-30T15:14:26+00:00" + "time": "2025-05-25T14:25:06+00:00" }, { "name": "onnov/detect-encoding", @@ -5648,21 +5654,21 @@ }, { "name": "osa-eg/laravel-teams-notification", - "version": "v2.1.2", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/osa-eg/laravel-teams-notification.git", - "reference": "76173689930aca92b5174a3b102e705279192c0f" + "reference": "1b36cdfe422881b810f31eba4acf61f72080124e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/osa-eg/laravel-teams-notification/zipball/76173689930aca92b5174a3b102e705279192c0f", - "reference": "76173689930aca92b5174a3b102e705279192c0f", + "url": "https://api.github.com/repos/osa-eg/laravel-teams-notification/zipball/1b36cdfe422881b810f31eba4acf61f72080124e", + "reference": "1b36cdfe422881b810f31eba4acf61f72080124e", "shasum": "" }, "require": { "guzzlehttp/guzzle": ">=6.5", - "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", "monolog/monolog": ">=1.0", "php": ">=7.0" }, @@ -5707,9 +5713,9 @@ ], "support": { "issues": "https://github.com/osa-eg/laravel-teams-notification/issues", - "source": "https://github.com/osa-eg/laravel-teams-notification/tree/v2.1.2" + "source": "https://github.com/osa-eg/laravel-teams-notification/tree/v2.1.4" }, - "time": "2024-09-23T05:24:48+00:00" + "time": "2025-02-25T08:49:16+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -6004,6 +6010,79 @@ }, "time": "2024-04-08T12:52:34+00:00" }, + { + "name": "php-debugbar/php-debugbar", + "version": "v2.2.4", + "source": { + "type": "git", + "url": "https://github.com/php-debugbar/php-debugbar.git", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "shasum": "" + }, + "require": { + "php": "^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4|^5|^6|^7" + }, + "replace": { + "maximebf/debugbar": "self.version" + }, + "require-dev": { + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/php-debugbar/php-debugbar", + "keywords": [ + "debug", + "debug bar", + "debugbar", + "dev" + ], + "support": { + "issues": "https://github.com/php-debugbar/php-debugbar/issues", + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4" + }, + "time": "2025-07-22T14:01:30+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -6059,16 +6138,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.1", + "version": "5.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", "shasum": "" }, "require": { @@ -6117,9 +6196,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" }, - "time": "2024-12-07T09:39:29+00:00" + "time": "2025-04-13T19:20:35+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -6256,16 +6335,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.43", + "version": "3.0.46", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "709ec107af3cb2f385b9617be72af8cf62441d02" + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02", - "reference": "709ec107af3cb2f385b9617be72af8cf62441d02", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", "shasum": "" }, "require": { @@ -6346,7 +6425,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.43" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" }, "funding": [ { @@ -6362,33 +6441,33 @@ "type": "tidelift" } ], - "time": "2024-12-14T21:12:59+00:00" + "time": "2025-06-26T16:29:55+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.20.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93" + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a0165c648cab6a80311c74ffc708a07bb53ecc93", - "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/35f1adb388946d92e6edab2aa2cb2b60e132ebd5", + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", + "php": "^7.4 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", - "phpstan/phpstan": "^1.9", + "phpstan/phpstan": "^2.1.13", "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, "type": "library", @@ -6430,36 +6509,36 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.20.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.22.0" }, - "time": "2024-11-19T13:12:41+00:00" + "time": "2025-04-29T14:58:06+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.33.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -6477,9 +6556,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" }, - "time": "2024-10-13T11:25:22+00:00" + "time": "2025-07-13T07:04:09+00:00" }, { "name": "pragmarx/google2fa", @@ -7133,16 +7212,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.7", + "version": "v0.12.9", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" + "reference": "1b801844becfe648985372cb4b12ad6840245ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", + "reference": "1b801844becfe648985372cb4b12ad6840245ace", "shasum": "" }, "require": { @@ -7206,9 +7285,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" }, - "time": "2024-12-10T01:58:33+00:00" + "time": "2025-06-23T02:35:06+00:00" }, { "name": "ralouphie/getallheaders", @@ -7256,16 +7335,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -7273,25 +7352,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -7329,37 +7405,26 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", - "ext-json": "*", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -7367,26 +7432,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -7421,19 +7483,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "robrichards/xmlseclibs", @@ -7479,16 +7531,16 @@ }, { "name": "rollbar/rollbar", - "version": "v4.1.1", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/rollbar/rollbar-php.git", - "reference": "146216bbfde503632c6969a7c334c8a63c98f50e" + "reference": "99eb7bd2ea1ef3be4a6e693b022911b1db1582bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rollbar/rollbar-php/zipball/146216bbfde503632c6969a7c334c8a63c98f50e", - "reference": "146216bbfde503632c6969a7c334c8a63c98f50e", + "url": "https://api.github.com/repos/rollbar/rollbar-php/zipball/99eb7bd2ea1ef3be4a6e693b022911b1db1582bf", + "reference": "99eb7bd2ea1ef3be4a6e693b022911b1db1582bf", "shasum": "" }, "require": { @@ -7536,28 +7588,28 @@ "support": { "email": "support@rollbar.com", "issues": "https://github.com/rollbar/rollbar-php/issues", - "source": "https://github.com/rollbar/rollbar-php/tree/v4.1.1" + "source": "https://github.com/rollbar/rollbar-php/tree/v4.1.3" }, - "time": "2025-03-21T19:07:30+00:00" + "time": "2025-04-17T19:11:14+00:00" }, { "name": "rollbar/rollbar-laravel", - "version": "v8.1.2", + "version": "v8.1.3", "source": { "type": "git", "url": "https://github.com/rollbar/rollbar-php-laravel.git", - "reference": "fbe96ea28601294aa7dc41cd16db62cddddeb0c2" + "reference": "d622878343a62f40c55f4157693f424c8ee69939" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rollbar/rollbar-php-laravel/zipball/fbe96ea28601294aa7dc41cd16db62cddddeb0c2", - "reference": "fbe96ea28601294aa7dc41cd16db62cddddeb0c2", + "url": "https://api.github.com/repos/rollbar/rollbar-php-laravel/zipball/d622878343a62f40c55f4157693f424c8ee69939", + "reference": "d622878343a62f40c55f4157693f424c8ee69939", "shasum": "" }, "require": { "illuminate/support": "^10.0|^11.0|^12.0", "php": "^8.1", - "rollbar/rollbar": "v4.1.1" + "rollbar/rollbar": "^v4.1.3" }, "require-dev": { "mockery/mockery": "^1", @@ -7608,22 +7660,22 @@ ], "support": { "issues": "https://github.com/rollbar/rollbar-php-laravel/issues", - "source": "https://github.com/rollbar/rollbar-php-laravel/tree/v8.1.2" + "source": "https://github.com/rollbar/rollbar-php-laravel/tree/v8.1.3" }, - "time": "2025-03-21T19:26:39+00:00" + "time": "2025-05-02T20:10:54+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "v8.7.0", + "version": "v8.9.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", - "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", "shasum": "" }, "require": { @@ -7631,7 +7683,8 @@ "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -7673,9 +7726,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" }, - "time": "2024-10-27T17:38:32+00:00" + "time": "2025-07-11T13:20:48+00:00" }, { "name": "sebastian/comparator", @@ -7964,16 +8017,16 @@ }, { "name": "spatie/backtrace", - "version": "1.7.1", + "version": "1.7.4", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b" + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", "shasum": "" }, "require": { @@ -8011,7 +8064,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.1" + "source": "https://github.com/spatie/backtrace/tree/1.7.4" }, "funding": [ { @@ -8023,20 +8076,20 @@ "type": "other" } ], - "time": "2024-12-02T13:28:15+00:00" + "time": "2025-05-08T15:41:09+00:00" }, { "name": "spatie/db-dumper", - "version": "3.7.1", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016" + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", - "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/91e1fd4dc000aefc9753cda2da37069fc996baee", + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee", "shasum": "" }, "require": { @@ -8074,7 +8127,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.7.1" + "source": "https://github.com/spatie/db-dumper/tree/3.8.0" }, "funding": [ { @@ -8086,34 +8139,34 @@ "type": "github" } ], - "time": "2024-11-18T14:54:31+00:00" + "time": "2025-02-14T15:04:22+00:00" }, { "name": "spatie/error-solutions", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", "shasum": "" }, "require": { "php": "^8.0" }, "require-dev": { - "illuminate/broadcasting": "^10.0|^11.0", - "illuminate/cache": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "livewire/livewire": "^2.11|^3.3.5", + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", "openai-php/client": "^0.10.1", - "orchestra/testbench": "^7.0|8.22.3|^9.0", - "pestphp/pest": "^2.20", - "phpstan/phpstan": "^1.11", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", "psr/simple-cache": "^3.0", "psr/simple-cache-implementation": "^3.0", "spatie/ray": "^1.28", @@ -8152,7 +8205,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.1.2" + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" }, "funding": [ { @@ -8160,24 +8213,24 @@ "type": "github" } ], - "time": "2024-12-11T09:51:56+00:00" + "time": "2025-02-14T12:29:50+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272" + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^8.0", "spatie/backtrace": "^1.6.1", "symfony/http-foundation": "^5.2|^6.0|^7.0", @@ -8221,7 +8274,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.10.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" }, "funding": [ { @@ -8229,20 +8282,20 @@ "type": "github" } ], - "time": "2024-12-02T14:30:06+00:00" + "time": "2025-02-14T13:42:06+00:00" }, { "name": "spatie/ignition", - "version": "1.15.0", + "version": "1.15.1", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2" + "reference": "31f314153020aee5af3537e507fef892ffbf8c85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/e3a68e137371e1eb9edc7f78ffa733f3b98991d2", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2", + "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85", "shasum": "" }, "require": { @@ -8255,7 +8308,7 @@ "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52|^10.0|^11.0", + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", "mockery/mockery": "^1.4", "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", @@ -8312,7 +8365,7 @@ "type": "github" } ], - "time": "2024-06-12T14:55:22+00:00" + "time": "2025-02-21T14:31:39+00:00" }, { "name": "spatie/laravel-backup", @@ -8415,23 +8468,23 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.9.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "62042df15314b829d0f26e02108f559018e2aad0" + "reference": "1baee07216d6748ebd3a65ba97381b051838707a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/62042df15314b829d0f26e02108f559018e2aad0", - "reference": "62042df15314b829d0f26e02108f559018e2aad0", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a", + "reference": "1baee07216d6748ebd3a65ba97381b051838707a", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0|^12.0", "php": "^8.1", "spatie/ignition": "^1.15", "symfony/console": "^6.2.3|^7.0", @@ -8440,12 +8493,12 @@ "require-dev": { "livewire/livewire": "^2.11|^3.3.5", "mockery/mockery": "^1.5.1", - "openai-php/client": "^0.8.1", - "orchestra/testbench": "8.22.3|^9.0", - "pestphp/pest": "^2.34", + "openai-php/client": "^0.8.1|^0.10", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.34|^3.7", "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan-deprecation-rules": "^1.1.1", - "phpstan/phpstan-phpunit": "^1.3.16", + "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", + "phpstan/phpstan-phpunit": "^1.3.16|^2.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -8502,31 +8555,32 @@ "type": "github" } ], - "time": "2024-12-02T08:43:31+00:00" + "time": "2025-02-20T13:13:55+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.18.2", + "version": "1.92.7", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "d41c44a7eab604c3eb0cad93210612d4c1429c20" + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d41c44a7eab604c3eb0cad93210612d4c1429c20", - "reference": "d41c44a7eab604c3eb0cad93210612d4c1429c20", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", "shasum": "" }, "require": { - "illuminate/contracts": "^9.28|^10.0|^11.0", + "illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7|^8.0|^9.0", - "pestphp/pest": "^1.22|^2", - "phpunit/phpunit": "^9.5.24|^10.5", + "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", + "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.5.24|^10.5|^11.5", "spatie/pest-plugin-test-time": "^1.1|^2.2" }, "type": "library", @@ -8554,7 +8608,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.18.2" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" }, "funding": [ { @@ -8562,24 +8616,24 @@ "type": "github" } ], - "time": "2025-01-20T14:14:17+00:00" + "time": "2025-07-17T15:46:43+00:00" }, { "name": "spatie/laravel-signal-aware-command", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-signal-aware-command.git", - "reference": "49a5e671c3a3fd992187a777d01385fc6a84759d" + "reference": "8e8a226ed7fb45302294878ef339e75ffa9a878d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/49a5e671c3a3fd992187a777d01385fc6a84759d", - "reference": "49a5e671c3a3fd992187a777d01385fc6a84759d", + "url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/8e8a226ed7fb45302294878ef339e75ffa9a878d", + "reference": "8e8a226ed7fb45302294878ef339e75ffa9a878d", "shasum": "" }, "require": { - "illuminate/contracts": "^11.0", + "illuminate/contracts": "^11.0|^12.0", "php": "^8.2", "spatie/laravel-package-tools": "^1.4.3", "symfony/console": "^7.0" @@ -8588,8 +8642,8 @@ "brianium/paratest": "^6.2|^7.0", "ext-pcntl": "*", "nunomaduro/collision": "^5.3|^6.0|^7.0|^8.0", - "orchestra/testbench": "^9.0", - "pestphp/pest-plugin-laravel": "^1.3|^2.0", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest-plugin-laravel": "^1.3|^2.0|^3.0", "phpunit/phpunit": "^9.5|^10|^11", "spatie/laravel-ray": "^1.17" }, @@ -8629,7 +8683,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-signal-aware-command/issues", - "source": "https://github.com/spatie/laravel-signal-aware-command/tree/2.0.0" + "source": "https://github.com/spatie/laravel-signal-aware-command/tree/2.1.0" }, "funding": [ { @@ -8637,7 +8691,7 @@ "type": "github" } ], - "time": "2024-02-05T13:37:25+00:00" + "time": "2025-02-14T09:55:51+00:00" }, { "name": "spatie/temporary-directory", @@ -8702,7 +8756,7 @@ }, { "name": "symfony/clock", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", @@ -8756,7 +8810,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.2.0" + "source": "https://github.com/symfony/clock/tree/v7.3.0" }, "funding": [ { @@ -8776,23 +8830,24 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -8849,7 +8904,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.3.1" }, "funding": [ { @@ -8865,7 +8920,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/css-selector", @@ -8935,16 +8990,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -8957,7 +9012,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -8982,7 +9037,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -8998,20 +9053,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/error-handler", - "version": "v7.2.1", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "6150b89186573046167796fa5f3f76601d5145f8" + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", - "reference": "6150b89186573046167796fa5f3f76601d5145f8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", "shasum": "" }, "require": { @@ -9024,9 +9079,11 @@ "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -9057,7 +9114,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.1" + "source": "https://github.com/symfony/error-handler/tree/v7.3.1" }, "funding": [ { @@ -9073,20 +9130,20 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2025-06-13T07:48:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", "shasum": "" }, "require": { @@ -9137,7 +9194,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" }, "funding": [ { @@ -9153,20 +9210,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-04-22T09:11:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -9180,7 +9237,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -9213,7 +9270,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -9229,20 +9286,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", "shasum": "" }, "require": { @@ -9277,7 +9334,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.0" }, "funding": [ { @@ -9293,20 +9350,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2024-12-30T19:00:26+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.2.2", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588" + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588", - "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", "shasum": "" }, "require": { @@ -9323,6 +9380,7 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -9355,7 +9413,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.2" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" }, "funding": [ { @@ -9371,20 +9429,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2025-06-23T15:07:14+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.2", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306" + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3c432966bd8c7ec7429663105f5a02d7e75b4306", - "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", "shasum": "" }, "require": { @@ -9392,8 +9450,8 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -9469,7 +9527,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" }, "funding": [ { @@ -9485,20 +9543,20 @@ "type": "tidelift" } ], - "time": "2024-12-31T14:59:40+00:00" + "time": "2025-06-28T08:24:55+00:00" }, { "name": "symfony/mailer", - "version": "v7.2.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", - "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", "shasum": "" }, "require": { @@ -9549,7 +9607,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.0" + "source": "https://github.com/symfony/mailer/tree/v7.3.1" }, "funding": [ { @@ -9565,20 +9623,20 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:21:05+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/mime", - "version": "v7.2.1", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", - "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", "shasum": "" }, "require": { @@ -9633,7 +9691,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.1" + "source": "https://github.com/symfony/mime/tree/v7.3.0" }, "funding": [ { @@ -9649,11 +9707,11 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2025-02-19T08:51:26+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -9712,7 +9770,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -9732,7 +9790,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -9790,7 +9848,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -9810,16 +9868,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -9873,7 +9931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -9889,11 +9947,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -9954,7 +10012,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -9974,19 +10032,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -10034,7 +10093,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -10050,20 +10109,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -10114,7 +10173,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -10130,11 +10189,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -10190,7 +10249,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -10210,7 +10269,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -10269,7 +10328,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -10289,16 +10348,16 @@ }, { "name": "symfony/process", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -10330,7 +10389,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -10346,11 +10405,11 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2025-04-17T09:11:12+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", @@ -10413,7 +10472,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.3.0" }, "funding": [ { @@ -10433,16 +10492,16 @@ }, { "name": "symfony/routing", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" + "reference": "8e213820c5fea844ecea29203d2a308019007c15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", - "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", + "reference": "8e213820c5fea844ecea29203d2a308019007c15", "shasum": "" }, "require": { @@ -10494,7 +10553,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.2.0" + "source": "https://github.com/symfony/routing/tree/v7.3.0" }, "funding": [ { @@ -10510,20 +10569,20 @@ "type": "tidelift" } ], - "time": "2024-11-25T11:08:51+00:00" + "time": "2025-05-24T20:43:28+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -10541,7 +10600,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -10577,7 +10636,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -10593,20 +10652,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", "shasum": "" }, "require": { @@ -10664,7 +10723,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.3.0" }, "funding": [ { @@ -10680,55 +10739,56 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:19:01+00:00" }, { "name": "symfony/translation", - "version": "v6.4.13", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" + "reference": "241d5ac4910d256660238a7ecf250deba4c73063" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", + "reference": "241d5ac4910d256660238a7ecf250deba4c73063", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "symfony/config": "<5.4", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.4", "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4" + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10759,7 +10819,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.13" + "source": "https://github.com/symfony/translation/tree/v7.3.1" }, "funding": [ { @@ -10775,20 +10835,20 @@ "type": "tidelift" } ], - "time": "2024-09-27T18:14:25+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -10801,7 +10861,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -10837,7 +10897,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -10853,20 +10913,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/uid", - "version": "v7.2.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2d294d0c48df244c71c105a169d0190bfb080426" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", - "reference": "2d294d0c48df244c71c105a169d0190bfb080426", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { @@ -10911,7 +10971,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.2.0" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -10927,24 +10987,25 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.2.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", - "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -10994,7 +11055,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" }, "funding": [ { @@ -11010,32 +11071,34 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:48:14+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "tabuna/breadcrumbs", - "version": "4.2.1", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/tabuna/breadcrumbs.git", - "reference": "1d9047306f67e7fcc86fc7e608f1432f247636da" + "reference": "d3b21ea43c7c157fd279d684b1f591df9d25a78b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tabuna/breadcrumbs/zipball/1d9047306f67e7fcc86fc7e608f1432f247636da", - "reference": "1d9047306f67e7fcc86fc7e608f1432f247636da", + "url": "https://api.github.com/repos/tabuna/breadcrumbs/zipball/d3b21ea43c7c157fd279d684b1f591df9d25a78b", + "reference": "d3b21ea43c7c157fd279d684b1f591df9d25a78b", "shasum": "" }, "require": { "ext-json": "*", - "laravel/framework": "^10.0|^11.0", + "laravel/framework": "^10.0|^11.0|^12.0", "laravel/serializable-closure": "^1.0|^2.0", "php": "^8.1" }, "require-dev": { - "orchestra/testbench": "^8.0|^9.0", - "phpunit/php-code-coverage": "^10.|^11.0", - "phpunit/phpunit": "^10.5|^11.0" + "laravel/pint": "^1.0", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "phpunit/php-code-coverage": "^10.|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", + "vimeo/psalm": "^5.0|^6.0" }, "type": "library", "extra": { @@ -11074,9 +11137,9 @@ "description": "An easy way to add breadcrumbs to your Laravel app.", "support": { "issues": "https://github.com/tabuna/breadcrumbs/issues", - "source": "https://github.com/tabuna/breadcrumbs/tree/4.2.1" + "source": "https://github.com/tabuna/breadcrumbs/tree/4.3.0" }, - "time": "2024-11-26T12:21:27+00:00" + "time": "2025-02-25T08:18:04+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -11249,16 +11312,16 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.8.0", + "version": "6.10.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731" + "reference": "ca5b6de294512145db96bcbc94e61696599c391d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/14ffa0e308f5634aa2489568b4b90b24073b6731", - "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/ca5b6de294512145db96bcbc94e61696599c391d", + "reference": "ca5b6de294512145db96bcbc94e61696599c391d", "shasum": "" }, "require": { @@ -11271,8 +11334,6 @@ "config", "include", "tcpdf.php", - "tcpdf_parser.php", - "tcpdf_import.php", "tcpdf_barcodes_1d.php", "tcpdf_barcodes_2d.php", "include/tcpdf_colors.php", @@ -11310,15 +11371,15 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.8.0" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.10.0" }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", + "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ", "type": "custom" } ], - "time": "2024-12-23T13:34:57+00:00" + "time": "2025-05-27T18:02:28+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11632,16 +11693,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -11700,7 +11761,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -11712,7 +11773,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "voku/portable-ascii", @@ -11790,24 +11851,24 @@ }, { "name": "watson/validating", - "version": "8.2.0", + "version": "8.3.0", "source": { "type": "git", "url": "https://github.com/dwightwatson/validating.git", - "reference": "7798363f63d05565ccae01ae95c2227519c1739f" + "reference": "5f24d15727e69857c723aa7bb4c9399931f37827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dwightwatson/validating/zipball/7798363f63d05565ccae01ae95c2227519c1739f", - "reference": "7798363f63d05565ccae01ae95c2227519c1739f", + "url": "https://api.github.com/repos/dwightwatson/validating/zipball/5f24d15727e69857c723aa7bb4c9399931f37827", + "reference": "5f24d15727e69857c723aa7bb4c9399931f37827", "shasum": "" }, "require": { - "illuminate/contracts": "~9.0|~10.0|~11.0", - "illuminate/database": "~9.0|~10.0|~11.0", - "illuminate/events": "~9.0|~10.0|~11.0", - "illuminate/support": "~9.0|~10.0|~11.0", - "illuminate/validation": "~9.0|~10.0|~11.0", + "illuminate/contracts": "~9.0|~10.0|~11.0|~12.0", + "illuminate/database": "~9.0|~10.0|~11.0|~12.0", + "illuminate/events": "~9.0|~10.0|~11.0|~12.0", + "illuminate/support": "~9.0|~10.0|~11.0|~12.0", + "illuminate/validation": "~9.0|~10.0|~11.0|~12.0", "php": "^8.1" }, "require-dev": { @@ -11838,9 +11899,9 @@ ], "support": { "issues": "https://github.com/dwightwatson/validating/issues", - "source": "https://github.com/dwightwatson/validating/tree/8.2.0" + "source": "https://github.com/dwightwatson/validating/tree/8.3.0" }, - "time": "2024-03-13T21:31:03+00:00" + "time": "2025-03-03T21:50:02+00:00" }, { "name": "webmozart/assert", @@ -11968,24 +12029,24 @@ }, { "name": "cmgmyr/phploc", - "version": "8.0.4", + "version": "8.0.6", "source": { "type": "git", "url": "https://github.com/cmgmyr/phploc.git", - "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7" + "reference": "5d785f8fc8b891483cdbee3fb25f2b348c50c03f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", - "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", + "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/5d785f8fc8b891483cdbee3fb25f2b348c50c03f", + "reference": "5d785f8fc8b891483cdbee3fb25f2b348c50c03f", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", "php": "^7.4 || ^8.0", - "phpunit/php-file-iterator": "^3.0|^4.0|^5.0", - "sebastian/cli-parser": "^1.0|^2.0|^3.0" + "phpunit/php-file-iterator": "^3.0|^4.0|^5.0|^6.0", + "sebastian/cli-parser": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", @@ -12021,7 +12082,7 @@ "homepage": "https://github.com/cmgmyr/phploc", "support": { "issues": "https://github.com/cmgmyr/phploc/issues", - "source": "https://github.com/cmgmyr/phploc/tree/8.0.4" + "source": "https://github.com/cmgmyr/phploc/tree/8.0.6" }, "funding": [ { @@ -12029,7 +12090,7 @@ "type": "github" } ], - "time": "2024-10-31T19:26:53+00:00" + "time": "2025-03-29T16:41:46+00:00" }, { "name": "composer/pcre", @@ -12259,28 +12320,28 @@ }, { "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.0.0", + "version": "v1.1.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "4be43904336affa5c2f70744a348312336afd0da" + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", - "reference": "4be43904336affa5c2f70744a348312336afd0da", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", + "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0", + "composer-plugin-api": "^2.2", "php": ">=5.4", "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" }, "require-dev": { - "composer/composer": "*", + "composer/composer": "^2.2", "ext-json": "*", "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", + "php-parallel-lint/php-parallel-lint": "^1.4.0", "phpcompatibility/php-compatibility": "^9.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -12300,9 +12361,9 @@ "authors": [ { "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" }, { "name": "Contributors", @@ -12310,7 +12371,6 @@ } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", "keywords": [ "PHPCodeSniffer", "PHP_CodeSniffer", @@ -12331,9 +12391,28 @@ ], "support": { "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", "source": "https://github.com/PHPCSStandards/composer-installer" }, - "time": "2023-01-05T11:28:13+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-07-17T20:45:56+00:00" }, { "name": "evenement/evenement", @@ -12382,69 +12461,6 @@ }, "time": "2023-08-08T05:53:35+00:00" }, - { - "name": "fakerphp/faker", - "version": "v1.24.1", - "source": { - "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." - }, - "type": "library", - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" - }, - "time": "2024-11-21T13:46:39+00:00" - }, { "name": "fidry/cpu-core-counter", "version": "1.2.0", @@ -12508,57 +12524,59 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.68.1", + "version": "v3.84.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff" + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b9db2b2ea3cdba7201067acee46f984ef2397cff", - "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca", + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca", "shasum": "" }, "require": { "clue/ndjson-react": "^1.0", "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", + "composer/xdebug-handler": "^3.0.5", "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", "fidry/cpu-core-counter": "^1.2", "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.5", + "react/child-process": "^0.6.6", "react/event-loop": "^1.0", - "react/promise": "^2.0 || ^3.0", + "react/promise": "^2.11 || ^3.0", "react/socket": "^1.0", "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.1 || ^6.0", - "symfony/console": "^5.4 || ^6.4 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", - "symfony/finder": "^5.4 || ^6.4 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", - "symfony/polyfill-mbstring": "^1.31", - "symfony/polyfill-php80": "^1.31", - "symfony/polyfill-php81": "^1.31", - "symfony/process": "^5.4 || ^6.4 || ^7.2", - "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0", + "symfony/polyfill-mbstring": "^1.32", + "symfony/polyfill-php80": "^1.32", + "symfony/polyfill-php81": "^1.32", + "symfony/process": "^5.4.47 || ^6.4.20 || ^7.2", + "symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.4", - "infection/infection": "^0.29.8", - "justinrainbow/json-schema": "^5.3 || ^6.0", - "keradus/cli-executor": "^2.1", + "facile-it/paraunit": "^1.3.1 || ^2.6", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^5.3 || ^6.4", + "keradus/cli-executor": "^2.2", "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.7", + "php-coveralls/php-coveralls": "^2.8", "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", - "phpunit/phpunit": "^9.6.22 || ^10.5.40 || ^11.5.2", - "symfony/var-dumper": "^5.4.48 || ^6.4.15 || ^7.2.0", - "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.2.0" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25", + "symfony/polyfill-php84": "^1.32", + "symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1", + "symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -12599,7 +12617,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0" }, "funding": [ { @@ -12607,24 +12625,24 @@ "type": "github" } ], - "time": "2025-01-17T09:20:36+00:00" + "time": "2025-07-15T18:21:57+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -12632,8 +12650,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -12656,36 +12674,87 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { - "name": "justinrainbow/json-schema", - "version": "5.3.0", + "name": "iamcal/sql-parser", + "version": "v0.5", "source": { "type": "git", - "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" + "url": "https://github.com/iamcal/SQLParser.git", + "reference": "644fd994de3b54e5d833aecf406150aa3b66ca88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", - "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/644fd994de3b54e5d833aecf406150aa3b66ca88", + "reference": "644fd994de3b54e5d833aecf406150aa3b66ca88", + "shasum": "" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^5|^6|^7|^8|^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "iamcal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cal Henderson", + "email": "cal@iamcal.com" + } + ], + "description": "MySQL schema parser", + "support": { + "issues": "https://github.com/iamcal/SQLParser/issues", + "source": "https://github.com/iamcal/SQLParser/tree/v0.5" + }, + "time": "2024-03-22T22:46:32+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.4.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "friendsofphp/php-cs-fixer": "3.3.0", "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" }, "bin": [ "bin/validate-json" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -12714,47 +12783,47 @@ } ], "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", + "homepage": "https://github.com/jsonrainbow/json-schema", "keywords": [ "json", "schema" ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" }, - "time": "2024-07-06T21:00:26+00:00" + "time": "2025-06-03T18:27:04+00:00" }, { "name": "larastan/larastan", - "version": "v2.9.12", + "version": "v2.11.2", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "19012b39fbe4dede43dbe0c126d9681827a5e908" + "reference": "1aae902a5851c03dc1a58cbd9010a0c3ef8def63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/19012b39fbe4dede43dbe0c126d9681827a5e908", - "reference": "19012b39fbe4dede43dbe0c126d9681827a5e908", + "url": "https://api.github.com/repos/larastan/larastan/zipball/1aae902a5851c03dc1a58cbd9010a0c3ef8def63", + "reference": "1aae902a5851c03dc1a58cbd9010a0c3ef8def63", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.16", + "iamcal/sql-parser": "^0.5.0", + "illuminate/console": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/container": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/contracts": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/database": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/http": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/pipeline": "^9.52.20 || ^10.48.28 || ^11.41.3", + "illuminate/support": "^9.52.20 || ^10.48.28 || ^11.41.3", "php": "^8.0.2", - "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.12.11" + "phpstan/phpstan": "^1.12.17" }, "require-dev": { - "doctrine/coding-standard": "^12.0", - "laravel/framework": "^9.52.16 || ^10.28.0 || ^11.16", + "doctrine/coding-standard": "^13", + "laravel/framework": "^9.52.20 || ^10.48.28 || ^11.41.3", "mockery/mockery": "^1.5.1", "nikic/php-parser": "^4.19.1", "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2", @@ -12789,10 +12858,6 @@ { "name": "Can Vural", "email": "can9119@gmail.com" - }, - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" } ], "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", @@ -12808,7 +12873,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.9.12" + "source": "https://github.com/larastan/larastan/tree/v2.11.2" }, "funding": [ { @@ -12816,25 +12881,94 @@ "type": "github" } ], - "time": "2024-11-26T23:09:02+00:00" + "time": "2025-06-10T22:06:33+00:00" }, { - "name": "league/container", - "version": "4.2.4", + "name": "laravel/telescope", + "version": "v5.11.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/container.git", - "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec" + "url": "https://github.com/laravel/telescope.git", + "reference": "62e1a21db3db3e7440e9ca02ffa3efce14d6b85e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/7ea728b013b9a156c409c6f0fc3624071b742dec", - "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec", + "url": "https://api.github.com/repos/laravel/telescope/zipball/62e1a21db3db3e7440e9ca02ffa3efce14d6b85e", + "reference": "62e1a21db3db3e7440e9ca02ffa3efce14d6b85e", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "psr/container": "^1.1 || ^2.0" + "ext-json": "*", + "laravel/framework": "^8.37|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0", + "symfony/console": "^5.3|^6.0|^7.0", + "symfony/var-dumper": "^5.0|^6.0|^7.0" + }, + "require-dev": { + "ext-gd": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "laravel/octane": "^1.4|^2.0|dev-develop", + "orchestra/testbench": "^6.40|^7.37|^8.17|^9.0|^10.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.0|^10.5|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Telescope\\TelescopeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Telescope\\": "src/", + "Laravel\\Telescope\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mohamed Said", + "email": "mohamed@laravel.com" + } + ], + "description": "An elegant debug assistant for the Laravel framework.", + "keywords": [ + "debugging", + "laravel", + "monitoring" + ], + "support": { + "issues": "https://github.com/laravel/telescope/issues", + "source": "https://github.com/laravel/telescope/tree/v5.11.2" + }, + "time": "2025-08-16T00:52:51+00:00" + }, + { + "name": "league/container", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "041c52d266763887fff2256fb5dc9392d808f8f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/041c52d266763887fff2256fb5dc9392d808f8f3", + "reference": "041c52d266763887fff2256fb5dc9392d808f8f3", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^2.0.2" }, "provide": { "psr/container-implementation": "^1.0" @@ -12843,13 +12977,13 @@ "orno/di": "~2.0" }, "require-dev": { - "nette/php-generator": "^3.4", - "nikic/php-parser": "^4.10", - "phpstan/phpstan": "^0.12.47", - "phpunit/phpunit": "^8.5.17", + "nette/php-generator": "^4.1", + "nikic/php-parser": "^5.0", + "phpstan/phpstan": "^2.1.11", + "phpunit/phpunit": "^10.5.45|^11.5.15|^12.0", "roave/security-advisories": "dev-latest", - "scrutinizer/ocular": "^1.8", - "squizlabs/php_codesniffer": "^3.6" + "scrutinizer/ocular": "^1.9", + "squizlabs/php_codesniffer": "^3.9" }, "type": "library", "extra": { @@ -12858,7 +12992,8 @@ "dev-2.x": "2.x-dev", "dev-3.x": "3.x-dev", "dev-4.x": "4.x-dev", - "dev-master": "4.x-dev" + "dev-5.x": "5.x-dev", + "dev-master": "5.x-dev" } }, "autoload": { @@ -12890,7 +13025,7 @@ ], "support": { "issues": "https://github.com/thephpleague/container/issues", - "source": "https://github.com/thephpleague/container/tree/4.2.4" + "source": "https://github.com/thephpleague/container/tree/5.1.0" }, "funding": [ { @@ -12898,7 +13033,80 @@ "type": "github" } ], - "time": "2024-11-10T12:42:13+00:00" + "time": "2025-05-28T07:37:56+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.1", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "time": "2024-11-28T04:54:44+00:00" }, { "name": "mockery/mockery", @@ -12985,16 +13193,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -13033,7 +13241,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -13041,55 +13249,52 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nunomaduro/phpinsights", - "version": "v2.12.0", + "version": "v2.13.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/phpinsights.git", - "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650" + "reference": "77572bb0d3a6fbbd36aa000a619fd5c89b10d3df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/5c12a8d626712de6db5e6d2db52b1eb4e9596650", - "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650", + "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/77572bb0d3a6fbbd36aa000a619fd5c89b10d3df", + "reference": "77572bb0d3a6fbbd36aa000a619fd5c89b10d3df", "shasum": "" }, "require": { - "cmgmyr/phploc": "^8.0.3", - "composer/semver": "^3.4", + "cmgmyr/phploc": "^8.0.6", + "composer/semver": "^3.4.3", "ext-iconv": "*", "ext-json": "*", "ext-mbstring": "*", "ext-tokenizer": "*", - "friendsofphp/php-cs-fixer": "^3.40.0", - "justinrainbow/json-schema": "^5.2.13", - "league/container": "^3.2|^4.2", - "php": "^7.4|^8.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "psr/container": "^1.0|^2.0.2", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "sebastian/diff": "^4.0|^5.0.3|^6.0", - "slevomat/coding-standard": "^8.14.1", - "squizlabs/php_codesniffer": "^3.7.2", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.4|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.4|^7.0" + "friendsofphp/php-cs-fixer": "^3.74.0", + "justinrainbow/json-schema": "^6.3.1", + "league/container": "^5.0.1", + "php": "^8.1", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "psr/container": "^2.0.2", + "psr/simple-cache": "^2.0|^3.0", + "sebastian/diff": "^5.1.1|^6.0.2|^7.0.0", + "slevomat/coding-standard": "^8.16.2", + "squizlabs/php_codesniffer": "^3.12.0", + "symfony/cache": "^6.4.20|^7.2.5", + "symfony/console": "^6.4.20|^7.2.5", + "symfony/finder": "^6.4.17|^7.2.2", + "symfony/http-client": "^6.4.19|^7.2.4", + "symfony/process": "^6.4.20|^7.2.5" }, "require-dev": { - "ergebnis/phpstan-rules": "^0.15.3", - "illuminate/console": "^5.8|^6.0|^7.0|^8.0|^9.20|^10.0", - "illuminate/support": "^5.8|^6.0|^7.0|^8.0|^9.52.16|^10.0", - "mockery/mockery": "^1.6.6", - "phpstan/phpstan-strict-rules": "^0.12.11", - "phpunit/phpunit": "^8.0|^9.0|^10.4.2", - "rector/rector": "0.11.56", - "symfony/var-dumper": "^5.4|^6.0|^7.0", - "thecodingmachine/phpstan-strict-rules": "^0.12.2" + "illuminate/console": "^10.48.28|^11.44.2|^12.4", + "illuminate/support": "^10.48.28|^11.44.2|^12.4", + "mockery/mockery": "^1.6.12", + "phpstan/phpstan": "^2.1.11", + "phpunit/phpunit": "^10.5.45|^11.5.15", + "symfony/var-dumper": "^6.4.18|^7.2.3" }, "suggest": { "ext-simplexml": "It is needed for the checkstyle formatter" @@ -13131,23 +13336,15 @@ ], "support": { "issues": "https://github.com/nunomaduro/phpinsights/issues", - "source": "https://github.com/nunomaduro/phpinsights/tree/v2.12.0" + "source": "https://github.com/nunomaduro/phpinsights/tree/v2.13.1" }, "funding": [ - { - "url": "https://github.com/JustSteveKing", - "type": "github" - }, - { - "url": "https://github.com/cmgmyr", - "type": "github" - }, { "url": "https://github.com/nunomaduro", "type": "github" } ], - "time": "2024-11-11T14:42:55+00:00" + "time": "2025-03-30T15:28:32+00:00" }, { "name": "phar-io/manifest", @@ -13269,27 +13466,27 @@ }, { "name": "php-mock/php-mock", - "version": "2.5.1", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "8f58972dce4de5a804dc0459383a11bc651416cf" + "reference": "c7b6789056dfc3c45389cabbe9930dc33aeb2bf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/8f58972dce4de5a804dc0459383a11bc651416cf", - "reference": "8f58972dce4de5a804dc0459383a11bc651416cf", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/c7b6789056dfc3c45389cabbe9930dc33aeb2bf0", + "reference": "c7b6789056dfc3c45389cabbe9930dc33aeb2bf0", "shasum": "" }, "require": { "php": "^5.6 || ^7.0 || ^8.0", - "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4" + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" }, "replace": { "malkusch/php-mock": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", "squizlabs/php_codesniffer": "^3.8" }, "suggest": { @@ -13333,7 +13530,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.5.1" + "source": "https://github.com/php-mock/php-mock/tree/2.6.1" }, "funding": [ { @@ -13341,29 +13538,29 @@ "type": "github" } ], - "time": "2024-12-07T20:52:37+00:00" + "time": "2025-02-28T18:11:56+00:00" }, { "name": "php-mock/php-mock-integration", - "version": "2.3.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "ec6a00a8129d50ed0f07907c91e3274ca4ade877" + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/ec6a00a8129d50ed0f07907c91e3274ca4ade877", - "reference": "ec6a00a8129d50ed0f07907c91e3274ca4ade877", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/8ceb860f343a143af604efeb66a7a124381cc52e", + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e", "shasum": "" }, "require": { "php": ">=5.6", "php-mock/php-mock": "^2.5", - "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4" + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11" + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -13396,7 +13593,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-integration/issues", - "source": "https://github.com/php-mock/php-mock-integration/tree/2.3.0" + "source": "https://github.com/php-mock/php-mock-integration/tree/3.0.0" }, "funding": [ { @@ -13404,26 +13601,26 @@ "type": "github" } ], - "time": "2024-02-10T21:37:25+00:00" + "time": "2025-03-08T19:22:38+00:00" }, { "name": "php-mock/php-mock-phpunit", - "version": "2.10.0", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "e1f7e795990b00937376e345883ea68ca3bda7e0" + "reference": "498e5e25ee7824570332581304c2bb7e37d75e80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/e1f7e795990b00937376e345883ea68ca3bda7e0", - "reference": "e1f7e795990b00937376e345883ea68ca3bda7e0", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/498e5e25ee7824570332581304c2bb7e37d75e80", + "reference": "498e5e25ee7824570332581304c2bb7e37d75e80", "shasum": "" }, "require": { "php": ">=7", - "php-mock/php-mock-integration": "^2.3", - "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11" + "php-mock/php-mock-integration": "^3.0", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9" }, "require-dev": { "mockery/mockery": "^1.3.6" @@ -13464,7 +13661,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.10.0" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.13.0" }, "funding": [ { @@ -13472,7 +13669,7 @@ "type": "github" } ], - "time": "2024-02-11T07:24:16+00:00" + "time": "2025-03-19T20:58:51+00:00" }, { "name": "php-parallel-lint/php-parallel-lint", @@ -13535,105 +13732,18 @@ }, "time": "2024-03-27T12:14:49+00:00" }, - { - "name": "phpmyadmin/sql-parser", - "version": "5.10.3", - "source": { - "type": "git", - "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/5346664973d10cf1abff20837fb1183f3c11a055", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "phpmyadmin/motranslator": "<3.0" - }, - "require-dev": { - "phpbench/phpbench": "^1.1", - "phpmyadmin/coding-standard": "^3.0", - "phpmyadmin/motranslator": "^4.0 || ^5.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.9.12", - "phpstan/phpstan-phpunit": "^1.3.3", - "phpunit/phpunit": "^8.5 || ^9.6", - "psalm/plugin-phpunit": "^0.16.1", - "vimeo/psalm": "^4.11", - "zumba/json-serializer": "~3.0.2" - }, - "suggest": { - "ext-mbstring": "For best performance", - "phpmyadmin/motranslator": "Translate messages to your favorite locale" - }, - "bin": [ - "bin/highlight-query", - "bin/lint-query", - "bin/sql-parser", - "bin/tokenize-query" - ], - "type": "library", - "autoload": { - "psr-4": { - "PhpMyAdmin\\SqlParser\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "The phpMyAdmin Team", - "email": "developers@phpmyadmin.net", - "homepage": "https://www.phpmyadmin.net/team/" - } - ], - "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", - "homepage": "https://github.com/phpmyadmin/sql-parser", - "keywords": [ - "analysis", - "lexer", - "parser", - "query linter", - "sql", - "sql lexer", - "sql linter", - "sql parser", - "sql syntax highlighter", - "sql tokenizer" - ], - "support": { - "issues": "https://github.com/phpmyadmin/sql-parser/issues", - "source": "https://github.com/phpmyadmin/sql-parser" - }, - "funding": [ - { - "url": "https://www.phpmyadmin.net/donate/", - "type": "other" - } - ], - "time": "2025-01-19T04:14:02+00:00" - }, { "name": "phpstan/phpstan", - "version": "1.12.15", + "version": "1.12.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1" + "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1", - "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", + "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", "shasum": "" }, "require": { @@ -13678,7 +13788,7 @@ "type": "github" } ], - "time": "2025-01-05T16:40:22+00:00" + "time": "2025-07-17T17:15:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -14003,16 +14113,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.41", + "version": "10.5.48", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e76586fa3d49714f230221734b44892e384109d7" + "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e76586fa3d49714f230221734b44892e384109d7", - "reference": "e76586fa3d49714f230221734b44892e384109d7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e0a2bc39f6fae7617989d690d76c48e6d2eb541", + "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541", "shasum": "" }, "require": { @@ -14022,7 +14132,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.3", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -14084,7 +14194,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.41" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.48" }, "funding": [ { @@ -14095,12 +14205,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-01-13T09:33:05+00:00" + "time": "2025-07-11T04:07:17+00:00" }, { "name": "react/cache", @@ -15261,32 +15379,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.15.0", + "version": "8.20.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "7d1d957421618a3803b593ec31ace470177d7817" + "reference": "b4f9f02edd4e6a586777f0cabe8d05574323f3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", - "reference": "7d1d957421618a3803b593ec31ace470177d7817", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f9f02edd4e6a586777f0cabe8d05574323f3eb", + "reference": "b4f9f02edd4e6a586777f0cabe8d05574323f3eb", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.23.1", - "squizlabs/php_codesniffer": "^3.9.0" + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", + "php": "^7.4 || ^8.0", + "phpstan/phpdoc-parser": "^2.2.0", + "squizlabs/php_codesniffer": "^3.13.2" }, "require-dev": { - "phing/phing": "2.17.4", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.60", - "phpstan/phpstan-deprecation-rules": "1.1.4", - "phpstan/phpstan-phpunit": "1.3.16", - "phpstan/phpstan-strict-rules": "1.5.2", - "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" + "phing/phing": "3.0.1|3.1.0", + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/phpstan": "2.1.19", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "2.0.6", + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.27|12.2.7" }, "type": "phpcodesniffer-standard", "extra": { @@ -15310,7 +15428,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.20.0" }, "funding": [ { @@ -15322,20 +15440,20 @@ "type": "tidelift" } ], - "time": "2024-03-09T15:20:58+00:00" + "time": "2025-07-26T15:35:10+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.11.2", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", - "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", "shasum": "" }, "require": { @@ -15400,29 +15518,33 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-12-11T16:04:26+00:00" + "time": "2025-06-17T22:17:01+00:00" }, { "name": "symfony/cache", - "version": "v7.2.1", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" + "reference": "a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", - "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", + "url": "https://api.github.com/repos/symfony/cache/zipball/a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e", + "reference": "a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e", "shasum": "" }, "require": { "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", + "symfony/cache-contracts": "^3.6", "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", "symfony/var-exporter": "^6.4|^7.0" @@ -15484,7 +15606,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.1" + "source": "https://github.com/symfony/cache/tree/v7.3.1" }, "funding": [ { @@ -15500,20 +15622,20 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:08:50+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -15527,7 +15649,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -15560,7 +15682,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -15576,7 +15698,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/dom-crawler", @@ -15654,7 +15776,7 @@ }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -15700,7 +15822,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" }, "funding": [ { @@ -15720,16 +15842,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.2", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "339ba21476eb184290361542f732ad12c97591ec" + "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", - "reference": "339ba21476eb184290361542f732ad12c97591ec", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", + "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", "shasum": "" }, "require": { @@ -15741,6 +15863,7 @@ }, "conflict": { "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.4" }, @@ -15753,7 +15876,6 @@ "require-dev": { "amphp/http-client": "^4.2.1|^5.0", "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", @@ -15795,7 +15917,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.2" + "source": "https://github.com/symfony/http-client/tree/v7.3.1" }, "funding": [ { @@ -15811,20 +15933,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T18:35:15+00:00" + "time": "2025-06-28T07:58:39+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -15837,7 +15959,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -15873,7 +15995,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -15889,20 +16011,20 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", "shasum": "" }, "require": { @@ -15940,7 +16062,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" }, "funding": [ { @@ -15956,11 +16078,11 @@ "type": "tidelift" } ], - "time": "2024-11-20T11:17:29+00:00" + "time": "2025-04-04T13:12:05+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -16016,7 +16138,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -16036,16 +16158,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.2.2", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { @@ -16078,7 +16200,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" }, "funding": [ { @@ -16094,24 +16216,25 @@ "type": "tidelift" } ], - "time": "2024-12-18T14:28:33+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", - "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", @@ -16154,7 +16277,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" }, "funding": [ { @@ -16170,7 +16293,7 @@ "type": "tidelift" } ], - "time": "2024-10-18T07:58:17+00:00" + "time": "2025-05-15T09:04:05+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index 9de088089f..b494da5981 100755 --- a/config/app.php +++ b/config/app.php @@ -207,7 +207,7 @@ return [ /* |-------------------------------------------------------------------------- - | Require SAML Login + | Require SAML Login |-------------------------------------------------------------------------- | | Disable the ability to login via form login, and disables the 'nosaml' @@ -220,6 +220,23 @@ return [ 'require_saml' => env('REQUIRE_SAML', false), + /* + |-------------------------------------------------------------------------- + | SAML KEYS + |-------------------------------------------------------------------------- + | + | This is the size of the keys used by openssl_pkey_new for SAML authentication. + | The default is 2048 bits, but this can be changed to 3072 or 4096 bits + | for higher security. Note that this will increase the time it takes to + | generate the keys, so it is not recommended to set this to a very high value + | unless you have a specific need for it. + | + | The European Commission now requires at least 3072-bit keys for new SAML certificates + | @link https://github.com/grokability/snipe-it/issues/17386 + */ + + 'saml_key_size' => env('SAML_KEY_SIZE', 2048), + /* |-------------------------------------------------------------------------- diff --git a/config/auth.php b/config/auth.php index 1603c93337..de74b33663 100644 --- a/config/auth.php +++ b/config/auth.php @@ -104,6 +104,16 @@ return [ ] ], + + 'invites' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => env('INVITE_PASSWORD_LINK_EXPIRES', 2880), + 'throttle' => [ + 'max_attempts' => env('LOGIN_MAX_ATTEMPTS', 5), + 'lockout_duration' => env('LOGIN_LOCKOUT_DURATION', 60), + ] + ], ], /* diff --git a/config/database.php b/config/database.php index fd7ff6aa0f..83f92861f6 100755 --- a/config/database.php +++ b/config/database.php @@ -18,7 +18,7 @@ $dump_options = [ //'add_extra_option' => '--optionname=optionvalue', ]; -// Some versions of mysql do not support the --skip-ssl option and will fail if it is +// Some versions of mysql do not support the --skip-ssl option and will fail if it is even set if (env('DB_DUMP_SKIP_SSL') == 'true') { $dump_options['skip_ssl'] = true; } diff --git a/config/dompdf.php b/config/dompdf.php index 22f5f6c27e..4c95f98f18 100644 --- a/config/dompdf.php +++ b/config/dompdf.php @@ -83,7 +83,7 @@ return array( /** * Whether to enable font subsetting or not. */ - "enable_font_subsetting" => false, + "enable_font_subsetting" => true, /** * The PDF rendering backend to use diff --git a/config/filesystems.php b/config/filesystems.php index 50b2675589..6098438ed2 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -99,10 +99,73 @@ $config = [ ], + ]; // copy the selected PUBLIC_FILESYSTEM_DISK's configuration to the 'public' key for easy use // (by default, the PUBLIC_FILESYSTEM DISK is 'local_public', in the public/uploads directory) $config['disks']['public'] = $config['disks'][env('PUBLIC_FILESYSTEM_DISK','local_public')]; +// This is used to determine which files to accept, and also to populate the language strings for the upload-file blade +$config['allowed_upload_extensions_array'] = [ + 'avif', + 'doc', + 'doc', + 'docx', + 'docx', + 'gif', + 'ico', + 'jpeg', + 'jpg', + 'json', + 'key', + 'lic', + 'mov', + 'mp3', + 'mp4', + 'odp', + 'ods', + 'odt', + 'ogg', + 'pdf', + 'png', + 'rar', + 'rtf', + 'svg', + 'txt', + 'wav', + 'webm', + 'webp', + 'xls', + 'xlsx', + 'xml', + 'zip', +]; + + +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types +$config['allowed_upload_mimetypes_array'] = [ + 'application/json', + 'application/msword', + 'application/pdf', + 'application/vnd.ms-excel', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/x-rar-compressed', + 'application/zip', + 'audio/*', + 'image/*', + 'text/plain', + 'text/rtf', + 'text/xml', + 'video/*', +]; + +$config['allowed_upload_mimetypes'] = implode(',', $config['allowed_upload_mimetypes_array']); +$config['allowed_upload_extensions_for_validator'] = implode(',', $config['allowed_upload_extensions_array']); +$config['allowed_upload_extensions'] = '.'.implode(', .', $config['allowed_upload_extensions_array']); + return $config; \ No newline at end of file diff --git a/config/telescope.php b/config/telescope.php new file mode 100644 index 0000000000..5208137e9d --- /dev/null +++ b/config/telescope.php @@ -0,0 +1,205 @@ + env('TELESCOPE_ENABLED', false), + + /* + |-------------------------------------------------------------------------- + | Telescope Domain + |-------------------------------------------------------------------------- + | + | This is the subdomain where Telescope will be accessible from. If the + | setting is null, Telescope will reside under the same domain as the + | application. Otherwise, this value will be used as the subdomain. + | + */ + + 'domain' => env('TELESCOPE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Telescope Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Telescope will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('TELESCOPE_PATH', 'telescope'), + + /* + |-------------------------------------------------------------------------- + | Telescope Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the storage driver that will + | be used to store Telescope's data. In addition, you may set any + | custom options as needed by the particular driver you choose. + | + */ + + 'driver' => env('TELESCOPE_DRIVER', 'database'), + + 'storage' => [ + 'database' => [ + 'connection' => env('DB_CONNECTION', 'mysql'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Queue + |-------------------------------------------------------------------------- + | + | This configuration options determines the queue connection and queue + | which will be used to process ProcessPendingUpdate jobs. This can + | be changed if you would prefer to use a non-default connection. + | + */ + + 'queue' => [ + 'connection' => env('TELESCOPE_QUEUE_CONNECTION'), + 'queue' => env('TELESCOPE_QUEUE'), + 'delay' => env('TELESCOPE_QUEUE_DELAY', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Telescope route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Allowed / Ignored Paths & Commands + |-------------------------------------------------------------------------- + | + | The following array lists the URI paths and Artisan commands that will + | not be watched by Telescope. In addition to this list, some Laravel + | commands, like migrations and queue commands, are always ignored. + | + */ + + 'only_paths' => [ + // 'api/*' + ], + + 'ignore_paths' => [ + 'livewire*', + ], + + 'ignore_commands' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Watchers + |-------------------------------------------------------------------------- + | + | The following array lists the "watchers" that will be registered with + | Telescope. The watchers gather the application's profile data when + | a request or task is executed. Feel free to customize this list. + | + */ + + 'watchers' => [ + Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), + + Watchers\CacheWatcher::class => [ + 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), + 'hidden' => [], + 'ignore' => [], + ], + + Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true), + + Watchers\CommandWatcher::class => [ + 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), + 'ignore' => [], + ], + + Watchers\DumpWatcher::class => [ + 'enabled' => env('TELESCOPE_DUMP_WATCHER', true), + 'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false), + ], + + Watchers\EventWatcher::class => [ + 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), + 'ignore' => [], + ], + + Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), + + Watchers\GateWatcher::class => [ + 'enabled' => env('TELESCOPE_GATE_WATCHER', true), + 'ignore_abilities' => [], + 'ignore_packages' => true, + 'ignore_paths' => [], + ], + + Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true), + + Watchers\LogWatcher::class => [ + 'enabled' => env('TELESCOPE_LOG_WATCHER', true), + 'level' => 'error', + ], + + Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true), + + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.*'], + 'hydrations' => true, + ], + + Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true), + + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'ignore_packages' => true, + 'ignore_paths' => [], + 'slow' => 100, + ], + + Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), + + Watchers\RequestWatcher::class => [ + 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), + 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), + 'ignore_http_methods' => [], + 'ignore_status_codes' => [], + ], + + Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true), + Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), + ], +]; diff --git a/config/version.php b/config/version.php index dbb12addc1..536e65ac78 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v8.0.4', - 'full_app_version' => 'v8.0.4 - build 17704-g0dcb315d9', - 'build_version' => '17704', + 'app_version' => 'v8.3.1', + 'full_app_version' => 'v8.3.1 - build 19577-g7dd493da3', + 'build_version' => '19577', 'prerelease_version' => '', - 'hash_version' => 'g0dcb315d9', - 'full_hash' => 'v8.0.4-506-g0dcb315d9', + 'hash_version' => 'g7dd493da3', + 'full_hash' => 'v8.3.1-15-g7dd493da3', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/ActionlogFactory.php b/database/factories/ActionlogFactory.php index ad07f7082b..4773b39532 100644 --- a/database/factories/ActionlogFactory.php +++ b/database/factories/ActionlogFactory.php @@ -84,6 +84,28 @@ class ActionlogFactory extends Factory }); } + /** + * This sets up an ActionLog representing a manually added note tied to an Asset, + * with an optional User as the creator. If no User is provided, one is generated. + * + * @param User|null $user Optional user to associate as the creator of the note. + * @return \Illuminate\Database\Eloquent\Factories\Factory + */ + public function assetNote(?User $user=null) + { + return $this + ->state(function () use ($user) { + return [ + 'action_type' => 'note added', + 'item_type' => Asset::class, + 'target_type' => 'asset', + 'note' => 'Factory-generated manual note', + 'created_by' => $user?->id ?? User::factory(), + ]; + }) + ->for($user ?? User::factory(), 'user'); + } + public function licenseCheckoutToUser() { return $this->state(function () { diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 3adb933e3e..ac31b57984 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -33,6 +33,7 @@ class AssetModelFactory extends Factory 'category_id' => Category::factory(), 'model_number' => $this->faker->creditCardNumber(), 'notes' => 'Created by demo seeder', + 'require_serial' => 0, ]; } diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index 449af0caa5..733c52668e 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -28,7 +28,7 @@ class CategoryFactory extends Factory 'checkin_email' => true, 'eula_text' => $this->faker->paragraph(), 'require_acceptance' => false, - 'use_default_eula' => $this->faker->boolean(), + 'use_default_eula' => false, 'created_by' => User::factory()->superuser(), 'notes' => 'Created by DB seeder', ]; diff --git a/database/factories/CheckoutAcceptanceFactory.php b/database/factories/CheckoutAcceptanceFactory.php index a15e942a84..bb56ab2b4a 100644 --- a/database/factories/CheckoutAcceptanceFactory.php +++ b/database/factories/CheckoutAcceptanceFactory.php @@ -64,6 +64,24 @@ class CheckoutAcceptanceFactory extends Factory ]); } + public function withoutAlerting() + { + return $this->state(function () { + return [ + 'alert_on_response_id' => null, + ]; + }); + } + + public function withAlertingTo(User $user) + { + return $this->state(function () use ($user) { + return [ + 'alert_on_response_id' => $user->id, + ]; + }); + } + private function createdAssociatedActionLogEntry(CheckoutAcceptance $acceptance): void { $acceptance->checkoutable->assetlog()->create([ diff --git a/database/factories/CheckoutRequestFactory.php b/database/factories/CheckoutRequestFactory.php new file mode 100644 index 0000000000..5363b5e281 --- /dev/null +++ b/database/factories/CheckoutRequestFactory.php @@ -0,0 +1,44 @@ + Asset::factory(), + 'requestable_type' => Asset::class, + 'quantity' => 1, + 'user_id' => User::factory(), + ]; + } + + public function forAsset() + { + return $this->state(function (array $attributes) { + return [ + 'requestable_id' => Asset::factory(), + 'requestable_type' => Asset::class, + ]; + }); + } + + public function forAssetModel() + { + return $this->state(function (array $attributes) { + return [ + 'requestable_id' => AssetModel::factory(), + 'requestable_type' => AssetModel::class, + ]; + }); + } +} diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php index 44ab0707e0..30c41ce4e5 100644 --- a/database/factories/CustomFieldFactory.php +++ b/database/factories/CustomFieldFactory.php @@ -93,6 +93,42 @@ class CustomFieldFactory extends Factory }); } + public function encrypt() + { + return $this->state(function () { + return [ + 'field_encrypted' => '1', + ]; + }); + } + + public function alpha() + { + return $this->state(function () { + return [ + 'format' => 'alpha', + ]; + }); + } + + public function numeric() + { + return $this->state(function () { + return [ + 'format' => 'numeric', + ]; + }); + } + + public function email() + { + return $this->state(function () { + return [ + 'format' => 'email', + ]; + }); + } + public function testCheckbox() { return $this->state(function () { diff --git a/database/factories/ImportFactory.php b/database/factories/ImportFactory.php index 11fdbe17a9..5355cac834 100644 --- a/database/factories/ImportFactory.php +++ b/database/factories/ImportFactory.php @@ -165,4 +165,72 @@ class ImportFactory extends Factory }); } + /** + * Create a supplier import type. + * + * @return static + */ + public function suppliers() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\SuppliersImportFileBuilder::new(); + $attributes['name'] = "Supplier {$attributes['name']}"; + $attributes['import_type'] = 'supplier'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create an supplier import type. + * + * @return static + */ + public function locations() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\SuppliersImportFileBuilder::new(); + $attributes['name'] = "Location {$attributes['name']}"; + $attributes['import_type'] = 'location'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a supplier import type. + * + * @return static + */ + public function manufacturers() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ManufacturersImportFileBuilder::new(); + $attributes['name'] = "Manufacturer {$attributes['name']}"; + $attributes['import_type'] = 'manufacturer'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + public function categories() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\CategoriesImportFileBuilder::new(); + $attributes['name'] = "Category {$attributes['name']}"; + $attributes['import_type'] = 'category'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + } diff --git a/database/factories/AssetMaintenanceFactory.php b/database/factories/MaintenanceFactory.php similarity index 81% rename from database/factories/AssetMaintenanceFactory.php rename to database/factories/MaintenanceFactory.php index ada73f7ed8..e07de8d24d 100644 --- a/database/factories/AssetMaintenanceFactory.php +++ b/database/factories/MaintenanceFactory.php @@ -3,18 +3,18 @@ namespace Database\Factories; use App\Models\Asset; -use App\Models\AssetMaintenance; +use App\Models\Maintenance; use App\Models\Supplier; use Illuminate\Database\Eloquent\Factories\Factory; -class AssetMaintenanceFactory extends Factory +class MaintenanceFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ - protected $model = AssetMaintenance::class; + protected $model = Maintenance::class; /** * Define the model's default state. @@ -27,7 +27,7 @@ class AssetMaintenanceFactory extends Factory 'asset_id' => Asset::factory(), 'supplier_id' => Supplier::factory(), 'asset_maintenance_type' => $this->faker->randomElement(['maintenance', 'repair', 'upgrade']), - 'title' => $this->faker->sentence(), + 'name' => $this->faker->sentence(), 'start_date' => $this->faker->date(), 'is_warranty' => $this->faker->boolean(), 'notes' => $this->faker->paragraph(), diff --git a/database/factories/ManufacturerFactory.php b/database/factories/ManufacturerFactory.php index a8d6208d7e..0a8a0a543e 100644 --- a/database/factories/ManufacturerFactory.php +++ b/database/factories/ManufacturerFactory.php @@ -166,4 +166,49 @@ class ManufacturerFactory extends Factory ]; }); } + + public function samsung() + { + return $this->state(function () { + return [ + 'name' => 'Samsung', + 'url' => 'https://www.samsung.com', + 'support_url' => 'https://www.samsung.com/support/', + 'image' => 'samsung.png', + ]; + }); + } + + public function google() + { + return $this->state(function () { + return [ + 'name' => 'Google', + 'url' => 'https://www.google.com', + 'image' => 'google.webp', + ]; + }); + } + + public function huawei() + { + return $this->state(function () { + return [ + 'name' => 'Huawei', + 'url' => 'https://consumer.huawei.com/', + 'image' => 'huawei.webp', + ]; + }); + } + + public function sony() + { + return $this->state(function () { + return [ + 'name' => 'Sony', + 'url' => 'https://electronics.sony.com', + 'image' => 'sony.png', + ]; + }); + } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index d8b1ac141c..989fad08f2 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -28,13 +28,15 @@ class UserFactory extends Factory 'email' => $this->faker->safeEmail(), 'employee_num' => $this->faker->numberBetween(3500, 35050), 'first_name' => $this->faker->firstName(), - 'jobtitle' => $this->faker->jobTitle(), 'last_name' => $this->faker->lastName(), + 'display_name' => null, + 'jobtitle' => $this->faker->jobTitle(), 'locale' => 'en-US', 'notes' => 'Created by DB seeder', 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'permissions' => '{}', 'phone' => $this->faker->phoneNumber(), + 'mobile' => $this->faker->phoneNumber(), 'state' => $this->faker->stateAbbr(), 'username' => $this->faker->unique()->username(), 'zip' => $this->faker->postcode(), @@ -285,6 +287,11 @@ class UserFactory extends Factory return $this->appendPermission(['components.checkout' => '1']); } + public function viewCompanies() + { + return $this->appendPermission(['companies.view' => '1']); + } + public function createCompanies() { return $this->appendPermission(['companies.create' => '1']); @@ -345,6 +352,17 @@ class UserFactory extends Factory return $this->appendPermission(['import' => '1']); } + public function createCustomFields() + { + return $this->appendPermission(['customfields.create' => '1']); + } + + public function viewCustomFields() + { + return $this->appendPermission(['customfields.view' => '1']); + } + + public function deleteCustomFields() { return $this->appendPermission(['customfields.delete' => '1']); diff --git a/database/migrations/2015_06_26_213716_create_asset_maintenances_table.php b/database/migrations/2015_06_26_213716_create_asset_maintenances_table.php index f328bb61f8..6838dde854 100644 --- a/database/migrations/2015_06_26_213716_create_asset_maintenances_table.php +++ b/database/migrations/2015_06_26_213716_create_asset_maintenances_table.php @@ -2,7 +2,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Lang; + use Illuminate\Support\Facades\Schema; class CreateAssetMaintenancesTable extends Migration { @@ -40,9 +40,9 @@ protected function getEnumFields() { return [ - trans('admin/asset_maintenances/general.maintenance'), - trans('admin/asset_maintenances/general.repair'), - trans('admin/asset_maintenances/general.upgrade'), + trans('admin/maintenances/general.maintenance'), + trans('admin/maintenances/general.repair'), + trans('admin/maintenances/general.upgrade'), ]; } diff --git a/database/migrations/2018_08_08_100000_create_telescope_entries_table.php b/database/migrations/2018_08_08_100000_create_telescope_entries_table.php new file mode 100644 index 0000000000..700a83f095 --- /dev/null +++ b/database/migrations/2018_08_08_100000_create_telescope_entries_table.php @@ -0,0 +1,70 @@ +getConnection()); + + $schema->create('telescope_entries', function (Blueprint $table) { + $table->bigIncrements('sequence'); + $table->uuid('uuid'); + $table->uuid('batch_id'); + $table->string('family_hash')->nullable(); + $table->boolean('should_display_on_index')->default(true); + $table->string('type', 20); + $table->longText('content'); + $table->dateTime('created_at')->nullable(); + + $table->unique('uuid'); + $table->index('batch_id'); + $table->index('family_hash'); + $table->index('created_at'); + $table->index(['type', 'should_display_on_index']); + }); + + $schema->create('telescope_entries_tags', function (Blueprint $table) { + $table->uuid('entry_uuid'); + $table->string('tag'); + + $table->primary(['entry_uuid', 'tag']); + $table->index('tag'); + + $table->foreign('entry_uuid') + ->references('uuid') + ->on('telescope_entries') + ->onDelete('cascade'); + }); + + $schema->create('telescope_monitoring', function (Blueprint $table) { + $table->string('tag')->primary(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $schema = Schema::connection($this->getConnection()); + + $schema->dropIfExists('telescope_entries_tags'); + $schema->dropIfExists('telescope_entries'); + $schema->dropIfExists('telescope_monitoring'); + } +}; diff --git a/database/migrations/2023_12_19_081112_fix_language_dirs.php b/database/migrations/2023_12_19_081112_fix_language_dirs.php index 64b9598048..b3dcf4a0e3 100644 --- a/database/migrations/2023_12_19_081112_fix_language_dirs.php +++ b/database/migrations/2023_12_19_081112_fix_language_dirs.php @@ -1,8 +1,7 @@ index(['action_date']); + }); + + DB::update('update '.DB::getTablePrefix().'action_logs set action_date = created_at where created_at IS NOT NULL and action_date IS NULL'); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php b/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php new file mode 100644 index 0000000000..49b228b42d --- /dev/null +++ b/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php @@ -0,0 +1,21 @@ +unsignedInteger('label2_empty_row_count')->default(0)->after('label2_fields'); + }); + } + + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('label2_empty_row_count'); + }); + } +}; diff --git a/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php b/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php new file mode 100644 index 0000000000..ee0e37a628 --- /dev/null +++ b/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php @@ -0,0 +1,28 @@ +boolean( 'require_serial')->after('category_id')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('models', function (Blueprint $table) { + $table->dropColumn('require_serial'); + }); + } +}; diff --git a/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php new file mode 100644 index 0000000000..be14ea04ea --- /dev/null +++ b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php @@ -0,0 +1,41 @@ +first(); + + if ($settings) { + /** If webhook settings were cleared via the integration settings page, + * the webhook_selected was cleared as well when it should have reset to "slack". + */ + if ( + empty($settings->webhook_selected) && + (empty($settings->webhook_botname) && empty($settings->webhook_channel) && empty($settings->webhook_endpoint)) + ) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + + /** If webhook settings were cleared via the integration settings page, + * then slack settings were re-added; then webhook_selected was not being set to "slack" as needed. + */ + if (str_contains($settings->webhook_endpoint, 'slack.com')) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php new file mode 100644 index 0000000000..d28115a6d2 --- /dev/null +++ b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php @@ -0,0 +1,27 @@ +boolean('admin_cc_always')->after('admin_cc_email')->default(1); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('admin_cc_always'); + }); + } +}; diff --git a/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php b/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php new file mode 100644 index 0000000000..345c52a7dd --- /dev/null +++ b/database/migrations/2025_06_03_000000_add_manager_view_enabled_to_settings_table.php @@ -0,0 +1,34 @@ +boolean('manager_view_enabled') + ->default(false) + ->comment('Allow managers to view assets assigned to their subordinates'); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasColumn('settings', 'manager_view_enabled')) { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('manager_view_enabled'); + }); + } + } +}; diff --git a/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php b/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php new file mode 100644 index 0000000000..9d6a1c8046 --- /dev/null +++ b/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php @@ -0,0 +1,23 @@ +whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php b/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php new file mode 100644 index 0000000000..c9f853599b --- /dev/null +++ b/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php @@ -0,0 +1,28 @@ +index('deleted_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('action_logs', function (Blueprint $table) { + $table->dropIndex('deleted_at'); + }); + } +}; diff --git a/database/migrations/2025_06_05_200518_add_alert_on_response_id_to_checkout_acceptances_table.php b/database/migrations/2025_06_05_200518_add_alert_on_response_id_to_checkout_acceptances_table.php new file mode 100644 index 0000000000..ff9ad310f5 --- /dev/null +++ b/database/migrations/2025_06_05_200518_add_alert_on_response_id_to_checkout_acceptances_table.php @@ -0,0 +1,27 @@ +unsignedBigInteger('alert_on_response_id')->nullable()->after('note'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('checkout_acceptances', function (Blueprint $table) { + $table->dropColumn('alert_on_response_id'); + }); + } +}; diff --git a/database/migrations/2025_06_05_204139_add_alert_on_response_to_categories_table.php b/database/migrations/2025_06_05_204139_add_alert_on_response_to_categories_table.php new file mode 100644 index 0000000000..dc2bdae1b2 --- /dev/null +++ b/database/migrations/2025_06_05_204139_add_alert_on_response_to_categories_table.php @@ -0,0 +1,27 @@ +boolean('alert_on_response')->default(0)->after('require_acceptance'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn('alert_on_response'); + }); + } +}; diff --git a/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php b/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php new file mode 100644 index 0000000000..f71eeb8d96 --- /dev/null +++ b/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php @@ -0,0 +1,27 @@ +integer('supplier_id')->nullable()->default(null)->change(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/migrations/2025_07_16_142036_clean_checkout_acceptances.php b/database/migrations/2025_07_16_142036_clean_checkout_acceptances.php new file mode 100644 index 0000000000..b44bbc05b6 --- /dev/null +++ b/database/migrations/2025_07_16_142036_clean_checkout_acceptances.php @@ -0,0 +1,26 @@ +text('image')->after('notes')->nullable()->default(null); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('asset_maintenances', function (Blueprint $table) { + $table->dropColumn('image'); + }); + } +}; diff --git a/database/migrations/2025_08_10_111553_rename_title_to_name_on_asset_maintenances.php b/database/migrations/2025_08_10_111553_rename_title_to_name_on_asset_maintenances.php new file mode 100644 index 0000000000..424157e987 --- /dev/null +++ b/database/migrations/2025_08_10_111553_rename_title_to_name_on_asset_maintenances.php @@ -0,0 +1,28 @@ +renameColumn('title', 'name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('asset_maintenances', function (Blueprint $table) { + $table->renameColumn('name', 'title'); + }); + } +}; diff --git a/database/migrations/2025_08_10_113444_rename_asset_maintenances_to_maintenances.php b/database/migrations/2025_08_10_113444_rename_asset_maintenances_to_maintenances.php new file mode 100644 index 0000000000..f4cc30e803 --- /dev/null +++ b/database/migrations/2025_08_10_113444_rename_asset_maintenances_to_maintenances.php @@ -0,0 +1,24 @@ +update(['item_type' => 'App\\Models\\Maintenance']); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2025_08_11_181519_add_mobile_number_to_users_table.php b/database/migrations/2025_08_11_181519_add_mobile_number_to_users_table.php new file mode 100644 index 0000000000..b0e0bbdd31 --- /dev/null +++ b/database/migrations/2025_08_11_181519_add_mobile_number_to_users_table.php @@ -0,0 +1,30 @@ +text('mobile')->after('phone')->nullable()->default(null); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('mobile'); + }); + } +}; diff --git a/database/migrations/2025_08_19_114742_add_display_name_to_users.php b/database/migrations/2025_08_19_114742_add_display_name_to_users.php new file mode 100644 index 0000000000..d55e755d3a --- /dev/null +++ b/database/migrations/2025_08_19_114742_add_display_name_to_users.php @@ -0,0 +1,32 @@ +text('display_name')->after('last_name')->nullable()->default(null); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + if (Schema::hasColumn('users', 'display_name')) { + $table->dropColumn('display_name'); + } + }); + } +}; diff --git a/database/migrations/2025_08_19_122533_add_category_indexes.php b/database/migrations/2025_08_19_122533_add_category_indexes.php new file mode 100644 index 0000000000..530a1f4398 --- /dev/null +++ b/database/migrations/2025_08_19_122533_add_category_indexes.php @@ -0,0 +1,59 @@ +index(['deleted_at']); + }); + Schema::table('accessories', function (Blueprint $table) { + $table->index(['deleted_at','category_id']); + }); + Schema::table('consumables', function (Blueprint $table) { + $table->index(['deleted_at','category_id']); + }); + Schema::table('components', function (Blueprint $table) { + $table->index(['deleted_at','category_id']); + }); + Schema::table('licenses', function (Blueprint $table) { + $table->index(['deleted_at','category_id']); + }); + Schema::table('models', function (Blueprint $table) { + $table->index(['deleted_at','category_id']); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('categories', function (Blueprint $table) { + $table->dropIndex(['deleted_at']); + }); + Schema::table('accessories', function (Blueprint $table) { + $table->dropIndex(['deleted_at','category_id']); + }); + Schema::table('consumables', function (Blueprint $table) { + $table->dropIndex(['deleted_at','category_id']); + }); + Schema::table('components', function (Blueprint $table) { + $table->dropIndex(['deleted_at','category_id']); + }); + Schema::table('licenses', function (Blueprint $table) { + $table->dropIndex(['deleted_at','category_id']); + }); + Schema::table('models', function (Blueprint $table) { + $table->dropIndex(['deleted_at','category_id']); + }); + } +}; diff --git a/database/migrations/2025_08_19_174823_add_display_name_to_ldap_settings.php b/database/migrations/2025_08_19_174823_add_display_name_to_ldap_settings.php new file mode 100644 index 0000000000..fb980af6dd --- /dev/null +++ b/database/migrations/2025_08_19_174823_add_display_name_to_ldap_settings.php @@ -0,0 +1,82 @@ +string('ldap_display_name', 191)->after('ldap_fname_field')->nullable()->default(null); + } + + if (!Schema::hasColumn('settings', 'ldap_zip')) { + $table->string('ldap_zip', 191)->after('ldap_manager')->nullable()->default(null); + } + + if (!Schema::hasColumn('settings', 'ldap_state')) { + $table->string('ldap_state', 191)->after('ldap_manager')->nullable()->default(null); + } + + if (!Schema::hasColumn('settings', 'ldap_city')) { + $table->string('ldap_city', 191)->after('ldap_manager')->nullable()->default(null); + } + + if (!Schema::hasColumn('settings', 'ldap_address')) { + $table->string('ldap_address', 191)->after('ldap_manager')->nullable()->default(null); + } + + if (!Schema::hasColumn('settings', 'ldap_mobile')) { + $table->string('ldap_mobile', 191)->after('ldap_phone_field')->nullable()->default(null); + } + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_display_name')) { + $table->dropColumn('ldap_display_name'); + } + }); + + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_zip')) { + $table->dropColumn('ldap_zip'); + } + }); + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_address')) { + $table->dropColumn('ldap_address'); + } + }); + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_city')) { + $table->dropColumn('ldap_city'); + } + }); + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_state')) { + $table->dropColumn('ldap_state'); + } + }); + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'ldap_mobile')) { + $table->dropColumn('ldap_mobile'); + } + }); + + + } +}; diff --git a/database/migrations/2025_08_20_190617_add_created_at_index_to_models.php b/database/migrations/2025_08_20_190617_add_created_at_index_to_models.php new file mode 100644 index 0000000000..5a73a94f3b --- /dev/null +++ b/database/migrations/2025_08_20_190617_add_created_at_index_to_models.php @@ -0,0 +1,28 @@ +index(['created_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('models', function (Blueprint $table) { + $table->dropIndex(['created_at']); + }); + } +}; diff --git a/database/seeders/AssetModelSeeder.php b/database/seeders/AssetModelSeeder.php index f2902ffe7c..b28d6179d5 100755 --- a/database/seeders/AssetModelSeeder.php +++ b/database/seeders/AssetModelSeeder.php @@ -51,8 +51,6 @@ class AssetModelSeeder extends Seeder $del_files = Storage::files($dst); foreach ($del_files as $del_file) { // iterate files - $file_to_delete = str_replace($src, '', $del_file); - Log::debug('Deleting: '.$file_to_delete); try { Storage::disk('public')->delete($dst.$del_file); } catch (\Exception $e) { @@ -63,7 +61,6 @@ class AssetModelSeeder extends Seeder $add_files = glob($src.'/*.*'); foreach ($add_files as $add_file) { $file_to_copy = str_replace($src, '', $add_file); - Log::debug('Copying: '.$file_to_copy); try { Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy)); } catch (\Exception $e) { diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 5e26a9a257..6816df5804 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -53,7 +53,7 @@ class DatabaseSeeder extends Seeder Model::reguard(); DB::table('imports')->truncate(); - DB::table('asset_maintenances')->truncate(); + DB::table('maintenances')->truncate(); DB::table('requested_assets')->truncate(); } } diff --git a/database/seeders/ManufacturerSeeder.php b/database/seeders/ManufacturerSeeder.php index adc13dc73e..cdf7596d45 100644 --- a/database/seeders/ManufacturerSeeder.php +++ b/database/seeders/ManufacturerSeeder.php @@ -27,6 +27,10 @@ class ManufacturerSeeder extends Seeder Manufacturer::factory()->count(1)->adobe()->create(['created_by' => $admin->id]); Manufacturer::factory()->count(1)->avery()->create(['created_by' => $admin->id]); Manufacturer::factory()->count(1)->crucial()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->samsung()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->google()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->huawei()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->sony()->create(['created_by' => $admin->id]); $src = public_path('/img/demo/manufacturers/'); $dst = 'manufacturers'.'/'; diff --git a/dev.docker-compose.yml b/dev.docker-compose.yml index 6cf4a1e2f2..0cd8139c0e 100644 --- a/dev.docker-compose.yml +++ b/dev.docker-compose.yml @@ -21,7 +21,7 @@ services: - .env.dev.docker mariadb: - image: mariadb:11.5.2 + image: mariadb:11.4.7 volumes: - db:/var/lib/mysql env_file: @@ -36,7 +36,7 @@ services: retries: 5 redis: - image: redis:7.4.0 + image: redis:7.4.3 mailhog: image: mailhog/mailhog:v1.0.1 diff --git a/docker-compose.yml b/docker-compose.yml index d5e9b3ae89..d874d2e008 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: - .env db: - image: mariadb:11.5.2 + image: mariadb:11.4.7 restart: unless-stopped volumes: - db_data:/var/lib/mysql diff --git a/docker/startup.sh b/docker/startup.sh index 2aabb3ca83..c0edd5cf46 100644 --- a/docker/startup.sh +++ b/docker/startup.sh @@ -69,7 +69,7 @@ for dir in \ 'data/private_uploads/consumables' \ 'data/private_uploads/eula-pdfs' \ 'data/private_uploads/imports' \ - 'data/private_uploads/assetmodels' \ + 'data/private_uploads/models' \ 'data/private_uploads/users' \ 'data/private_uploads/licenses' \ 'data/private_uploads/signatures' \ @@ -100,9 +100,13 @@ chown -R docker:root /var/www/html/storage/framework/cache # Fix php settings if [ -v "PHP_UPLOAD_LIMIT" ] then - echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}" - sed -i "s/^upload_max_filesize.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" /etc/php/*/apache2/php.ini - sed -i "s/^post_max_size.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" /etc/php/*/apache2/php.ini + find /etc/php -type f -name php.ini | while IFS= read -r ini; do + echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}M in $ini" + sed -i \ + -e "s/^;\? *upload_max_filesize *=.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" \ + -e "s/^;\? *post_max_size *=.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" \ + "$ini" + done fi # If the Oauth DB files are not present copy the vendor files over to the db migrations @@ -120,4 +124,9 @@ php artisan migrate --force php artisan config:clear php artisan config:cache +# we do this after the artisan commands to ensure that if the laravel +# log got created by root, we set the permissions back +touch /var/www/html/storage/logs/laravel.log +chown -R docker:root /var/www/html/storage/logs/laravel.log + exec supervisord -c /supervisord.conf diff --git a/docker/startup_alpine.sh b/docker/startup_alpine.sh index 0c99b013bc..bb9574f387 100644 --- a/docker/startup_alpine.sh +++ b/docker/startup_alpine.sh @@ -79,13 +79,18 @@ done chown -R apache:root /var/lib/snipeit/data/* chown -R apache:root /var/lib/snipeit/dumps chown -R apache:root /var/lib/snipeit/keys +chown -R apache:root /var/www/html/storage/framework/cache # Fix php settings if [ ! -z "${PHP_UPLOAD_LIMIT}" ] then - echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}" - sed -i "s/^upload_max_filesize.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" /etc/php*/php.ini - sed -i "s/^post_max_size.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" /etc/php*/php.ini + find /etc -type f -name php.ini | while IFS= read -r ini; do + echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}M in $ini" + sed -i \ + -e "s/^;\? *upload_max_filesize *=.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" \ + -e "s/^;\? *post_max_size *=.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" \ + "$ini" + done fi # If the Oauth DB files are not present copy the vendor files over to the db migrations diff --git a/docker/startup_alpine_fpm.sh b/docker/startup_alpine_fpm.sh index 3b783419eb..981d8bb4f8 100755 --- a/docker/startup_alpine_fpm.sh +++ b/docker/startup_alpine_fpm.sh @@ -99,10 +99,7 @@ then cp -a /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/ fi -# Create laravel log file -touch /var/www/html/storage/logs/laravel.log # Add correct permissions for files and directories -chown www-data:www-data /var/www/html/storage/logs/laravel.log chown -R www-data:www-data \ /var/lib/snipeit/data \ /var/lib/snipeit/dumps \ @@ -114,6 +111,11 @@ php artisan migrate --force php artisan config:clear php artisan config:cache +# Create laravel log file +touch /var/www/html/storage/logs/laravel.log +# ensure it's owned by www:data in case it was created by root +chown www-data:www-data /var/www/html/storage/logs/laravel.log + echo [INFO docker entrypoint] End script execution -exec "$@" \ No newline at end of file +exec "$@" diff --git a/pa11y.js b/pa11y.js new file mode 100644 index 0000000000..4d5a1dbba9 --- /dev/null +++ b/pa11y.js @@ -0,0 +1,24 @@ +const pa11y = require('pa11y'); + +pa11y('http://snipe-it.test', { + standard: "WCAG2AA", + level: "error", + defaults: { + "timeout": 500000, + "wait": 2000, + "ignore": [ + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18", + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail", + ], + "viewport": { + "width": 1280, + "height": 1024 + }, + }, + actions: [ + 'set field #username to admin', + 'set field #password to password', + 'click element #submit', + 'wait for path to be /', + ] +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8b6219adaa..c43d68251f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,18 +15,18 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.24.1", + "bootstrap-table": "1.24.2", "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", "css-loader": "^5.0.0", "ekko-lightbox": "^5.1.1", - "imagemin": "^8.0.1", + "imagemin": "^9.0.1", "jquery-slimscroll": "^1.3.8", "jquery-ui": "^1.14.1", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.4", + "jspdf-autotable": "^5.0.2", "less": "^4.2.2", "less-loader": "^6.0", "list.js": "^1.5.0", @@ -35,18 +35,17 @@ "select2": "4.0.13", "sheetjs": "^2.0.0", "signature_pad": "^4.2.0", - "tableexport.jquery.plugin": "1.32.0", - "tether": "^1.4.0", - "webpack": "^5.98.0" + "tableexport.jquery.plugin": "^1.33.0", + "tether": "^1.4.0" }, "devDependencies": { "all-contributors-cli": "^6.26.1", - "axios": "^1.7.2", - "babel-preset-latest": "^6.24.1", + "axios": "^1.11.0", "jquery": "<3.6.0", "laravel-mix": "^6.0.49", "lodash": "^4.17.20", - "postcss": "^8.4.5" + "postcss": "^8.5.6", + "webpack": "^5.98.0" }, "engines": { "node": ">=0.12" @@ -65,12 +64,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -481,17 +482,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -518,97 +521,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.5", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { - "version": "7.24.5", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1809,23 +1741,22 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.24.0", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1881,26 +1812,18 @@ "license": "MIT" }, "node_modules/@babel/types": { - "version": "7.24.5", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/types/node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@colors/colors": { "version": "1.5.0", "dev": true, @@ -2006,6 +1929,33 @@ "node": ">= 8" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" + }, + "node_modules/@sindresorhus/is": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.1.tgz", + "integrity": "sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@swc/helpers": { "version": "0.3.17", "license": "MIT", @@ -2015,7 +1965,8 @@ }, "node_modules/@tokenizer/token": { "version": "0.3.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, "node_modules/@trysound/sax": { "version": "0.2.0", @@ -2553,8 +2504,9 @@ } }, "node_modules/admin-lte": { - "version": "v2.4.18", - "license": "MIT", + "version": "2.4.18", + "resolved": "https://registry.npmjs.org/admin-lte/-/admin-lte-2.4.18.tgz", + "integrity": "sha512-AfIdoUWdbQA0OmW7PnP8GJ3u6RMKNXefN3DRTBHCQXd7VeyJahUfZWtV62ppDxcdjpx0L08ypPV55ARmdGdOIw==", "dependencies": { "bootstrap": "^3.4", "bootstrap-colorpicker": "^2.5.3", @@ -2747,16 +2699,6 @@ "dev": true, "license": "MIT" }, - "node_modules/array-union": { - "version": "3.0.1", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/asn1.js": { "version": "4.10.1", "license": "MIT", @@ -2796,8 +2738,9 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/atob": { "version": "2.1.2", @@ -2859,192 +2802,16 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-call-delegate": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-define-map": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-function-name": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-get-function-arity": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-hoist-variables": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-optimise-call-expression": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-regex": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-helper-replace-supers": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, "node_modules/babel-loader": { "version": "8.3.0", "dev": true, @@ -3080,22 +2847,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/babel-messages": { - "version": "6.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.11", "dev": true, @@ -3140,389 +2891,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "node_modules/babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" - } - }, - "node_modules/babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-transform-regenerator": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-transform": "^0.10.0" - } - }, - "node_modules/babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "node_modules/babel-preset-es2015": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.24.1", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.22.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-for-of": "^6.22.0", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-umd": "^6.24.1", - "babel-plugin-transform-es2015-object-super": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", - "babel-plugin-transform-regenerator": "^6.24.1" - } - }, - "node_modules/babel-preset-es2016": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-transform-exponentiation-operator": "^6.24.1" - } - }, - "node_modules/babel-preset-es2017": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.24.1" - } - }, - "node_modules/babel-preset-latest": { - "version": "6.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-preset-es2015": "^6.24.1", - "babel-preset-es2016": "^6.24.1", - "babel-preset-es2017": "^6.24.1" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-template": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-types": { - "version": "6.26.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "node_modules/babylon": { - "version": "6.18.0", - "dev": true, - "license": "MIT", - "bin": { - "babylon": "bin/babylon.js" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "license": "MIT" @@ -3607,9 +2975,10 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.2", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, - "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -3619,7 +2988,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -3629,28 +2998,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.2.1", "dev": true, @@ -3702,26 +3049,28 @@ "license": "MIT" }, "node_modules/bootstrap-table": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.24.1.tgz", - "integrity": "sha512-que7o2Z6R0I3cfwfm6qg4XnoUK9A/8162HUErFYg3fGtzjk5OjLJx6Ji9p3oKz+IAIVvBCr9sqMqujEG47k3zA==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.24.2.tgz", + "integrity": "sha512-h/09vDvypSssCdL9BlLg+o5UWBR5+0DTGZBJkeathz3h/DSbH1A+vunZcatYDFTVsLJiKYiT7o6AjGG8fW+nzA==", "peerDependencies": { "jquery": "3" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "license": "MIT", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { - "version": "3.0.2", - "license": "MIT", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4027,9 +3376,10 @@ "license": "MIT" }, "node_modules/bytes": { - "version": "3.0.0", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4039,14 +3389,41 @@ "license": "MIT" }, "node_modules/call-bind": { - "version": "1.0.7", - "license": "MIT", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -4092,9 +3469,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "funding": [ { "type": "opencollective", @@ -4120,8 +3497,9 @@ } }, "node_modules/canvg": { - "version": "3.0.10", - "license": "MIT", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", "optional": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -4178,6 +3556,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-file-extension": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/change-file-extension/-/change-file-extension-0.1.1.tgz", + "integrity": "sha512-lB0j9teu8JtDPDHRfU8pNH33w4wMu5bOaKoT4PxH+AKugBrIfpiJMTTKIm0TErNeJPkeQEgvH31YpccTwOKPRg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chardet": { "version": "0.7.0", "dev": true, @@ -4425,8 +3814,9 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4459,26 +3849,31 @@ } }, "node_modules/compression": { - "version": "1.7.4", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, - "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, - "license": "MIT" + "engines": { + "node": ">= 0.6" + } }, "node_modules/concat": { "version": "1.0.3", @@ -4549,21 +3944,34 @@ }, "node_modules/content-type": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4583,12 +3991,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/core-js": { - "version": "2.6.12", - "dev": true, - "hasInstallScript": true, - "license": "MIT" - }, "node_modules/core-js-compat": { "version": "3.37.0", "dev": true, @@ -4666,9 +4068,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5027,8 +4430,9 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -5039,8 +4443,9 @@ }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5068,8 +4473,9 @@ }, "node_modules/destroy": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5119,6 +4525,7 @@ }, "node_modules/dir-glob": { "version": "3.0.1", + "dev": true, "license": "MIT", "dependencies": { "path-type": "^4.0.0" @@ -5210,8 +4617,9 @@ } }, "node_modules/dompurify": { - "version": "2.5.2", - "license": "(MPL-2.0 OR Apache-2.0)", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", + "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==", "optional": true }, "node_modules/domutils": { @@ -5250,6 +4658,31 @@ "tslib": "^2.0.3" } }, + "node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotenv": { "version": "10.0.0", "dev": true, @@ -5263,6 +4696,19 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "license": "BSD-3-Clause", @@ -5272,8 +4718,9 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true }, "node_modules/ekko-lightbox": { "version": "5.3.0", @@ -5285,8 +4732,9 @@ "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==" }, "node_modules/elliptic": { - "version": "6.5.5", - "license": "MIT", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -5314,9 +4762,10 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5352,6 +4801,17 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/errno": { "version": "0.1.8", "license": "MIT", @@ -5372,11 +4832,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -5414,6 +4872,32 @@ "version": "1.5.2", "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5480,8 +4964,9 @@ }, "node_modules/etag": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5532,36 +5017,37 @@ } }, "node_modules/express": { - "version": "4.19.2", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -5570,20 +5056,10 @@ }, "engines": { "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/external-editor": { @@ -5603,15 +5079,24 @@ "version": "3.1.3", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { - "version": "3.3.2", - "license": "MIT", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -5670,8 +5155,9 @@ } }, "node_modules/fflate": { - "version": "0.4.8", - "license": "MIT" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" }, "node_modules/figures": { "version": "3.2.0", @@ -5711,23 +5197,52 @@ "license": "MIT" }, "node_modules/file-type": { - "version": "16.5.4", - "license": "MIT", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/file-type/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { - "version": "7.0.1", - "license": "MIT", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5736,12 +5251,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, - "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -5833,19 +5349,29 @@ } }, "node_modules/for-each": { - "version": "0.3.3", - "license": "MIT", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/form-data": { - "version": "4.0.0", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5881,8 +5407,9 @@ }, "node_modules/fresh": { "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5936,6 +5463,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/functions-have-names": { "version": "1.2.3", "license": "MIT", @@ -5964,14 +5502,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5980,6 +5524,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "dev": true, @@ -6023,37 +5579,50 @@ "version": "0.4.1", "license": "BSD-2-Clause" }, - "node_modules/globals": { - "version": "9.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/globby": { - "version": "12.2.0", - "license": "MIT", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "dependencies": { - "array-union": "^3.0.1", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby/node_modules/slash": { - "version": "4.0.0", - "license": "MIT", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6067,10 +5636,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6097,25 +5667,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "license": "MIT", @@ -6140,19 +5691,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -6367,8 +5909,9 @@ }, "node_modules/http-errors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -6399,9 +5942,10 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, - "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -6454,6 +5998,20 @@ "postcss": "^8.1.0" } }, + "node_modules/identifier-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.0.tgz", + "integrity": "sha512-Rcy5cjBOM9iTR+Vwy0Llyip9u0cA99T1yiWOhDW/+PDaTQhyski0tMovsipQ/FRNDkudjLWusJ/IMVIlG5WZnQ==", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ieee754": { "version": "1.2.1", "funding": [ @@ -6474,11 +6032,26 @@ }, "node_modules/ignore": { "version": "5.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/image-dimensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/image-dimensions/-/image-dimensions-2.3.0.tgz", + "integrity": "sha512-8Ar3lsO6+/JLfnUeHnR8Jp/IyQR85Jut5t4Swy1yiXNwj/xM9h5V53v5KE/m/ZSMG4qGRopnSy37uPzKyQCv0A==", + "bin": { + "image-dimensions": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/image-size": { "version": "0.5.5", "license": "MIT", @@ -6491,57 +6064,48 @@ } }, "node_modules/imagemin": { - "version": "8.0.1", - "license": "MIT", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-9.0.1.tgz", + "integrity": "sha512-UoHOfynN8QeqRoUGunn6ilMnLpJ+utbmleP2ufcFqaGal8mY/PeOpV43N31uqtb+CBMFqQ7hxgKzIaAAnmcrdA==", "dependencies": { - "file-type": "^16.5.3", - "globby": "^12.0.0", - "graceful-fs": "^4.2.8", - "junk": "^3.1.0", + "change-file-extension": "^0.1.1", + "environment": "^1.0.0", + "file-type": "^19.0.0", + "globby": "^14.0.1", + "image-dimensions": "^2.3.0", + "junk": "^4.0.1", + "ow": "^2.0.0", "p-pipe": "^4.0.0", - "replace-ext": "^2.0.0", - "slash": "^3.0.0" + "slash": "^5.1.0", + "uint8array-extras": "^1.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/img-loader": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "loader-utils": "^1.1.0" - }, + "node_modules/imagemin/node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", "engines": { - "node": ">=12" + "node": ">=12.20" }, - "peerDependencies": { - "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/img-loader/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/img-loader/node_modules/loader-utils": { - "version": "1.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, + "node_modules/imagemin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "engines": { - "node": ">=4.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/import-fresh": { @@ -6669,14 +6233,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/ion-rangeslider": { "version": "2.3.1", "license": "MIT", @@ -6773,7 +6329,8 @@ }, "node_modules/is-callable": { "version": "1.2.7", - "license": "MIT", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "engines": { "node": ">= 0.4" }, @@ -6856,6 +6413,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-identifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", + "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", + "dependencies": { + "identifier-regex": "^1.0.0", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "license": "MIT", @@ -6868,7 +6440,8 @@ }, "node_modules/is-number": { "version": "7.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "engines": { "node": ">=0.12.0" } @@ -6983,10 +6556,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "license": "MIT", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -7115,9 +6689,10 @@ "license": "MIT" }, "node_modules/js-tokens": { - "version": "3.0.2", - "dev": true, - "license": "MIT" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/jsesc": { "version": "2.5.2", @@ -7194,27 +6769,28 @@ } }, "node_modules/jspdf": { - "version": "2.5.1", - "license": "MIT", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz", + "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==", "dependencies": { - "@babel/runtime": "^7.14.0", + "@babel/runtime": "^7.23.2", "atob": "^2.1.2", "btoa": "^1.2.1", - "fflate": "^0.4.8" + "fflate": "^0.8.1" }, "optionalDependencies": { "canvg": "^3.0.6", "core-js": "^3.6.0", - "dompurify": "^2.2.0", + "dompurify": "^2.5.4", "html2canvas": "^1.0.0-rc.5" } }, "node_modules/jspdf-autotable": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz", - "integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz", + "integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==", "peerDependencies": { - "jspdf": "^2.5.1" + "jspdf": "^2 || ^3" } }, "node_modules/jspdf/node_modules/core-js": { @@ -7229,6 +6805,7 @@ }, "node_modules/junk": { "version": "3.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7402,6 +6979,47 @@ "node": ">=8" } }, + "node_modules/laravel-mix/node_modules/img-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-4.0.0.tgz", + "integrity": "sha512-UwRcPQdwdOyEHyCxe1V9s9YFwInwEWCpoO+kJGfIqDrBDqA8jZUsEZTxQ0JteNPGw/Gupmwesk2OhLTcnw6tnQ==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/laravel-mix/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/laravel-mix/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/laravel-mix/node_modules/p-pipe": { "version": "3.1.0", "dev": true, @@ -7711,17 +7329,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lower-case": { "version": "2.0.2", "dev": true, @@ -7760,6 +7367,14 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/md5": { "version": "2.3.0", "dev": true, @@ -7786,8 +7401,9 @@ }, "node_modules/media-typer": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7804,9 +7420,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, - "license": "MIT" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -7828,10 +7448,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "license": "MIT", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -8003,14 +7624,15 @@ "license": "ISC" }, "node_modules/nanoid": { - "version": "3.3.7", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8245,8 +7867,9 @@ }, "node_modules/on-finished": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -8255,9 +7878,10 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8311,6 +7935,36 @@ "node": ">=0.10.0" } }, + "node_modules/ow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-2.0.0.tgz", + "integrity": "sha512-ESUigmGrdhUZ2nQSFNkeKSl6ZRPupXzprMs3yF9DYlNVpJ8XAjM/fI9RUZxA7PI1K9HQDCCvBo1jr/GEIo9joQ==", + "dependencies": { + "@sindresorhus/is": "^6.3.0", + "callsites": "^4.1.0", + "dot-prop": "^8.0.2", + "environment": "^1.0.0", + "fast-equals": "^5.0.1", + "is-identifier": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "2.3.0", "dev": true, @@ -8507,31 +8161,63 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "dev": true, - "license": "MIT" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/pbkdf2": { - "version": "3.1.2", - "license": "MIT", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" }, "engines": { "node": ">=0.12" } }, + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/pdfkit": { "version": "0.12.3", "license": "MIT", @@ -8567,10 +8253,11 @@ } }, "node_modules/peek-readable": { - "version": "4.1.0", - "license": "MIT", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "type": "github", @@ -8641,7 +8328,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -8656,11 +8345,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9182,14 +8870,6 @@ "node": ">=4" } }, - "node_modules/private": { - "version": "0.1.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/process": { "version": "0.11.10", "license": "MIT", @@ -9253,8 +8933,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.12.1", - "license": "BSD-3-Clause", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { "side-channel": "^1.0.6" }, @@ -9329,8 +9010,9 @@ }, "node_modules/raw-body": { "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, - "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -9341,14 +9023,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/read-only-stream": { "version": "2.0.0", "license": "MIT", @@ -9380,32 +9054,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "3.6.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "dev": true, @@ -9444,20 +9092,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "license": "MIT" - }, - "node_modules/regenerator-transform": { - "version": "0.10.1", - "dev": true, - "license": "BSD", - "dependencies": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "license": "MIT", @@ -9474,39 +9108,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpu-core": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "node_modules/regjsgen": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.1.5", - "dev": true, - "license": "BSD", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/relateurl": { "version": "0.2.7", "dev": true, @@ -9515,13 +9116,6 @@ "node": ">= 0.10" } }, - "node_modules/replace-ext": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "dev": true, @@ -9547,6 +9141,17 @@ "dev": true, "license": "MIT" }, + "node_modules/reserved-identifiers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.0.0.tgz", + "integrity": "sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "1.22.8", "license": "MIT", @@ -9791,9 +9396,10 @@ "license": "ISC" }, "node_modules/send": { - "version": "0.18.0", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -9813,10 +9419,20 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -9883,14 +9499,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, - "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -9936,8 +9553,9 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, "node_modules/sha.js": { "version": "2.4.11", @@ -10051,6 +9669,7 @@ }, "node_modules/slash": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10091,8 +9710,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "license": "BSD-3-Clause", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -10213,8 +9833,9 @@ }, "node_modules/statuses": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -10315,14 +9936,15 @@ } }, "node_modules/strtok3": { - "version": "6.3.0", - "license": "MIT", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "peek-readable": "^5.3.1" }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "type": "github", @@ -10370,6 +9992,21 @@ "minimist": "^1.1.0" } }, + "node_modules/super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dependencies": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", "dev": true, @@ -10434,9 +10071,9 @@ } }, "node_modules/tableexport.jquery.plugin": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/tableexport.jquery.plugin/-/tableexport.jquery.plugin-1.32.0.tgz", - "integrity": "sha512-TiZpNFzazMv+gipe9u2JHgBL0guh/kloZDBA0vayWZp35nOSZkIq9XrqjxLc8oX57r7JP58QcM6qEfAgI3xEqw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/tableexport.jquery.plugin/-/tableexport.jquery.plugin-1.33.0.tgz", + "integrity": "sha512-GAN0fOL5+6hqowPgklnqCTjzQ53Lyh6QEQmOKwHsF6Cg87QS+fU5syajpL3Ztfr8F3MtpZDaR1b0anlMHzQbgg==", "dependencies": { "file-saver": ">=2.0.4", "html2canvas": ">=1.0.0", @@ -10585,6 +10222,20 @@ "dev": true, "license": "MIT" }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/timers-browserify": { "version": "2.0.12", "dev": true, @@ -10620,17 +10271,28 @@ "dev": true, "license": "MIT" }, - "node_modules/to-fast-properties": { - "version": "1.0.3", - "dev": true, - "license": "MIT", + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/to-regex-range": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": { "is-number": "^7.0.0" }, @@ -10640,21 +10302,23 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/token-types": { - "version": "4.2.1", - "license": "MIT", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.4.tgz", + "integrity": "sha512-MD9MjpVNhVyH4fyd5rKphjvt/1qj+PtQUz65aFqAZA6XniWAuSFRjLk3e2VALEFlh9OwBpXUN7rfeqSnT/Fmkw==", "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "type": "github", @@ -10688,8 +10352,9 @@ }, "node_modules/type-is": { "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -10698,10 +10363,34 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typedarray": { "version": "0.0.6", "license": "MIT" }, + "node_modules/uint8array-extras": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", + "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/umd": { "version": "3.0.3", "license": "MIT", @@ -10783,6 +10472,17 @@ "version": "0.2.9", "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.1", "dev": true, @@ -10793,8 +10493,9 @@ }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11424,13 +11125,16 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.15", - "license": "MIT", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { @@ -11477,9 +11181,10 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.0", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 68687095c0..3237ef285a 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ }, "devDependencies": { "all-contributors-cli": "^6.26.1", - "axios": "^1.7.2", - "babel-preset-latest": "^6.24.1", + "axios": "^1.11.0", "jquery": "<3.6.0", "laravel-mix": "^6.0.49", "lodash": "^4.17.20", - "postcss": "^8.4.5" + "postcss": "^8.5.6", + "webpack": "^5.98.0" }, "dependencies": { "@fortawesome/fontawesome-free": "^6.7.2", @@ -35,18 +35,18 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.24.1", + "bootstrap-table": "1.24.2", "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", "css-loader": "^5.0.0", "ekko-lightbox": "^5.1.1", - "imagemin": "^8.0.1", + "imagemin": "^9.0.1", "jquery-slimscroll": "^1.3.8", "jquery-ui": "^1.14.1", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.4", + "jspdf-autotable": "^5.0.2", "less": "^4.2.2", "less-loader": "^6.0", "list.js": "^1.5.0", @@ -55,8 +55,7 @@ "select2": "4.0.13", "sheetjs": "^2.0.0", "signature_pad": "^4.2.0", - "tableexport.jquery.plugin": "1.32.0", - "tether": "^1.4.0", - "webpack": "^5.98.0" + "tableexport.jquery.plugin": "^1.33.0", + "tether": "^1.4.0" } } diff --git a/public/css/build/AdminLTE.css b/public/css/build/AdminLTE.css index 46885f5f02..99de297d8c 100644 --- a/public/css/build/AdminLTE.css +++ b/public/css/build/AdminLTE.css @@ -5418,3 +5418,5 @@ hr { } } + +/*# sourceMappingURL=AdminLTE.css.map*/ \ No newline at end of file diff --git a/public/css/build/AdminLTE.css.map b/public/css/build/AdminLTE.css.map index 8d6c361dd7..aa7ebac23b 100644 --- a/public/css/build/AdminLTE.css.map +++ b/public/css/build/AdminLTE.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./node_modules/admin-lte/build/less/AdminLTE.less","webpack:///./node_modules/admin-lte/build/less/core.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/clearfix.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/vendor-prefixes.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/grid.less","webpack:///./node_modules/admin-lte/build/less/header.less","webpack:///./node_modules/admin-lte/build/less/mixins.less","webpack:///./node_modules/admin-lte/build/less/sidebar.less","webpack:///./node_modules/admin-lte/build/less/sidebar-mini.less","webpack:///./node_modules/admin-lte/build/less/control-sidebar.less","webpack:///./node_modules/admin-lte/build/less/dropdown.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/border-radius.less","webpack:///./node_modules/admin-lte/build/less/forms.less","webpack:///./node_modules/admin-lte/build/less/progress-bars.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/progress-bar.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/gradients.less","webpack:///./node_modules/admin-lte/build/less/small-box.less","webpack:///./node_modules/admin-lte/build/less/boxes.less","webpack:///./node_modules/admin-lte/build/less/info-box.less","webpack:///./node_modules/admin-lte/build/less/timeline.less","webpack:///./node_modules/admin-lte/build/less/buttons.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/opacity.less","webpack:///./node_modules/admin-lte/build/less/callout.less","webpack:///./node_modules/admin-lte/build/less/alerts.less","webpack:///./node_modules/admin-lte/build/less/navs.less","webpack:///./node_modules/admin-lte/build/less/products.less","webpack:///./node_modules/admin-lte/build/less/table.less","webpack:///./node_modules/admin-lte/build/less/labels.less","webpack:///./node_modules/admin-lte/build/less/direct-chat.less","webpack:///./node_modules/admin-lte/build/less/users-list.less","webpack:///./node_modules/admin-lte/build/less/carousel.less","webpack:///./node_modules/admin-lte/build/less/modal.less","webpack:///./node_modules/admin-lte/build/less/social-widgets.less","webpack:///./node_modules/admin-lte/build/less/treeview.less","webpack:///./node_modules/admin-lte/build/less/mailbox.less","webpack:///./node_modules/admin-lte/build/less/lockscreen.less","webpack:///./node_modules/admin-lte/build/less/login_and_register.less","webpack:///./node_modules/admin-lte/build/less/404_500_errors.less","webpack:///./node_modules/admin-lte/build/less/invoice.less","webpack:///./node_modules/admin-lte/build/less/profile.less","webpack:///./node_modules/admin-lte/build/less/bootstrap-social.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/buttons.less","webpack:///./node_modules/admin-lte/build/less/fullcalendar.less","webpack:///./node_modules/admin-lte/build/less/select2.less","webpack:///./node_modules/admin-lte/build/less/datepicker.less","webpack:///./node_modules/admin-lte/build/less/miscellaneous.less","webpack:///./node_modules/admin-lte/build/less/print.less"],"names":[],"mappings":"AAAA;;;;;;;GCOE,gDACE,YAIJ,KACE,sEACA,eACA,CAKF,cALE,kBACA,gBCHA,SDSA,YACA,iBACA,CCXA,+BAEE,YACA,cAEF,eACE,WDOF,uBACE,iBACA,cACA,gBACA,4EACA,kBAIJ,cACE,yBAMF,8BEyKE,4EACG,4EFpKH,gPACA,YAEA,8DACE,cAEF,uDACE,eAIA,2FACE,eAKF,mFE6EF,mCACI,0BAEe,EF1ErB,iBACE,+BACA,yBACA,YAGF,yBACE,iBACE,gCAIJ,aACE,gBACA,aACA,WACA,6BAIF,2DAII,eAJJ,oBAOI,MACA,QACA,OATJ,2CAaI,iBACA,oEACE,mBAGJ,6BAEI,eApBN,gBAwBI,gBAIJ,6QEuEE,wBACK,eACG,CF1DV,SACE,iBACA,aGlIA,kBACA,iBACA,kBACA,mBHoIF,0CAYE,sCAAa,CAIf,EACE,cAGF,yBAGE,aACA,qBACA,cAIF,aACE,mBACA,eAFF,mBAKI,WACA,cACA,eI5KJ,aACE,kBACA,iBACA,aAHF,qBFyLE,+CACK,uCEnLH,gBACA,kBACA,YACA,gBACA,gBACA,qCACE,cAbN,+CAkBI,8BACA,yBACA,2GAEE,4BACA,8BAEF,iEACE,WACA,UAEF,qEACE,WAEF,0EACE,WAjCN,4DAuCI,YACA,yFAEI,cACA,wBAKJ,oDACE,WACA,4CACE,mBAHJ,8BAOI,cACA,UAxDR,6BA8DI,WACA,6BACA,sBACA,aAEA,wBACA,oCACE,eAAS,CAEX,mCACE,WAEF,uEAEE,uBAGF,iCACE,gCAAa,CACb,wCACE,gBACA,gBAnFR,uCAwFI,aAxFJ,gIA+FM,iBA/FN,sCAqGI,kBACA,QACA,UACA,kBACA,cACA,gBACA,eA3GJ,mBFyLE,yCACK,iCEzEH,cACA,WACA,YACA,eACA,iBACA,kBACA,YACA,sDACA,eACA,gBACA,gBA3HJ,uBA8HM,YACA,yCACA,cAhIN,4BAuIM,cAvIN,gCA0IQ,gBACA,gBA3IR,6CA8IQ,eACA,kBACA,iBAhJR,8BAoJM,aApJN,kCAuJQ,eACA,gBAxJR,+CA2JQ,eACA,kBACA,iBA7JR,oCAkKM,WACA,YACA,WApKN,2BAyKI,WAKJ,gBACE,kBACA,oBAFF,mBAKI,SACA,eANJ,yBAQM,eACA,qBACA,iBACA,gBAXN,4BAgBI,YACA,uBACA,aACA,gBACA,eACA,gBACA,kBACA,SACA,WCnIF,kBD2GF,iCA2BM,WACA,qBACA,qBA7BN,uHA+BQ,iBA/BR,yCAmCM,cAAS,CAIb,qDAEI,kBACA,eACA,MACA,QACA,WACA,mBACA,kBARJ,sCAUM,eAMR,eACE,WACA,SACA,SACA,aAIF,yBACE,mCACE,WAIF,gCACE,SACA,WAGF,qCACE,iBACA,oBACA,kBAKJ,yBACE,aACE,kBADF,wCAII,WACA,WALJ,qBAQI,SARJ,iCAWI,aAMJ,oDACE,qBADF,+CAGI,cACA,kBACA,MACA,YExRN,cACE,kBACA,MACA,OACA,iBACA,gBACA,YACA,YJuMA,2EACG,2EAEK,2NIpMR,uCACE,sDJ4HE,2BAEe,CI9HjB,CAQA,yDJmHF,oCACI,2BAEe,EIhHjB,qDJ6GF,+BACI,sBAEe,EI1GrB,SACE,oBAIF,0BAEI,yBAKJ,YACE,kBACA,WACA,aACA,gBL1CA,qCAEE,YACA,cAEF,kBACE,WKgCJ,uBAOI,WACA,eACA,YATJ,kBAYI,yBACA,cACA,kBACA,UAfJ,oBAiBM,gBACA,kBAlBN,oBAqBM,qBACA,kBACA,eACA,eAxBN,gFA4BQ,iBAOR,cACE,gBACA,SACA,UAHF,iBAMI,kBACA,SACA,UARJ,mBAUM,2BACA,cAXN,6EAeQ,WAfR,gDAoBM,iBApBN,wBAuBM,eAvBN,wBA2BI,4BACA,eA5BJ,0FAgCI,WACA,YACA,UACA,kBJqEF,sCACK,8CACG,6HI1GV,kCAuCI,kBACA,QACA,WACA,gBA1CJ,0GJkEE,iCACI,wBAEe,CIrErB,qCAoDI,cCnIF,yBAEE,uIAMI,2BACA,YAPJ,6CLqIF,+BACI,uBKxHE,qBACA,YAfJ,gDAoBM,kBApBN,kDAsBQ,eAtBR,uDAyBQ,4BAGF,sEAEI,+BA9BV,+DAoCQ,gBACA,mBACA,+BAtCR,0ZAoDI,uBACA,+BAAmB,CArDvB,kDA2DM,WA3DN,6DA6DQ,cACA,kBACA,mBACA,eAhER,2DAmEQ,aAnER,oDAyEM,kBAOV,yBAEE,uNAOM,wBACA,kBACA,YACA,UAVN,+FAeM,MACA,iBACA,2BACA,yBAlBN,gHAsBM,4BACA,YACA,qBACA,qBACA,oBACA,YA3BN,2IA6BQ,aA7BR,uGAiCM,SACA,eAMR,oFAGI,iBAHJ,yCAMI,oFAKJ,gEAGE,mBACA,gBAGF,oBACE,iBAGF,sCAEE,gBACA,mBAGF,mBACE,kBADF,yCAGI,kBACA,WACA,QACA,gBChKJ,oBACE,eACA,aACA,SAIF,qCAEE,MACA,aACA,YN8KA,yCACK,gCACG,CM3KV,iBACE,kBACA,iBACA,aAEA,0CACE,mBANJ,8BAUI,kBAIA,gGAEE,QAMN,yJAII,wCAIJ,iFAGI,QAEF,qIAII,oBAMN,wBAEI,eACA,YACA,gBACA,oBAQE,kKAGE,oBANR,oCHJE,gBGiBI,8EAEE,gBACA,kBACA,kCACA,oCAlBR,0CAqBQ,eAMA,+LAIE,gBACA,kBACA,mBAMR,wDACE,cADF,kCAGI,oBAMN,yBACE,gBACA,eACA,eACA,mBAIF,4BACE,cACA,gBACA,eAIF,sBACE,gBACA,UACA,eAHF,2BAMI,cACA,kBPrIF,mEAEE,YACA,cAEF,iCACE,WOwHJ,uDASM,aATN,iCAaI,WACA,WACA,YACA,kBACA,kBACA,iBAlBJ,iCAqBI,iBACA,eAtBJ,6DAwBM,SAxBN,mCA2BM,SACA,eA5BN,gCAgCI,SAKJ,sBACE,cAEA,gEAEE,mBALJ,qDASI,sBATJ,0DAYQ,mBACA,cAEA,0LAGE,0BACA,4BAEF,iMAGE,mBAEF,gEACE,WAMA,uRAIE,mBACA,WAtCZ,iGA+CI,WAMI,uDACE,mBAtDV,8DA0DY,cASZ,uBACE,cAEA,kEAEE,mBACA,8BANJ,sDAUI,sBAVJ,2DAaQ,mBACA,WAEA,6LAGE,0BACA,4BAEF,oMAGE,mBAMA,2RAIE,mBACA,WApCZ,mGA6CI,WA7CJ,6CAiDI,kBAGI,wDACE,mBArDV,+DAyDY,cCtSZ,eACE,wCACA,kBAFF,oBAII,WAJJ,gFASI,kBATJ,0BAYI,yBACA,WAbJ,wBAgBI,sBAKJ,gIAQI,YAEA,UACA,SACA,SAZJ,yIAMM,kBANN,8JJmDE,2BACA,4BACA,6BACA,4BIrCE,sBACA,iBACA,gCACA,WACA,eArBJ,oKJmDE,yBACA,0BACA,+BACA,8BI3BE,eACA,sBACA,iBACA,6BACA,qBAKA,kBAJA,6LACE,0BACA,sBAIF,sLACE,qBACA,gBAxCN,2JA8CI,iBACA,SACA,UACA,gBACA,kBAlDJ,0KAoDM,cACA,mBACA,gCAEA,4LACE,mBACA,qBAOR,6DAIM,WACA,gBACA,uBACA,aAPN,2MAYQ,WAQR,wDAKM,SAEA,aAPN,gEAUQ,2BACA,WACA,YAZR,2DAgBQ,UACA,kBACA,WACA,eACA,kBApBR,iEAuBU,WACA,eACA,kBACA,MACA,QA3BV,0DAgCQ,kBACA,eACA,WRrIN,6HAEE,YACA,cAEF,8DACE,WQ0IJ,qDAGM,aAHN,wDAMQ,eACA,UACA,gBACA,WATR,+DAaQ,UACA,SAOR,sCC/KE,0BACC,yBDiLC,gBACA,mBACA,YAEA,uFC9KF,+BACC,8BDsKH,qDAaM,aACA,aACA,kBAfN,yDAkBQ,UACA,YACA,WACA,iBACA,yBACA,gCAvBR,uDA0BQ,UACA,WACA,yBACA,eAEA,gBA/BR,6DAiCU,cACA,eAlCV,iDAyCM,aACA,gCACA,0BRhNJ,+GAEE,YACA,cAEF,uDACE,WQ+JJ,mDA8CQ,qBACA,4EACE,0BACA,sBAjDV,mDAwDM,yBACA,aR9NJ,mHAEE,YACA,cAEF,yDACE,WQ+JJ,gEA4DQ,WAEE,+FACE,0BA/DZ,mCAsEI,WACA,WACA,YACA,kBACA,kBACA,gBACA,4DACE,WACA,eACA,gBACA,kBAON,2CACE,8CPvPA,yEACK,0BACG,CO0PV,mBACE,GACE,gGACA,8EACA,UAGF,IACE,kGACA,8EAGF,IACE,gGACA,UAGF,IACE,+FAA8B,CAGhC,GACE,iEAAW,EAIf,2BACE,GACE,oDACA,2CACA,UAGF,IACE,qDACA,2CAGF,IACE,oDACA,UAGF,IACE,mDAAsC,CAGxC,GACE,oCAAmB,EAKvB,mCAEI,kBAFJ,kDAIM,kBACA,QACA,UAKN,yBACE,gCACE,YADF,mCAGI,gBAHJ,kDAKM,kBACA,SACA,UACA,sBACA,iBErVR,cNoEE,gBMjEA,qBACA,kCAFA,uCACA,CAKA,oBAHE,oBACA,CAEF,6GAGE,WACA,UAGF,0BACE,wBACA,qBACA,gBAKF,8BAEI,cAFJ,iFAMI,qBACA,wCAPJ,oCAUI,cAIJ,8BAEI,cAFJ,iFAMI,qBACA,wCAPJ,oCAUI,cAIJ,4BAEI,cAFJ,6EAMI,qBACA,wCAPJ,kCAUI,cAMN,gCNCE,gBMEE,qBACA,sBAOA,+FNVF,gBMgBF,cACE,eAIF,0BACE,iBAGF,qIAGE,iBAGF,qIAGE,iBCpGF,kCV8DE,wBACQ,gBU5DR,gGP+DA,kBOzDF,0BAEE,YACA,gFPsDA,kBOjDF,0BAEE,WACA,gFP8CA,kBOzCF,4BAEE,WACA,oFPsCA,kBOhCF,mBACE,kBACA,WACA,aACA,qBACA,kBALF,iCAOI,WACA,kBACA,SAIF,qDAEE,WAGF,qDAEE,WAEF,uDAEE,UAKJ,+BAEI,gBAFJ,iCAKI,YAKJ,uBAEI,SAMJ,+CCpFE,yBAGA,mFCkDE,qKAAkB,CFoCtB,0CCzFE,yBAGA,8ECkDE,qKAAkB,CFyCtB,sCC9FE,yBAGA,0ECkDE,qKAAkB,CF8CtB,2CCnGE,yBAGA,+ECkDE,qKAAkB,CFmDtB,uCCxGE,yBAGA,2ECkDE,qKAAkB,CCnDtB,WVmEE,kBUjEA,kBACA,cACA,mBACA,gFALF,kBAQI,aARJ,6BAYI,kBACA,kBACA,cACA,WACA,yBACA,cACA,WACA,0BACA,qBACA,mCACE,WACA,2BAvBN,cA4BI,eACA,gBACA,gBACA,mBACA,UAhCJ,aAqCI,eArCJ,mBAuCM,cACA,cACA,eACA,eA1CN,2BA+CI,UA/CJ,iBbyLE,kCACK,0BarIH,kBACA,UACA,WACA,UACA,eACA,sBAIF,iBACE,qBACA,cAFF,uBAKI,eAKN,yBAEE,WACE,kBADF,iBAGI,aAHJ,aAMI,gBCjFN,KACE,kBXmEA,kBWjEA,gBACA,6BACA,mBACA,WACA,gFAGA,iBACE,yBAEF,cACE,yBAEF,gBACE,yBAEF,iBACE,yBAEF,iBACE,yBAEF,iBACE,yBAIF,4DAGI,aAjCN,qBAuCM,gCACA,SACA,kCACE,mBAMN,8BAEI,iBACA,cAnDN,mBAwDI,+BAxDJ,kBA2DI,8BAOF,eACE,aADF,4CAIM,uBAIA,yEACE,0BAMN,2BXxCF,yBWwCE,uCXtCA,WACA,mBACA,yBWoCA,qFXjCE,WWoCF,2BX3CF,yBW2CE,uCXzCA,WACA,mBACA,yBWuCA,qFXpCE,WWuCF,wBX9CF,yBW8CE,oCX5CA,WACA,mBACA,yBW0CA,+EXvCE,WW0CF,0BXjDF,yBWiDE,sCX/CA,WACA,mBACA,yBW6CA,mFX1CE,WW6CF,2BXpDF,yBWoDE,uCXlDA,WACA,mBACA,yBWgDA,qFX7CE,WWgDF,2BXvDF,yBWuDE,uCXrDA,WACA,mBACA,yBWmDA,qFXhDE,WWkBJ,2CAmCI,SACA,wCAIF,sCAEI,WA5GR,qBAqHM,kBArHN,iBA2HI,kBACA,WACA,gBACA,eACA,mBAIJ,wFAKI,kBACA,MACA,OACA,WACA,YATJ,wCAaI,WACA,8BX7EF,kBW+DF,gDAiBM,kBACA,QACA,SACA,kBACA,iBACA,WACA,eAvBN,kDA4BI,0BftJF,2GAEE,YACA,cAEF,oDACE,We4JJ,YACE,WACA,cACA,aACA,kBAGA,wBACE,gCACA,uCACE,mBAVN,+EAmBI,qBACA,eACA,SACA,cAtBJ,wDA2BI,iBA3BJ,uBA8BI,YACA,gBACA,mBAhCJ,6CAkCM,kBAGF,iDAEI,QACA,UAxCR,2CA6CM,qBAMN,cACE,YACA,eACA,uBACA,cACA,wCAEE,cAEF,yBACE,wCAKJ,UXnKE,yBACA,0BACA,+BACA,8BWkKA,aACA,qBNjPA,4BACC,2BM6OH,iBAQI,gBARJ,cAaI,eAbJ,4BAiBI,aAEF,uCACE,YApBJ,oBXnKE,yBACA,0BACA,6BACA,8BWgKF,0BXhKE,4BWgMF,sCXnME,yBACA,0BACA,8BACA,CWuMF,YXvME,8BWkMA,6BACA,aACA,sBAGF,cAEE,cAEE,0CACE,WACA,mBAMN,cACE,mBADF,2BAII,cACA,6Bf5RF,mEAEE,YACA,cAEF,iCACE,WeuRA,wCACE,gBAEF,yCACE,cAVN,+BAcM,WAdN,4BAkBI,iBACA,WAnBJ,wBAsBI,WACA,cACA,gBAxBJ,0BA2BI,gBACA,eASJ,WACE,SACA,UACA,gBACA,cAJF,cXjQE,kBWyQE,aACA,mBACA,kBACA,8BACA,WACA,2BACE,gBAdN,mCAkBM,oBAlBN,oBAsBM,qBACA,gBACA,gBAxBN,qBA6BM,iBACA,cA9BN,qBAmCM,aACA,YACA,cArCN,mFAwCQ,iBACA,eAIJ,2BACE,qBAGF,mBACE,WADF,yBAGI,6BACA,gBAJJ,0BAQI,6BAzDR,mBAgEI,0BAhEJ,oBAmEI,0BAnEJ,iBAsEI,0BAtEJ,oBAyEI,0BAzEJ,oBA4EI,0BA5EJ,mBAgFI,qBACA,YACA,aAQJ,MACE,0BADF,YAKI,mBf3ZF,qCAEE,YACA,cAEF,kBACE,WegZJ,gBAQM,WACA,YACA,6BXrWJ,kBW2VF,oBAeM,yBAfN,qBAkBM,yBAlBN,qBAuBM,iBACA,iBAxBN,2BA0BQ,cACA,gBA3BR,wBX3VE,kBW6XI,mBACA,iBACA,kBACA,aArCN,2BAuCQ,eACA,gBACA,eAzCR,4DA4CQ,gBACA,eACA,kBACA,SfrcN,6DAEE,YACA,cAEF,8BACE,We2cJ,WACE,gBAKF,mBAEI,WCleJ,UACE,cACA,gBACA,gBACA,WACA,4CZ+DA,sDY7DA,mBAPF,gBASI,eATJ,oBAYI,0BACA,iBACA,WACA,sDZqDF,gBYpEF,kCAoBM,gBAKN,eZiDE,2BACA,0BACA,6BACA,8BYlDA,cACA,WACA,YACA,WACA,kBACA,eACA,iBACA,0BATF,mBAWI,eAIJ,kBACE,iBACA,iBAGF,iBACE,cACA,gBACA,eAGF,qCAEE,cACA,eACA,mBACA,gBACA,uBAGF,eACE,yBAGF,eACE,cAGF,sBACE,SCpEF,UACE,kBACA,gBACA,UACA,gBAGA,iBACE,WACA,kBACA,MACA,SACA,UACA,gBACA,UACA,SboDF,kBanEF,aAoBI,kBACA,kBACA,mBjBdF,uCAEE,YACA,cAEF,mBACE,WiBdJ,4BhB+DE,4CACQ,oCGGR,kBatCI,aACA,gBACA,WACA,iBACA,kBACA,UACA,kBAnCN,kCAuCQ,WACA,YACA,aACA,eA1CR,6CA6CQ,SACA,WACA,gCACA,aACA,eACA,gBAlDR,+CAoDU,gBApDV,wFAyDQ,aAzDR,2DAkEM,WACA,YACA,eACA,iBACA,kBACA,WACA,mBACA,kBACA,kBACA,UACA,MA5EN,2BAmFM,gBACA,YACA,qBACA,sBbnBJ,kBa0BF,oCAGM,mBACA,sBhBlCJ,wBACQ,gBgB6BV,qDAOQ,yBCpGR,KdmEE,kBHJA,wBACQ,gBiB7DR,6BAEA,eACE,yBAIF,cdyDA,gBcvDE,wBACA,gBAEA,iBAIF,YACE,oDACA,2CACA,CAGF,WACE,aAIF,cACE,kBACA,gBAFF,+BAII,kBACA,MACA,QACA,eACA,gBACA,gBACA,iBC1CJ,UAGA,wBDyCI,aACA,gBACA,eACA,cAMN,aACE,yBACA,WACA,kBACA,0DAGE,yBAIJ,aACE,yBACA,qBACA,0DACE,yBAIJ,aACE,yBACA,qBACA,0DACE,yBAIJ,UACE,yBACA,qBACA,iDACE,yBAIJ,YACE,yBACA,qBACA,uDACE,yBAIJ,aACE,yBACA,qBACA,0DACE,yBAIJ,aACE,sBACA,uBACA,WACA,0DAGE,yBACA,gCAIJ,UjBnDE,wBACQ,gBiBuDV,uBjBxDE,kDACQ,0CiB4DV,SdzDE,kBc2DA,kBACA,iBACA,qBACA,eACA,YACA,kBACA,WACA,sBACA,yBACA,eAXF,+CAcI,eACA,cAGF,eACE,mBACA,WACA,kBAGF,+BACE,oDACA,2CACA,CA3BJ,gBAgCI,kBACA,SACA,YACA,eACA,gBE/JJ,ShBkEE,kBgBhEA,gBACA,4BACA,2BAJF,WAMI,WACA,0BACA,iBACE,WATN,YAaI,aACA,gBAdJ,sBAiBI,gBAjBJ,kCAqBI,sBAIF,wBAEE,qBAEF,yBAEE,qBAEF,sBAEE,qBAEF,yBAEE,qBCxCJ,OjBmEE,kBiBnEF,UAGI,gBAHJ,aAMI,kBANJ,cASI,WFXF,WAGA,yBEUE,oBFbF,WAGA,yBEDF,SAgBI,WACA,0BAKJ,eAEE,qBAGF,2BAGE,qBAGF,eAEE,qBAGF,YAEE,qBCxCF,iDAII,WACA,mBAKJ,gBlByDE,gBkBtDE,iCACA,WAJJ,oEAQM,iBARN,iFAcI,yBAdJ,uBAiBI,gBAKJ,kBlBmCE,gBkBhCE,aACA,kCACA,WALJ,wDASI,uBACA,WACA,aACA,0BAZJ,uBAgBI,6BACA,WACA,mBACA,iBACA,yBAKJ,iBACE,mBACA,gBACA,gFACA,kBAJF,2BAMI,SACA,4BblEF,4BACC,2Ba0DH,8BAWM,iCACA,mBA4BA,iBA1BA,yCACE,WAfR,gCAmBQ,WlBTN,gBkBWM,2CACE,WAEF,sEAEE,uBACA,SAEF,sCACE,WAGJ,0JAII,yBArCV,qCA4CM,yBACA,oFAEE,sBACA,WAhDR,uCAmDQ,6BACA,0BACA,2BArDR,4CA2DM,cACA,qDAEI,8BAMN,sCACE,qBADF,yCAGI,YAHJ,uDAMI,eANJ,yDAQM,sBAEF,gEAEI,0BACA,+BAjFZ,qCAwFM,iBACA,eACA,eACA,WA3FN,mIA+FQ,iBA/FR,8BAqGI,gBACA,abzJF,+BACC,8Ba6JC,iFAEE,uBACA,WAIJ,iDAGM,yBAIN,8CAGM,yBAIN,gDAGM,yBAIN,iDAGM,yBAIN,iDAGM,yBAIN,iDAGM,yBAOR,iBAEI,mBACA,WAEF,iClB1JA,0BmBpEF,eACE,gBACA,SACA,UAHF,qBnBoEE,kBHJA,4CACQ,oCsBzDN,eACA,gBvBAF,uDAEE,YACA,cAEF,2BACE,WuBfJ,4BAYI,WAZJ,gCAcM,WACA,YAfN,6BAmBI,iBAnBJ,8BAsBI,gBAtBJ,oCAyBI,cACA,WACA,gBACA,mBACA,uBAIJ,2BtB+BE,wBACQ,gBGGR,gBmBhCA,gCACA,wCACE,sBCrCJ,kHAQQ,6BARR,mBAcI,gCAdJ,uBAkBI,eAKJ,wLAQQ,yBARR,wDAeM,wBAMJ,yDAGE,SAMF,4DACE,kBAIJ,gBAEI,gBAFJ,gBAKI,iBChEJ,eACE,yBACA,WCFF,uBjBOE,6BACC,4BiBLC,kBACA,kBACA,UASJ,wEzB4HE,+BACI,sBAEe,CyBxHrB,sBALE,aACA,aACA,cAGF,mCAEE,cAGF,iBAEE,mB1BnBA,+CAEE,YACA,cAEF,uBACE,W0BgBJ,4CzBgLE,qDACG,6CAEK,4GyB9KV,kBtBgCE,kBsB9BA,kBACA,iBACA,mBACA,yBACA,oBACA,WAGA,iDAEE,kBACA,WACA,SACA,yBACA,2BACA,YACA,SACA,QACA,oBAGF,wBACE,iBACA,gBAEF,yBACE,iBACA,gBAEF,yBACE,kBACA,cACA,+DAEE,WACA,UACA,+BACA,0BAKN,iBtBZE,kBsBcA,WACA,WACA,YACA,wBACE,YAIJ,kBACE,cACA,kBACA,eAGF,kBACE,gBAGF,uBACE,WAIF,iDzBiCE,+BACI,sBAEe,CyB9BrB,sBzB2BE,kCACI,0ByB1BJ,kBACA,MACA,SACA,aACA,WACA,mBACA,WACA,cAIF,kBAII,uCACA,aACA,S1BzHF,iDAEE,YACA,cAEF,wBACE,W0BoHA,+BACE,mBAKN,mBtBrEE,kBsBuEA,WACA,WAGF,oBACE,iBACA,WAGF,0CAEE,cAGF,oBACE,gBAGF,sBACE,eAGF,oBACE,WACA,gBAGF,mBACE,WAIF,6CtBnHI,mBACA,qBACA,WACA,uGAEE,0BsBkHN,8CtBvHI,mBACA,qBACA,WACA,yGAEE,0BsBsHN,8CtB3HI,mBACA,qBACA,WACA,yGAEE,0BsB0HN,2CtB/HI,mBACA,qBACA,WACA,mGAEE,0BsB8HN,8CtBnII,mBACA,qBACA,WACA,yGAEE,0BuB7DN,eAGI,UACA,WACA,aACA,kBANJ,mBvBoEE,kBuB3DI,eACA,YAGA,+DAEE,WAMR,kCAEE,cAGF,iBACE,gBACA,WACA,gBACA,mBACA,uBAGF,iBACE,WACA,eCnCA,+CAEE,sBAHJ,sBAMI,eACA,kBACA,QACA,UACA,qBACA,iBCXJ,OACE,0BAGF,ezBgEE,gBHJA,8CACQ,sC4B1DR,SACA,wC5BwDA,8CACQ,uC4BpDV,cACE,4BAGF,cACE,yBAIF,0DAOI,qBAIJ,0DAOI,qBAIJ,oDAOI,qBAIJ,0DAOI,qBAIJ,wDAOI,qBCxEJ,YACE,YACA,kBAIF,iCAGI,aACA,arBZF,4BACC,2BqBOH,mCASI,aACA,kBACA,eACA,gBACA,qCAbJ,+BAiBI,aAjBJ,gCAqBI,kBACA,SACA,SACA,kBAxBJ,oCA0BM,WACA,YACA,sBA5BN,yBAgCI,iBAKJ,mCAGI,arBhDF,4BACC,2BqB4CH,qCAQI,eACA,kBACA,eACA,gBAXJ,iCAeI,aAfJ,sEAmBI,iBAnBJ,sCAwBM,WACA,YACA,WCzEN,eACC,aACA,gBACA,UACA,SACA,iBALD,8BAOG,kBAPH,kBAUG,SAVH,oBAYK,yBACA,cACA,eAdL,gFAkBO,WAlBP,wLAwBO,WAMP,2BAEE,gBACA,YACA,wBACA,2BAGF,qCAEG,iBACC,YCtCJ,yBAEI,SAIJ,kBACE,YAMF,iDAJI,gCAIJ,mBAEE,aAFF,sBAII,eACA,SALJ,sBAQI,SACA,gBAIJ,mBACE,WACA,eAGF,sBACE,aAGF,wBAGI,WACA,YACA,sBACA,mBACA,kBAIJ,yBACE,gBACA,WAGF,2EAGE,cAGF,yBACE,aACA,mBAGF,yBACE,WACA,eAGF,yBACE,kBACA,eACA,WACA,kBACA,iCACE,UADF,qCAGI,eACA,YC3EN,YACE,mBAGF,iBACE,eACA,kBACA,mBACA,gBAJF,mBAMI,WAIJ,oBACE,gBACA,cACA,eAIF,6BACE,kBACA,gBAIF,iB7BwCE,kB6BtCA,UACA,gBACA,kBACA,sBACA,YAIF,kB7B8BE,kB6B5BA,kBACA,WACA,UACA,gBACA,YACA,WAPF,sB7B8BE,kB6BpBE,WACA,YAKJ,wBACE,iBADF,sCAGI,SAHJ,6BAMI,sBACA,SACA,eAIJ,mBACE,gBClEF,2BAEE,eACA,kBACA,mBACA,gBALF,+BAOI,WAIJ,2BAEE,YACA,mBAGF,yBAEE,YACA,eACA,kDACE,UACA,iBAIJ,mCAEE,gBACA,aACA,aACA,WALF,iFAOI,WAIJ,iCAEE,SACA,kBACA,oBAGF,mBACE,cC/CF,YACE,YACA,mBACA,qCACE,YAJJ,sBAQI,WACA,gBACA,gBACA,+CACE,WACA,mBAbN,2BAkBI,kBAWA,cAVA,oDACE,eApBN,8BAuBM,gBACA,eACA,uDACE,mBCzBR,SACE,kBACA,gBACA,yBACA,aACA,iBAGF,eACE,aCTF,kBACE,cACA,YACA,YACA,yBAGF,kBACE,eACA,eAGF,MACE,gCACA,mBACA,oBACA,WACA,mBACE,gBACA,gBACA,iBARJ,kBAWI,mBCRJ,YACE,kBACA,kBACA,gBACA,mBACA,gBACA,uBANF,yBAQI,kBACA,OACA,MACA,SACA,WACA,iBACA,gBACA,kBACA,sCAEF,mBACE,kBADF,gCAGI,iBACA,WACA,gBAGJ,mBACE,kBADF,gCAGI,iBACA,WACA,gBAGJ,mBACE,kBADF,gCAGI,iBACA,WACA,gBAKN,iBA3CE,kBACA,kBACA,gBACA,mBACA,gBACA,uBAwCA,YACA,WACA,UAJF,8BApCI,kBACA,OACA,MACA,SACA,WACA,iBACA,gBAEA,sCAEF,wBACE,kBADF,qCAGI,iBACA,WACA,gBAGJ,wBACE,kBADF,qCAGI,iBACA,WACA,gBAGJ,wBACE,kBADF,qCAGI,iBACA,WACA,gBAKN,8BAMI,YACA,kBACA,WAEF,wBACE,YACA,WACA,eACA,gBAEF,wBACE,YACA,WACA,eACA,gBAEF,wBACE,YACA,WACA,eACA,gBASJ,SC7FE,WACA,yBACA,4BAaA,4GAGE,WACA,yBACI,4BAEJ,mPAGE,WACA,yBACI,4BAGR,+DAGE,sBAKA,yPAGE,yBACI,4BDmDV,gBC9CI,cACA,sBDiDJ,eCjGE,WACA,yBACA,4BAaA,gJAGE,WACA,yBACI,4BAEJ,ySAGE,WACA,yBACI,4BAGR,iFAGE,sBAKA,+SAGE,yBACI,4BDuDV,sBClDI,cACA,sBDqDJ,aCrGE,WACA,yBACA,4BAaA,oIAGE,WACA,yBACI,4BAEJ,uRAGE,WACA,yBACI,4BAGR,2EAGE,sBAKA,6RAGE,yBACI,4BD2DV,oBCtDI,cACA,sBDyDJ,cCzGE,WACA,yBACA,4BAaA,0IAGE,WACA,yBACI,4BAEJ,gSAGE,WACA,yBACI,4BAGR,8EAGE,sBAKA,sSAGE,yBACI,4BD+DV,qBC1DI,cACA,sBD6DJ,YC7GE,WACA,yBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,yBACI,4BDmEV,mBC9DI,cACA,sBDiEJ,gBCjHE,WACA,yBACA,4BAaA,sJAGE,WACA,yBACI,4BAEJ,kTAGE,WACA,yBACI,4BAGR,oFAGE,sBAKA,wTAGE,yBACI,4BDuEV,uBClEI,cACA,sBDqEJ,YCrHE,WACA,sBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,sBACI,4BD2EV,mBCtEI,WACA,sBDyEJ,YCzHE,WACA,yBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,yBACI,4BD+EV,mBC1EI,cACA,sBD6EJ,eC7HE,WACA,yBACA,4BAaA,gJAGE,WACA,yBACI,4BAEJ,ySAGE,WACA,yBACI,4BAGR,iFAGE,sBAKA,+SAGE,yBACI,4BDmFV,sBC9EI,cACA,sBDiFJ,cCjIE,WACA,yBACA,4BAaA,0IAGE,WACA,yBACI,4BAEJ,gSAGE,WACA,yBACI,4BAGR,8EAGE,sBAKA,sSAGE,yBACI,4BDuFV,qBClFI,cACA,sBDqFJ,eCrIE,WACA,yBACA,4BAaA,gJAGE,WACA,yBACI,4BAEJ,ySAGE,WACA,yBACI,4BAGR,iFAGE,sBAKA,+SAGE,yBACI,4BD2FV,sBCtFI,cACA,sBDyFJ,YCzIE,WACA,yBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,yBACI,4BD+FV,mBC1FI,cACA,sBD6FJ,eC7IE,WACA,yBACA,4BAaA,gJAGE,WACA,yBACI,4BAEJ,ySAGE,WACA,yBACI,4BAGR,iFAGE,sBAKA,+SAGE,yBACI,4BDmGV,sBC9FI,cACA,sBDiGJ,YCjJE,WACA,yBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,yBACI,4BDuGV,mBClGI,cACA,sBDqGJ,gBCrJE,WACA,sBACA,4BAaA,sJAGE,WACA,sBACI,4BAEJ,kTAGE,WACA,yBACI,4BAGR,oFAGE,sBAKA,wTAGE,sBACI,4BD2GV,uBCtGI,WACA,sBDyGJ,YCzJE,WACA,yBACA,4BAaA,8HAGE,WACA,yBACI,4BAEJ,8QAGE,WACA,yBACI,4BAGR,wEAGE,sBAKA,oRAGE,yBACI,4BD+GV,mBC1GI,cACA,sBD6GJ,aC7JE,WACA,yBACA,4BAaA,oIAGE,WACA,yBACI,4BAEJ,uRAGE,WACA,yBACI,4BAGR,2EAGE,sBAKA,6RAGE,yBACI,4BDmHV,oBC9GI,cACA,sBDiHJ,WCjKE,WACA,yBACA,4BAaA,wHAGE,WACA,yBACI,4BAEJ,qQAGE,WACA,yBACI,4BAGR,qEAGE,sBAKA,2QAGE,yBACI,4BDuHV,kBClHI,cACA,sBDqHJ,QCrKE,WACA,yBACA,4BAaA,sGAGE,WACA,yBACI,4BAEJ,0OAGE,WACA,yBACI,4BAGR,4DAGE,sBAKA,gPAGE,yBACI,4BD2HV,eCtHI,cACA,sBDyHJ,WCzKE,WACA,yBACA,4BAaA,wHAGE,WACA,yBACI,4BAEJ,qQAGE,WACA,yBACI,4BAGR,qEAGE,sBAKA,2QAGE,yBACI,4BD+HV,kBC1HI,cACA,sBC3CJ,WACE,mBACA,sBACA,WACA,kBACA,yBACA,oDAGE,yBAKJ,oBACE,eACA,kBACA,WACA,iBAGF,iBACE,mBAGF,gBACE,kBAIF,kBACE,mBAGF,SACE,WACA,SAGF,iEAEE,cACA,eAGF,+DAEE,eAGF,YACE,aACA,SAGF,eACE,eACA,gBACA,mBAGF,iBACE,gBACA,SACA,UAHF,oBAKI,WACA,eACA,iBACA,iBARJ,wBvC2IE,gDACG,wCAEK,6FuCnIJ,8BvCqEJ,gCACI,uBAEe,CuCjErB,evCoGE,kCACK,yBACG,CuClGV,gBACE,iBACA,gBACA,kBACA,gFACA,qCACA,kBACA,YACA,sBACE,0FC1FF,yNAGE,aALJ,qGAQI,yBACA,gBACA,iBACA,YAIJ,oDACE,qBAGF,kBACE,yBACA,gBAGF,iFACE,yBACA,WAGF,yBACE,iBACA,4DACA,yBAGF,2EACE,eACA,gBACA,YACA,gBAGF,oFACE,kBACA,kBAGF,iFACE,YACA,UAGF,mFACE,aAGF,wFAGI,yBACA,oGACE,aAMN,+JAEE,+BAGF,yEACE,WAGF,yEACE,sBACA,wJAEE,WAKJ,yDAEI,yBACA,gBACA,+DACE,qBAGJ,kFACE,qBAIJ,oFACE,yBACA,qBACA,iBACA,WAGF,4FACE,iBACA,yBACA,kGACE,WAIJ,2EACE,mBCzHI,8IAEE,WAEE,kLACE,oCAGA,oXAEE,WCTd,KACE,aAGF,QACE,YAGF,eACE,mBAGF,oBACE,gBAGF,YACE,iBAIF,QACE,eAIF,mBACE,cACA,cACA,kBACA,iCACE,mBALJ,uCAQI,SACA,UACA,gBACA,eAXJ,qCAcI,yBAKJ,siCA8BE,qBAGF,SACE,WACA,mCAGF,eACE,yBAGF,UACE,gCAGF,mGACE,mCAGF,6FACE,mCAGF,+EACE,mCAGF,SACE,mCAGF,yDACE,mCAGF,4FACE,mCAGF,SACE,mCAGF,SACE,mCAGF,UACE,mCAGF,SACE,mCAGF,WACE,mCAGF,YACE,mCAGF,WACE,mCAGF,WACE,mCAIF,gBACE,WACA,mCAGF,iBACE,gCAGF,uEACE,mCAGF,4EACE,mCAGF,oEACE,mCAGF,gBACE,mCAGF,gFACE,mCAGF,2EACE,mCAGF,gBACE,mCAGF,gBACE,mCAGF,iBACE,mCAGF,gBACE,mCAGF,kBACE,mCAGF,mBACE,mCAGF,kBACE,mCAGF,kBACE,mCAIF,sBxBxNE,YAGA,yBwB0NF,UACE,wBAGF,aACE,wBAGF,WACE,wBAGF,WACE,wBAGF,YACE,qBAGF,iBACE,wBAGF,YACE,wBAGF,WACE,wBAGF,WACE,wBAGF,WACE,wBAGF,YACE,wBAGF,WACE,wBAGF,aACE,wBAGF,cACE,wBAGF,aACE,wBAGF,aACE,wBAGF,YACE,cACA,oCAEE,cAIJ,YACE,WACA,oCAEE,WAKJ,MACE,uBAIF,WACE,mBAIF,YACE,oBAIF,WACE,mBAIF,WACE,4DAIF,6EACE,gBACA,SACA,UAGF,wCAEI,cACA,eACA,gBACA,eACA,gBAKJ,MvCnRE,0BuCwRA,mDACE,gBAIJ,SACE,eAIF,YACE,sBACA,qBACA,sBAIF,kBvC3RE,6BACA,yDAaA,0HuC+QA,WAGF,wBvChSE,6BACA,yDAaA,0HuCoRA,WAGF,kBvCrSE,6BACA,yDAaA,0HuCyRA,WAGF,kBvC1SE,6BACA,yDAaA,0HuC8RA,WAGF,oBvC/SE,6BACA,yDAaA,0HuCmSA,WAGF,oBvCpTE,6BACA,yDAaA,0HuCwSA,WAGF,mBvCzTE,6BACA,yDAaA,0HuC6SA,WAGF,iBvC9TE,6BACA,yDAaA,0HuCkTA,WAGF,mBvCnUE,0BACA,sDAaA,uHuCuTA,WAGF,oBvCxUE,6BACA,yDAaA,0HuC4TA,WAIF,qCAEI,eAKJ,YACE,cAIF,iBACE,0BAIF,aACE,eACA,iBACA,gBACA,WAGF,gBACE,WACA,mBACA,kBAGF,aAEI,YACA,WACA,mBACE,WAMN,YACE,gB3CjcA,qCAEE,YACA,cAEF,kBACE,W2C+bJ,gBAGI,WACA,YACA,WALJ,oEAUI,cACA,iBAXJ,sBAcI,eACA,gBAfJ,yBAkBI,WACA,eAEF,8GAOI,iBAPJ,oCAUI,eAMN,qFAGE,WAGF,qEACE,qBACA,sBAFF,kBAII,iBAIJ,QACE,WACA,YAFF,kBAII,iBAIJ,QACE,YACA,aAFF,kBAII,kBAKJ,cACE,yBACA,YAGF,iBACE,yBACA,YAIF,kBACE,yBACA,YACA,mBACA,mBAJF,kCAOI,gBACA,iBACA,YACA,WAVJ,qCAaI,kBAbJ,sCAgBI,SAhBJ,mCAmBI,WAIJ,mBACE,iBAGF,6BACE,SACA,mBACA,WACA,YACA,gBACA,UACA,kBACA,UAGF,gBACE,mBACA,uBACA,mBAGF,oBxB1kBE,YAGA,yBwBykBA,0BxB5kBA,UAGA,0BwB+kBF,OACE,kBACA,gBACA,WAHF,yBAMI,qBAKJ,GACE,0BAKF,uBACI,mBAGJ,wBACI,mBAGJ,yBACI,mBAGJ,0BACI,mBAGJ,wBACI,mBAGJ,0BACI,mBCvnBJ,aAEE,gEACE,uBAYF,0CAGE,wBACA,uB3CuHF,yCACI,gCAEI,C2CtHR,2CAEE,wBAIF,SACE,WACA,SACA,SACA,UAGF,aACE,WACA,kBAIF,kBACE,cADF,8DAII","file":"css/build/AdminLTE.css","sourcesContent":["/*!\n * AdminLTE v2.4.18\n * \n * Author: Colorlib\n * Support: \n * Repository: git://github.com/ColorlibHQ/AdminLTE.git\n * License: MIT \n */html,body{height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{height:100%;position:relative;overflow-x:hidden;overflow-y:auto}.wrapper:before,.wrapper:after{content:\" \";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background-color:#f9fafc}.content-wrapper,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper{min-height:calc(100vh - 101px);background-color:#ecf0f5;z-index:800}@media (max-width:767px){.content-wrapper{min-height:calc(100vh - 151px)}}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}.fixed .wrapper{overflow:hidden}.hold-transition .content-wrapper,.hold-transition .right-side,.hold-transition .main-footer,.hold-transition .main-sidebar,.hold-transition .left-side,.hold-transition .main-header .navbar,.hold-transition .main-header .logo,.hold-transition .menu-open .fa-angle-left{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#3c8dbc}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#72afd2}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header .navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header .navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:\"\\f0c9\"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle.fa5{font-family:\"Font Awesome\\ 5 Free\"}.main-header .sidebar-toggle.fa5:before{content:\"\\f0c9\";font-weight:900}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo img{padding:4px;object-fit:contain;margin:0 auto}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-lg img{max-width:200px;max-height:50px}.main-header .logo .logo-lg .brandlogo-image{margin-top:8px;margin-right:10px;margin-left:-5px}.main-header .logo .logo-mini{display:none}.main-header .logo .logo-mini img{max-width:50px;max-height:50px}.main-header .logo .logo-mini .brandlogo-image{margin-top:8px;margin-right:10px;margin-left:10px}.main-header .logo .brandlogo-image{float:left;height:34px;width:auto}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar{padding-top:100px}}@media (max-width:767px){.main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:\" \";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-right:5px}.sidebar-menu>li .badge{margin-top:3px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left,.sidebar-menu li>a>.pull-right-container>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px;-webkit-transition:transform .5s ease;-o-transition:transform .5s ease;transition:transform .5s ease}.sidebar-menu li>a>.fa-angle-left{position:absolute;top:50%;right:10px;margin-top:-8px}.sidebar-menu .menu-open>a>.fa-angle-left,.sidebar-menu .menu-open>a>.pull-right-container>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu .active>.treeview-menu{display:block}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}@media (min-width:768px){.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container{position:relative !important;float:right;width:auto !important;left:180px !important;top:-22px !important;z-index:900}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container>.label:not(:first-of-type){display:none}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}}.sidebar-expanded-on-hover .main-footer,.sidebar-expanded-on-hover .content-wrapper{margin-left:50px}.sidebar-expanded-on-hover .main-sidebar{box-shadow:3px 0 8px rgba(0,0,0,0.125)}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right-container{position:absolute;right:10px;top:50%;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:767px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-hold-transition .control-sidebar-bg,.control-sidebar-hold-transition .control-sidebar,.control-sidebar-hold-transition .content-wrapper{transition:none}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.fixed .control-sidebar{position:fixed;height:100%;overflow-y:auto;padding-bottom:50px}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:\" \";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#b8c7ce}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:\" \";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:\" \";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:\" \";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#d2d6de}.form-control:focus{border-color:#3c8dbc;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control,.form-group.has-success .input-group-addon{border-color:#00a65a;box-shadow:none}.form-group.has-success .help-block{color:#00a65a}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control,.form-group.has-warning .input-group-addon{border-color:#f39c12;box-shadow:none}.form-group.has-warning .help-block{color:#f39c12}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control,.form-group.has-error .input-group-addon{border-color:#dd4b39;box-shadow:none}.form-group.has-error .help-block{color:#dd4b39}.input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#3c8dbc}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#d2d6de}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #d2d6de}.box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #3c8dbc}.box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:\" \";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{float:right;margin-top:-5px;margin-bottom:-5px}.box-header>.box-tools [data-toggle=\"tooltip\"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.box-header>.box-tools .dropdown-menu>li>a{color:#444!important}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:\" \";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#d2d6de !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#3c8dbc}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:\" \";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:\" \";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:\" \";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#3c8dbc;border-color:#367fa9}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li.disabled>a{color:#777}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none !important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef}.nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39}.nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12}.nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a}.nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#d2d6de}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:\" \";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#d2d6de;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:\" \";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:\" \";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.treeview-menu .treeview-menu{padding-left:20px}.treeview-menu>li{margin:0}.treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.treeview-menu>li>a>.fa,.treeview-menu>li>a>.glyphicon,.treeview-menu>li>a>.ion{width:20px}.treeview-menu>li>a>.pull-right-container>.fa-angle-left,.treeview-menu>li>a>.pull-right-container>.fa-angle-down,.treeview-menu>li>a>.fa-angle-left,.treeview-menu>li>a>.fa-angle-down{width:auto}.treeview>ul.treeview-menu{overflow:hidden;height:auto;padding-top:0px !important;padding-bottom:0px !important}.treeview.menu-open>ul.treeview-menu{overflow:visible;height:auto}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#d2d6de}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{height:auto;background:#d2d6de}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active:hover,.btn-adn.active:hover,.open>.dropdown-toggle.btn-adn:hover,.btn-adn:active:focus,.btn-adn.active:focus,.open>.dropdown-toggle.btn-adn:focus,.btn-adn:active.focus,.btn-adn.active.focus,.open>.dropdown-toggle.btn-adn.focus{color:#fff;background-color:#b94630;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn.disabled:hover,.btn-adn[disabled]:hover,fieldset[disabled] .btn-adn:hover,.btn-adn.disabled:focus,.btn-adn[disabled]:focus,fieldset[disabled] .btn-adn:focus,.btn-adn.disabled.focus,.btn-adn[disabled].focus,fieldset[disabled] .btn-adn.focus{background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active:hover,.btn-bitbucket.active:hover,.open>.dropdown-toggle.btn-bitbucket:hover,.btn-bitbucket:active:focus,.btn-bitbucket.active:focus,.open>.dropdown-toggle.btn-bitbucket:focus,.btn-bitbucket:active.focus,.btn-bitbucket.active.focus,.open>.dropdown-toggle.btn-bitbucket.focus{color:#fff;background-color:#0f253c;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket.disabled:hover,.btn-bitbucket[disabled]:hover,fieldset[disabled] .btn-bitbucket:hover,.btn-bitbucket.disabled:focus,.btn-bitbucket[disabled]:focus,fieldset[disabled] .btn-bitbucket:focus,.btn-bitbucket.disabled.focus,.btn-bitbucket[disabled].focus,fieldset[disabled] .btn-bitbucket.focus{background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active:hover,.btn-dropbox.active:hover,.open>.dropdown-toggle.btn-dropbox:hover,.btn-dropbox:active:focus,.btn-dropbox.active:focus,.open>.dropdown-toggle.btn-dropbox:focus,.btn-dropbox:active.focus,.btn-dropbox.active.focus,.open>.dropdown-toggle.btn-dropbox.focus{color:#fff;background-color:#0a568c;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox.disabled:hover,.btn-dropbox[disabled]:hover,fieldset[disabled] .btn-dropbox:hover,.btn-dropbox.disabled:focus,.btn-dropbox[disabled]:focus,fieldset[disabled] .btn-dropbox:focus,.btn-dropbox.disabled.focus,.btn-dropbox[disabled].focus,fieldset[disabled] .btn-dropbox.focus{background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active:hover,.btn-facebook.active:hover,.open>.dropdown-toggle.btn-facebook:hover,.btn-facebook:active:focus,.btn-facebook.active:focus,.open>.dropdown-toggle.btn-facebook:focus,.btn-facebook:active.focus,.btn-facebook.active.focus,.open>.dropdown-toggle.btn-facebook.focus{color:#fff;background-color:#23345a;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook.disabled:hover,.btn-facebook[disabled]:hover,fieldset[disabled] .btn-facebook:hover,.btn-facebook.disabled:focus,.btn-facebook[disabled]:focus,fieldset[disabled] .btn-facebook:focus,.btn-facebook.disabled.focus,.btn-facebook[disabled].focus,fieldset[disabled] .btn-facebook.focus{background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active:hover,.btn-flickr.active:hover,.open>.dropdown-toggle.btn-flickr:hover,.btn-flickr:active:focus,.btn-flickr.active:focus,.open>.dropdown-toggle.btn-flickr:focus,.btn-flickr:active.focus,.btn-flickr.active.focus,.open>.dropdown-toggle.btn-flickr.focus{color:#fff;background-color:#a80057;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr.disabled:hover,.btn-flickr[disabled]:hover,fieldset[disabled] .btn-flickr:hover,.btn-flickr.disabled:focus,.btn-flickr[disabled]:focus,fieldset[disabled] .btn-flickr:focus,.btn-flickr.disabled.focus,.btn-flickr[disabled].focus,fieldset[disabled] .btn-flickr.focus{background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active:hover,.btn-foursquare.active:hover,.open>.dropdown-toggle.btn-foursquare:hover,.btn-foursquare:active:focus,.btn-foursquare.active:focus,.open>.dropdown-toggle.btn-foursquare:focus,.btn-foursquare:active.focus,.btn-foursquare.active.focus,.open>.dropdown-toggle.btn-foursquare.focus{color:#fff;background-color:#e30742;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare.disabled:hover,.btn-foursquare[disabled]:hover,fieldset[disabled] .btn-foursquare:hover,.btn-foursquare.disabled:focus,.btn-foursquare[disabled]:focus,fieldset[disabled] .btn-foursquare:focus,.btn-foursquare.disabled.focus,.btn-foursquare[disabled].focus,fieldset[disabled] .btn-foursquare.focus{background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active:hover,.btn-github.active:hover,.open>.dropdown-toggle.btn-github:hover,.btn-github:active:focus,.btn-github.active:focus,.open>.dropdown-toggle.btn-github:focus,.btn-github:active.focus,.btn-github.active.focus,.open>.dropdown-toggle.btn-github.focus{color:#fff;background-color:#191919;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github.disabled:hover,.btn-github[disabled]:hover,fieldset[disabled] .btn-github:hover,.btn-github.disabled:focus,.btn-github[disabled]:focus,fieldset[disabled] .btn-github:focus,.btn-github.disabled.focus,.btn-github[disabled].focus,fieldset[disabled] .btn-github.focus{background-color:#444444;border-color:rgba(0,0,0,0.2)}.btn-github .badge{color:#444444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active:hover,.btn-google.active:hover,.open>.dropdown-toggle.btn-google:hover,.btn-google:active:focus,.btn-google.active:focus,.open>.dropdown-toggle.btn-google:focus,.btn-google:active.focus,.btn-google.active.focus,.open>.dropdown-toggle.btn-google.focus{color:#fff;background-color:#a32b1c;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google.disabled:hover,.btn-google[disabled]:hover,fieldset[disabled] .btn-google:hover,.btn-google.disabled:focus,.btn-google[disabled]:focus,fieldset[disabled] .btn-google:focus,.btn-google.disabled.focus,.btn-google[disabled].focus,fieldset[disabled] .btn-google.focus{background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active:hover,.btn-instagram.active:hover,.open>.dropdown-toggle.btn-instagram:hover,.btn-instagram:active:focus,.btn-instagram.active:focus,.open>.dropdown-toggle.btn-instagram:focus,.btn-instagram:active.focus,.btn-instagram.active.focus,.open>.dropdown-toggle.btn-instagram.focus{color:#fff;background-color:#26455d;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram.disabled:hover,.btn-instagram[disabled]:hover,fieldset[disabled] .btn-instagram:hover,.btn-instagram.disabled:focus,.btn-instagram[disabled]:focus,fieldset[disabled] .btn-instagram:focus,.btn-instagram.disabled.focus,.btn-instagram[disabled].focus,fieldset[disabled] .btn-instagram.focus{background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active:hover,.btn-linkedin.active:hover,.open>.dropdown-toggle.btn-linkedin:hover,.btn-linkedin:active:focus,.btn-linkedin.active:focus,.open>.dropdown-toggle.btn-linkedin:focus,.btn-linkedin:active.focus,.btn-linkedin.active.focus,.open>.dropdown-toggle.btn-linkedin.focus{color:#fff;background-color:#00405f;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin.disabled:hover,.btn-linkedin[disabled]:hover,fieldset[disabled] .btn-linkedin:hover,.btn-linkedin.disabled:focus,.btn-linkedin[disabled]:focus,fieldset[disabled] .btn-linkedin:focus,.btn-linkedin.disabled.focus,.btn-linkedin[disabled].focus,fieldset[disabled] .btn-linkedin.focus{background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active:hover,.btn-microsoft.active:hover,.open>.dropdown-toggle.btn-microsoft:hover,.btn-microsoft:active:focus,.btn-microsoft.active:focus,.open>.dropdown-toggle.btn-microsoft:focus,.btn-microsoft:active.focus,.btn-microsoft.active.focus,.open>.dropdown-toggle.btn-microsoft.focus{color:#fff;background-color:#0f4bac;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft.disabled:hover,.btn-microsoft[disabled]:hover,fieldset[disabled] .btn-microsoft:hover,.btn-microsoft.disabled:focus,.btn-microsoft[disabled]:focus,fieldset[disabled] .btn-microsoft:focus,.btn-microsoft.disabled.focus,.btn-microsoft[disabled].focus,fieldset[disabled] .btn-microsoft.focus{background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active:hover,.btn-openid.active:hover,.open>.dropdown-toggle.btn-openid:hover,.btn-openid:active:focus,.btn-openid.active:focus,.open>.dropdown-toggle.btn-openid:focus,.btn-openid:active.focus,.btn-openid.active.focus,.open>.dropdown-toggle.btn-openid.focus{color:#fff;background-color:#b86607;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid.disabled:hover,.btn-openid[disabled]:hover,fieldset[disabled] .btn-openid:hover,.btn-openid.disabled:focus,.btn-openid[disabled]:focus,fieldset[disabled] .btn-openid:focus,.btn-openid.disabled.focus,.btn-openid[disabled].focus,fieldset[disabled] .btn-openid.focus{background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active:hover,.btn-pinterest.active:hover,.open>.dropdown-toggle.btn-pinterest:hover,.btn-pinterest:active:focus,.btn-pinterest.active:focus,.open>.dropdown-toggle.btn-pinterest:focus,.btn-pinterest:active.focus,.btn-pinterest.active.focus,.open>.dropdown-toggle.btn-pinterest.focus{color:#fff;background-color:#801419;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest.disabled:hover,.btn-pinterest[disabled]:hover,fieldset[disabled] .btn-pinterest:hover,.btn-pinterest.disabled:focus,.btn-pinterest[disabled]:focus,fieldset[disabled] .btn-pinterest:focus,.btn-pinterest.disabled.focus,.btn-pinterest[disabled].focus,fieldset[disabled] .btn-pinterest.focus{background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active:hover,.btn-reddit.active:hover,.open>.dropdown-toggle.btn-reddit:hover,.btn-reddit:active:focus,.btn-reddit.active:focus,.open>.dropdown-toggle.btn-reddit:focus,.btn-reddit:active.focus,.btn-reddit.active.focus,.open>.dropdown-toggle.btn-reddit.focus{color:#000;background-color:#98ccff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit.disabled:hover,.btn-reddit[disabled]:hover,fieldset[disabled] .btn-reddit:hover,.btn-reddit.disabled:focus,.btn-reddit[disabled]:focus,fieldset[disabled] .btn-reddit:focus,.btn-reddit.disabled.focus,.btn-reddit[disabled].focus,fieldset[disabled] .btn-reddit.focus{background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#ff5500;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active:hover,.btn-soundcloud.active:hover,.open>.dropdown-toggle.btn-soundcloud:hover,.btn-soundcloud:active:focus,.btn-soundcloud.active:focus,.open>.dropdown-toggle.btn-soundcloud:focus,.btn-soundcloud:active.focus,.btn-soundcloud.active.focus,.open>.dropdown-toggle.btn-soundcloud.focus{color:#fff;background-color:#a83800;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud.disabled:hover,.btn-soundcloud[disabled]:hover,fieldset[disabled] .btn-soundcloud:hover,.btn-soundcloud.disabled:focus,.btn-soundcloud[disabled]:focus,fieldset[disabled] .btn-soundcloud:focus,.btn-soundcloud.disabled.focus,.btn-soundcloud[disabled].focus,fieldset[disabled] .btn-soundcloud.focus{background-color:#ff5500;border-color:rgba(0,0,0,0.2)}.btn-soundcloud .badge{color:#ff5500;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active:hover,.btn-tumblr.active:hover,.open>.dropdown-toggle.btn-tumblr:hover,.btn-tumblr:active:focus,.btn-tumblr.active:focus,.open>.dropdown-toggle.btn-tumblr:focus,.btn-tumblr:active.focus,.btn-tumblr.active.focus,.open>.dropdown-toggle.btn-tumblr.focus{color:#fff;background-color:#111c26;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr.disabled:hover,.btn-tumblr[disabled]:hover,fieldset[disabled] .btn-tumblr:hover,.btn-tumblr.disabled:focus,.btn-tumblr[disabled]:focus,fieldset[disabled] .btn-tumblr:focus,.btn-tumblr.disabled.focus,.btn-tumblr[disabled].focus,fieldset[disabled] .btn-tumblr.focus{background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active:hover,.btn-twitter.active:hover,.open>.dropdown-toggle.btn-twitter:hover,.btn-twitter:active:focus,.btn-twitter.active:focus,.open>.dropdown-toggle.btn-twitter:focus,.btn-twitter:active.focus,.btn-twitter.active.focus,.open>.dropdown-toggle.btn-twitter.focus{color:#fff;background-color:#1583d7;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter.disabled:hover,.btn-twitter[disabled]:hover,fieldset[disabled] .btn-twitter:hover,.btn-twitter.disabled:focus,.btn-twitter[disabled]:focus,fieldset[disabled] .btn-twitter:focus,.btn-twitter.disabled.focus,.btn-twitter[disabled].focus,fieldset[disabled] .btn-twitter.focus{background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active:hover,.btn-vimeo.active:hover,.open>.dropdown-toggle.btn-vimeo:hover,.btn-vimeo:active:focus,.btn-vimeo.active:focus,.open>.dropdown-toggle.btn-vimeo:focus,.btn-vimeo:active.focus,.btn-vimeo.active.focus,.open>.dropdown-toggle.btn-vimeo.focus{color:#fff;background-color:#0f7b9f;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo.disabled:hover,.btn-vimeo[disabled]:hover,fieldset[disabled] .btn-vimeo:hover,.btn-vimeo.disabled:focus,.btn-vimeo[disabled]:focus,fieldset[disabled] .btn-vimeo:focus,.btn-vimeo.disabled.focus,.btn-vimeo[disabled].focus,fieldset[disabled] .btn-vimeo.focus{background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active:hover,.btn-vk.active:hover,.open>.dropdown-toggle.btn-vk:hover,.btn-vk:active:focus,.btn-vk.active:focus,.open>.dropdown-toggle.btn-vk:focus,.btn-vk:active.focus,.btn-vk.active.focus,.open>.dropdown-toggle.btn-vk.focus{color:#fff;background-color:#3a526b;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk.disabled:hover,.btn-vk[disabled]:hover,fieldset[disabled] .btn-vk:hover,.btn-vk.disabled:focus,.btn-vk[disabled]:focus,fieldset[disabled] .btn-vk:focus,.btn-vk.disabled.focus,.btn-vk[disabled].focus,fieldset[disabled] .btn-vk.focus{background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active:hover,.btn-yahoo.active:hover,.open>.dropdown-toggle.btn-yahoo:hover,.btn-yahoo:active:focus,.btn-yahoo.active:focus,.open>.dropdown-toggle.btn-yahoo:focus,.btn-yahoo:active.focus,.btn-yahoo.active.focus,.open>.dropdown-toggle.btn-yahoo.focus{color:#fff;background-color:#39074e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo.disabled:hover,.btn-yahoo[disabled]:hover,fieldset[disabled] .btn-yahoo:hover,.btn-yahoo.disabled:focus,.btn-yahoo[disabled]:focus,fieldset[disabled] .btn-yahoo:focus,.btn-yahoo.disabled.focus,.btn-yahoo[disabled].focus,fieldset[disabled] .btn-yahoo.focus{background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#3c8dbc}.select2-dropdown{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir=\"rtl\"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none}.select2-container--default.select2-container--focus .select2-selection--multiple,.select2-container--default .select2-search--dropdown .select2-search__field{border-color:#3c8dbc !important}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.box .datepicker-inline,.box .datepicker-inline .datepicker-days,.box .datepicker-inline>table,.box .datepicker-inline .datepicker-days>table{width:100%}.box .datepicker-inline td:hover,.box .datepicker-inline .datepicker-days td:hover,.box .datepicker-inline>table td:hover,.box .datepicker-inline .datepicker-days>table td:hover{background-color:rgba(255,255,255,0.3)}.box .datepicker-inline td.day.old,.box .datepicker-inline .datepicker-days td.day.old,.box .datepicker-inline>table td.day.old,.box .datepicker-inline .datepicker-days>table td.day.old,.box .datepicker-inline td.day.new,.box .datepicker-inline .datepicker-days td.day.new,.box .datepicker-inline>table td.day.new,.box .datepicker-inline .datepicker-days>table td.day.new{color:#777}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#d2d6de !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#0073b7 !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001F3F !important}.bg-teal{background-color:#39CCCC !important}.bg-olive{background-color:#3D9970 !important}.bg-lime{background-color:#01FF70 !important}.bg-orange{background-color:#FF851B !important}.bg-fuchsia{background-color:#F012BE !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#D81B60 !important}.bg-gray-active{color:#000;background-color:#b5bbc8 !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#005384 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^=\"bg-\"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#0073b7 !important}.text-black{color:#111 !important}.text-light-blue{color:#3c8dbc !important}.text-green{color:#00a65a !important}.text-gray{color:#d2d6de !important}.text-navy{color:#001F3F !important}.text-teal{color:#39CCCC !important}.text-olive{color:#3D9970 !important}.text-lime{color:#01FF70 !important}.text-orange{color:#FF851B !important}.text-fuchsia{color:#F012BE !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#D81B60 !important}.link-muted{color:#7a869d}.link-muted:hover,.link-muted:focus{color:#606c84}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none !important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px !important;width:auto !important;height:auto !important}.bg-teal-gradient{background:#39CCCC !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39CCCC), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39CCCC, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39CCCC 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39CCCC) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39CCCC', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#D81B60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #D81B60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #D81B60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #D81B60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #D81B60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#D81B60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static !important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:\" \";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #d2d6de;padding:3px}.img-bordered-sm{border:2px solid #d2d6de;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100% !important}hr{border-top:1px solid #555}#red .slider-selection{background:#f56954}#blue .slider-selection{background:#3c8dbc}#green .slider-selection{background:#00a65a}#yellow .slider-selection{background:#f39c12}#aqua .slider-selection{background:#00c0ef}#purple .slider-selection{background:#932ab6}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}}\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/AdminLTE.less","/*\n * Core: General Layout Style\n * -------------------------\n */\nhtml,\nbody {\n height: 100%;\n .layout-boxed & {\n height: 100%;\n }\n}\n\nbody {\n font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-weight: 400;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n/* Layout */\n.wrapper {\n .clearfix();\n height: 100%;\n position: relative;\n overflow-x: hidden;\n overflow-y: auto;\n .layout-boxed & {\n max-width: 1250px;\n margin: 0 auto;\n min-height: 100%;\n box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);\n position: relative;\n }\n}\n\n.layout-boxed {\n background-color: @sidebar-light-bg;\n}\n\n/*\n * Content Wrapper - contains the main content\n */\n.content-wrapper,\n.main-footer {\n // Using disposable variable to join statements with a comma\n @transition-rule: @transition-speed @transition-fn,\n margin @transition-speed @transition-fn;\n .transition-transform(@transition-rule);\n margin-left: @sidebar-width;\n z-index: 820;\n // Top nav layout\n .layout-top-nav & {\n margin-left: 0;\n }\n @media (max-width: @screen-xs-max) {\n margin-left: 0;\n }\n // When opening the sidebar on large screens\n .sidebar-collapse & {\n @media (min-width: @screen-sm) {\n margin-left: 0;\n }\n }\n // When opening the sidebar on small screens\n .sidebar-open & {\n @media (max-width: @screen-xs-max) {\n .translate(@sidebar-width, 0);\n }\n }\n}\n\n.content-wrapper {\n min-height: ~\"calc(100vh - 101px)\";\n background-color: @content-bg;\n z-index: 800;\n}\n\n@media (max-width: @screen-header-collapse) {\n .content-wrapper {\n min-height: ~\"calc(100vh - 151px)\";\n }\n}\n\n.main-footer {\n background: #fff;\n padding: 15px;\n color: #444;\n border-top: 1px solid @gray-lte;\n}\n\n/* Fixed layout */\n.fixed {\n .main-header,\n .main-sidebar,\n .left-side {\n position: fixed;\n }\n .main-header {\n top: 0;\n right: 0;\n left: 0;\n }\n .content-wrapper,\n .right-side {\n padding-top: 50px;\n @media (max-width: @screen-header-collapse) {\n padding-top: 100px;\n }\n }\n &.layout-boxed {\n .wrapper {\n max-width: 100%;\n }\n }\n .wrapper {\n overflow: hidden;\n }\n}\n\n.hold-transition {\n .content-wrapper,\n .right-side,\n .main-footer,\n .main-sidebar,\n .left-side,\n .main-header .navbar,\n .main-header .logo,\n .menu-open .fa-angle-left {\n /* Fix for IE */\n .transition(none);\n }\n}\n\n/* Content */\n.content {\n min-height: 250px;\n padding: 15px;\n .container-fixed(@grid-gutter-width);\n}\n\n/* H1 - H6 font */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: 'Source Sans Pro', sans-serif;\n}\n\n/* General Links */\na {\n color: @link-color;\n}\n\na:hover,\na:active,\na:focus {\n outline: none;\n text-decoration: none;\n color: @link-hover-color;\n}\n\n/* Page Header */\n.page-header {\n margin: 10px 0 20px 0;\n font-size: 22px;\n\n > small {\n color: #666;\n display: block;\n margin-top: 5px;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/core.less","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/clearfix.less","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/vendor-prefixes.less","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/grid.less","/*\n * Component: Main Header\n * ----------------------\n */\n\n.main-header {\n position: relative;\n max-height: 100px;\n z-index: 1030;\n //Navbar\n .navbar {\n .transition(margin-left @transition-speed @transition-fn);\n margin-bottom: 0;\n margin-left: @sidebar-width;\n border: none;\n min-height: @navbar-height;\n border-radius: 0;\n .layout-top-nav & {\n margin-left: 0;\n }\n }\n //Navbar search text input\n #navbar-search-input.form-control {\n background: rgba(255, 255, 255, .2);\n border-color: transparent;\n &:focus,\n &:active {\n border-color: rgba(0, 0, 0, .1);\n background: rgba(255, 255, 255, .9);\n }\n &::-moz-placeholder {\n color: #ccc;\n opacity: 1;\n }\n &:-ms-input-placeholder {\n color: #ccc;\n }\n &::-webkit-input-placeholder {\n color: #ccc;\n }\n }\n //Navbar Right Menu\n .navbar-custom-menu,\n .navbar-right {\n float: right;\n @media (max-width: @screen-sm-max) {\n a {\n color: inherit;\n background: transparent;\n }\n }\n }\n .navbar-right {\n @media (max-width: @screen-header-collapse) {\n float: none;\n .navbar-collapse & {\n margin: 7.5px -15px;\n }\n\n > li {\n color: inherit;\n border: 0;\n }\n }\n }\n //Navbar toggle button\n .sidebar-toggle {\n float: left;\n background-color: transparent;\n background-image: none;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n //Add the fontawesome bars icon\n font-family: fontAwesome;\n &:before {\n content: \"\\f0c9\";\n }\n &:hover {\n color: #fff;\n }\n &:focus,\n &:active {\n background: transparent;\n }\n\n &.fa5 {\n font-family: \"Font Awesome\\ 5 Free\";\n &:before {\n content: \"\\f0c9\";\n font-weight: 900;\n }\n }\n }\n .sidebar-toggle .icon-bar {\n display: none;\n }\n //Navbar User Menu\n .navbar .nav > li.user > a {\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n\n //Labels in navbar\n .navbar .nav > li > a > .label {\n position: absolute;\n top: 9px;\n right: 7px;\n text-align: center;\n font-size: 9px;\n padding: 2px 3px;\n line-height: .9;\n }\n\n //Logo bar\n .logo {\n .transition(width @transition-speed @transition-fn);\n display: block;\n float: left;\n height: @navbar-height;\n font-size: 20px;\n line-height: 50px;\n text-align: center;\n width: @sidebar-width;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n padding: 0 15px;\n font-weight: 300;\n overflow: hidden;\n\n img {\n padding: 4px;\n object-fit: contain;\n margin: 0 auto;\n }\n\n //Add support to sidebar mini by allowing the user to create\n //2 logo designs. mini and lg\n .logo-lg {\n //should be visibile when sidebar isn't collapsed\n display: block;\n\n img {\n max-width: 200px;\n max-height: 50px;\n }\n .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: -5px;\n }\n }\n .logo-mini {\n display: none;\n\n img {\n max-width: 50px;\n max-height: 50px;\n }\n .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: 10px;\n }\n }\n\n .brandlogo-image {\n float: left;\n height: 34px;\n width: auto;\n }\n }\n //Navbar Brand. Alternative logo with layout-top-nav\n .navbar-brand {\n color: #fff;\n }\n}\n\n// Content Header\n.content-header {\n position: relative;\n padding: 15px 15px 0 15px;\n // Header Text\n > h1 {\n margin: 0;\n font-size: 24px;\n > small {\n font-size: 15px;\n display: inline-block;\n padding-left: 4px;\n font-weight: 300;\n }\n }\n\n > .breadcrumb {\n float: right;\n background: transparent;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 12px;\n padding: 7px 5px;\n position: absolute;\n top: 15px;\n right: 10px;\n .border-radius(2px);\n > li > a {\n color: #444;\n text-decoration: none;\n display: inline-block;\n > .fa, > .glyphicon, > .ion {\n margin-right: 5px;\n }\n }\n > li + li:before {\n content: '>\\00a0';\n }\n }\n\n @media (max-width: @screen-sm-max) {\n > .breadcrumb {\n position: relative;\n margin-top: 5px;\n top: 0;\n right: 0;\n float: none;\n background: @gray-lte;\n padding-left: 10px;\n li:before {\n color: darken(@gray-lte, 20%);\n }\n }\n }\n}\n\n.navbar-toggle {\n color: #fff;\n border: 0;\n margin: 0;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n}\n\n//Control navbar scaffolding on x-small screens\n@media (max-width: @screen-sm-max) {\n .navbar-custom-menu .navbar-nav > li {\n float: left;\n }\n\n //Dont't let links get full width\n .navbar-custom-menu .navbar-nav {\n margin: 0;\n float: left;\n }\n\n .navbar-custom-menu .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n line-height: 20px;\n }\n}\n\n// Collapse header\n@media (max-width: @screen-header-collapse) {\n .main-header {\n position: relative;\n .logo,\n .navbar {\n width: 100%;\n float: none;\n }\n .navbar {\n margin: 0;\n }\n .navbar-custom-menu {\n float: right;\n }\n }\n}\n\n.navbar-collapse.pull-left {\n @media (max-width: @screen-sm-max) {\n float: none !important;\n + .navbar-custom-menu {\n display: block;\n position: absolute;\n top: 0;\n right: 40px;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/header.less","//AdminLTE mixins\n//===============\n\n//Changes the color and the hovering properties of the navbar\n.navbar-variant(@color; @font-color: rgba(255, 255, 255, 0.8); @hover-color: #f6f6f6; @hover-bg: rgba(0, 0, 0, 0.1)) {\n background-color: @color;\n //Navbar links\n .nav > li > a {\n color: @font-color;\n }\n\n .nav > li > a:hover,\n .nav > li > a:active,\n .nav > li > a:focus,\n .nav .open > a,\n .nav .open > a:hover,\n .nav .open > a:focus,\n .nav > .active > a {\n background: @hover-bg;\n color: @hover-color;\n }\n\n //Add color to the sidebar toggle button\n .sidebar-toggle {\n color: @font-color;\n &:hover {\n color: @hover-color;\n background: @hover-bg;\n }\n }\n}\n\n//Logo color variation\n.logo-variant(@bg-color; @color: #fff; @border-bottom-color: transparent; @border-bottom-width: 0) {\n background-color: @bg-color;\n color: @color;\n border-bottom: @border-bottom-width solid @border-bottom-color;\n\n &:hover {\n background-color: darken(@bg-color, 1%);\n }\n}\n\n//Box solid color variantion creator\n.box-solid-variant(@color; @text-color: #fff) {\n border: 1px solid @color;\n > .box-header {\n color: @text-color;\n background: @color;\n background-color: @color;\n a,\n .btn {\n color: @text-color;\n }\n }\n}\n\n//Direct Chat Variant\n.direct-chat-variant(@bg-color; @color: #fff) {\n .right > .direct-chat-text {\n background: @bg-color;\n border-color: @bg-color;\n color: @color;\n &:after,\n &:before {\n border-left-color: @bg-color;\n }\n }\n}\n\n//border radius creator\n.border-radius(@radius) {\n border-radius: @radius;\n}\n\n//Different radius each side\n.border-radius(@top-left, @top-right, @bottom-left, @bottom-right)\n{\n border-top-left-radius: @top-left;\n border-top-right-radius: @top-right;\n border-bottom-right-radius: @bottom-right;\n border-bottom-left-radius: @bottom-left;\n}\n\n//Gradient background\n.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {\n background: @color;\n background: -webkit-gradient(linear,\n left bottom,\n left top,\n color-stop(0, @start),\n color-stop(1, @stop));\n background: -ms-linear-gradient(bottom,\n @start,\n @stop);\n background: -moz-linear-gradient(center bottom,\n @start 0%,\n @stop 100%);\n background: -o-linear-gradient(@stop,\n @start);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",@stop,@start));\n}\n\n//Added 2.1.0\n//Skins Mixins\n\n//Dark Sidebar Mixin\n.skin-dark-sidebar(@link-hover-border-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-dark-bg;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: #fff;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n //Section Headning\n &.header {\n color: lighten(@sidebar-dark-bg, 20%);\n background: darken(@sidebar-dark-bg, 4%);\n }\n //links\n > a {\n border-left: 3px solid transparent;\n }\n //Hover and active states\n &:hover > a,\n &.active > a,\n &.menu-open > a {\n color: @sidebar-dark-hover-color;\n background: @sidebar-dark-hover-bg;\n }\n &.active > a {\n border-left-color: @link-hover-border-color;\n }\n //First Level Submenu\n > .treeview-menu {\n margin: 0 1px;\n background: @sidebar-dark-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-dark-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .sidebar-menu .treeview-menu {\n > li {\n > a {\n color: @sidebar-dark-submenu-color;\n }\n &.active > a, > a:hover {\n color: @sidebar-dark-submenu-hover-color;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid lighten(@sidebar-dark-bg, 10%);\n margin: 10px 10px;\n input[type=\"text\"], .btn {\n box-shadow: none;\n background-color: lighten(@sidebar-dark-bg, 10%);\n border: 1px solid transparent;\n height: 35px;\n //.transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n}\n\n//Light Sidebar Mixin\n.skin-light-sidebar(@icon-active-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-light-bg;\n }\n .content-wrapper,\n .main-footer {\n //border-left: 1px solid @gray-lte;\n }\n .main-sidebar {\n border-right: 1px solid @gray-lte;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: @sidebar-light-color;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n .transition(border-left-color .3s ease);\n //border-left: 3px solid transparent;\n //Section Headning\n &.header {\n color: lighten(@sidebar-light-color, 25%);\n background: @sidebar-light-bg;\n }\n //links\n > a {\n border-left: 3px solid transparent;\n font-weight: 600;\n }\n //Hover and active states\n &:hover > a,\n &.active > a {\n color: @sidebar-light-hover-color;\n background: @sidebar-light-hover-bg;\n }\n &:hover > a {\n\n }\n &.active {\n border-left-color: @icon-active-color;\n > a {\n font-weight: 600;\n }\n }\n //First Level Submenu\n > .treeview-menu {\n background: @sidebar-light-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-light-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .sidebar-menu .treeview-menu {\n > li {\n > a {\n color: @sidebar-light-submenu-color;\n }\n &.active > a,\n > a:hover {\n color: @sidebar-light-submenu-hover-color;\n }\n &.active > a {\n font-weight: 600;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid @gray-lte; //darken(@sidebar-light-bg, 5%);\n margin: 10px 10px;\n input[type=\"text\"],\n .btn {\n box-shadow: none;\n background-color: #fff; //darken(@sidebar-light-bg, 3%);\n border: 1px solid transparent;\n height: 35px;\n //.transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n @media (min-width: @screen-sm-min) {\n &.sidebar-mini.sidebar-collapse {\n .sidebar-menu > li > .treeview-menu {\n border-left: 1px solid @gray-lte;\n }\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/mixins.less","/*\n * Component: Sidebar\n * ------------------\n */\n// Main Sidebar\n.main-sidebar {\n position: absolute;\n top: 0;\n left: 0;\n padding-top: 50px;\n min-height: 100%;\n width: @sidebar-width;\n z-index: 810;\n\n // Using disposable variable to join statements with a comma\n @transition-rule: @transition-speed @transition-fn, width @transition-speed @transition-fn;\n .transition-transform(@transition-rule);\n\n @media (max-width: @screen-header-collapse) {\n padding-top: 100px;\n }\n\n @media (max-width: @screen-xs-max) {\n .translate(-@sidebar-width, 0);\n }\n\n .sidebar-collapse & {\n @media (min-width: @screen-sm) {\n .translate(-@sidebar-width, 0);\n }\n }\n\n .sidebar-open & {\n @media (max-width: @screen-xs-max) {\n .translate(0, 0);\n }\n }\n}\n\n.sidebar {\n padding-bottom: 10px;\n}\n\n// Remove border from form\n.sidebar-form {\n input:focus {\n border-color: transparent;\n }\n}\n\n// Sidebar user panel\n.user-panel {\n position: relative;\n width: 100%;\n padding: 10px;\n overflow: hidden;\n .clearfix();\n > .image > img {\n width: 100%;\n max-width: 45px;\n height: auto;\n }\n > .info {\n padding: 5px 5px 5px 15px;\n line-height: 1;\n position: absolute;\n left: 55px;\n > p {\n font-weight: 600;\n margin-bottom: 9px;\n }\n > a {\n text-decoration: none;\n padding-right: 5px;\n margin-top: 3px;\n font-size: 11px;\n > .fa,\n > .ion,\n > .glyphicon {\n margin-right: 3px;\n }\n }\n }\n}\n\n// Sidebar menu\n.sidebar-menu {\n list-style: none;\n margin: 0;\n padding: 0;\n //First Level\n > li {\n position: relative;\n margin: 0;\n padding: 0;\n > a {\n padding: 12px 5px 12px 15px;\n display: block;\n > .fa,\n > .glyphicon,\n > .ion {\n width: 20px;\n }\n }\n .label,\n .badge {\n margin-right: 5px;\n }\n .badge {\n margin-top: 3px;\n }\n }\n li.header {\n padding: 10px 25px 10px 15px;\n font-size: 12px;\n }\n li > a > .fa-angle-left,\n li > a > .pull-right-container > .fa-angle-left {\n width: auto;\n height: auto;\n padding: 0;\n margin-right: 10px;\n .transition(transform .5s ease);\n }\n li > a > .fa-angle-left {\n position: absolute;\n top: 50%;\n right: 10px;\n margin-top: -8px;\n }\n\n .menu-open {\n > a > .fa-angle-left,\n > a > .pull-right-container > .fa-angle-left {\n .rotate(-90deg);\n }\n }\n .active > .treeview-menu {\n display: block;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/sidebar.less","/*\n * Component: Sidebar Mini\n */\n\n//Add sidebar-mini class to the body tag to activate this feature\n.sidebar-mini {\n //Sidebar mini should work only on devices larger than @screen-sm\n @media (min-width: @screen-sm) {\n //When the sidebar is collapsed...\n &.sidebar-collapse {\n\n //Apply the new margining to the main content and footer\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-left: 50px !important;\n z-index: 840;\n }\n\n //Modify the sidebar to shrink instead of disappearing\n .main-sidebar {\n //Don't go away! Just shrink\n .translate(0, 0);\n width: 50px !important;\n z-index: 850;\n }\n\n .sidebar-menu {\n > li {\n position: relative;\n > a {\n margin-right: 0;\n }\n > a > span {\n border-top-right-radius: 4px;\n }\n\n &:not(.treeview) {\n > a > span {\n border-bottom-right-radius: 4px;\n }\n }\n\n > .treeview-menu {\n // Add some padding to the treeview menu\n padding-top: 5px;\n padding-bottom: 5px;\n border-bottom-right-radius: 4px;\n }\n }\n }\n\n //Make the sidebar links, menus, labels, badges\n //and angle icons disappear\n .main-sidebar .user-panel > .info,\n .sidebar-form,\n .sidebar-menu > li > a > span,\n .sidebar-menu > li > .treeview-menu,\n .sidebar-menu > li > a > .pull-right,\n .sidebar-menu > li > a > span > .pull-right,\n .sidebar-menu li.header {\n display: none !important;\n -webkit-transform: translateZ(0);\n }\n\n .main-header {\n //Let's make the logo also shrink and the mini logo to appear\n .logo {\n width: 50px;\n > .logo-mini {\n display: block;\n margin-left: -15px;\n margin-right: -15px;\n font-size: 18px;\n }\n > .logo-lg {\n display: none;\n }\n }\n\n //Since the logo got smaller, we need to fix the navbar's position\n .navbar {\n margin-left: 50px;\n }\n }\n }\n }\n}\n\n@media (min-width: @screen-sm) {\n // Show menu items on hover\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse {\n .sidebar-menu > li:hover {\n > a {\n //overflow: visible;\n }\n > a > span:not(.pull-right), //:not(.pull-right-container),\n > .treeview-menu {\n display: block !important;\n position: absolute;\n width: @sidebar-width - 50;\n left: 50px;\n }\n\n //position the header & treeview menus\n > a > span {\n top: 0;\n margin-left: -3px;\n padding: 12px 5px 12px 20px;\n background-color: inherit;\n }\n > a > .pull-right-container {\n //display: block!important;\n position: relative !important;\n float: right;\n width: auto !important;\n left: 200px - 20px !important;\n top: -22px !important;\n z-index: 900;\n > .label:not(:first-of-type) {\n display: none;\n }\n }\n > .treeview-menu {\n top: 44px;\n margin-left: 0;\n }\n }\n }\n}\n\n.sidebar-expanded-on-hover {\n .main-footer,\n .content-wrapper {\n margin-left: 50px;\n }\n .main-sidebar {\n box-shadow: @sidebar-expanded-shadow;\n }\n}\n\n//A fix for text overflow while transitioning from sidebar mini to full sidebar\n.sidebar-menu,\n.main-sidebar .user-panel,\n.sidebar-menu > li.header {\n white-space: nowrap;\n overflow: hidden;\n}\n\n.sidebar-menu:hover {\n overflow: visible;\n}\n\n.sidebar-form,\n.sidebar-menu > li.header {\n overflow: hidden;\n text-overflow: clip;\n}\n\n.sidebar-menu li > a {\n position: relative;\n > .pull-right-container {\n position: absolute;\n right: 10px;\n top: 50%;\n margin-top: -7px;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/sidebar-mini.less","/*\n * Component: Control sidebar. By default, this is the right sidebar.\n */\n// The sidebar's background control class\n// This is a hack to make the background visible while scrolling\n.control-sidebar-bg {\n position: fixed;\n z-index: 1000;\n bottom: 0;\n}\n\n// Transitions\n.control-sidebar-bg,\n.control-sidebar {\n top: 0;\n right: -@control-sidebar-width;\n width: @control-sidebar-width;\n .transition(right @transition-speed ease-in-out);\n}\n\n// The sidebar\n.control-sidebar {\n position: absolute;\n padding-top: @navbar-height;\n z-index: 1010;\n // Fix position after header collapse\n @media (max-width: @screen-xs-max) {\n padding-top: @navbar-height + 50;\n }\n // Tab panes\n > .tab-content {\n padding: 10px 15px;\n }\n // Open state with slide over content effect\n &.control-sidebar-open {\n &,\n + .control-sidebar-bg {\n right: 0;\n }\n }\n}\n\n// Open without slide over content\n.control-sidebar-hold-transition {\n .control-sidebar-bg,\n .control-sidebar,\n .content-wrapper {\n transition: none;\n }\n\n}\n.control-sidebar-open {\n .control-sidebar-bg,\n .control-sidebar {\n right: 0;\n }\n @media (min-width: @screen-sm) {\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-right: @control-sidebar-width;\n }\n }\n}\n\n// Fixed Layout\n.fixed {\n .control-sidebar {\n position: fixed;\n height: 100%;\n overflow-y: auto;\n padding-bottom: 50px;\n }\n}\n\n// Control sidebar tabs\n.nav-tabs.control-sidebar-tabs {\n > li {\n &:first-of-type > a {\n &,\n &:hover,\n &:focus {\n border-left-width: 0;\n }\n }\n > a {\n .border-radius(0);\n\n // Hover and active states\n &,\n &:hover {\n border-top: none;\n border-right: none;\n border-left: 1px solid transparent;\n border-bottom: 1px solid transparent;\n }\n .icon {\n font-size: 16px;\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n border-top: none;\n border-right: none;\n border-bottom: none;\n }\n }\n }\n }\n // Remove responsiveness on small screens\n @media (max-width: @screen-sm) {\n display: table;\n > li {\n display: table-cell;\n }\n }\n}\n\n// Headings in the sidebar content\n.control-sidebar-heading {\n font-weight: 400;\n font-size: 16px;\n padding: 10px 0;\n margin-bottom: 10px;\n}\n\n// Subheadings\n.control-sidebar-subheading {\n display: block;\n font-weight: 400;\n font-size: 14px;\n}\n\n// Control Sidebar Menu\n.control-sidebar-menu {\n list-style: none;\n padding: 0;\n margin: 0 -15px;\n > li > a {\n .clearfix();\n display: block;\n padding: 10px 15px;\n > .control-sidebar-subheading {\n margin-top: 0;\n }\n }\n .menu-icon {\n float: left;\n width: 35px;\n height: 35px;\n border-radius: 50%;\n text-align: center;\n line-height: 35px;\n }\n .menu-info {\n margin-left: 45px;\n margin-top: 3px;\n > .control-sidebar-subheading {\n margin: 0;\n }\n > p {\n margin: 0;\n font-size: 11px;\n }\n }\n .progress {\n margin: 0;\n }\n}\n\n// Dark skin\n.control-sidebar-dark {\n color: @sidebar-dark-color;\n // Background\n &,\n + .control-sidebar-bg {\n background: @sidebar-dark-bg;\n }\n // Sidebar tabs\n .nav-tabs.control-sidebar-tabs {\n border-bottom: darken(@sidebar-dark-bg, 3%);\n > li {\n > a {\n background: darken(@sidebar-dark-bg, 5%);\n color: @sidebar-dark-color;\n // Hover and active states\n &,\n &:hover,\n &:focus {\n border-left-color: darken(@sidebar-dark-bg, 7%);\n border-bottom-color: darken(@sidebar-dark-bg, 7%);\n }\n &:hover,\n &:focus,\n &:active {\n background: darken(@sidebar-dark-bg, 3%);\n }\n &:hover {\n color: #fff;\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n background: @sidebar-dark-bg;\n color: #fff;\n }\n }\n }\n }\n }\n // Heading & subheading\n .control-sidebar-heading,\n .control-sidebar-subheading {\n color: #fff;\n }\n // Sidebar list\n .control-sidebar-menu {\n > li {\n > a {\n &:hover {\n background: @sidebar-dark-hover-bg;\n }\n .menu-info {\n > p {\n color: @sidebar-dark-color;\n }\n }\n }\n }\n }\n}\n\n// Light skin\n.control-sidebar-light {\n color: lighten(@sidebar-light-color, 10%);\n // Background\n &,\n + .control-sidebar-bg {\n background: @sidebar-light-bg;\n border-left: 1px solid @gray-lte;\n }\n // Sidebar tabs\n .nav-tabs.control-sidebar-tabs {\n border-bottom: @gray-lte;\n > li {\n > a {\n background: darken(@sidebar-light-bg, 5%);\n color: @sidebar-light-color;\n // Hover and active states\n &,\n &:hover,\n &:focus {\n border-left-color: @gray-lte;\n border-bottom-color: @gray-lte;\n }\n &:hover,\n &:focus,\n &:active {\n background: darken(@sidebar-light-bg, 3%);\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n background: @sidebar-light-bg;\n color: #111;\n }\n }\n }\n }\n }\n // Heading & subheading\n .control-sidebar-heading,\n .control-sidebar-subheading {\n color: #111;\n }\n // Sidebar list\n .control-sidebar-menu {\n margin-left: -14px;\n > li {\n > a {\n &:hover {\n background: @sidebar-light-hover-bg;\n }\n .menu-info {\n > p {\n color: lighten(@sidebar-light-color, 10%);\n }\n }\n }\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/control-sidebar.less","/*\n * Component: Dropdown menus\n * -------------------------\n */\n\n/*Dropdowns in general*/\n.dropdown-menu {\n box-shadow: none;\n border-color: #eee;\n > li > a {\n color: #777;\n }\n > li > a > .glyphicon,\n > li > a > .fa,\n > li > a > .ion {\n margin-right: 10px;\n }\n > li > a:hover {\n background-color: lighten(@gray-lte, 5%);\n color: #333;\n }\n > .divider {\n background-color: #eee;\n }\n}\n\n//Navbar custom dropdown menu\n.navbar-nav > .notifications-menu,\n.navbar-nav > .messages-menu,\n.navbar-nav > .tasks-menu {\n //fix width and padding\n > .dropdown-menu {\n > li {\n position: relative;\n }\n width: 280px;\n //Remove padding and margins\n padding: 0 0 0 0;\n margin: 0;\n top: 100%;\n }\n //Define header class\n > .dropdown-menu > li.header {\n .border-radius(4px; 4px; 0; 0);\n background-color: #ffffff;\n padding: 7px 10px;\n border-bottom: 1px solid #f4f4f4;\n color: #444444;\n font-size: 14px;\n }\n\n //Define footer class\n > .dropdown-menu > li.footer > a {\n .border-radius(0; 0; 4px; 4px);\n font-size: 12px;\n background-color: #fff;\n padding: 7px 10px;\n border-bottom: 1px solid #eeeeee;\n color: #444 !important;\n @media (max-width: @screen-sm-max) {\n background: #fff !important;\n color: #444 !important;\n }\n text-align: center;\n //Hover state\n &:hover {\n text-decoration: none;\n font-weight: normal;\n }\n }\n\n //Clear inner menu padding and margins\n > .dropdown-menu > li .menu {\n max-height: 200px;\n margin: 0;\n padding: 0;\n list-style: none;\n overflow-x: hidden;\n > li > a {\n display: block;\n white-space: nowrap; /* Prevent text from breaking */\n border-bottom: 1px solid #f4f4f4;\n // Hove state\n &:hover {\n background: #f4f4f4;\n text-decoration: none;\n }\n }\n }\n}\n\n//Notifications menu\n.navbar-nav > .notifications-menu {\n > .dropdown-menu > li .menu {\n // Links inside the menu\n > li > a {\n color: #444444;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 10px;\n // Icons inside the menu\n > .glyphicon,\n > .fa,\n > .ion {\n width: 20px;\n }\n }\n\n }\n}\n\n//Messages menu\n.navbar-nav > .messages-menu {\n //Inner menu\n > .dropdown-menu > li .menu {\n // Messages menu item\n > li > a {\n margin: 0;\n //line-height: 20px;\n padding: 10px 10px;\n // User image\n > div > img {\n margin: auto 10px auto auto;\n width: 40px;\n height: 40px;\n }\n // Message heading\n > h4 {\n padding: 0;\n margin: 0 0 0 45px;\n color: #444444;\n font-size: 15px;\n position: relative;\n // Small for message time display\n > small {\n color: #999999;\n font-size: 10px;\n position: absolute;\n top: 0;\n right: 0;\n }\n }\n\n > p {\n margin: 0 0 0 45px;\n font-size: 12px;\n color: #888888;\n }\n\n .clearfix();\n\n }\n\n }\n}\n\n//Tasks menu\n.navbar-nav > .tasks-menu {\n > .dropdown-menu > li .menu {\n > li > a {\n padding: 10px;\n\n > h3 {\n font-size: 14px;\n padding: 0;\n margin: 0 0 10px 0;\n color: #666666;\n }\n\n > .progress {\n padding: 0;\n margin: 0;\n }\n }\n }\n}\n\n//User menu\n.navbar-nav > .user-menu {\n > .dropdown-menu {\n .border-top-radius(0);\n padding: 1px 0 0 0;\n border-top-width: 0;\n width: 280px;\n\n &,\n > .user-body {\n .border-bottom-radius(4px);\n }\n // Header menu\n > li.user-header {\n height: 175px;\n padding: 10px;\n text-align: center;\n // User image\n > img {\n z-index: 5;\n height: 90px;\n width: 90px;\n border: 3px solid;\n border-color: transparent;\n border-color: rgba(255, 255, 255, 0.2);\n }\n > p {\n z-index: 5;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n font-size: 17px;\n //text-shadow: 2px 2px 3px #333333;\n margin-top: 10px;\n > small {\n display: block;\n font-size: 12px;\n }\n }\n }\n\n // Menu Body\n > .user-body {\n padding: 15px;\n border-bottom: 1px solid #f4f4f4;\n border-top: 1px solid #dddddd;\n .clearfix();\n a {\n color: #444 !important;\n @media (max-width: @screen-sm-max) {\n background: #fff !important;\n color: #444 !important;\n }\n }\n }\n\n // Menu Footer\n > .user-footer {\n background-color: #f9f9f9;\n padding: 10px;\n .clearfix();\n .btn-default {\n color: #666666;\n &:hover {\n @media (max-width: @screen-sm-max) {\n background-color: #f9f9f9;\n }\n }\n }\n }\n }\n .user-image {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n margin-top: -2px;\n @media (max-width: @screen-xs-max) {\n float: none;\n margin-right: 0;\n margin-top: -8px;\n line-height: 10px;\n }\n }\n}\n\n/* Add fade animation to dropdown menus by appending\n the class .animated-dropdown-menu to the .dropdown-menu ul (or ol)*/\n.open:not(.dropup) > .animated-dropdown-menu {\n backface-visibility: visible !important;\n .animation(flipInX .7s both);\n\n}\n\n@keyframes flipInX {\n 0% {\n transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n transition-timing-function: ease-in;\n opacity: 0;\n }\n\n 40% {\n transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n transition-timing-function: ease-in;\n }\n\n 60% {\n transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n\n 80% {\n transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n\n 100% {\n transform: perspective(400px);\n }\n}\n\n@-webkit-keyframes flipInX {\n 0% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n -webkit-transition-timing-function: ease-in;\n opacity: 0;\n }\n\n 40% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n -webkit-transition-timing-function: ease-in;\n }\n\n 60% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n\n 80% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n\n 100% {\n -webkit-transform: perspective(400px);\n }\n}\n\n/* Fix dropdown menu in navbars */\n.navbar-custom-menu > .navbar-nav {\n > li {\n position: relative;\n > .dropdown-menu {\n position: absolute;\n right: 0;\n left: auto;\n }\n }\n}\n\n@media (max-width: @screen-sm-max) {\n .navbar-custom-menu > .navbar-nav {\n float: right;\n > li {\n position: static;\n > .dropdown-menu {\n position: absolute;\n right: 5%;\n left: auto;\n border: 1px solid #ddd;\n background: #fff;\n }\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/dropdown.less","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/border-radius.less","/*\n * Component: Form\n * ---------------\n */\n.form-control {\n .border-radius(@input-radius);\n box-shadow: none;\n border-color: @gray-lte;\n &:focus {\n border-color: @light-blue;\n box-shadow: none;\n }\n &::-moz-placeholder,\n &:-ms-input-placeholder,\n &::-webkit-input-placeholder {\n color: #bbb;\n opacity: 1;\n }\n\n &:not(select) {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n }\n}\n\n.form-group {\n &.has-success {\n label {\n color: @green;\n }\n .form-control,\n .input-group-addon {\n border-color: @green;\n box-shadow: none;\n }\n .help-block {\n color: @green;\n }\n }\n\n &.has-warning {\n label {\n color: @yellow;\n }\n .form-control,\n .input-group-addon {\n border-color: @yellow;\n box-shadow: none;\n }\n .help-block {\n color: @yellow;\n }\n }\n\n &.has-error {\n label {\n color: @red;\n }\n .form-control,\n .input-group-addon {\n border-color: @red;\n box-shadow: none;\n }\n .help-block {\n color: @red;\n }\n }\n}\n\n/* Input group */\n.input-group {\n .input-group-addon {\n .border-radius(@input-radius);\n border-color: @gray-lte;\n background-color: #fff;\n }\n}\n\n/* button groups */\n.btn-group-vertical {\n .btn {\n &.btn-flat:first-of-type, &.btn-flat:last-of-type {\n .border-radius(0);\n }\n }\n}\n\n.icheck > label {\n padding-left: 0;\n}\n\n/* support Font Awesome icons in form-control */\n.form-control-feedback.fa {\n line-height: @input-height-base;\n}\n\n.input-lg + .form-control-feedback.fa,\n.input-group-lg + .form-control-feedback.fa,\n.form-group-lg .form-control + .form-control-feedback.fa {\n line-height: @input-height-large;\n}\n\n.input-sm + .form-control-feedback.fa,\n.input-group-sm + .form-control-feedback.fa,\n.form-group-sm .form-control + .form-control-feedback.fa {\n line-height: @input-height-small;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/forms.less","/*\n * Component: Progress Bar\n * -----------------------\n */\n\n//General CSS\n.progress,\n.progress > .progress-bar {\n .box-shadow(none);\n &, .progress-bar {\n .border-radius(@progress-bar-border-radius);\n }\n}\n\n/* size variation */\n.progress.sm,\n.progress-sm {\n height: 10px;\n &, .progress-bar {\n .border-radius(@progress-bar-sm-border-radius);\n }\n}\n\n.progress.xs,\n.progress-xs {\n height: 7px;\n &, .progress-bar {\n .border-radius(@progress-bar-xs-border-radius);\n }\n}\n\n.progress.xxs,\n.progress-xxs {\n height: 3px;\n &, .progress-bar {\n .border-radius(@progress-bar-xs-border-radius);\n }\n}\n\n/* Vertical bars */\n.progress.vertical {\n position: relative;\n width: 30px;\n height: 200px;\n display: inline-block;\n margin-right: 10px;\n > .progress-bar {\n width: 100%;\n position: absolute;\n bottom: 0;\n }\n\n //Sizes\n &.sm,\n &.progress-sm {\n width: 20px;\n }\n\n &.xs,\n &.progress-xs {\n width: 10px;\n }\n &.xxs,\n &.progress-xxs {\n width: 3px;\n }\n}\n\n//Progress Groups\n.progress-group {\n .progress-text {\n font-weight: 600;\n }\n .progress-number {\n float: right;\n }\n}\n\n/* Remove margins from progress bars when put in a table */\n.table {\n tr > td .progress {\n margin: 0;\n }\n}\n\n// Variations\n// -------------------------\n.progress-bar-light-blue,\n.progress-bar-primary {\n .progress-bar-variant(@light-blue);\n}\n\n.progress-bar-green,\n.progress-bar-success {\n .progress-bar-variant(@green);\n}\n\n.progress-bar-aqua,\n.progress-bar-info {\n .progress-bar-variant(@aqua);\n}\n\n.progress-bar-yellow,\n.progress-bar-warning {\n .progress-bar-variant(@yellow);\n}\n\n.progress-bar-red,\n.progress-bar-danger {\n .progress-bar-variant(@red);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/progress-bars.less","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/progress-bar.less","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/gradients.less","/*\n * Component: Small Box\n * --------------------\n */\n\n.small-box {\n .border-radius(2px);\n position: relative;\n display: block;\n margin-bottom: 20px;\n box-shadow: @box-boxshadow;\n // content wrapper\n > .inner {\n padding: 10px;\n }\n\n > .small-box-footer {\n position: relative;\n text-align: center;\n padding: 3px 0;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n display: block;\n z-index: 10;\n background: rgba(0, 0, 0, 0.1);\n text-decoration: none;\n &:hover {\n color: #fff;\n background: rgba(0, 0, 0, 0.15);\n }\n }\n\n h3 {\n font-size: 38px;\n font-weight: bold;\n margin: 0 0 10px 0;\n white-space: nowrap;\n padding: 0;\n\n }\n\n p {\n font-size: 15px;\n > small {\n display: block;\n color: #f9f9f9;\n font-size: 13px;\n margin-top: 5px;\n }\n }\n\n h3, p {\n z-index: 5;\n }\n\n // the icon\n .icon {\n .transition(all @transition-speed linear);\n position: absolute;\n top: -10px;\n right: 10px;\n z-index: 0;\n font-size: 90px;\n color: rgba(0, 0, 0, 0.15);\n }\n\n // Small box hover state\n &:hover {\n text-decoration: none;\n color: #f9f9f9;\n // Animate icons on small box hover\n .icon {\n font-size: 95px;\n }\n }\n}\n\n@media (max-width: @screen-xs-max) {\n // No need for icons on very small devices\n .small-box {\n text-align: center;\n .icon {\n display: none;\n }\n p {\n font-size: 12px;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/small-box.less","/*\n * Component: Box\n * --------------\n */\n.box {\n position: relative;\n .border-radius(@box-border-radius);\n background: #ffffff;\n border-top: 3px solid @box-default-border-top-color;\n margin-bottom: 20px;\n width: 100%;\n box-shadow: @box-boxshadow;\n\n // Box color variations\n &.box-primary {\n border-top-color: @light-blue;\n }\n &.box-info {\n border-top-color: @aqua;\n }\n &.box-danger {\n border-top-color: @red;\n }\n &.box-warning {\n border-top-color: @yellow;\n }\n &.box-success {\n border-top-color: @green;\n }\n &.box-default {\n border-top-color: @gray-lte;\n }\n\n // collapsed mode\n &.collapsed-box {\n .box-body,\n .box-footer {\n display: none;\n }\n }\n\n .nav-stacked {\n > li {\n border-bottom: 1px solid @box-border-color;\n margin: 0;\n &:last-of-type {\n border-bottom: none;\n }\n }\n }\n\n // fixed height to 300px\n &.height-control {\n .box-body {\n max-height: 300px;\n overflow: auto;\n }\n }\n\n .border-right {\n border-right: 1px solid @box-border-color;\n }\n .border-left {\n border-left: 1px solid @box-border-color;\n }\n\n //SOLID BOX\n //---------\n //use this class to get a colored header and borders\n\n &.box-solid {\n border-top: 0;\n > .box-header {\n .btn.btn-default {\n background: transparent;\n }\n .btn,\n a {\n &:hover {\n background: rgba(0, 0, 0, 0.1);\n }\n }\n }\n\n // Box color variations\n &.box-default {\n .box-solid-variant(@gray-lte, #444);\n }\n &.box-primary {\n .box-solid-variant(@light-blue);\n }\n &.box-info {\n .box-solid-variant(@aqua);\n }\n &.box-danger {\n .box-solid-variant(@red);\n }\n &.box-warning {\n .box-solid-variant(@yellow);\n }\n &.box-success {\n .box-solid-variant(@green);\n }\n\n > .box-header > .box-tools .btn {\n border: 0;\n box-shadow: none;\n }\n\n // Fix font color for tiles\n &[class*='bg'] {\n > .box-header {\n color: #fff;\n }\n }\n\n }\n\n //BOX GROUP\n .box-group {\n > .box {\n margin-bottom: 5px;\n }\n }\n\n // jQuery Knob in a box\n .knob-label {\n text-align: center;\n color: #333;\n font-weight: 100;\n font-size: 12px;\n margin-bottom: 0.3em;\n }\n}\n\n.box,\n.overlay-wrapper {\n // Box overlay for LOADING STATE effect\n > .overlay,\n > .loading-img {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n\n .overlay {\n z-index: 50;\n background: rgba(255, 255, 255, 0.7);\n .border-radius(@box-border-radius);\n > .fa {\n position: absolute;\n top: 50%;\n left: 50%;\n margin-left: -15px;\n margin-top: -15px;\n color: #000;\n font-size: 30px;\n }\n }\n\n .overlay.dark {\n background: rgba(0, 0, 0, 0.5);\n }\n}\n\n//Add clearfix to header, body and footer\n.box-header,\n.box-body,\n.box-footer {\n .clearfix();\n}\n\n//Box header\n.box-header {\n color: #444;\n display: block;\n padding: @box-padding;\n position: relative;\n\n //Add bottom border\n &.with-border {\n border-bottom: 1px solid @box-border-color;\n .collapsed-box & {\n border-bottom: none;\n }\n }\n\n //Icons and box title\n > .fa,\n > .glyphicon,\n > .ion,\n .box-title {\n display: inline-block;\n font-size: 18px;\n margin: 0;\n line-height: 1;\n }\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n > .box-tools {\n float: right;\n margin-top: -5px;\n margin-bottom: -5px;\n [data-toggle=\"tooltip\"] {\n position: relative;\n }\n\n &.pull-right {\n .dropdown-menu {\n right: 0;\n left: auto;\n }\n }\n\n .dropdown-menu > li > a {\n color: #444!important;\n }\n }\n}\n\n//Box Tools Buttons\n.btn-box-tool {\n padding: 5px;\n font-size: 12px;\n background: transparent;\n color: darken(@box-default-border-top-color, 20%);\n .open &,\n &:hover {\n color: darken(@box-default-border-top-color, 40%);\n }\n &.btn:active {\n box-shadow: none;\n }\n}\n\n//Box Body\n.box-body {\n .border-radius(0; 0; @box-border-radius; @box-border-radius);\n padding: @box-padding;\n .no-header & {\n .border-top-radius(@box-border-radius);\n }\n // Tables within the box body\n > .table {\n margin-bottom: 0;\n }\n\n // Calendar within the box body\n .fc {\n margin-top: 5px;\n }\n\n .full-width-chart {\n margin: -19px;\n }\n &.no-padding .full-width-chart {\n margin: -9px;\n }\n\n .box-pane {\n .border-radius(0; 0; @box-border-radius; 0);\n }\n .box-pane-right {\n .border-radius(0; 0; 0; @box-border-radius);\n }\n}\n\n//Box footer\n.box-footer {\n .border-radius(0; 0; @box-border-radius; @box-border-radius);\n border-top: 1px solid @box-border-color;\n padding: @box-padding;\n background-color: @box-footer-bg;\n}\n\n.chart-legend {\n &:extend(.list-unstyled);\n margin: 10px 0;\n > li {\n @media (max-width: @screen-sm-max) {\n float: left;\n margin-right: 10px;\n }\n }\n}\n\n//Comment Box\n.box-comments {\n background: #f7f7f7;\n .box-comment {\n .clearfix();\n padding: 8px 0;\n border-bottom: 1px solid #eee;\n &:last-of-type {\n border-bottom: 0;\n }\n &:first-of-type {\n padding-top: 0;\n }\n img {\n &:extend(.img-sm);\n float: left;\n }\n }\n .comment-text {\n margin-left: 40px;\n color: #555;\n }\n .username {\n color: #444;\n display: block;\n font-weight: 600;\n }\n .text-muted {\n font-weight: 400;\n font-size: 12px;\n }\n}\n\n//Widgets\n//-----------\n\n/* Widget: TODO LIST */\n\n.todo-list {\n margin: 0;\n padding: 0;\n list-style: none;\n overflow: auto;\n // Todo list element\n > li {\n .border-radius(2px);\n padding: 10px;\n background: #f4f4f4;\n margin-bottom: 2px;\n border-left: 2px solid #e6e7e8;\n color: #444;\n &:last-of-type {\n margin-bottom: 0;\n }\n\n > input[type='checkbox'] {\n margin: 0 10px 0 5px;\n }\n\n .text {\n display: inline-block;\n margin-left: 5px;\n font-weight: 600;\n }\n\n // Time labels\n .label {\n margin-left: 10px;\n font-size: 9px;\n }\n\n // Tools and options box\n .tools {\n display: none;\n float: right;\n color: @red;\n // icons\n > .fa, > .glyphicon, > .ion {\n margin-right: 5px;\n cursor: pointer;\n }\n\n }\n &:hover .tools {\n display: inline-block;\n }\n\n &.done {\n color: #999;\n .text {\n text-decoration: line-through;\n font-weight: 500;\n }\n\n .label {\n background: @gray-lte !important;\n }\n }\n }\n\n // Color varaity\n .danger {\n border-left-color: @red;\n }\n .warning {\n border-left-color: @yellow;\n }\n .info {\n border-left-color: @aqua;\n }\n .success {\n border-left-color: @green;\n }\n .primary {\n border-left-color: @light-blue;\n }\n\n .handle {\n display: inline-block;\n cursor: move;\n margin: 0 5px;\n }\n\n}\n\n// END TODO WIDGET\n\n/* Chat widget (DEPRECATED - this will be removed in the next major release. Use Direct Chat instead)*/\n.chat {\n padding: 5px 20px 5px 10px;\n\n .item {\n .clearfix();\n margin-bottom: 10px;\n // The image\n > img {\n width: 40px;\n height: 40px;\n border: 2px solid transparent;\n .border-radius(50%);\n }\n\n > .online {\n border: 2px solid @green;\n }\n > .offline {\n border: 2px solid @red;\n }\n\n // The message body\n > .message {\n margin-left: 55px;\n margin-top: -40px;\n > .name {\n display: block;\n font-weight: 600;\n }\n }\n\n // The attachment\n > .attachment {\n .border-radius(@attachment-border-radius);\n background: #f4f4f4;\n margin-left: 65px;\n margin-right: 15px;\n padding: 10px;\n > h4 {\n margin: 0 0 5px 0;\n font-weight: 600;\n font-size: 14px;\n }\n > p, > .filename {\n font-weight: 600;\n font-size: 13px;\n font-style: italic;\n margin: 0;\n\n }\n .clearfix();\n }\n }\n\n}\n\n//END CHAT WIDGET\n\n//Input in box\n.box-input {\n max-width: 200px;\n}\n\n//A fix for panels body text color when placed within\n// a modal\n.modal {\n .panel-body {\n color: #444;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/boxes.less","/*\n * Component: Info Box\n * -------------------\n */\n.info-box {\n display: block;\n min-height: 90px;\n background: #fff;\n width: 100%;\n box-shadow: @box-boxshadow;\n .border-radius(2px);\n margin-bottom: 15px;\n small {\n font-size: 14px;\n }\n .progress {\n background: rgba(0, 0, 0, .2);\n margin: 5px -10px 5px -10px;\n height: 2px;\n &,\n & .progress-bar {\n .border-radius(0);\n }\n .progress-bar {\n background: #fff;\n }\n }\n}\n\n.info-box-icon {\n .border-radius(2px; 0; 2px; 0);\n display: block;\n float: left;\n height: 90px;\n width: 90px;\n text-align: center;\n font-size: 45px;\n line-height: 90px;\n background: rgba(0, 0, 0, 0.2);\n > img {\n max-width: 100%;\n }\n}\n\n.info-box-content {\n padding: 5px 10px;\n margin-left: 90px;\n}\n\n.info-box-number {\n display: block;\n font-weight: bold;\n font-size: 18px;\n}\n\n.progress-description,\n.info-box-text {\n display: block;\n font-size: 14px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.info-box-text {\n text-transform: uppercase;\n}\n\n.info-box-more {\n display: block;\n}\n\n.progress-description {\n margin: 0;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/info-box.less","/*\n * Component: Timeline\n * -------------------\n */\n\n.timeline {\n position: relative;\n margin: 0 0 30px 0;\n padding: 0;\n list-style: none;\n\n // The line\n &:before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n width: 4px;\n background: #ddd;\n left: 31px;\n margin: 0;\n .border-radius(2px);\n }\n\n > li {\n position: relative;\n margin-right: 10px;\n margin-bottom: 15px;\n .clearfix();\n\n // The content\n > .timeline-item {\n .box-shadow(@box-boxshadow);\n .border-radius(@box-border-radius);\n margin-top: 0;\n background: #fff;\n color: #444;\n margin-left: 60px;\n margin-right: 15px;\n padding: 0;\n position: relative;\n\n // The time and header\n > .time {\n color: #999;\n float: right;\n padding: 10px;\n font-size: 12px;\n }\n > .timeline-header {\n margin: 0;\n color: #555;\n border-bottom: 1px solid @box-border-color;\n padding: 10px;\n font-size: 16px;\n line-height: 1.1;\n > a {\n font-weight: 600;\n }\n }\n // Item body and footer\n > .timeline-body, > .timeline-footer {\n padding: 10px;\n }\n\n }\n\n // The icons\n > .fa,\n > .glyphicon,\n > .ion {\n width: 30px;\n height: 30px;\n font-size: 15px;\n line-height: 30px;\n position: absolute;\n color: #666;\n background: @gray-lte;\n border-radius: 50%;\n text-align: center;\n left: 18px;\n top: 0;\n }\n }\n\n // Time label\n > .time-label {\n > span {\n font-weight: 600;\n padding: 5px;\n display: inline-block;\n background-color: #fff;\n\n .border-radius(4px);\n }\n }\n}\n\n.timeline-inverse {\n > li {\n > .timeline-item {\n background: #f0f0f0;\n border: 1px solid #ddd;\n .box-shadow(none);\n > .timeline-header {\n border-bottom-color: #ddd;\n }\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/timeline.less","/*\n * Component: Button\n * -----------------\n */\n\n.btn {\n .border-radius(@btn-border-radius);\n .box-shadow(@btn-boxshadow);\n border: 1px solid transparent;\n\n &.uppercase {\n text-transform: uppercase\n }\n\n // Flat buttons\n &.btn-flat {\n .border-radius(0);\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n border-width: 1px;\n }\n\n // Active state\n &:active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n }\n\n &:focus {\n outline: none;\n }\n\n // input file btn\n &.btn-file {\n position: relative;\n overflow: hidden;\n > input[type='file'] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n .opacity(0);\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n }\n }\n}\n\n//Button color variations\n.btn-default {\n background-color: #f4f4f4;\n color: #444;\n border-color: #ddd;\n &:hover,\n &:active,\n &.hover {\n background-color: darken(#f4f4f4, 5%);\n }\n}\n\n.btn-primary {\n background-color: @light-blue;\n border-color: darken(@light-blue, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@light-blue, 5%);\n }\n}\n\n.btn-success {\n background-color: @green;\n border-color: darken(@green, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@green, 5%);\n }\n}\n\n.btn-info {\n background-color: @aqua;\n border-color: darken(@aqua, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@aqua, 5%);\n }\n}\n\n.btn-danger {\n background-color: @red;\n border-color: darken(@red, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@red, 5%);\n }\n}\n\n.btn-warning {\n background-color: @yellow;\n border-color: darken(@yellow, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@yellow, 5%);\n }\n}\n\n.btn-outline {\n border: 1px solid #fff;\n background: transparent;\n color: #fff;\n &:hover,\n &:focus,\n &:active {\n color: rgba(255, 255, 255, .7);\n border-color: rgba(255, 255, 255, .7);\n }\n}\n\n.btn-link {\n .box-shadow(none);\n}\n\n//General .btn with bg class\n.btn[class*='bg-']:hover {\n .box-shadow(inset 0 0 100px rgba(0, 0, 0, 0.2));\n}\n\n// Application buttons\n.btn-app {\n .border-radius(3px);\n position: relative;\n padding: 15px 5px;\n margin: 0 0 10px 10px;\n min-width: 80px;\n height: 60px;\n text-align: center;\n color: #666;\n border: 1px solid #ddd;\n background-color: #f4f4f4;\n font-size: 12px;\n //Icons within the btn\n > .fa, > .glyphicon, > .ion {\n font-size: 20px;\n display: block;\n }\n\n &:hover {\n background: #f4f4f4;\n color: #444;\n border-color: #aaa;\n }\n\n &:active, &:focus {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n }\n\n //The badge\n > .badge {\n position: absolute;\n top: -3px;\n right: -10px;\n font-size: 10px;\n font-weight: 400;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/buttons.less","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/opacity.less","/*\n * Component: Callout\n * ------------------\n */\n\n// Base styles (regardless of theme)\n.callout {\n .border-radius(3px);\n margin: 0 0 20px 0;\n padding: 15px 30px 15px 15px;\n border-left: 5px solid #eee;\n a {\n color: #fff;\n text-decoration: underline;\n &:hover {\n color: #eee;\n }\n }\n h4 {\n margin-top: 0;\n font-weight: 600;\n }\n p:last-child {\n margin-bottom: 0;\n }\n code,\n .highlight {\n background-color: #fff;\n }\n\n // Themes for different contexts\n &.callout-danger {\n &:extend(.bg-red);\n border-color: darken(@red, 10%);\n }\n &.callout-warning {\n &:extend(.bg-yellow);\n border-color: darken(@yellow, 10%);\n }\n &.callout-info {\n &:extend(.bg-aqua);\n border-color: darken(@aqua, 10%);\n }\n &.callout-success {\n &:extend(.bg-green);\n border-color: darken(@green, 10%);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/callout.less","/*\n * Component: alert\n * ----------------\n */\n\n.alert {\n .border-radius(3px);\n h4 {\n font-weight: 600;\n }\n .icon {\n margin-right: 10px;\n }\n .close {\n color: #000;\n .opacity(.2);\n &:hover {\n .opacity(.5);\n }\n }\n a {\n color: #fff;\n text-decoration: underline;\n }\n}\n\n//Alert Variants\n.alert-success {\n &:extend(.bg-green);\n border-color: darken(@green, 5%);\n}\n\n.alert-danger,\n.alert-error {\n &:extend(.bg-red);\n border-color: darken(@red, 5%);\n}\n\n.alert-warning {\n &:extend(.bg-yellow);\n border-color: darken(@yellow, 5%);\n}\n\n.alert-info {\n &:extend(.bg-aqua);\n border-color: darken(@aqua, 5%);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/alerts.less","/*\n * Component: Nav\n * --------------\n */\n\n.nav {\n > li > a:hover,\n > li > a:active,\n > li > a:focus {\n color: #444;\n background: #f7f7f7;\n }\n}\n\n/* NAV PILLS */\n.nav-pills {\n > li > a {\n .border-radius(0);\n border-top: 3px solid transparent;\n color: #444;\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n > li.active > a,\n > li.active > a:hover,\n > li.active > a:focus {\n border-top-color: @light-blue;\n }\n > li.active > a {\n font-weight: 600;\n }\n}\n\n/* NAV STACKED */\n.nav-stacked {\n > li > a {\n .border-radius(0);\n border-top: 0;\n border-left: 3px solid transparent;\n color: #444;\n }\n > li.active > a,\n > li.active > a:hover {\n background: transparent;\n color: #444;\n border-top: 0;\n border-left-color: @light-blue;\n }\n\n > li.header {\n border-bottom: 1px solid #ddd;\n color: #777;\n margin-bottom: 10px;\n padding: 5px 10px;\n text-transform: uppercase;\n }\n}\n\n/* NAV TABS */\n.nav-tabs-custom {\n margin-bottom: 20px;\n background: #fff;\n box-shadow: @box-boxshadow;\n border-radius: @box-border-radius;\n > .nav-tabs {\n margin: 0;\n border-bottom-color: #f4f4f4;\n\n .border-top-radius(@box-border-radius);\n > li {\n border-top: 3px solid transparent;\n margin-bottom: -2px;\n\n &.disabled > a {\n color: #777;\n }\n\n > a {\n color: #444;\n .border-radius(0);\n &.text-muted {\n color: #999;\n }\n &,\n &:hover {\n background: transparent;\n margin: 0;\n }\n &:hover {\n color: #999;\n }\n }\n &:not(.active) {\n > a:hover,\n > a:focus,\n > a:active {\n border-color: transparent;\n }\n }\n margin-right: 5px;\n }\n\n > li.active {\n border-top-color: @light-blue;\n & > a,\n &:hover > a {\n background-color: #fff;\n color: #444;\n }\n > a {\n border-top-color: transparent;\n border-left-color: #f4f4f4;\n border-right-color: #f4f4f4;\n }\n\n }\n\n > li:first-of-type {\n margin-left: 0;\n &.active {\n > a {\n border-left-color: transparent;\n }\n }\n }\n\n //Pulled to the right\n &.pull-right {\n float: none !important;\n > li {\n float: right;\n }\n > li:first-of-type {\n margin-right: 0;\n > a {\n border-left-width: 1px;\n }\n &.active {\n > a {\n border-left-color: #f4f4f4;\n border-right-color: transparent;\n }\n }\n }\n }\n\n > li.header {\n line-height: 35px;\n padding: 0 10px;\n font-size: 20px;\n color: #444;\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n }\n\n > .tab-content {\n background: #fff;\n padding: 10px;\n .border-bottom-radius(@box-border-radius);\n }\n\n .dropdown.open > a {\n &:active,\n &:focus {\n background: transparent;\n color: #999;\n }\n }\n // Tab color variations\n &.tab-primary {\n > .nav-tabs {\n > li.active {\n border-top-color: @light-blue;\n }\n }\n }\n &.tab-info {\n > .nav-tabs {\n > li.active {\n border-top-color: @aqua;\n }\n }\n }\n &.tab-danger {\n > .nav-tabs {\n > li.active {\n border-top-color: @red;\n }\n }\n }\n &.tab-warning {\n > .nav-tabs {\n > li.active {\n border-top-color: @yellow;\n }\n }\n }\n &.tab-success {\n > .nav-tabs {\n > li.active {\n border-top-color: @green;\n }\n }\n }\n &.tab-default {\n > .nav-tabs {\n > li.active {\n border-top-color: @gray-lte;\n }\n }\n }\n}\n\n/* PAGINATION */\n.pagination {\n > li > a {\n background: #fafafa;\n color: #666;\n }\n &.pagination-flat {\n > li > a {\n .border-radius(0) !important;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/navs.less","/*\n * Component: Products List\n * ------------------------\n */\n.products-list {\n list-style: none;\n margin: 0;\n padding: 0;\n > .item {\n .border-radius(@box-border-radius);\n .box-shadow(@box-boxshadow);\n .clearfix();\n padding: 10px 0;\n background: #fff;\n }\n .product-img {\n float: left;\n img {\n width: 50px;\n height: 50px;\n }\n }\n .product-info {\n margin-left: 60px;\n }\n .product-title {\n font-weight: 600;\n }\n .product-description {\n display: block;\n color: #999;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n}\n\n.product-list-in-box > .item {\n .box-shadow(none);\n .border-radius(0);\n border-bottom: 1px solid @box-border-color;\n &:last-of-type {\n border-bottom-width: 0;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/products.less","/*\n * Component: Table\n * ----------------\n */\n\n.table {\n //Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border-top: 1px solid @box-border-color;\n }\n }\n }\n //thead cells\n > thead > tr > th {\n border-bottom: 2px solid @box-border-color;\n }\n //progress bars in tables\n tr td .progress {\n margin-top: 5px;\n }\n}\n\n//Bordered Table\n.table-bordered {\n border: 1px solid @box-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @box-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n.table.no-border {\n &,\n td,\n th {\n border: 0;\n }\n}\n\n/* .text-center in tables */\ntable.text-center {\n &, td, th {\n text-align: center;\n }\n}\n\n.table.align {\n th {\n text-align: left;\n }\n td {\n text-align: right;\n }\n}\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/table.less","/*\n * Component: Label\n * ----------------\n */\n.label-default {\n background-color: @gray-lte;\n color: #444;\n}\n\n.label-danger {\n &:extend(.bg-red);\n}\n\n.label-info {\n &:extend(.bg-aqua);\n}\n\n.label-warning {\n &:extend(.bg-yellow);\n}\n\n.label-primary {\n &:extend(.bg-light-blue);\n}\n\n.label-success {\n &:extend(.bg-green);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/labels.less","/*\n * Component: Direct Chat\n * ----------------------\n */\n.direct-chat {\n .box-body {\n .border-bottom-radius(0);\n position: relative;\n overflow-x: hidden;\n padding: 0;\n }\n &.chat-pane-open {\n .direct-chat-contacts {\n .translate(0, 0);\n }\n }\n}\n\n.direct-chat-messages {\n .translate(0, 0);\n padding: 10px;\n height: 250px;\n overflow: auto;\n}\n\n.direct-chat-msg,\n.direct-chat-text {\n display: block;\n}\n\n.direct-chat-msg {\n .clearfix();\n margin-bottom: 10px;\n}\n\n.direct-chat-messages,\n.direct-chat-contacts {\n .transition-transform(.5s ease-in-out);\n}\n\n.direct-chat-text {\n .border-radius(5px);\n position: relative;\n padding: 5px 10px;\n background: @direct-chat-default-msg-bg;\n border: 1px solid @direct-chat-default-msg-border-color;\n margin: 5px 0 0 50px;\n color: @direct-chat-default-font-color;\n\n //Create the arrow\n &:after,\n &:before {\n position: absolute;\n right: 100%;\n top: 15px;\n border: solid transparent;\n border-right-color: @direct-chat-default-msg-border-color;\n content: ' ';\n height: 0;\n width: 0;\n pointer-events: none;\n }\n\n &:after {\n border-width: 5px;\n margin-top: -5px;\n }\n &:before {\n border-width: 6px;\n margin-top: -6px;\n }\n .right & {\n margin-right: 50px;\n margin-left: 0;\n &:after,\n &:before {\n right: auto;\n left: 100%;\n border-right-color: transparent;\n border-left-color: @direct-chat-default-msg-border-color;\n }\n }\n}\n\n.direct-chat-img {\n .border-radius(50%);\n float: left;\n width: 40px;\n height: 40px;\n .right & {\n float: right;\n }\n}\n\n.direct-chat-info {\n display: block;\n margin-bottom: 2px;\n font-size: 12px;\n}\n\n.direct-chat-name {\n font-weight: 600;\n}\n\n.direct-chat-timestamp {\n color: #999;\n}\n\n//Direct chat contacts pane\n.direct-chat-contacts-open {\n .direct-chat-contacts {\n .translate(0, 0);\n }\n}\n\n.direct-chat-contacts {\n .translate(101%, 0);\n position: absolute;\n top: 0;\n bottom: 0;\n height: 250px;\n width: 100%;\n background: #222d32;\n color: #fff;\n overflow: auto;\n}\n\n//Contacts list -- for displaying contacts in direct chat contacts pane\n.contacts-list {\n &:extend(.list-unstyled);\n > li {\n .clearfix();\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n padding: 10px;\n margin: 0;\n &:last-of-type {\n border-bottom: none;\n }\n }\n}\n\n.contacts-list-img {\n .border-radius(50%);\n width: 40px;\n float: left;\n}\n\n.contacts-list-info {\n margin-left: 45px;\n color: #fff;\n}\n\n.contacts-list-name,\n.contacts-list-status {\n display: block;\n}\n\n.contacts-list-name {\n font-weight: 600;\n}\n\n.contacts-list-status {\n font-size: 12px;\n}\n\n.contacts-list-date {\n color: #aaa;\n font-weight: normal;\n}\n\n.contacts-list-msg {\n color: #999;\n}\n\n//Direct Chat Variants\n.direct-chat-danger {\n .direct-chat-variant(@red);\n}\n\n.direct-chat-primary {\n .direct-chat-variant(@light-blue);\n}\n\n.direct-chat-warning {\n .direct-chat-variant(@yellow);\n}\n\n.direct-chat-info {\n .direct-chat-variant(@aqua);\n}\n\n.direct-chat-success {\n .direct-chat-variant(@green);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/direct-chat.less","/*\n * Component: Users List\n * ---------------------\n */\n.users-list {\n &:extend(.list-unstyled);\n > li {\n width: 25%;\n float: left;\n padding: 10px;\n text-align: center;\n img {\n .border-radius(50%);\n max-width: 100%;\n height: auto;\n }\n > a:hover {\n &,\n .users-list-name {\n color: #999;\n }\n }\n }\n}\n\n.users-list-name,\n.users-list-date {\n display: block;\n}\n\n.users-list-name {\n font-weight: 600;\n color: #444;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.users-list-date {\n color: #999;\n font-size: 12px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/users-list.less","/*\n * Component: Carousel\n * -------------------\n */\n.carousel-control {\n &.left,\n &.right {\n background-image: none;\n }\n > .fa {\n font-size: 40px;\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -20px;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/carousel.less","/*\n * Component: modal\n * ----------------\n */\n.modal {\n background: rgba(0, 0, 0, .3);\n}\n\n.modal-content {\n .border-radius(0);\n .box-shadow(0 2px 3px rgba(0, 0, 0, .125));\n border: 0;\n @media (min-width: @screen-sm-min) {\n .box-shadow(0 2px 3px rgba(0, 0, 0, .125));\n }\n}\n\n.modal-header {\n border-bottom-color: @box-border-color;\n}\n\n.modal-footer {\n border-top-color: @box-border-color;\n}\n\n//Modal variants\n.modal-primary {\n .modal-body {\n &:extend(.bg-light-blue);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-light-blue-active);\n border-color: darken(@light-blue, 10%);\n }\n}\n\n.modal-warning {\n .modal-body {\n &:extend(.bg-yellow);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-yellow-active);\n border-color: darken(@yellow, 10%);\n }\n}\n\n.modal-info {\n .modal-body {\n &:extend(.bg-aqua);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-aqua-active);\n border-color: darken(@aqua, 10%);\n }\n}\n\n.modal-success {\n .modal-body {\n &:extend(.bg-green);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-green-active);\n border-color: darken(@green, 10%);\n }\n}\n\n.modal-danger {\n .modal-body {\n &:extend(.bg-red);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-red-active);\n border-color: darken(@red, 10%);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/modal.less","/*\n * Component: Social Widgets\n * -------------------------\n */\n//General widget style\n.box-widget {\n border: none;\n position: relative;\n}\n\n//User Widget Style 1\n.widget-user {\n //User name container\n .widget-user-header {\n padding: 20px;\n height: 120px;\n .border-top-radius(@box-border-radius);\n }\n //User name\n .widget-user-username {\n margin-top: 0;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n }\n //User single line description\n .widget-user-desc {\n margin-top: 0;\n }\n //User image container\n .widget-user-image {\n position: absolute;\n top: 65px;\n left: 50%;\n margin-left: -45px;\n > img {\n width: 90px;\n height: auto;\n border: 3px solid #fff;\n }\n }\n .box-footer {\n padding-top: 30px;\n }\n}\n\n//User Widget Style 2\n.widget-user-2 {\n //User name container\n .widget-user-header {\n padding: 20px;\n .border-top-radius(@box-border-radius);\n }\n //User name\n .widget-user-username {\n margin-top: 5px;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n }\n //User single line description\n .widget-user-desc {\n margin-top: 0;\n }\n .widget-user-username,\n .widget-user-desc {\n margin-left: 75px;\n }\n //User image container\n .widget-user-image {\n > img {\n width: 65px;\n height: auto;\n float: left;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/social-widgets.less","\t// Tree view menu\n.treeview-menu {\n\tdisplay: none;\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tpadding-left: 5px;\n\t.treeview-menu {\n\t padding-left: 20px;\n\t}\n\t> li {\n\t margin: 0;\n\t > a {\n\t padding: 5px 5px 5px 15px;\n\t display: block;\n\t font-size: 14px;\n\t > .fa,\n\t > .glyphicon,\n\t > .ion {\n\t width: 20px;\n\t }\n\t > .pull-right-container > .fa-angle-left,\n\t > .pull-right-container > .fa-angle-down,\n\t > .fa-angle-left,\n\t > .fa-angle-down {\n\t width: auto;\n\t }\n\t }\n\t}\n}\n\n.treeview {\n\t> ul.treeview-menu {\n\t\toverflow: hidden;\n\t\theight:auto;\n\t\tpadding-top:0px !important;\n\t\tpadding-bottom: 0px !important;\n\t}\n}\n.treeview.menu-open {\n\t> ul.treeview-menu {\n\t\t overflow: visible;\n \t\theight:auto;\n\t}\n}\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/treeview.less","/*\n * Page: Mailbox\n * -------------\n */\n.mailbox-messages {\n > .table {\n margin: 0;\n }\n}\n\n.mailbox-controls {\n padding: 5px;\n &.with-border {\n border-bottom: 1px solid @box-border-color;\n }\n}\n\n.mailbox-read-info {\n border-bottom: 1px solid @box-border-color;\n padding: 10px;\n h3 {\n font-size: 20px;\n margin: 0;\n }\n h5 {\n margin: 0;\n padding: 5px 0 0 0;\n }\n}\n\n.mailbox-read-time {\n color: #999;\n font-size: 13px;\n}\n\n.mailbox-read-message {\n padding: 10px;\n}\n\n.mailbox-attachments {\n &:extend(.list-unstyled);\n li {\n float: left;\n width: 200px;\n border: 1px solid #eee;\n margin-bottom: 10px;\n margin-right: 10px;\n }\n}\n\n.mailbox-attachment-name {\n font-weight: bold;\n color: #666;\n}\n\n.mailbox-attachment-icon,\n.mailbox-attachment-info,\n.mailbox-attachment-size {\n display: block;\n}\n\n.mailbox-attachment-info {\n padding: 10px;\n background: #f4f4f4;\n}\n\n.mailbox-attachment-size {\n color: #999;\n font-size: 12px;\n}\n\n.mailbox-attachment-icon {\n text-align: center;\n font-size: 65px;\n color: #666;\n padding: 20px 10px;\n &.has-img {\n padding: 0;\n > img {\n max-width: 100%;\n height: auto;\n }\n }\n}\n\n.mailbox-attachment-close {\n &:extend(.close);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/mailbox.less","/*\n * Page: Lock Screen\n * -----------------\n */\n/* ADD THIS CLASS TO THE TAG */\n.lockscreen {\n background: @gray-lte;\n}\n\n.lockscreen-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n a {\n color: #444;\n }\n}\n\n.lockscreen-wrapper {\n max-width: 400px;\n margin: 0 auto;\n margin-top: 10%;\n}\n\n/* User name [optional] */\n.lockscreen .lockscreen-name {\n text-align: center;\n font-weight: 600;\n}\n\n/* Will contain the image and the sign in form */\n.lockscreen-item {\n .border-radius(4px);\n padding: 0;\n background: #fff;\n position: relative;\n margin: 10px auto 30px auto;\n width: 290px;\n}\n\n/* User image */\n.lockscreen-image {\n .border-radius(50%);\n position: absolute;\n left: -10px;\n top: -25px;\n background: #fff;\n padding: 5px;\n z-index: 10;\n > img {\n .border-radius(50%);\n width: 70px;\n height: 70px;\n }\n}\n\n/* Contains the password input and the login button */\n.lockscreen-credentials {\n margin-left: 70px;\n .form-control {\n border: 0;\n }\n .btn {\n background-color: #fff;\n border: 0;\n padding: 0 10px;\n }\n}\n\n.lockscreen-footer {\n margin-top: 10px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/lockscreen.less","/*\n * Page: Login & Register\n * ----------------------\n */\n\n.login-logo,\n.register-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n a {\n color: #444;\n }\n}\n\n.login-page,\n.register-page {\n height: auto;\n background: @gray-lte;\n}\n\n.login-box,\n.register-box {\n width: 360px;\n margin: 7% auto;\n @media (max-width: @screen-sm) {\n width: 90%;\n margin-top: 20px;\n }\n}\n\n.login-box-body,\n.register-box-body {\n background: #fff;\n padding: 20px;\n border-top: 0;\n color: #666;\n .form-control-feedback {\n color: #777;\n }\n}\n\n.login-box-msg,\n.register-box-msg {\n margin: 0;\n text-align: center;\n padding: 0 20px 20px 20px;\n}\n\n.social-auth-links {\n margin: 10px 0;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/login_and_register.less","/*\n * Page: 400 and 500 error pages\n * ------------------------------\n */\n.error-page {\n width: 600px;\n margin: 20px auto 0 auto;\n @media (max-width: @screen-sm-max) {\n width: 100%;\n }\n //For the error number e.g: 404\n > .headline {\n float: left;\n font-size: 100px;\n font-weight: 300;\n @media (max-width: @screen-sm-max) {\n float: none;\n text-align: center;\n }\n }\n //For the message\n > .error-content {\n margin-left: 190px;\n @media (max-width: @screen-sm-max) {\n margin-left: 0;\n }\n > h3 {\n font-weight: 300;\n font-size: 25px;\n @media (max-width: @screen-sm-max) {\n text-align: center;\n }\n }\n display: block;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/404_500_errors.less","/*\n * Page: Invoice\n * -------------\n */\n\n.invoice {\n position: relative;\n background: #fff;\n border: 1px solid #f4f4f4;\n padding: 20px;\n margin: 10px 25px;\n}\n\n.invoice-title {\n margin-top: 0;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/invoice.less","/*\n * Page: Profile\n * -------------\n */\n\n.profile-user-img {\n margin: 0 auto;\n width: 100px;\n padding: 3px;\n border: 3px solid @gray-lte;\n}\n\n.profile-username {\n font-size: 21px;\n margin-top: 5px;\n}\n\n.post {\n border-bottom: 1px solid @gray-lte;\n margin-bottom: 15px;\n padding-bottom: 15px;\n color: #666;\n &:last-of-type {\n border-bottom: 0;\n margin-bottom: 0;\n padding-bottom: 0;\n }\n .user-block {\n margin-bottom: 15px;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/profile.less","/*\n * Social Buttons for Bootstrap\n *\n * Copyright 2013-2015 Panayiotis Lipiridis\n * Licensed under the MIT License\n *\n * https://github.com/lipis/bootstrap-social\n */\n\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n@bs-height-base: (@line-height-computed + @padding-base-vertical * 2);\n@bs-height-lg: (floor(@font-size-large * @line-height-base) + @padding-large-vertical * 2);\n@bs-height-sm: (floor(@font-size-small * 1.5) + @padding-small-vertical * 2);\n@bs-height-xs: (floor(@font-size-small * 1.2) + @padding-small-vertical + 1);\n\n.btn-social {\n position: relative;\n padding-left: (@bs-height-base + @padding-base-horizontal);\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n > :first-child {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: @bs-height-base;\n line-height: (@bs-height-base + 2);\n font-size: 1.6em;\n text-align: center;\n border-right: 1px solid rgba(0, 0, 0, 0.2);\n }\n &.btn-lg {\n padding-left: (@bs-height-lg + @padding-large-horizontal);\n > :first-child {\n line-height: @bs-height-lg;\n width: @bs-height-lg;\n font-size: 1.8em;\n }\n }\n &.btn-sm {\n padding-left: (@bs-height-sm + @padding-small-horizontal);\n > :first-child {\n line-height: @bs-height-sm;\n width: @bs-height-sm;\n font-size: 1.4em;\n }\n }\n &.btn-xs {\n padding-left: (@bs-height-xs + @padding-small-horizontal);\n > :first-child {\n line-height: @bs-height-xs;\n width: @bs-height-xs;\n font-size: 1.2em;\n }\n }\n}\n\n.btn-social-icon {\n .btn-social;\n height: (@bs-height-base + 2);\n width: (@bs-height-base + 2);\n padding: 0;\n > :first-child {\n border: none;\n text-align: center;\n width: 100%;\n }\n &.btn-lg {\n height: @bs-height-lg;\n width: @bs-height-lg;\n padding-left: 0;\n padding-right: 0;\n }\n &.btn-sm {\n height: (@bs-height-sm + 2);\n width: (@bs-height-sm + 2);\n padding-left: 0;\n padding-right: 0;\n }\n &.btn-xs {\n height: (@bs-height-xs + 2);\n width: (@bs-height-xs + 2);\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n.btn-social(@color-bg, @color: #fff) {\n background-color: @color-bg;\n .button-variant(@color, @color-bg, rgba(0, 0, 0, .2));\n}\n\n.btn-adn {\n .btn-social(#d87a68);\n}\n\n.btn-bitbucket {\n .btn-social(#205081);\n}\n\n.btn-dropbox {\n .btn-social(#1087dd);\n}\n\n.btn-facebook {\n .btn-social(#3b5998);\n}\n\n.btn-flickr {\n .btn-social(#ff0084);\n}\n\n.btn-foursquare {\n .btn-social(#f94877);\n}\n\n.btn-github {\n .btn-social(#444444);\n}\n\n.btn-google {\n .btn-social(#dd4b39);\n}\n\n.btn-instagram {\n .btn-social(#3f729b);\n}\n\n.btn-linkedin {\n .btn-social(#007bb6);\n}\n\n.btn-microsoft {\n .btn-social(#2672ec);\n}\n\n.btn-openid {\n .btn-social(#f7931e);\n}\n\n.btn-pinterest {\n .btn-social(#cb2027);\n}\n\n.btn-reddit {\n .btn-social(#eff7ff, #000);\n}\n\n.btn-soundcloud {\n .btn-social(#ff5500);\n}\n\n.btn-tumblr {\n .btn-social(#2c4762);\n}\n\n.btn-twitter {\n .btn-social(#55acee);\n}\n\n.btn-vimeo {\n .btn-social(#1ab7ea);\n}\n\n.btn-vk {\n .btn-social(#587ea3);\n}\n\n.btn-yahoo {\n .btn-social(#720e9e);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/bootstrap-social.less","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/bootstrap-less/mixins/buttons.less","/*\n * Plugin: Full Calendar\n * ---------------------\n */\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n// Fullcalendar buttons\n.fc-button {\n background: #f4f4f4;\n background-image: none;\n color: #444;\n border-color: #ddd;\n border-bottom-color: #ddd;\n &:hover,\n &:active,\n &.hover {\n background-color: #e9e9e9;\n }\n}\n\n// Calendar title\n.fc-header-title h2 {\n font-size: 15px;\n line-height: 1.6em;\n color: #666;\n margin-left: 10px;\n}\n\n.fc-header-right {\n padding-right: 10px;\n}\n\n.fc-header-left {\n padding-left: 10px;\n}\n\n// Calendar table header cells\n.fc-widget-header {\n background: #fafafa;\n}\n\n.fc-grid {\n width: 100%;\n border: 0;\n}\n\n.fc-widget-header:first-of-type,\n.fc-widget-content:first-of-type {\n border-left: 0;\n border-right: 0;\n}\n\n.fc-widget-header:last-of-type,\n.fc-widget-content:last-of-type {\n border-right: 0;\n}\n\n.fc-toolbar {\n padding: @box-padding;\n margin: 0;\n}\n\n.fc-day-number {\n font-size: 20px;\n font-weight: 300;\n padding-right: 10px;\n}\n\n.fc-color-picker {\n list-style: none;\n margin: 0;\n padding: 0;\n > li {\n float: left;\n font-size: 30px;\n margin-right: 5px;\n line-height: 30px;\n .fa {\n .transition-transform(linear .3s);\n &:hover {\n .rotate(30deg);\n }\n }\n }\n}\n\n#add-new-event {\n .transition(all linear .3s);\n}\n\n.external-event {\n padding: 5px 10px;\n font-weight: bold;\n margin-bottom: 4px;\n box-shadow: @box-boxshadow;\n text-shadow: @box-boxshadow;\n border-radius: @box-border-radius;\n cursor: move;\n &:hover {\n box-shadow: inset 0 0 90px rgba(0, 0, 0, 0.2);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/fullcalendar.less","/*\n * Plugin: Select2\n * ---------------\n */\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n//Signle select\n.select2-container--default,\n.select2-selection {\n &.select2-container--focus,\n &:focus,\n &:active {\n outline: none;\n }\n .select2-selection--single {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n padding: 6px 12px;\n height: 34px;\n }\n}\n\n.select2-container--default.select2-container--open {\n border-color: @light-blue;\n}\n\n.select2-dropdown {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n}\n\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: @light-blue;\n color: white;\n}\n\n.select2-results__option {\n padding: 6px 12px;\n user-select: none;\n -webkit-user-select: none;\n}\n\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-left: 0;\n padding-right: 0;\n height: auto;\n margin-top: -4px;\n}\n\n.select2-container[dir=\"rtl\"] .select2-selection--single .select2-selection__rendered {\n padding-right: 6px;\n padding-left: 20px;\n}\n\n.select2-container--default .select2-selection--single .select2-selection__arrow {\n height: 28px;\n right: 3px;\n}\n\n.select2-container--default .select2-selection--single .select2-selection__arrow b {\n margin-top: 0;\n}\n\n.select2-dropdown,\n.select2-search--inline {\n .select2-search__field {\n border: 1px solid @gray-lte;\n &:focus {\n outline: none;\n //border: 1px solid @light-blue;\n }\n }\n}\n\n.select2-container--default.select2-container--focus .select2-selection--multiple,\n.select2-container--default .select2-search--dropdown .select2-search__field {\n border-color: @light-blue !important;\n}\n\n.select2-container--default .select2-results__option[aria-disabled=true] {\n color: #999;\n}\n\n.select2-container--default .select2-results__option[aria-selected=true] {\n background-color: #ddd;\n &,\n &:hover {\n color: #444;\n }\n}\n\n//Multiple select\n.select2-container--default {\n .select2-selection--multiple {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n &:focus {\n border-color: @light-blue;\n }\n }\n &.select2-container--focus .select2-selection--multiple {\n border-color: @gray-lte;\n }\n}\n\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @light-blue;\n border-color: darken(@light-blue, 5%);\n padding: 1px 10px;\n color: #fff;\n}\n\n.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {\n margin-right: 5px;\n color: rgba(255, 255, 255, .7);\n &:hover {\n color: #fff;\n }\n}\n\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-right: 10px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/select2.less",".box {\n .datepicker-inline {\n &,\n .datepicker-days {\n &,\n > table {\n width: 100%;\n td {\n &:hover {\n background-color: rgba(255, 255, 255, .3);\n }\n &.day {\n &.old,\n &.new {\n color: #777;\n }\n }\n }\n }\n }\n }\n}\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/datepicker.less","/*\n * General: Miscellaneous\n * ----------------------\n */\n// 10px padding and margins\n.pad {\n padding: 10px;\n}\n\n.margin {\n margin: 10px;\n}\n\n.margin-bottom {\n margin-bottom: 20px;\n}\n\n.margin-bottom-none {\n margin-bottom: 0;\n}\n\n.margin-r-5 {\n margin-right: 5px;\n}\n\n// Display inline\n.inline {\n display: inline;\n}\n\n// Description Blocks\n.description-block {\n display: block;\n margin: 10px 0;\n text-align: center;\n &.margin-bottom {\n margin-bottom: 25px;\n }\n > .description-header {\n margin: 0;\n padding: 0;\n font-weight: 600;\n font-size: 16px;\n }\n > .description-text {\n text-transform: uppercase;\n }\n}\n\n// Background colors\n.bg-red,\n.bg-yellow,\n.bg-aqua,\n.bg-blue,\n.bg-light-blue,\n.bg-green,\n.bg-navy,\n.bg-teal,\n.bg-olive,\n.bg-lime,\n.bg-orange,\n.bg-fuchsia,\n.bg-purple,\n.bg-maroon,\n.bg-black,\n.bg-red-active,\n.bg-yellow-active,\n.bg-aqua-active,\n.bg-blue-active,\n.bg-light-blue-active,\n.bg-green-active,\n.bg-navy-active,\n.bg-teal-active,\n.bg-olive-active,\n.bg-lime-active,\n.bg-orange-active,\n.bg-fuchsia-active,\n.bg-purple-active,\n.bg-maroon-active,\n.bg-black-active {\n color: #fff !important;\n}\n\n.bg-gray {\n color: #000;\n background-color: @gray-lte !important;\n}\n\n.bg-gray-light {\n background-color: #f7f7f7;\n}\n\n.bg-black {\n background-color: @black !important;\n}\n\n.bg-red {\n background-color: @red !important;\n}\n\n.bg-yellow {\n background-color: @yellow !important;\n}\n\n.bg-aqua {\n background-color: @aqua !important;\n}\n\n.bg-blue {\n background-color: @blue !important;\n}\n\n.bg-light-blue {\n background-color: @light-blue !important;\n}\n\n.bg-green {\n background-color: @green !important;\n}\n\n.bg-navy {\n background-color: @navy !important;\n}\n\n.bg-teal {\n background-color: @teal !important;\n}\n\n.bg-olive {\n background-color: @olive !important;\n}\n\n.bg-lime {\n background-color: @lime !important;\n}\n\n.bg-orange {\n background-color: @orange !important;\n}\n\n.bg-fuchsia {\n background-color: @fuchsia !important;\n}\n\n.bg-purple {\n background-color: @purple !important;\n}\n\n.bg-maroon {\n background-color: @maroon !important;\n}\n\n//Set of Active Background Colors\n.bg-gray-active {\n color: #000;\n background-color: darken(@gray-lte, 10%) !important;\n}\n\n.bg-black-active {\n background-color: darken(@black, 10%) !important;\n}\n\n.bg-red-active {\n background-color: darken(@red , 6%) !important;\n}\n\n.bg-yellow-active {\n background-color: darken(@yellow , 6%) !important;\n}\n\n.bg-aqua-active {\n background-color: darken(@aqua , 6%) !important;\n}\n\n.bg-blue-active {\n background-color: darken(@blue , 10%) !important;\n}\n\n.bg-light-blue-active {\n background-color: darken(@light-blue , 6%) !important;\n}\n\n.bg-green-active {\n background-color: darken(@green , 5%) !important;\n}\n\n.bg-navy-active {\n background-color: darken(@navy , 2%) !important;\n}\n\n.bg-teal-active {\n background-color: darken(@teal , 5%) !important;\n}\n\n.bg-olive-active {\n background-color: darken(@olive , 5%) !important;\n}\n\n.bg-lime-active {\n background-color: darken(@lime , 5%) !important;\n}\n\n.bg-orange-active {\n background-color: darken(@orange , 5%) !important;\n}\n\n.bg-fuchsia-active {\n background-color: darken(@fuchsia , 5%) !important;\n}\n\n.bg-purple-active {\n background-color: darken(@purple , 5%) !important;\n}\n\n.bg-maroon-active {\n background-color: darken(@maroon , 3%) !important;\n}\n\n//Disabled!\n[class^=\"bg-\"].disabled {\n .opacity(.65);\n}\n\n// Text colors\n.text-red {\n color: @red !important;\n}\n\n.text-yellow {\n color: @yellow !important;\n}\n\n.text-aqua {\n color: @aqua !important;\n}\n\n.text-blue {\n color: @blue !important;\n}\n\n.text-black {\n color: @black !important;\n}\n\n.text-light-blue {\n color: @light-blue !important;\n}\n\n.text-green {\n color: @green !important;\n}\n\n.text-gray {\n color: @gray-lte !important;\n}\n\n.text-navy {\n color: @navy !important;\n}\n\n.text-teal {\n color: @teal !important;\n}\n\n.text-olive {\n color: @olive !important;\n}\n\n.text-lime {\n color: @lime !important;\n}\n\n.text-orange {\n color: @orange !important;\n}\n\n.text-fuchsia {\n color: @fuchsia !important;\n}\n\n.text-purple {\n color: @purple !important;\n}\n\n.text-maroon {\n color: @maroon !important;\n}\n\n.link-muted {\n color: darken(@gray-lte, 30%);\n &:hover,\n &:focus {\n color: darken(@gray-lte, 40%);\n }\n}\n\n.link-black {\n color: #666;\n &:hover,\n &:focus {\n color: #999;\n }\n}\n\n// Hide elements by display none only\n.hide {\n display: none !important;\n}\n\n// Remove borders\n.no-border {\n border: 0 !important;\n}\n\n// Remove padding\n.no-padding {\n padding: 0 !important;\n}\n\n// Remove margins\n.no-margin {\n margin: 0 !important;\n}\n\n// Remove box shadow\n.no-shadow {\n box-shadow: none !important;\n}\n\n// Unstyled List\n.list-unstyled {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.list-group-unbordered {\n > .list-group-item {\n border-left: 0;\n border-right: 0;\n border-radius: 0;\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n// Remove border radius\n.flat {\n .border-radius(0) !important;\n}\n\n.text-bold {\n &, &.table td, &.table th {\n font-weight: 700;\n }\n}\n\n.text-sm {\n font-size: 12px;\n}\n\n// _fix for sparkline tooltip\n.jqstooltip {\n padding: 5px !important;\n width: auto !important;\n height: auto !important;\n}\n\n// Gradient Background colors\n.bg-teal-gradient {\n .gradient(@teal; @teal; lighten(@teal, 16%)) !important;\n color: #fff;\n}\n\n.bg-light-blue-gradient {\n .gradient(@light-blue; @light-blue; lighten(@light-blue, 12%)) !important;\n color: #fff;\n}\n\n.bg-blue-gradient {\n .gradient(@blue; @blue; lighten(@blue, 7%)) !important;\n color: #fff;\n}\n\n.bg-aqua-gradient {\n .gradient(@aqua; @aqua; lighten(@aqua, 7%)) !important;\n color: #fff;\n}\n\n.bg-yellow-gradient {\n .gradient(@yellow; @yellow; lighten(@yellow, 16%)) !important;\n color: #fff;\n}\n\n.bg-purple-gradient {\n .gradient(@purple; @purple; lighten(@purple, 16%)) !important;\n color: #fff;\n}\n\n.bg-green-gradient {\n .gradient(@green; @green; lighten(@green, 7%)) !important;\n color: #fff;\n}\n\n.bg-red-gradient {\n .gradient(@red; @red; lighten(@red, 10%)) !important;\n color: #fff;\n}\n\n.bg-black-gradient {\n .gradient(@black; @black; lighten(@black, 10%)) !important;\n color: #fff;\n}\n\n.bg-maroon-gradient {\n .gradient(@maroon; @maroon; lighten(@maroon, 10%)) !important;\n color: #fff;\n}\n\n//Description Block Extension\n.description-block {\n .description-icon {\n font-size: 16px;\n }\n}\n\n//Remove top padding\n.no-pad-top {\n padding-top: 0;\n}\n\n//Make position static\n.position-static {\n position: static !important;\n}\n\n//List utility classes\n.list-header {\n font-size: 15px;\n padding: 10px 4px;\n font-weight: bold;\n color: #666;\n}\n\n.list-seperator {\n height: 1px;\n background: @box-border-color;\n margin: 15px 0 9px 0;\n}\n\n.list-link {\n > a {\n padding: 4px;\n color: #777;\n &:hover {\n color: #222;\n }\n }\n}\n\n//Light font weight\n.font-light {\n font-weight: 300;\n}\n\n//User block\n.user-block {\n .clearfix();\n img {\n width: 40px;\n height: 40px;\n float: left;\n }\n .username,\n .description,\n .comment {\n display: block;\n margin-left: 50px;\n }\n .username {\n font-size: 16px;\n font-weight: 600;\n }\n .description {\n color: #999;\n font-size: 13px;\n }\n &.user-block-sm {\n img {\n &:extend(.img-sm);\n }\n .username,\n .description,\n .comment {\n margin-left: 40px;\n }\n .username {\n font-size: 14px;\n }\n }\n}\n\n//Image sizes\n.img-sm,\n.img-md,\n.img-lg {\n float: left;\n}\n\n.img-sm {\n width: 30px !important;\n height: 30px !important;\n + .img-push {\n margin-left: 40px;\n }\n}\n\n.img-md {\n width: 60px;\n height: 60px;\n + .img-push {\n margin-left: 70px;\n }\n}\n\n.img-lg {\n width: 100px;\n height: 100px;\n + .img-push {\n margin-left: 110px;\n }\n}\n\n// Image bordered\n.img-bordered {\n border: 3px solid @gray-lte;\n padding: 3px;\n}\n\n.img-bordered-sm {\n border: 2px solid @gray-lte;\n padding: 2px;\n}\n\n//General attachemnt block\n.attachment-block {\n border: 1px solid @box-border-color;\n padding: 5px;\n margin-bottom: 10px;\n background: #f7f7f7;\n\n .attachment-img {\n max-width: 100px;\n max-height: 100px;\n height: auto;\n float: left;\n }\n .attachment-pushed {\n margin-left: 110px;\n }\n .attachment-heading {\n margin: 0;\n }\n .attachment-text {\n color: #555;\n }\n}\n\n.connectedSortable {\n min-height: 100px;\n}\n\n.ui-helper-hidden-accessible {\n border: 0;\n clip: rect(0 0 0 0);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n}\n\n.sort-highlight {\n background: #f4f4f4;\n border: 1px dashed #ddd;\n margin-bottom: 10px;\n}\n\n.full-opacity-hover {\n .opacity(.65);\n &:hover {\n .opacity(1);\n }\n}\n\n// Charts\n.chart {\n position: relative;\n overflow: hidden;\n width: 100%;\n svg,\n canvas {\n width: 100% !important;\n }\n}\n\n// Horizontal rules\nhr {\n border-top: 1px solid @hr-border;\n}\n\n// bootstrap slider\n\n#red .slider-selection {\n background: #f56954;\n}\n\n#blue .slider-selection {\n background: #3c8dbc;\n}\n\n#green .slider-selection {\n background: #00a65a;\n}\n\n#yellow .slider-selection {\n background: #f39c12;\n}\n\n#aqua .slider-selection {\n background: #00c0ef;\n}\n\n#purple .slider-selection {\n background: #932ab6;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/miscellaneous.less","/*\n * Misc: print\n * -----------\n */\n@media print {\n //Add to elements that you do not want to show when printing\n .no-print {\n display: none !important;\n }\n\n //Elements that we want to hide when printing\n .main-sidebar,\n .left-side,\n .main-header,\n .content-header {\n &:extend(.no-print);\n }\n\n //This is the only element that should appear, so let's remove the margins\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-left: 0 !important;\n min-height: 0 !important;\n .translate(0, 0) !important;\n }\n\n .fixed .content-wrapper,\n .fixed .right-side {\n padding-top: 0 !important;\n }\n\n //Invoice printing\n .invoice {\n width: 100%;\n border: 0;\n margin: 0;\n padding: 0;\n }\n\n .invoice-col {\n float: left;\n width: 33.3333333%;\n }\n\n //Make sure table content displays properly\n .table-responsive {\n overflow: auto;\n > .table tr th,\n > .table tr td {\n white-space: normal !important;\n }\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/admin-lte/build/less/print.less"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"css/build/AdminLTE.css","mappings":"AAAA;;;;;;;EAOE;AACF;;;EAGE;ACPF;;EAEE;ADSF;ACRE;;EACE;ADWJ;ACPA;EACE;EACA;EACA;EACA;ADSF;AACA,WAAW;ACNX;EAEE;EACA;EACA;EACA;ADOF;AEnBE;;EAEE;EACA;AFqBJ;AEnBE;EACE;AFqBJ;ACdE;EACE;EACA;EACA;EACA;EACA;ADgBJ;ACZA;EACE;ADcF;AACA;;EAEE;ACXF;;EE4KU;EFtKR;EACA;ADaF;ACXE;;EACE;ADcJ;ACZE;EAAA;;IACE;EDgBF;AACF;ACbI;EAAA;;IACE;EDiBJ;AACF;ACbI;EAAA;;IEgFM;EH3DR;AACF;AChBA;EACE;EACA;EACA;ADkBF;ACfA;EACE;IACE;EDiBF;AACF;ACdA;EACE;EACA;EACA;EACA;ADgBF;AACA,iBAAiB;ACbjB;;;EAII;ADcJ;AClBA;EAOI;EACA;EACA;ADcJ;ACvBA;;EAaI;ADcJ;ACbI;EAAA;;IACE;EDiBJ;AACF;ACfE;EAEI;ADgBN;ACpCA;EAwBI;ADeJ;ACXA;;;;;;;;EDoBE,eAAe;EGqDP;AHjDV;AACA,YAAY;ACVZ;EACE;EACA;EGlIA;EACA;EACA;EACA;AJ+IF;AACA,iBAAiB;ACZjB;;;;;;;;;;;;EAYE;ADcF;AACA,kBAAkB;ACXlB;EACE;ADaF;ACVA;;;EAGE;EACA;EACA;ADYF;AACA,gBAAgB;ACThB;EACE;EACA;ADWF;ACbA;EAKI;EACA;EACA;ADWJ;AACA;;;EAGE;AK3LF;EACE;EACA;EACA;AL6LF;AKhMA;EF2LU;EEpLN;EACA;EACA;EACA;EACA;AL+LJ;AK9LI;EACE;ALgMN;AK7MA;EAkBI;EACA;AL8LJ;AK7LI;;EAEE;EACA;AL+LN;AK7LI;EACE;EACA;AL+LN;AK7LI;EACE;AL+LN;AK7LI;EACE;AL+LN;AKhOA;;EAuCI;AL6LJ;AK5LI;EAAA;;IAEI;IACA;EL+LN;AACF;AK3LI;EAAA;IACE;EL8LJ;EK7LI;IACE;EL+LN;EKlME;IAOI;IACA;EL8LN;AACF;AKvPA;EA8DI;EACA;EACA;EACA;EAEA;AL2LJ;AK1LI;EACE;AL4LN;AK1LI;EACE;AL4LN;AK1LI;;EAEE;AL4LN;AKzLI;EACE;AL2LN;AK1LM;EACE;EACA;AL4LR;AK/QA;EAwFI;AL0LJ;AKlRA;;;EA+FM;ALwLN;AKvRA;EAqGI;EACA;EACA;EACA;EACA;EACA;EACA;ALqLJ;AKhSA;EF2LU;EE1EN;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ALqLJ;AKhTA;EA8HM;EACA;KAAA;EACA;ALqLN;AKrTA;EAuIM;ALiLN;AKxTA;EA0IQ;EACA;ALiLR;AK5TA;EA8IQ;EACA;EACA;ALiLR;AKjUA;EAoJM;ALgLN;AKpUA;EAuJQ;EACA;ALgLR;AKxUA;EA2JQ;EACA;EACA;ALgLR;AK7UA;EAkKM;EACA;EACA;AL8KN;AKlVA;EAyKI;AL4KJ;AKvKA;EACE;EACA;ALyKF;AK3KA;EAKI;EACA;ALyKJ;AK/KA;EAQM;EACA;EACA;EACA;AL0KN;AKrLA;EAgBI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ECnIF;AN4SF;AKjMA;EA2BM;EACA;EACA;ALyKN;AKtMA;;;EA+BQ;AL4KR;AK3MA;EAmCM;AL2KN;AKvKE;EAAA;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;ELyKJ;EKjLA;IAUM;EL0KN;AACF;AKrKA;EACE;EACA;EACA;EACA;ALuKF;AKnKA;EACE;IACE;ELqKF;EKjKA;IACE;IACA;ELmKF;EKhKA;IACE;IACA;IACA;ELkKF;AACF;AK9JA;EACE;IACE;ELgKF;EKjKA;;IAII;IACA;ELiKJ;EKtKA;IAQI;ELiKJ;EKzKA;IAWI;ELiKJ;AACF;AK5JE;EAAA;IACE;EL+JF;EKhKA;IAGI;IACA;IACA;IACA;ELgKJ;AACF;AACA;;;EAGE;AO7bF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EJ0MQ;AHyPV;AO7bE;EAAA;IACE;EPgcF;AACF;AO9bE;EAAA;IJ2HQ;EH0UR;AACF;AOjcI;EAAA;IJsHM;EHkVR;AACF;AOncI;EAAA;IJgHM;EH0VR;AACF;AOrcA;EACE;APucF;AOncA;EAEI;APocJ;AO/bA;EACE;EACA;EACA;EACA;APicF;AE3eE;;EAEE;EACA;AF6eJ;AE3eE;EACE;AF6eJ;AO7cA;EAOI;EACA;EACA;APycJ;AOldA;EAYI;EACA;EACA;EACA;APycJ;AOxdA;EAiBM;EACA;AP0cN;AO5dA;EAqBM;EACA;EACA;EACA;AP0cN;AOleA;;;EA4BQ;AP2cR;AOpcA;EACE;EACA;EACA;APscF;AOzcA;EAMI;EACA;EACA;APscJ;AO9cA;EAUM;EACA;APucN;AOldA;;;EAeQ;APwcR;AOvdA;;EAoBM;APucN;AO3dA;EAuBM;APucN;AO9dA;EA2BI;EACA;APscJ;AOleA;;EAgCI;EACA;EACA;EACA;EJuEM;AHkYV;AO5eA;EAuCI;EACA;EACA;EACA;APwcJ;AOlfA;;EJqEU;AHobV;AOzfA;EAoDI;APwcJ;AACA;;EAEE;AQ9kBA;EAEE;;;IAMI;IACA;ER4kBN;EQnlBE;ILwIM;IK1HF;IACA;ER4kBN;EQ3lBE;IAoBM;ER0kBR;EQ9lBE;IAsBQ;ER2kBV;EQjmBE;IAyBQ;ER2kBV;EQxkBQ;IAEI;ERykBZ;EQvmBE;IAoCQ;IACA;IACA;ERskBV;EQ5mBE;;;;;;;IAoDI;IACA;ERikBN;EQtnBE;IA2DM;ER8jBR;EQznBE;IA6DQ;IACA;IACA;IACA;ER+jBV;EQ/nBE;IAmEQ;ER+jBV;EQloBE;IAyEM;ER4jBR;AACF;AQtjBA;EAEE;;IAOM;IACA;IACA;IACA;ERkjBN;EQ5jBA;IAeM;IACA;IACA;IACA;ERgjBN;EQlkBA;IAsBM;IACA;IACA;IACA;IACA;IACA;ER+iBN;EQ1kBA;IA6BQ;ERgjBR;EQ7kBA;IAiCM;IACA;ER+iBN;AACF;AQ1iBA;;EAGI;AR2iBJ;AQ9iBA;EAMI;AR2iBJ;AQtiBA;;;EAGE;EACA;ARwiBF;AQriBA;EACE;ARuiBF;AQpiBA;;EAEE;EACA;ARsiBF;AQniBA;EACE;ARqiBF;AQtiBA;EAGI;EACA;EACA;EACA;ARsiBJ;AACA;;EAEE;ASzsBF;EACE;EACA;EACA;AT2sBF;ASvsBA;;EAEE;EACA;EACA;ENgLQ;AH4hBV;ASvsBA;EACE;EACA;EACA;ATysBF;ASvsBE;EAAA;IACE;ET0sBF;AACF;ASjtBA;EAUI;AT0sBJ;AStsBI;;EAEE;ATwsBN;ASlsBA;;;EAII;ATmsBJ;AS/rBA;;EAGI;ATgsBJ;AS9rBE;EAAA;;;IAII;ETgsBJ;AACF;AS3rBA;EAEI;EACA;EACA;EACA;AT4rBJ;ASprBM;;;EAGE;ATsrBR;AS5rBA;EHJE;ANmsBF;ASlrBM;;EAEE;EACA;EACA;EACA;ATorBR;AStsBA;EAqBQ;ATorBR;AS9qBQ;;;;EAIE;EACA;EACA;ATgrBV;AS1qBE;EAAA;IACE;ET6qBF;ES9qBA;IAGI;ET8qBJ;AACF;ASzqBA;EACE;EACA;EACA;EACA;AT2qBF;ASvqBA;EACE;EACA;EACA;ATyqBF;ASrqBA;EACE;EACA;EACA;ATuqBF;AS1qBA;EAMI;EACA;ATuqBJ;AE5yBE;;EAEE;EACA;AF8yBJ;AE5yBE;EACE;AF8yBJ;AStrBA;EASM;ATgrBN;ASzrBA;EAaI;EACA;EACA;EACA;EACA;EACA;AT+qBJ;ASjsBA;EAqBI;EACA;AT+qBJ;ASrsBA;EAwBM;ATgrBN;ASxsBA;EA2BM;EACA;ATgrBN;AS5sBA;EAgCI;AT+qBJ;AS1qBA;EACE;AT4qBF;AS1qBE;;EAEE;AT4qBJ;ASjrBA;EASI;AT2qBJ;ASprBA;EAYQ;EACA;AT2qBR;ASzqBQ;;;EAGE;EACA;AT2qBV;ASzqBQ;;;EAGE;AT2qBV;ASzqBQ;EACE;AT2qBV;ASrqBU;;;;EAIE;EACA;ATuqBZ;AS7sBA;;EA+CI;ATkqBJ;AS5pBQ;EACE;AT8pBV;ASptBA;EA0DY;AT6pBZ;ASppBA;EACE;ATspBF;ASppBE;;EAEE;EACA;ATspBJ;AS5pBA;EAUI;ATqpBJ;AS/pBA;EAaQ;EACA;ATqpBR;ASnpBQ;;;EAGE;EACA;ATqpBV;ASnpBQ;;;EAGE;ATqpBV;AS/oBU;;;;EAIE;EACA;ATipBZ;ASrrBA;;EA6CI;AT4oBJ;ASzrBA;EAiDI;AT2oBJ;ASxoBQ;EACE;AT0oBV;AS/rBA;EAyDY;ATyoBZ;AACA;;;EAGE;AACF,uBAAuB;AUp7BvB;EACE;EACA;AVs7BF;AUx7BA;EAII;AVu7BJ;AU37BA;;;EASI;AVu7BJ;AUh8BA;EAYI;EACA;AVu7BJ;AUp8BA;EAgBI;AVu7BJ;AUl7BA;;;EAQI;EAEA;EACA;EACA;AV86BJ;AU17BA;;;EAMM;AVy7BN;AU/7BA;;;EJmDE;EACA;EACA;EACA;EIrCE;EACA;EACA;EACA;EACA;AVu7BJ;AU58BA;;;EJmDE;EACA;EACA;EACA;EI3BE;EACA;EACA;EACA;EACA;EAKA;AVs7BJ;AU17BI;EAAA;;;IACE;IACA;EV+7BJ;AACF;AU57BI;;;EACE;EACA;AVg8BN;AUx+BA;;;EA8CI;EACA;EACA;EACA;EACA;AV+7BJ;AUj/BA;;;EAoDM;EACA;EVk8BJ,+BAA+B;EUj8B3B;AVm8BN;AUj8BM;;;EACE;EACA;AVq8BR;AU97BA;EAIM;EACA;EACA;EACA;AV67BN;AUp8BA;;;EAYQ;AV67BR;AUr7BA;EAKM;EAEA;AVk7BN;AUz7BA;EAUQ;EACA;EACA;AVk7BR;AU97BA;EAgBQ;EACA;EACA;EACA;EACA;AVi7BR;AUr8BA;EAuBU;EACA;EACA;EACA;EACA;AVi7BV;AU58BA;EAgCQ;EACA;EACA;AV+6BR;AEpjCE;;EAEE;EACA;AFsjCJ;AEpjCE;EACE;AFsjCJ;AU56BA;EAGM;AV46BN;AU/6BA;EAMQ;EACA;EACA;EACA;AV46BR;AUr7BA;EAaQ;EACA;AV26BR;AUp6BA;EC/KE;EACC;EDiLC;EACA;EACA;AVs6BJ;AUp6BI;;EC9KF;EACC;AXslCH;AUh7BA;EAaM;EACA;EACA;AVs6BN;AUr7BA;EAkBQ;EACA;EACA;EACA;EACA;EACA;AVs6BR;AU77BA;EA0BQ;EACA;EACA;EACA;EAEA;AVq6BR;AUp8BA;EAiCU;EACA;AVs6BV;AUx8BA;EAyCM;EACA;EACA;AVk6BN;AElnCE;;EAEE;EACA;AFonCJ;AElnCE;EACE;AFonCJ;AUr9BA;EA8CQ;AV06BR;AUz6BQ;EAAA;IACE;IACA;EV46BR;AACF;AU99BA;EAwDM;EACA;AVy6BN;AEvoCE;;EAEE;EACA;AFyoCJ;AEvoCE;EACE;AFyoCJ;AU1+BA;EA4DQ;AVi7BR;AU/6BU;EAAA;IACE;EVk7BV;AACF;AUl/BA;EAsEI;EACA;EACA;EACA;EACA;EACA;AV+6BJ;AU96BI;EAAA;IACE;IACA;IACA;IACA;EVi7BJ;AACF;AACA;oEACoE;AU76BpE;EACE;EPrPQ;AHuqCV;AU76BA;EACE;IACE;IACA;IACA;EV+6BF;EU56BA;IACE;IACA;EV86BF;EU36BA;IACE;IACA;EV66BF;EU16BA;IACE;EV46BF;EUz6BA;IACE;EV26BF;AACF;AAsBA,iCAAiC;AUn6BjC;EAEI;AVo6BJ;AUt6BA;EAIM;EACA;EACA;AVq6BN;AUh6BA;EACE;IACE;EVk6BF;EUn6BA;IAGI;EVm6BJ;EUt6BA;IAKM;IACA;IACA;IACA;IACA;EVo6BN;AACF;AACA;;;EAGE;AY9vCF;ENoEE;EMlEA;EACA;AZgwCF;AY/vCE;EACE;EACA;AZiwCJ;AY/vCE;;;EAGE;EACA;AZiwCJ;AY9vCE;EACE;EACA;EACA;AZgwCJ;AY3vCE;EAEI;AZ4vCN;AY9vCE;;EAMI;EACA;AZ4vCN;AYnwCE;EAUI;AZ4vCN;AYxvCE;EAEI;AZyvCN;AY3vCE;;EAMI;EACA;AZyvCN;AYhwCE;EAUI;AZyvCN;AYrvCE;EAEI;AZsvCN;AYxvCE;;EAMI;EACA;AZsvCN;AY7vCE;EAUI;AZsvCN;AACA,gBAAgB;AYjvChB;ENCE;EMEE;EACA;AZkvCJ;AACA,kBAAkB;AY5uCd;;ENVF;AN0vCF;AY1uCA;EACE;AZ4uCF;AACA,+CAA+C;AYzuC/C;EACE;AZ2uCF;AYxuCA;;;EAGE;AZ0uCF;AYvuCA;;;EAGE;AZyuCF;AACA;;;EAGE;Aaj1CF;;EV+DU;AHuxCV;Aan1CE;;;;EP+DA;AN0xCF;AACA,mBAAmB;Aap1CnB;;EAEE;Abs1CF;Aar1CE;;;;EPsDA;ANqyCF;Aat1CA;;EAEE;Abw1CF;Aav1CE;;;;EP8CA;AN+yCF;Aax1CA;;EAEE;Ab01CF;Aaz1CE;;;;EPsCA;ANyzCF;AACA,kBAAkB;Aa11ClB;EACE;EACA;EACA;EACA;EACA;Ab41CF;Aaj2CA;EAOI;EACA;EACA;Ab61CJ;Aaz1CE;;EAEE;Ab21CJ;Aax1CE;;EAEE;Ab01CJ;Aax1CE;;EAEE;Ab01CJ;Aar1CA;EAEI;Abs1CJ;Aax1CA;EAKI;Abs1CJ;AACA,0DAA0D;Aal1C1D;EAEI;Abm1CJ;Aa70CA;;ECpFE;Adq6CF;Acl6CE;;ECkDE;Afs3CJ;Aal1CA;;ECzFE;Ad+6CF;Ac56CE;;ECkDE;Afg4CJ;Aav1CA;;EC9FE;Ady7CF;Act7CE;;ECkDE;Af04CJ;Aa51CA;;ECnGE;Adm8CF;Ach8CE;;ECkDE;Afo5CJ;Aaj2CA;;ECxGE;Ad68CF;Ac18CE;;ECkDE;Af85CJ;AACA;;;EAGE;AgBr9CF;EVmEE;EUjEA;EACA;EACA;EACA;AhBu9CF;AgB59CA;EAQI;AhBu9CJ;AgB/9CA;EAYI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AhBs9CJ;AgBr9CI;EACE;EACA;AhBu9CN;AgB9+CA;EA4BI;EACA;EACA;EACA;EACA;AhBq9CJ;AgBr/CA;EAqCI;AhBm9CJ;AgBx/CA;EAuCM;EACA;EACA;EACA;AhBo9CN;AgB9/CA;;EA+CI;AhBm9CJ;AgBlgDA;Eb2LU;EatIN;EACA;EACA;EACA;EACA;EACA;AhBm9CJ;AgB/8CE;EACE;EACA;AhBi9CJ;AgBn9CE;EAKI;AhBi9CN;AgB58CA;EAEE;IACE;EhB68CF;EgB98CA;IAGI;EhB88CJ;EgBj9CA;IAMI;EhB88CJ;AACF;AACA;;;EAGE;AiBpiDF;EACE;EXmEA;EWjEA;EACA;EACA;EACA;EACA;AjBsiDF;AiBniDE;EACE;AjBqiDJ;AiBniDE;EACE;AjBqiDJ;AiBniDE;EACE;AjBqiDJ;AiBniDE;EACE;AjBqiDJ;AiBniDE;EACE;AjBqiDJ;AiBniDE;EACE;AjBqiDJ;AiBjiDE;;EAGI;AjBkiDN;AiBnkDA;EAuCM;EACA;AjB+hDN;AiB9hDM;EACE;AjBgiDR;AiB1hDE;EAEI;EACA;AjB2hDN;AiB9kDA;EAwDI;AjByhDJ;AiBjlDA;EA2DI;AjByhDJ;AiBlhDE;EACE;AjBohDJ;AiBrhDE;EAIM;AjBohDR;AiBhhDQ;;EACE;AjBmhDV;AiB7gDI;EXxCF;ANwjDF;AiBhhDI;EXtCA;EACA;EACA;ANyjDJ;AiBrhDI;;EXjCE;AN0jDN;AiBthDI;EX3CF;ANokDF;AiBzhDI;EXzCA;EACA;EACA;ANqkDJ;AiB9hDI;;EXpCE;ANskDN;AiB/hDI;EX9CF;ANglDF;AiBliDI;EX5CA;EACA;EACA;ANilDJ;AiBviDI;;EXvCE;ANklDN;AiBxiDI;EXjDF;AN4lDF;AiB3iDI;EX/CA;EACA;EACA;AN6lDJ;AiBhjDI;;EX1CE;AN8lDN;AiBjjDI;EXpDF;ANwmDF;AiBpjDI;EXlDA;EACA;EACA;ANymDJ;AiBzjDI;;EX7CE;AN0mDN;AiB1jDI;EXvDF;ANonDF;AiB7jDI;EXrDA;EACA;EACA;ANqnDJ;AiBlkDI;;EXhDE;ANsnDN;AiBpmDE;EAmCI;EACA;AjBokDN;AiBhkDI;EAEI;AjBikDR;AiB7qDA;EAqHM;AjB2jDN;AiBhrDA;EA2HI;EACA;EACA;EACA;EACA;AjBwjDJ;AiBpjDA;;;;EAKI;EACA;EACA;EACA;EACA;AjBqjDJ;AiB9jDA;;EAaI;EACA;EX7EF;ANmoDF;AiBpkDA;;EAiBM;EACA;EACA;EACA;EACA;EACA;EACA;AjBujDN;AiB9kDA;;EA4BI;AjBsjDJ;AE5sDE;;;;;;EAEE;EACA;AFktDJ;AEhtDE;;;EACE;AFotDJ;AiBxjDA;EACE;EACA;EACA;EACA;AjB0jDF;AiBvjDE;EACE;AjByjDJ;AiBxjDI;EACE;AjB0jDN;AiBpkDA;;;;EAmBI;EACA;EACA;EACA;AjBujDJ;AiB7kDA;;;EA2BI;AjBujDJ;AiBllDA;EA8BI;EACA;EACA;AjBujDJ;AiBvlDA;EAkCM;AjBwjDN;AiBrjDI;EAEI;EACA;AjBsjDR;AiB9lDA;EA6CM;AjBojDN;AiB9iDA;EACE;EACA;EACA;EACA;AjBgjDF;AiB/iDE;;EAEE;AjBijDJ;AiB/iDE;EACE;AjBijDJ;AiB5iDA;EXnKE;EACA;EACA;EACA;EWkKA;AjBijDF;AiBhjDE;ENjPA;EACC;AXoyDH;AiBvjDA;EAQI;AjBkjDJ;AiB1jDA;EAaI;AjBgjDJ;AiB7jDA;EAiBI;AjB+iDJ;AiB7iDE;EACE;AjB+iDJ;AiBnkDA;EXnKE;EACA;EACA;EACA;ANyuDF;AiBzkDA;EXnKE;EACA;EACA;EACA;AN+uDF;AiB/iDA;EXnME;EACA;EACA;EACA;EWkMA;EACA;EACA;AjBojDF;AiBjjDA;EAEE;AjBkjDF;AiBhjDI;EAAA;IACE;IACA;EjBmjDJ;AACF;AiB9iDA;EACE;AjBgjDF;AiBjjDA;EAII;EACA;AjBgjDJ;AE50DE;;EAEE;EACA;AF80DJ;AE50DE;EACE;AF80DJ;AiBvjDI;EACE;AjByjDN;AiBvjDI;EACE;AjByjDN;AiBnkDA;EAcM;AjBwjDN;AiBtkDA;EAkBI;EACA;AjBujDJ;AiB1kDA;EAsBI;EACA;EACA;AjBujDJ;AiB/kDA;EA2BI;EACA;AjBujDJ;AACA,sBAAsB;AiB/iDtB;EACE;EACA;EACA;EACA;AjBijDF;AiBrjDA;EXjQE;EWyQE;EACA;EACA;EACA;EACA;AjBijDJ;AiBhjDI;EACE;AjBkjDN;AiBhkDA;EAkBM;AjBijDN;AiBnkDA;EAsBM;EACA;EACA;AjBgjDN;AiBxkDA;EA6BM;EACA;AjB8iDN;AiB5kDA;EAmCM;EACA;EACA;AjB4iDN;AiBjlDA;;;EAwCQ;EACA;AjB8iDR;AiB1iDI;EACE;AjB4iDN;AiBziDI;EACE;AjB2iDN;AiB5iDI;EAGI;EACA;AjB4iDR;AiBhjDI;EAQI;AjB2iDR;AiBpmDA;EAgEI;AjBuiDJ;AiBvmDA;EAmEI;AjBuiDJ;AiB1mDA;EAsEI;AjBuiDJ;AiB7mDA;EAyEI;AjBuiDJ;AiBhnDA;EA4EI;AjBuiDJ;AiBnnDA;EAgFI;EACA;EACA;AjBsiDJ;AACA,sGAAsG;AiB/hDtG;EACE;AjBiiDF;AiBliDA;EAKI;AjBgiDJ;AE37DE;;EAEE;EACA;AF67DJ;AE37DE;EACE;AF67DJ;AiB7iDA;EAQM;EACA;EACA;EXrWJ;AN84DF;AiBnjDA;EAeM;AjBuiDN;AiBtjDA;EAkBM;AjBuiDN;AiBzjDA;EAuBM;EACA;AjBqiDN;AiB7jDA;EA0BQ;EACA;AjBsiDR;AiBjkDA;EX3VE;EW6XI;EACA;EACA;EACA;AjBmiDN;AiBxkDA;EAuCQ;EACA;EACA;AjBoiDR;AiB7kDA;;EA4CQ;EACA;EACA;EACA;AjBqiDR;AE1+DE;;EAEE;EACA;AF4+DJ;AE1+DE;EACE;AF4+DJ;AiBjiDA;EACE;AjBmiDF;AiB9hDA;EAEI;AjB+hDJ;AACA;;;EAGE;AkBrgEF;EACE;EACA;EACA;EACA;EACA;EZ+DA;EY7DA;AlBugEF;AkB9gEA;EASI;AlBwgEJ;AkBjhEA;EAYI;EACA;EACA;AlBwgEJ;AkBvgEI;;EZqDF;ANs9DF;AkB1hEA;EAoBM;AlBygEN;AkBpgEA;EZiDE;EACA;EACA;EACA;EYlDA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AlBygEF;AkBlhEA;EAWI;AlB0gEJ;AkBtgEA;EACE;EACA;AlBwgEF;AkBrgEA;EACE;EACA;EACA;AlBugEF;AkBpgEA;;EAEE;EACA;EACA;EACA;EACA;AlBsgEF;AkBngEA;EACE;AlBqgEF;AkBlgEA;EACE;AlBogEF;AkBjgEA;EACE;AlBmgEF;AACA;;;EAGE;AmB3kEF;EACE;EACA;EACA;EACA;AnB6kEF;AmB1kEE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EboDF;ANyhEF;AmB5lEA;EAoBI;EACA;EACA;AnB2kEJ;AEzlEE;;EAEE;EACA;AF2lEJ;AEzlEE;EACE;AF2lEJ;AmBzmEA;EhBgEU;EGGR;EatCI;EACA;EACA;EACA;EACA;EACA;EACA;AnBklEN;AmBrnEA;EAuCQ;EACA;EACA;EACA;AnBilER;AmB3nEA;EA6CQ;EACA;EACA;EACA;EACA;EACA;AnBilER;AmBnoEA;EAoDU;AnBklEV;AmBtoEA;;EAyDQ;AnBilER;AmB1oEA;;;EAkEM;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AnB6kEN;AmBzpEA;EAmFM;EACA;EACA;EACA;EbnBJ;AN6lEF;AmBnkEA;EAGM;EACA;EhBjCI;AHsmEV;AmBzkEA;EAOQ;AnBqkER;AACA;;;EAGE;AoB7qEF;EdmEE;EHHQ;EiB7DR;ApBgrEF;AoB9qEE;EACE;ApBgrEJ;AoB5qEE;EdyDA;EcrDE;EACA;ApB8qEJ;AoB1qEE;EAGE;ApB4qEJ;AoBzqEE;EACE;ApB2qEJ;AoBvqEE;EACE;EACA;ApByqEJ;AoB3qEE;EAII;EACA;EACA;EACA;EACA;EACA;EACA;EC1CJ;EAGA;EDyCI;EACA;EACA;EACA;ApB2qEN;AoBrqEA;EACE;EACA;EACA;ApBuqEF;AoBtqEE;;;EAGE;ApBwqEJ;AoBpqEA;EACE;EACA;ApBsqEF;AoBrqEE;;;EACE;ApByqEJ;AoBrqEA;EACE;EACA;ApBuqEF;AoBtqEE;;;EACE;ApB0qEJ;AoBtqEA;EACE;EACA;ApBwqEF;AoBvqEE;;;EACE;ApB2qEJ;AoBvqEA;EACE;EACA;ApByqEF;AoBxqEE;;;EACE;ApB4qEJ;AoBxqEA;EACE;EACA;ApB0qEF;AoBzqEE;;;EACE;ApB6qEJ;AoBzqEA;EACE;EACA;EACA;ApB2qEF;AoB1qEE;;;EAGE;EACA;ApB4qEJ;AoBxqEA;EjBlDU;AH8tEV;AoBvqEA;EjBvDU;AHkuEV;AoBtqEA;EdzDE;Ec2DA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ApBwqEF;AoBnrEA;;;EAcI;EACA;ApB0qEJ;AoBvqEE;EACE;EACA;EACA;ApByqEJ;AoBtqEE;;EAGE;ApByqEJ;AoBpsEA;EAgCI;EACA;EACA;EACA;EACA;ApBuqEJ;AACA;;;EAGE;AsB10EF;EhBkEE;EgBhEA;EACA;EACA;AtB40EF;AsBh1EA;EAMI;EACA;AtB60EJ;AsB50EI;EACE;AtB80EN;AsBv1EA;EAaI;EACA;AtB60EJ;AsB31EA;EAiBI;AtB60EJ;AsB91EA;;EAqBI;AtB60EJ;AsBz0EE;EAEE;AtB00EJ;AsBx0EE;EAEE;AtBy0EJ;AsBv0EE;EAEE;AtBw0EJ;AsBt0EE;EAEE;AtBu0EJ;AACA;;;EAGE;AuBn3EF;EjBmEE;ANmzEF;AuBt3EA;EAGI;AvBs3EJ;AuBz3EA;EAMI;AvBs3EJ;AuB53EA;EASI;EFXF;EAGA;ArBg4EF;AuBt3EI;EFbF;EAGA;ArBo4EF;AuBr4EA;EAgBI;EACA;AvBw3EJ;AuBn3EA;EAEE;AvBo3EF;AuBj3EA;;EAGE;AvBk3EF;AuB/2EA;EAEE;AvBg3EF;AuB72EA;EAEE;AvB82EF;AACA;;;EAGE;AwB15EF;;;EAII;EACA;AxB25EJ;AACA,cAAc;AwBv5Ed;ElByDE;EkBtDE;EACA;AxBw5EJ;AwB55EA;;;EAQM;AxBy5EN;AwBj6EA;;;EAcI;AxBw5EJ;AwBt6EA;EAiBI;AxBw5EJ;AACA,gBAAgB;AwBp5EhB;ElBmCE;EkBhCE;EACA;EACA;AxBq5EJ;AwB15EA;;EASI;EACA;EACA;EACA;AxBq5EJ;AwBj6EA;EAgBI;EACA;EACA;EACA;EACA;AxBo5EJ;AACA,aAAa;AwBh5Eb;EACE;EACA;EACA;EACA;AxBk5EF;AwBt5EA;EAMI;EACA;EblEF;EACC;AXs9EH;AwB55EA;EAWM;EACA;EA4BA;AxBy3EN;AwBn5EM;EACE;AxBq5ER;AwBp6EA;EAmBQ;ElBTN;AN85EF;AwBn5EQ;EACE;AxBq5EV;AwBn5EQ;;EAEE;EACA;AxBq5EV;AwBn5EQ;EACE;AxBq5EV;AwBl5EM;;;EAII;AxBm5EV;AwBx7EA;EA4CM;AxB+4EN;AwB94EM;;EAEE;EACA;AxBg5ER;AwBh8EA;EAmDQ;EACA;EACA;AxBg5ER;AwBr8EA;EA2DM;AxB64EN;AwB54EM;EAEI;AxB64EV;AwBv4EI;EACE;AxBy4EN;AwB14EI;EAGI;AxB04ER;AwB74EI;EAMI;AxB04ER;AwBh5EI;EAQM;AxB24EV;AwBz4EQ;EAEI;EACA;AxB04EZ;AwB39EA;EAwFM;EACA;EACA;EACA;AxBs4EN;AwBj+EA;;;EA+FQ;AxBu4ER;AwBt+EA;EAqGI;EACA;EbzJF;EACC;AX8hFH;AwBj4EI;;EAEE;EACA;AxBm4EN;AwB/3EE;EAGM;AxB+3ER;AwB33EE;EAGM;AxB23ER;AwBv3EE;EAGM;AxBu3ER;AwBn3EE;EAGM;AxBm3ER;AwB/2EE;EAGM;AxB+2ER;AwB32EE;EAGM;AxB22ER;AACA,eAAe;AwBr2Ef;EAEI;EACA;AxBs2EJ;AwBp2EE;ElB1JA;ANigFF;AACA;;;EAGE;AyBzkFF;EACE;EACA;EACA;AzB2kFF;AyB9kFA;EnBoEE;EHHQ;EsBzDN;EACA;AzB4kFJ;AE5kFE;;EAEE;EACA;AF8kFJ;AE5kFE;EACE;AF8kFJ;AyB7lFA;EAYI;AzBolFJ;AyBhmFA;EAcM;EACA;AzBqlFN;AyBpmFA;EAmBI;AzBolFJ;AyBvmFA;EAsBI;AzBolFJ;AyB1mFA;EAyBI;EACA;EACA;EACA;EACA;AzBolFJ;AyBhlFA;EtBgCU;EGGR;EmBhCA;AzBmlFF;AyBllFE;EACE;AzBolFJ;AACA;;;EAGE;A0B7nFF;;;;;;EAQQ;A1B6nFR;A0BroFA;EAcI;A1B0nFJ;A0BxoFA;EAkBI;A1BynFJ;A0BpnFA;EACE;A1BsnFF;A0BvnFA;;;;;;EAQQ;A1BunFR;A0B/nFA;;EAeM;A1BonFN;A0B9mFE;;;EAGE;A1BgnFJ;AACA,2BAA2B;A0B3mFzB;;;EACE;A1B+mFJ;A0B3mFA;EAEI;A1B4mFJ;A0B9mFA;EAKI;A1B4mFJ;AACA;;;EAGE;A2BhrFF;EACE;EACA;A3BkrFF;AACA;;;EAGE;A4BxrFF;EjBOE;EACC;EiBLC;EACA;EACA;A5B0rFJ;A4BxrFE;EzBsIQ;AHwjFV;A4BvrFA;EzB+HU;EyB7HR;EACA;EACA;A5B4rFF;A4BzrFA;;EAEE;A5B2rFF;A4BxrFA;EAEE;A5ByrFF;AE5sFE;;EAEE;EACA;AF8sFJ;AE5sFE;EACE;AF8sFJ;A4B9rFA;;EzBmLU;AHkhFV;A4BhsFA;EtBgCE;EsB9BA;EACA;EACA;EACA;EACA;EACA;A5BksFF;A4B/rFE;;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;A5BisFJ;A4B9rFE;EACE;EACA;A5BgsFJ;A4B9rFE;EACE;EACA;A5BgsFJ;A4B9rFE;EACE;EACA;A5BgsFJ;A4B/rFI;;EAEE;EACA;EACA;EACA;A5BisFN;A4B5rFA;EtBZE;EsBcA;EACA;EACA;A5B8rFF;A4B7rFE;EACE;A5B+rFJ;A4B3rFA;EACE;EACA;EACA;A5B6rFF;A4B1rFA;EACE;A5B4rFF;A4BzrFA;EACE;A5B2rFF;A4BvrFA;EzBoCU;AHypFV;A4BvrFA;EzB8BU;EyB5BR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;A5B4rFF;A4BxrFA;EAII;EACA;EACA;A5BurFJ;AEhzFE;;EAEE;EACA;AFkzFJ;AEhzFE;EACE;AFkzFJ;A4B9rFI;EACE;A5BgsFN;A4B3rFA;EtBrEE;EsBuEA;EACA;A5B6rFF;A4B1rFA;EACE;EACA;A5B4rFF;A4BzrFA;;EAEE;A5B2rFF;A4BxrFA;EACE;A5B0rFF;A4BvrFA;EACE;A5ByrFF;A4BtrFA;EACE;EACA;A5BwrFF;A4BrrFA;EACE;A5BurFF;A4BnrFA;EtBnHI;EACA;EACA;ANyyFJ;AMxyFI;;EAEE;AN0yFN;A4BxrFA;EtBvHI;EACA;EACA;ANkzFJ;AMjzFI;;EAEE;ANmzFN;A4B7rFA;EtB3HI;EACA;EACA;AN2zFJ;AM1zFI;;EAEE;AN4zFN;A4BlsFA;EtB/HI;EACA;EACA;ANo0FJ;AMn0FI;;EAEE;ANq0FN;A4BvsFA;EtBnII;EACA;EACA;AN60FJ;AM50FI;;EAEE;AN80FN;AACA;;;EAGE;A6B/4FF;EAGI;EACA;EACA;EACA;A7B+4FJ;A6Br5FA;EvBoEE;EuB3DI;EACA;A7Bg5FN;A6B74FM;;EAEE;A7B+4FR;A6Bz4FA;;EAEE;A7B24FF;A6Bx4FA;EACE;EACA;EACA;EACA;EACA;A7B04FF;A6Bv4FA;EACE;EACA;A7By4FF;AACA;;;EAGE;A8Bh7FA;;EAEE;A9Bk7FJ;A8Br7FA;EAMI;EACA;EACA;EACA;EACA;EACA;A9Bk7FJ;AACA;;;EAGE;A+Bj8FF;EACE;A/Bm8FF;A+Bh8FA;EzBgEE;EHHQ;E4B1DR;A/Bm8FF;A+Bl8FE;EAAA;I5ByDQ;EH84FR;AACF;A+Bn8FA;EACE;A/Bq8FF;A+Bl8FA;EACE;A/Bo8FF;A+Bh8FA;;EAOI;A/B67FJ;A+Bz7FA;;EAOI;A/Bs7FJ;A+Bl7FA;;EAOI;A/B+6FJ;A+B36FA;;EAOI;A/Bw6FJ;A+Bp6FA;;EAOI;A/Bi6FJ;AACA;;;EAGE;AgC7+FF;EACE;EACA;AhC++FF;AgC3+FA;EAGI;EACA;ErBZF;EACC;AXw/FH;AgCj/FA;EASI;EACA;EACA;EACA;EACA;AhC2+FJ;AgCx/FA;EAiBI;AhC0+FJ;AgC3/FA;EAqBI;EACA;EACA;EACA;AhCy+FJ;AgCjgGA;EA0BM;EACA;EACA;AhC0+FN;AgCtgGA;EAgCI;AhCy+FJ;AgCp+FA;EAGI;ErBhDF;EACC;AXqhGH;AgCz+FA;EAQI;EACA;EACA;EACA;AhCo+FJ;AgC/+FA;EAeI;AhCm+FJ;AgCl/FA;;EAmBI;AhCm+FJ;AgCt/FA;EAwBM;EACA;EACA;AhCi+FN;AiC1iGA;EACC;EACA;EACA;EACA;EACA;AjC4iGD;AiCjjGA;EAOG;AjC6iGH;AiCpjGA;EAUG;AjC6iGH;AiCvjGA;EAYK;EACA;EACA;AjC8iGL;AiC5jGA;;;EAkBO;AjC+iGP;AiCjkGA;;;;EAwBO;AjC+iGP;AiCziGA;EAEE;EACA;EACA;EACA;AjC0iGF;AiCviGA;EAEG;EACC;AjCwiGJ;AACA;;;EAGE;AkCllGF;EAEI;AlCmlGJ;AkC/kGA;EACE;AlCilGF;AkChlGE;EACE;AlCklGJ;AkC9kGA;EACE;EACA;AlCglGF;AkCllGA;EAII;EACA;AlCilGJ;AkCtlGA;EAQI;EACA;AlCilGJ;AkC7kGA;EACE;EACA;AlC+kGF;AkC5kGA;EACE;AlC8kGF;AkC3kGA;EAGI;EACA;EACA;EACA;EACA;AlC2kGJ;AkCvkGA;EACE;EACA;AlCykGF;AkCtkGA;;;EAGE;AlCwkGF;AkCrkGA;EACE;EACA;AlCukGF;AkCpkGA;EACE;EACA;AlCskGF;AkCnkGA;EACE;EACA;EACA;EACA;AlCqkGF;AkCpkGE;EACE;AlCskGJ;AkCvkGE;EAGI;EACA;AlCukGN;AACA;;;EAGE;AACF,qCAAqC;AmCvpGrC;EACE;AnCypGF;AmCtpGA;EACE;EACA;EACA;EACA;AnCwpGF;AmC5pGA;EAMI;AnCypGJ;AmCrpGA;EACE;EACA;EACA;AnCupGF;AACA,yBAAyB;AmCppGzB;EACE;EACA;AnCspGF;AACA,gDAAgD;AmCnpGhD;E7BwCE;E6BtCA;EACA;EACA;EACA;EACA;AnCqpGF;AACA,eAAe;AmClpGf;E7B8BE;E6B5BA;EACA;EACA;EACA;EACA;EACA;AnCopGF;AmC3pGA;E7B8BE;E6BpBE;EACA;AnCqpGJ;AACA,qDAAqD;AmCjpGrD;EACE;AnCmpGF;AmCppGA;EAGI;AnCopGJ;AmCvpGA;EAMI;EACA;EACA;AnCopGJ;AmChpGA;EACE;AnCkpGF;AACA;;;EAGE;AoCxtGF;;EAEE;EACA;EACA;EACA;ApC0tGF;AoC/tGA;;EAOI;ApC4tGJ;AoCxtGA;;EAEE;EACA;ApC0tGF;AoCvtGA;;EAEE;EACA;ApCytGF;AoCxtGE;EAAA;;IACE;IACA;EpC4tGF;AACF;AoCztGA;;EAEE;EACA;EACA;EACA;ApC2tGF;AoChuGA;;EAOI;ApC6tGJ;AoCztGA;;EAEE;EACA;EACA;ApC2tGF;AoCxtGA;EACE;ApC0tGF;AACA;;;EAGE;AqC7wGF;EACE;EACA;ArC+wGF;AqC9wGE;EAAA;IACE;ErCixGF;AACF;AqCtxGA;EAQI;EACA;EACA;ArCixGJ;AqChxGI;EAAA;IACE;IACA;ErCmxGJ;AACF;AqCjyGA;EAkBI;EAWA;ArCwwGJ;AqClxGI;EAAA;IACE;ErCqxGJ;AACF;AqC1yGA;EAuBM;EACA;ArCsxGN;AqCrxGM;EAAA;IACE;ErCwxGN;AACF;AACA;;;EAGE;AsCtzGF;EACE;EACA;EACA;EACA;EACA;AtCwzGF;AsCrzGA;EACE;AtCuzGF;AACA;;;EAGE;AuCp0GF;EACE;EACA;EACA;EACA;AvCs0GF;AuCn0GA;EACE;EACA;AvCq0GF;AuCl0GA;EACE;EACA;EACA;EACA;AvCo0GF;AuCn0GE;EACE;EACA;EACA;AvCq0GJ;AuC70GA;EAWI;AvCq0GJ;AACA;;;;;;;EAOE;AwCr1GF;EACE;EACA;EACA;EACA;EACA;EACA;AxCu1GF;AwC71GA;EAQI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AxCw1GJ;AwCt1GE;EACE;AxCw1GJ;AwCz1GE;EAGI;EACA;EACA;AxCy1GN;AwCt1GE;EACE;AxCw1GJ;AwCz1GE;EAGI;EACA;EACA;AxCy1GN;AwCt1GE;EACE;AxCw1GJ;AwCz1GE;EAGI;EACA;EACA;AxCy1GN;AwCp1GA;EA3CE;EACA;EACA;EACA;EACA;EACA;EAwCA;EACA;EACA;AxC21GF;AwC/1GA;EApCI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AxCs4GJ;AwCp4GE;EACE;AxCs4GJ;AwCv4GE;EAGI;EACA;EACA;AxCu4GN;AwCp4GE;EACE;AxCs4GJ;AwCv4GE;EAGI;EACA;EACA;AxCu4GN;AwCp4GE;EACE;AxCs4GJ;AwCv4GE;EAGI;EACA;EACA;AxCu4GN;AwCl4GA;EAMI;EACA;EACA;AxC+3GJ;AwC73GE;EACE;EACA;EACA;EACA;AxC+3GJ;AwC73GE;EACE;EACA;EACA;EACA;AxC+3GJ;AwC73GE;EACE;EACA;EACA;EACA;AxC+3GJ;AwCt3GA;EC7FE;EACA;EACA;AzCs9GF;AyCp9GE;;EAEE;EACA;EACI;AzCs9GR;AyCp9GE;EACE;EACA;EACI;AzCs9GR;AyCp9GE;;;EAGE;EACA;EACI;AzCs9GR;AyCp9GI;;;;;;;;;EAGE;EACA;EACI;AzC49GV;AyCz9GE;;;EAGE;AzC29GJ;AyCt9GI;;;;;;;;;EAGE;EACI;AzC89GV;AwC36GA;EC9CI;EACA;AzC49GJ;AwC36GA;ECjGE;EACA;EACA;AzC+gHF;AyC7gHE;;EAEE;EACA;EACI;AzC+gHR;AyC7gHE;EACE;EACA;EACI;AzC+gHR;AyC7gHE;;;EAGE;EACA;EACI;AzC+gHR;AyC7gHI;;;;;;;;;EAGE;EACA;EACI;AzCqhHV;AyClhHE;;;EAGE;AzCohHJ;AyC/gHI;;;;;;;;;EAGE;EACI;AzCuhHV;AwCh+GA;EClDI;EACA;AzCqhHJ;AwCh+GA;ECrGE;EACA;EACA;AzCwkHF;AyCtkHE;;EAEE;EACA;EACI;AzCwkHR;AyCtkHE;EACE;EACA;EACI;AzCwkHR;AyCtkHE;;;EAGE;EACA;EACI;AzCwkHR;AyCtkHI;;;;;;;;;EAGE;EACA;EACI;AzC8kHV;AyC3kHE;;;EAGE;AzC6kHJ;AyCxkHI;;;;;;;;;EAGE;EACI;AzCglHV;AwCrhHA;ECtDI;EACA;AzC8kHJ;AwCrhHA;ECzGE;EACA;EACA;AzCioHF;AyC/nHE;;EAEE;EACA;EACI;AzCioHR;AyC/nHE;EACE;EACA;EACI;AzCioHR;AyC/nHE;;;EAGE;EACA;EACI;AzCioHR;AyC/nHI;;;;;;;;;EAGE;EACA;EACI;AzCuoHV;AyCpoHE;;;EAGE;AzCsoHJ;AyCjoHI;;;;;;;;;EAGE;EACI;AzCyoHV;AwC1kHA;EC1DI;EACA;AzCuoHJ;AwC1kHA;EC7GE;EACA;EACA;AzC0rHF;AyCxrHE;;EAEE;EACA;EACI;AzC0rHR;AyCxrHE;EACE;EACA;EACI;AzC0rHR;AyCxrHE;;;EAGE;EACA;EACI;AzC0rHR;AyCxrHI;;;;;;;;;EAGE;EACA;EACI;AzCgsHV;AyC7rHE;;;EAGE;AzC+rHJ;AyC1rHI;;;;;;;;;EAGE;EACI;AzCksHV;AwC/nHA;EC9DI;EACA;AzCgsHJ;AwC/nHA;ECjHE;EACA;EACA;AzCmvHF;AyCjvHE;;EAEE;EACA;EACI;AzCmvHR;AyCjvHE;EACE;EACA;EACI;AzCmvHR;AyCjvHE;;;EAGE;EACA;EACI;AzCmvHR;AyCjvHI;;;;;;;;;EAGE;EACA;EACI;AzCyvHV;AyCtvHE;;;EAGE;AzCwvHJ;AyCnvHI;;;;;;;;;EAGE;EACI;AzC2vHV;AwCprHA;EClEI;EACA;AzCyvHJ;AwCprHA;ECrHE;EACA;EACA;AzC4yHF;AyC1yHE;;EAEE;EACA;EACI;AzC4yHR;AyC1yHE;EACE;EACA;EACI;AzC4yHR;AyC1yHE;;;EAGE;EACA;EACI;AzC4yHR;AyC1yHI;;;;;;;;;EAGE;EACA;EACI;AzCkzHV;AyC/yHE;;;EAGE;AzCizHJ;AyC5yHI;;;;;;;;;EAGE;EACI;AzCozHV;AwCzuHA;ECtEI;EACA;AzCkzHJ;AwCzuHA;ECzHE;EACA;EACA;AzCq2HF;AyCn2HE;;EAEE;EACA;EACI;AzCq2HR;AyCn2HE;EACE;EACA;EACI;AzCq2HR;AyCn2HE;;;EAGE;EACA;EACI;AzCq2HR;AyCn2HI;;;;;;;;;EAGE;EACA;EACI;AzC22HV;AyCx2HE;;;EAGE;AzC02HJ;AyCr2HI;;;;;;;;;EAGE;EACI;AzC62HV;AwC9xHA;EC1EI;EACA;AzC22HJ;AwC9xHA;EC7HE;EACA;EACA;AzC85HF;AyC55HE;;EAEE;EACA;EACI;AzC85HR;AyC55HE;EACE;EACA;EACI;AzC85HR;AyC55HE;;;EAGE;EACA;EACI;AzC85HR;AyC55HI;;;;;;;;;EAGE;EACA;EACI;AzCo6HV;AyCj6HE;;;EAGE;AzCm6HJ;AyC95HI;;;;;;;;;EAGE;EACI;AzCs6HV;AwCn1HA;EC9EI;EACA;AzCo6HJ;AwCn1HA;ECjIE;EACA;EACA;AzCu9HF;AyCr9HE;;EAEE;EACA;EACI;AzCu9HR;AyCr9HE;EACE;EACA;EACI;AzCu9HR;AyCr9HE;;;EAGE;EACA;EACI;AzCu9HR;AyCr9HI;;;;;;;;;EAGE;EACA;EACI;AzC69HV;AyC19HE;;;EAGE;AzC49HJ;AyCv9HI;;;;;;;;;EAGE;EACI;AzC+9HV;AwCx4HA;EClFI;EACA;AzC69HJ;AwCx4HA;ECrIE;EACA;EACA;AzCghIF;AyC9gIE;;EAEE;EACA;EACI;AzCghIR;AyC9gIE;EACE;EACA;EACI;AzCghIR;AyC9gIE;;;EAGE;EACA;EACI;AzCghIR;AyC9gII;;;;;;;;;EAGE;EACA;EACI;AzCshIV;AyCnhIE;;;EAGE;AzCqhIJ;AyChhII;;;;;;;;;EAGE;EACI;AzCwhIV;AwC77HA;ECtFI;EACA;AzCshIJ;AwC77HA;ECzIE;EACA;EACA;AzCykIF;AyCvkIE;;EAEE;EACA;EACI;AzCykIR;AyCvkIE;EACE;EACA;EACI;AzCykIR;AyCvkIE;;;EAGE;EACA;EACI;AzCykIR;AyCvkII;;;;;;;;;EAGE;EACA;EACI;AzC+kIV;AyC5kIE;;;EAGE;AzC8kIJ;AyCzkII;;;;;;;;;EAGE;EACI;AzCilIV;AwCl/HA;EC1FI;EACA;AzC+kIJ;AwCl/HA;EC7IE;EACA;EACA;AzCkoIF;AyChoIE;;EAEE;EACA;EACI;AzCkoIR;AyChoIE;EACE;EACA;EACI;AzCkoIR;AyChoIE;;;EAGE;EACA;EACI;AzCkoIR;AyChoII;;;;;;;;;EAGE;EACA;EACI;AzCwoIV;AyCroIE;;;EAGE;AzCuoIJ;AyCloII;;;;;;;;;EAGE;EACI;AzC0oIV;AwCviIA;EC9FI;EACA;AzCwoIJ;AwCviIA;ECjJE;EACA;EACA;AzC2rIF;AyCzrIE;;EAEE;EACA;EACI;AzC2rIR;AyCzrIE;EACE;EACA;EACI;AzC2rIR;AyCzrIE;;;EAGE;EACA;EACI;AzC2rIR;AyCzrII;;;;;;;;;EAGE;EACA;EACI;AzCisIV;AyC9rIE;;;EAGE;AzCgsIJ;AyC3rII;;;;;;;;;EAGE;EACI;AzCmsIV;AwC5lIA;EClGI;EACA;AzCisIJ;AwC5lIA;ECrJE;EACA;EACA;AzCovIF;AyClvIE;;EAEE;EACA;EACI;AzCovIR;AyClvIE;EACE;EACA;EACI;AzCovIR;AyClvIE;;;EAGE;EACA;EACI;AzCovIR;AyClvII;;;;;;;;;EAGE;EACA;EACI;AzC0vIV;AyCvvIE;;;EAGE;AzCyvIJ;AyCpvII;;;;;;;;;EAGE;EACI;AzC4vIV;AwCjpIA;ECtGI;EACA;AzC0vIJ;AwCjpIA;ECzJE;EACA;EACA;AzC6yIF;AyC3yIE;;EAEE;EACA;EACI;AzC6yIR;AyC3yIE;EACE;EACA;EACI;AzC6yIR;AyC3yIE;;;EAGE;EACA;EACI;AzC6yIR;AyC3yII;;;;;;;;;EAGE;EACA;EACI;AzCmzIV;AyChzIE;;;EAGE;AzCkzIJ;AyC7yII;;;;;;;;;EAGE;EACI;AzCqzIV;AwCtsIA;EC1GI;EACA;AzCmzIJ;AwCtsIA;EC7JE;EACA;EACA;AzCs2IF;AyCp2IE;;EAEE;EACA;EACI;AzCs2IR;AyCp2IE;EACE;EACA;EACI;AzCs2IR;AyCp2IE;;;EAGE;EACA;EACI;AzCs2IR;AyCp2II;;;;;;;;;EAGE;EACA;EACI;AzC42IV;AyCz2IE;;;EAGE;AzC22IJ;AyCt2II;;;;;;;;;EAGE;EACI;AzC82IV;AwC3vIA;EC9GI;EACA;AzC42IJ;AwC3vIA;ECjKE;EACA;EACA;AzC+5IF;AyC75IE;;EAEE;EACA;EACI;AzC+5IR;AyC75IE;EACE;EACA;EACI;AzC+5IR;AyC75IE;;;EAGE;EACA;EACI;AzC+5IR;AyC75II;;;;;;;;;EAGE;EACA;EACI;AzCq6IV;AyCl6IE;;;EAGE;AzCo6IJ;AyC/5II;;;;;;;;;EAGE;EACI;AzCu6IV;AwChzIA;EClHI;EACA;AzCq6IJ;AwChzIA;ECrKE;EACA;EACA;AzCw9IF;AyCt9IE;;EAEE;EACA;EACI;AzCw9IR;AyCt9IE;EACE;EACA;EACI;AzCw9IR;AyCt9IE;;;EAGE;EACA;EACI;AzCw9IR;AyCt9II;;;;;;;;;EAGE;EACA;EACI;AzC89IV;AyC39IE;;;EAGE;AzC69IJ;AyCx9II;;;;;;;;;EAGE;EACI;AzCg+IV;AwCr2IA;ECtHI;EACA;AzC89IJ;AwCr2IA;ECzKE;EACA;EACA;AzCihJF;AyC/gJE;;EAEE;EACA;EACI;AzCihJR;AyC/gJE;EACE;EACA;EACI;AzCihJR;AyC/gJE;;;EAGE;EACA;EACI;AzCihJR;AyC/gJI;;;;;;;;;EAGE;EACA;EACI;AzCuhJV;AyCphJE;;;EAGE;AzCshJJ;AyCjhJI;;;;;;;;;EAGE;EACI;AzCyhJV;AwC15IA;EC1HI;EACA;AzCuhJJ;AACA;;;EAGE;A0CtkJF;EACE;EACA;EACA;EACA;EACA;A1CwkJF;A0CvkJE;;;EAGE;A1CykJJ;A0CpkJA;EACE;EACA;EACA;EACA;A1CskJF;A0CnkJA;EACE;A1CqkJF;A0ClkJA;EACE;A1CokJF;A0ChkJA;EACE;A1CkkJF;A0C/jJA;EACE;EACA;A1CikJF;A0C9jJA;;EAEE;EACA;A1CgkJF;A0C7jJA;;EAEE;A1C+jJF;A0C5jJA;EACE;EACA;A1C8jJF;A0C3jJA;EACE;EACA;EACA;A1C6jJF;A0C1jJA;EACE;EACA;EACA;A1C4jJF;A0C/jJA;EAKI;EACA;EACA;EACA;A1C6jJJ;A0CrkJA;EvC8IU;AH67IV;A0ChkJM;EvCwEI;AH8/IV;A0C/jJA;EvCsGU;AH89IV;A0ChkJA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;A1CkkJF;A0CjkJE;EACE;A1CmkJJ;AACA;;;EAGE;A2CjqJA;;;;;;EAGE;A3CsqJJ;A2C3qJA;;EAQI;EACA;EACA;EACA;A3CuqJJ;A2CnqJA;EACE;A3CqqJF;A2ClqJA;EACE;EACA;A3CoqJF;A2CjqJA;EACE;EACA;A3CmqJF;A2ChqJA;EACE;EACA;OAAA;EACA;A3CkqJF;A2C/pJA;EACE;EACA;EACA;EACA;A3CiqJF;A2C9pJA;EACE;EACA;A3CgqJF;A2C7pJA;EACE;EACA;A3C+pJF;A2C5pJA;EACE;A3C8pJF;A2C3pJA;;EAGI;A3C4pJJ;A2C3pJI;;EACE;A3C8pJN;A2CxpJA;;EAEE;A3C0pJF;A2CvpJA;EACE;A3CypJF;A2CtpJA;EACE;A3CwpJF;A2CvpJE;;EAEE;A3CypJJ;A2CppJA;EAEI;EACA;A3CqpJJ;A2CppJI;EACE;A3CspJN;A2CnpJE;EACE;A3CqpJJ;A2CjpJA;EACE;EACA;EACA;EACA;A3CmpJF;A2ChpJA;EACE;EACA;A3CkpJF;A2CjpJE;EACE;A3CmpJJ;A2C/oJA;EACE;A3CipJF;A4C1wJM;;;;EAEE;A5C8wJR;A4C5wJU;;;;EACE;A5CixJZ;A4C9wJY;;;;;;;;EAEE;A5CsxJd;AACA;;;EAGE;A6CnyJF;EACE;A7CqyJF;A6ClyJA;EACE;A7CoyJF;A6CjyJA;EACE;A7CmyJF;A6ChyJA;EACE;A7CkyJF;A6C/xJA;EACE;A7CiyJF;A6C7xJA;EACE;A7C+xJF;A6C3xJA;EACE;EACA;EACA;A7C6xJF;A6C5xJE;EACE;A7C8xJJ;A6CnyJA;EAQI;EACA;EACA;EACA;A7C8xJJ;A6CzyJA;EAcI;A7C8xJJ;A6CzxJA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BE;A7CwzJF;A6CrzJA;EACE;EACA;A7CuzJF;A6CpzJA;EACE;A7CszJF;A6CnzJA;EACE;A7CqzJF;A6ClzJA;;;;;;EACE;A7CyzJF;A6CtzJA;;;;;EACE;A7C4zJF;A6CzzJA;;;;;EACE;A7C+zJF;A6C5zJA;EACE;A7C8zJF;A6C3zJA;;;EACE;A7C+zJF;A6C5zJA;;;;;EACE;A7Ck0JF;A6C/zJA;EACE;A7Ci0JF;A6C9zJA;EACE;A7Cg0JF;A6C7zJA;EACE;A7C+zJF;A6C5zJA;EACE;A7C8zJF;A6C3zJA;EACE;A7C6zJF;A6C1zJA;EACE;A7C4zJF;A6CzzJA;EACE;A7C2zJF;A6CxzJA;EACE;A7C0zJF;A6CtzJA;EACE;EACA;A7CwzJF;A6CrzJA;EACE;A7CuzJF;A6CpzJA;;;EACE;A7CwzJF;A6CrzJA;;;EACE;A7CyzJF;A6CtzJA;;;EACE;A7C0zJF;A6CvzJA;EACE;A7CyzJF;A6CtzJA;;;EACE;A7C0zJF;A6CvzJA;;;EACE;A7C2zJF;A6CxzJA;EACE;A7C0zJF;A6CvzJA;EACE;A7CyzJF;A6CtzJA;EACE;A7CwzJF;A6CrzJA;EACE;A7CuzJF;A6CpzJA;EACE;A7CszJF;A6CnzJA;EACE;A7CqzJF;A6ClzJA;EACE;A7CozJF;A6CjzJA;EACE;A7CmzJF;A6C/yJA;ExBxNE;EAGA;ArBwgKF;A6C9yJA;EACE;A7CgzJF;A6C7yJA;EACE;A7C+yJF;A6C5yJA;EACE;A7C8yJF;A6C3yJA;EACE;A7C6yJF;A6C1yJA;EACE;A7C4yJF;A6CzyJA;EACE;A7C2yJF;A6CxyJA;EACE;A7C0yJF;A6CvyJA;EACE;A7CyyJF;A6CtyJA;EACE;A7CwyJF;A6CryJA;EACE;A7CuyJF;A6CpyJA;EACE;A7CsyJF;A6CnyJA;EACE;A7CqyJF;A6ClyJA;EACE;A7CoyJF;A6CjyJA;EACE;A7CmyJF;A6ChyJA;EACE;A7CkyJF;A6C/xJA;EACE;A7CiyJF;A6C9xJA;EACE;A7CgyJF;A6C/xJE;;EAEE;A7CiyJJ;A6C7xJA;EACE;A7C+xJF;A6C9xJE;;EAEE;A7CgyJJ;A6C3xJA;EACE;A7C6xJF;A6CzxJA;EACE;A7C2xJF;A6CvxJA;EACE;A7CyxJF;A6CrxJA;EACE;A7CuxJF;A6CnxJA;EACE;A7CqxJF;A6CjxJA;;;;;EACE;EACA;EACA;A7CuxJF;A6CpxJA;EAEI;EACA;EACA;EACA;EACA;A7CqxJJ;A6ChxJA;EvCnRE;ANsiKF;A6C9wJE;;;EACE;A7CkxJJ;A6C9wJA;EACE;A7CgxJF;A6C5wJA;EACE;EACA;EACA;A7C8wJF;A6C1wJA;EvC3RE;EAYA;EAEA;EuC+QA;A7CixJF;A6C9wJA;EvChSE;EAYA;EAEA;EuCoRA;A7CqxJF;A6ClxJA;EvCrSE;EAYA;EAEA;EuCyRA;A7CyxJF;A6CtxJA;EvC1SE;EAYA;EAEA;EuC8RA;A7C6xJF;A6C1xJA;EvC/SE;EAYA;EAEA;EuCmSA;A7CiyJF;A6C9xJA;EvCpTE;EAYA;EAEA;EuCwSA;A7CqyJF;A6ClyJA;EvCzTE;EAYA;EAEA;EuC6SA;A7CyyJF;A6CtyJA;EvC9TE;EAYA;EAEA;EuCkTA;A7C6yJF;A6C1yJA;EvCnUE;EAYA;EAEA;EuCuTA;A7CizJF;A6C9yJA;EvCxUE;EAYA;EAEA;EuC4TA;A7CqzJF;A6CjzJA;EAEI;A7CkzJJ;A6C7yJA;EACE;A7C+yJF;A6C3yJA;EACE;A7C6yJF;A6CzyJA;EACE;EACA;EACA;EACA;A7C2yJF;A6CxyJA;EACE;EACA;EACA;A7C0yJF;A6CvyJA;EAEI;EACA;A7CwyJJ;A6CvyJI;EACE;A7CyyJN;A6CnyJA;EACE;A7CqyJF;AEtuKE;;EAEE;EACA;AFwuKJ;AEtuKE;EACE;AFwuKJ;A6CzyJA;EAGI;EACA;EACA;A7CyyJJ;A6C9yJA;;;EAUI;EACA;A7CyyJJ;A6CpzJA;EAcI;EACA;A7CyyJJ;A6CxzJA;EAkBI;EACA;A7CyyJJ;A6CvyJE;;;EAOI;A7CqyJN;A6C5yJE;EAUI;A7CqyJN;A6C/xJA;;;;;EAGE;A7CmyJF;A6ChyJA;;;EACE;EACA;A7CoyJF;A6CtyJA;EAII;A7CqyJJ;A6CjyJA;EACE;EACA;A7CmyJF;A6CryJA;EAII;A7CoyJJ;A6ChyJA;EACE;EACA;A7CkyJF;A6CpyJA;EAII;A7CmyJJ;A6C9xJA;EACE;EACA;A7CgyJF;A6C7xJA;EACE;EACA;A7C+xJF;A6C3xJA;EACE;EACA;EACA;EACA;A7C6xJF;A6CjyJA;EAOI;EACA;EACA;EACA;A7C6xJJ;A6CvyJA;EAaI;A7C6xJJ;A6C1yJA;EAgBI;A7C6xJJ;A6C7yJA;EAmBI;A7C6xJJ;A6CzxJA;EACE;A7C2xJF;A6CxxJA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;A7C0xJF;A6CvxJA;EACE;EACA;EACA;A7CyxJF;A6CtxJA;ExB1kBE;EAGA;ArBi2KF;A6CxxJE;ExB5kBA;EAGA;ArBq2KF;A6CtxJA;EACE;EACA;EACA;A7CwxJF;A6C3xJA;;EAMI;A7CyxJJ;A6CpxJA;EACE;A7CsxJF;A6CjxJA;EACI;A7CmxJJ;A6ChxJA;EACI;A7CkxJJ;A6C/wJA;EACI;A7CixJJ;A6C9wJA;EACI;A7CgxJJ;A6C7wJA;EACI;A7C+wJJ;A6C5wJA;EACI;A7C8wJJ;AACA;;;EAGE;A8Cz4KF;EAEE;;;;;IACE;E9C84KF;E8Cl4KA;;;IAGE;IACA;I3C0HM;EH8wKR;E8Cp4KA;;IAEE;E9Cs4KF;E8Cl4KA;IACE;IACA;IACA;IACA;E9Co4KF;E8Cj4KA;IACE;IACA;E9Cm4KF;E8C/3KA;IACE;E9Ci4KF;E8Cl4KA;;IAII;E9Ck4KJ;AACF","sources":["webpack:///./node_modules/admin-lte/build/less/AdminLTE.less","webpack:///./node_modules/admin-lte/build/less/core.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/clearfix.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/vendor-prefixes.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/grid.less","webpack:///./node_modules/admin-lte/build/less/header.less","webpack:///./node_modules/admin-lte/build/less/mixins.less","webpack:///./node_modules/admin-lte/build/less/sidebar.less","webpack:///./node_modules/admin-lte/build/less/sidebar-mini.less","webpack:///./node_modules/admin-lte/build/less/control-sidebar.less","webpack:///./node_modules/admin-lte/build/less/dropdown.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/border-radius.less","webpack:///./node_modules/admin-lte/build/less/forms.less","webpack:///./node_modules/admin-lte/build/less/progress-bars.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/progress-bar.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/gradients.less","webpack:///./node_modules/admin-lte/build/less/small-box.less","webpack:///./node_modules/admin-lte/build/less/boxes.less","webpack:///./node_modules/admin-lte/build/less/info-box.less","webpack:///./node_modules/admin-lte/build/less/timeline.less","webpack:///./node_modules/admin-lte/build/less/buttons.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/opacity.less","webpack:///./node_modules/admin-lte/build/less/callout.less","webpack:///./node_modules/admin-lte/build/less/alerts.less","webpack:///./node_modules/admin-lte/build/less/navs.less","webpack:///./node_modules/admin-lte/build/less/products.less","webpack:///./node_modules/admin-lte/build/less/table.less","webpack:///./node_modules/admin-lte/build/less/labels.less","webpack:///./node_modules/admin-lte/build/less/direct-chat.less","webpack:///./node_modules/admin-lte/build/less/users-list.less","webpack:///./node_modules/admin-lte/build/less/carousel.less","webpack:///./node_modules/admin-lte/build/less/modal.less","webpack:///./node_modules/admin-lte/build/less/social-widgets.less","webpack:///./node_modules/admin-lte/build/less/treeview.less","webpack:///./node_modules/admin-lte/build/less/mailbox.less","webpack:///./node_modules/admin-lte/build/less/lockscreen.less","webpack:///./node_modules/admin-lte/build/less/login_and_register.less","webpack:///./node_modules/admin-lte/build/less/404_500_errors.less","webpack:///./node_modules/admin-lte/build/less/invoice.less","webpack:///./node_modules/admin-lte/build/less/profile.less","webpack:///./node_modules/admin-lte/build/less/bootstrap-social.less","webpack:///./node_modules/admin-lte/build/bootstrap-less/mixins/buttons.less","webpack:///./node_modules/admin-lte/build/less/fullcalendar.less","webpack:///./node_modules/admin-lte/build/less/select2.less","webpack:///./node_modules/admin-lte/build/less/datepicker.less","webpack:///./node_modules/admin-lte/build/less/miscellaneous.less","webpack:///./node_modules/admin-lte/build/less/print.less"],"sourcesContent":["/*!\n * AdminLTE v2.4.18\n * \n * Author: Colorlib\n * Support: \n * Repository: git://github.com/ColorlibHQ/AdminLTE.git\n * License: MIT \n */\n/*\n * Core: General Layout Style\n * -------------------------\n */\nhtml,\nbody {\n height: 100%;\n}\n.layout-boxed html,\n.layout-boxed body {\n height: 100%;\n}\nbody {\n font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-weight: 400;\n overflow-x: hidden;\n overflow-y: auto;\n}\n/* Layout */\n.wrapper {\n height: 100%;\n position: relative;\n overflow-x: hidden;\n overflow-y: auto;\n}\n.wrapper:before,\n.wrapper:after {\n content: \" \";\n display: table;\n}\n.wrapper:after {\n clear: both;\n}\n.layout-boxed .wrapper {\n max-width: 1250px;\n margin: 0 auto;\n min-height: 100%;\n box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);\n position: relative;\n}\n.layout-boxed {\n background-color: #f9fafc;\n}\n/*\n * Content Wrapper - contains the main content\n */\n.content-wrapper,\n.main-footer {\n -webkit-transition: -webkit-transform 0.3s ease-in-out, margin 0.3s ease-in-out;\n -moz-transition: -moz-transform 0.3s ease-in-out, margin 0.3s ease-in-out;\n -o-transition: -o-transform 0.3s ease-in-out, margin 0.3s ease-in-out;\n transition: transform 0.3s ease-in-out, margin 0.3s ease-in-out;\n margin-left: 230px;\n z-index: 820;\n}\n.layout-top-nav .content-wrapper,\n.layout-top-nav .main-footer {\n margin-left: 0;\n}\n@media (max-width: 767px) {\n .content-wrapper,\n .main-footer {\n margin-left: 0;\n }\n}\n@media (min-width: 768px) {\n .sidebar-collapse .content-wrapper,\n .sidebar-collapse .main-footer {\n margin-left: 0;\n }\n}\n@media (max-width: 767px) {\n .sidebar-open .content-wrapper,\n .sidebar-open .main-footer {\n -webkit-transform: translate(230px, 0);\n -ms-transform: translate(230px, 0);\n -o-transform: translate(230px, 0);\n transform: translate(230px, 0);\n }\n}\n.content-wrapper {\n min-height: calc(100vh - 101px);\n background-color: #ecf0f5;\n z-index: 800;\n}\n@media (max-width: 767px) {\n .content-wrapper {\n min-height: calc(100vh - 151px);\n }\n}\n.main-footer {\n background: #fff;\n padding: 15px;\n color: #444;\n border-top: 1px solid #d2d6de;\n}\n/* Fixed layout */\n.fixed .main-header,\n.fixed .main-sidebar,\n.fixed .left-side {\n position: fixed;\n}\n.fixed .main-header {\n top: 0;\n right: 0;\n left: 0;\n}\n.fixed .content-wrapper,\n.fixed .right-side {\n padding-top: 50px;\n}\n@media (max-width: 767px) {\n .fixed .content-wrapper,\n .fixed .right-side {\n padding-top: 100px;\n }\n}\n.fixed.layout-boxed .wrapper {\n max-width: 100%;\n}\n.fixed .wrapper {\n overflow: hidden;\n}\n.hold-transition .content-wrapper,\n.hold-transition .right-side,\n.hold-transition .main-footer,\n.hold-transition .main-sidebar,\n.hold-transition .left-side,\n.hold-transition .main-header .navbar,\n.hold-transition .main-header .logo,\n.hold-transition .menu-open .fa-angle-left {\n /* Fix for IE */\n -webkit-transition: none;\n -o-transition: none;\n transition: none;\n}\n/* Content */\n.content {\n min-height: 250px;\n padding: 15px;\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n/* H1 - H6 font */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: 'Source Sans Pro', sans-serif;\n}\n/* General Links */\na {\n color: #3c8dbc;\n}\na:hover,\na:active,\na:focus {\n outline: none;\n text-decoration: none;\n color: #72afd2;\n}\n/* Page Header */\n.page-header {\n margin: 10px 0 20px 0;\n font-size: 22px;\n}\n.page-header > small {\n color: #666;\n display: block;\n margin-top: 5px;\n}\n/*\n * Component: Main Header\n * ----------------------\n */\n.main-header {\n position: relative;\n max-height: 100px;\n z-index: 1030;\n}\n.main-header .navbar {\n -webkit-transition: margin-left 0.3s ease-in-out;\n -o-transition: margin-left 0.3s ease-in-out;\n transition: margin-left 0.3s ease-in-out;\n margin-bottom: 0;\n margin-left: 230px;\n border: none;\n min-height: 50px;\n border-radius: 0;\n}\n.layout-top-nav .main-header .navbar {\n margin-left: 0;\n}\n.main-header #navbar-search-input.form-control {\n background: rgba(255, 255, 255, 0.2);\n border-color: transparent;\n}\n.main-header #navbar-search-input.form-control:focus,\n.main-header #navbar-search-input.form-control:active {\n border-color: rgba(0, 0, 0, 0.1);\n background: rgba(255, 255, 255, 0.9);\n}\n.main-header #navbar-search-input.form-control::-moz-placeholder {\n color: #ccc;\n opacity: 1;\n}\n.main-header #navbar-search-input.form-control:-ms-input-placeholder {\n color: #ccc;\n}\n.main-header #navbar-search-input.form-control::-webkit-input-placeholder {\n color: #ccc;\n}\n.main-header .navbar-custom-menu,\n.main-header .navbar-right {\n float: right;\n}\n@media (max-width: 991px) {\n .main-header .navbar-custom-menu a,\n .main-header .navbar-right a {\n color: inherit;\n background: transparent;\n }\n}\n@media (max-width: 767px) {\n .main-header .navbar-right {\n float: none;\n }\n .navbar-collapse .main-header .navbar-right {\n margin: 7.5px -15px;\n }\n .main-header .navbar-right > li {\n color: inherit;\n border: 0;\n }\n}\n.main-header .sidebar-toggle {\n float: left;\n background-color: transparent;\n background-image: none;\n padding: 15px 15px;\n font-family: fontAwesome;\n}\n.main-header .sidebar-toggle:before {\n content: \"\\f0c9\";\n}\n.main-header .sidebar-toggle:hover {\n color: #fff;\n}\n.main-header .sidebar-toggle:focus,\n.main-header .sidebar-toggle:active {\n background: transparent;\n}\n.main-header .sidebar-toggle.fa5 {\n font-family: \"Font Awesome\\ 5 Free\";\n}\n.main-header .sidebar-toggle.fa5:before {\n content: \"\\f0c9\";\n font-weight: 900;\n}\n.main-header .sidebar-toggle .icon-bar {\n display: none;\n}\n.main-header .navbar .nav > li.user > a > .fa,\n.main-header .navbar .nav > li.user > a > .glyphicon,\n.main-header .navbar .nav > li.user > a > .ion {\n margin-right: 5px;\n}\n.main-header .navbar .nav > li > a > .label {\n position: absolute;\n top: 9px;\n right: 7px;\n text-align: center;\n font-size: 9px;\n padding: 2px 3px;\n line-height: 0.9;\n}\n.main-header .logo {\n -webkit-transition: width 0.3s ease-in-out;\n -o-transition: width 0.3s ease-in-out;\n transition: width 0.3s ease-in-out;\n display: block;\n float: left;\n height: 50px;\n font-size: 20px;\n line-height: 50px;\n text-align: center;\n width: 230px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n padding: 0 15px;\n font-weight: 300;\n overflow: hidden;\n}\n.main-header .logo img {\n padding: 4px;\n object-fit: contain;\n margin: 0 auto;\n}\n.main-header .logo .logo-lg {\n display: block;\n}\n.main-header .logo .logo-lg img {\n max-width: 200px;\n max-height: 50px;\n}\n.main-header .logo .logo-lg .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: -5px;\n}\n.main-header .logo .logo-mini {\n display: none;\n}\n.main-header .logo .logo-mini img {\n max-width: 50px;\n max-height: 50px;\n}\n.main-header .logo .logo-mini .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: 10px;\n}\n.main-header .logo .brandlogo-image {\n float: left;\n height: 34px;\n width: auto;\n}\n.main-header .navbar-brand {\n color: #fff;\n}\n.content-header {\n position: relative;\n padding: 15px 15px 0 15px;\n}\n.content-header > h1 {\n margin: 0;\n font-size: 24px;\n}\n.content-header > h1 > small {\n font-size: 15px;\n display: inline-block;\n padding-left: 4px;\n font-weight: 300;\n}\n.content-header > .breadcrumb {\n float: right;\n background: transparent;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 12px;\n padding: 7px 5px;\n position: absolute;\n top: 15px;\n right: 10px;\n border-radius: 2px;\n}\n.content-header > .breadcrumb > li > a {\n color: #444;\n text-decoration: none;\n display: inline-block;\n}\n.content-header > .breadcrumb > li > a > .fa,\n.content-header > .breadcrumb > li > a > .glyphicon,\n.content-header > .breadcrumb > li > a > .ion {\n margin-right: 5px;\n}\n.content-header > .breadcrumb > li + li:before {\n content: '>\\00a0';\n}\n@media (max-width: 991px) {\n .content-header > .breadcrumb {\n position: relative;\n margin-top: 5px;\n top: 0;\n right: 0;\n float: none;\n background: #d2d6de;\n padding-left: 10px;\n }\n .content-header > .breadcrumb li:before {\n color: #97a0b3;\n }\n}\n.navbar-toggle {\n color: #fff;\n border: 0;\n margin: 0;\n padding: 15px 15px;\n}\n@media (max-width: 991px) {\n .navbar-custom-menu .navbar-nav > li {\n float: left;\n }\n .navbar-custom-menu .navbar-nav {\n margin: 0;\n float: left;\n }\n .navbar-custom-menu .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n line-height: 20px;\n }\n}\n@media (max-width: 767px) {\n .main-header {\n position: relative;\n }\n .main-header .logo,\n .main-header .navbar {\n width: 100%;\n float: none;\n }\n .main-header .navbar {\n margin: 0;\n }\n .main-header .navbar-custom-menu {\n float: right;\n }\n}\n@media (max-width: 991px) {\n .navbar-collapse.pull-left {\n float: none !important;\n }\n .navbar-collapse.pull-left + .navbar-custom-menu {\n display: block;\n position: absolute;\n top: 0;\n right: 40px;\n }\n}\n/*\n * Component: Sidebar\n * ------------------\n */\n.main-sidebar {\n position: absolute;\n top: 0;\n left: 0;\n padding-top: 50px;\n min-height: 100%;\n width: 230px;\n z-index: 810;\n -webkit-transition: -webkit-transform 0.3s ease-in-out, width 0.3s ease-in-out;\n -moz-transition: -moz-transform 0.3s ease-in-out, width 0.3s ease-in-out;\n -o-transition: -o-transform 0.3s ease-in-out, width 0.3s ease-in-out;\n transition: transform 0.3s ease-in-out, width 0.3s ease-in-out;\n}\n@media (max-width: 767px) {\n .main-sidebar {\n padding-top: 100px;\n }\n}\n@media (max-width: 767px) {\n .main-sidebar {\n -webkit-transform: translate(-230px, 0);\n -ms-transform: translate(-230px, 0);\n -o-transform: translate(-230px, 0);\n transform: translate(-230px, 0);\n }\n}\n@media (min-width: 768px) {\n .sidebar-collapse .main-sidebar {\n -webkit-transform: translate(-230px, 0);\n -ms-transform: translate(-230px, 0);\n -o-transform: translate(-230px, 0);\n transform: translate(-230px, 0);\n }\n}\n@media (max-width: 767px) {\n .sidebar-open .main-sidebar {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n }\n}\n.sidebar {\n padding-bottom: 10px;\n}\n.sidebar-form input:focus {\n border-color: transparent;\n}\n.user-panel {\n position: relative;\n width: 100%;\n padding: 10px;\n overflow: hidden;\n}\n.user-panel:before,\n.user-panel:after {\n content: \" \";\n display: table;\n}\n.user-panel:after {\n clear: both;\n}\n.user-panel > .image > img {\n width: 100%;\n max-width: 45px;\n height: auto;\n}\n.user-panel > .info {\n padding: 5px 5px 5px 15px;\n line-height: 1;\n position: absolute;\n left: 55px;\n}\n.user-panel > .info > p {\n font-weight: 600;\n margin-bottom: 9px;\n}\n.user-panel > .info > a {\n text-decoration: none;\n padding-right: 5px;\n margin-top: 3px;\n font-size: 11px;\n}\n.user-panel > .info > a > .fa,\n.user-panel > .info > a > .ion,\n.user-panel > .info > a > .glyphicon {\n margin-right: 3px;\n}\n.sidebar-menu {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.sidebar-menu > li {\n position: relative;\n margin: 0;\n padding: 0;\n}\n.sidebar-menu > li > a {\n padding: 12px 5px 12px 15px;\n display: block;\n}\n.sidebar-menu > li > a > .fa,\n.sidebar-menu > li > a > .glyphicon,\n.sidebar-menu > li > a > .ion {\n width: 20px;\n}\n.sidebar-menu > li .label,\n.sidebar-menu > li .badge {\n margin-right: 5px;\n}\n.sidebar-menu > li .badge {\n margin-top: 3px;\n}\n.sidebar-menu li.header {\n padding: 10px 25px 10px 15px;\n font-size: 12px;\n}\n.sidebar-menu li > a > .fa-angle-left,\n.sidebar-menu li > a > .pull-right-container > .fa-angle-left {\n width: auto;\n height: auto;\n padding: 0;\n margin-right: 10px;\n -webkit-transition: transform 0.5s ease;\n -o-transition: transform 0.5s ease;\n transition: transform 0.5s ease;\n}\n.sidebar-menu li > a > .fa-angle-left {\n position: absolute;\n top: 50%;\n right: 10px;\n margin-top: -8px;\n}\n.sidebar-menu .menu-open > a > .fa-angle-left,\n.sidebar-menu .menu-open > a > .pull-right-container > .fa-angle-left {\n -webkit-transform: rotate(-90deg);\n -ms-transform: rotate(-90deg);\n -o-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n.sidebar-menu .active > .treeview-menu {\n display: block;\n}\n/*\n * Component: Sidebar Mini\n */\n@media (min-width: 768px) {\n .sidebar-mini.sidebar-collapse .content-wrapper,\n .sidebar-mini.sidebar-collapse .right-side,\n .sidebar-mini.sidebar-collapse .main-footer {\n margin-left: 50px !important;\n z-index: 840;\n }\n .sidebar-mini.sidebar-collapse .main-sidebar {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n width: 50px !important;\n z-index: 850;\n }\n .sidebar-mini.sidebar-collapse .sidebar-menu > li {\n position: relative;\n }\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > a {\n margin-right: 0;\n }\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span {\n border-top-right-radius: 4px;\n }\n .sidebar-mini.sidebar-collapse .sidebar-menu > li:not(.treeview) > a > span {\n border-bottom-right-radius: 4px;\n }\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu {\n padding-top: 5px;\n padding-bottom: 5px;\n border-bottom-right-radius: 4px;\n }\n .sidebar-mini.sidebar-collapse .main-sidebar .user-panel > .info,\n .sidebar-mini.sidebar-collapse .sidebar-form,\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span,\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu,\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > .pull-right,\n .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span > .pull-right,\n .sidebar-mini.sidebar-collapse .sidebar-menu li.header {\n display: none !important;\n -webkit-transform: translateZ(0);\n }\n .sidebar-mini.sidebar-collapse .main-header .logo {\n width: 50px;\n }\n .sidebar-mini.sidebar-collapse .main-header .logo > .logo-mini {\n display: block;\n margin-left: -15px;\n margin-right: -15px;\n font-size: 18px;\n }\n .sidebar-mini.sidebar-collapse .main-header .logo > .logo-lg {\n display: none;\n }\n .sidebar-mini.sidebar-collapse .main-header .navbar {\n margin-left: 50px;\n }\n}\n@media (min-width: 768px) {\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right),\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {\n display: block !important;\n position: absolute;\n width: 180px;\n left: 50px;\n }\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > a > span {\n top: 0;\n margin-left: -3px;\n padding: 12px 5px 12px 20px;\n background-color: inherit;\n }\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > a > .pull-right-container {\n position: relative !important;\n float: right;\n width: auto !important;\n left: 180px !important;\n top: -22px !important;\n z-index: 900;\n }\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > a > .pull-right-container > .label:not(:first-of-type) {\n display: none;\n }\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {\n top: 44px;\n margin-left: 0;\n }\n}\n.sidebar-expanded-on-hover .main-footer,\n.sidebar-expanded-on-hover .content-wrapper {\n margin-left: 50px;\n}\n.sidebar-expanded-on-hover .main-sidebar {\n box-shadow: 3px 0 8px rgba(0, 0, 0, 0.125);\n}\n.sidebar-menu,\n.main-sidebar .user-panel,\n.sidebar-menu > li.header {\n white-space: nowrap;\n overflow: hidden;\n}\n.sidebar-menu:hover {\n overflow: visible;\n}\n.sidebar-form,\n.sidebar-menu > li.header {\n overflow: hidden;\n text-overflow: clip;\n}\n.sidebar-menu li > a {\n position: relative;\n}\n.sidebar-menu li > a > .pull-right-container {\n position: absolute;\n right: 10px;\n top: 50%;\n margin-top: -7px;\n}\n/*\n * Component: Control sidebar. By default, this is the right sidebar.\n */\n.control-sidebar-bg {\n position: fixed;\n z-index: 1000;\n bottom: 0;\n}\n.control-sidebar-bg,\n.control-sidebar {\n top: 0;\n right: -230px;\n width: 230px;\n -webkit-transition: right 0.3s ease-in-out;\n -o-transition: right 0.3s ease-in-out;\n transition: right 0.3s ease-in-out;\n}\n.control-sidebar {\n position: absolute;\n padding-top: 50px;\n z-index: 1010;\n}\n@media (max-width: 767px) {\n .control-sidebar {\n padding-top: 100px;\n }\n}\n.control-sidebar > .tab-content {\n padding: 10px 15px;\n}\n.control-sidebar.control-sidebar-open,\n.control-sidebar.control-sidebar-open + .control-sidebar-bg {\n right: 0;\n}\n.control-sidebar-hold-transition .control-sidebar-bg,\n.control-sidebar-hold-transition .control-sidebar,\n.control-sidebar-hold-transition .content-wrapper {\n transition: none;\n}\n.control-sidebar-open .control-sidebar-bg,\n.control-sidebar-open .control-sidebar {\n right: 0;\n}\n@media (min-width: 768px) {\n .control-sidebar-open .content-wrapper,\n .control-sidebar-open .right-side,\n .control-sidebar-open .main-footer {\n margin-right: 230px;\n }\n}\n.fixed .control-sidebar {\n position: fixed;\n height: 100%;\n overflow-y: auto;\n padding-bottom: 50px;\n}\n.nav-tabs.control-sidebar-tabs > li:first-of-type > a,\n.nav-tabs.control-sidebar-tabs > li:first-of-type > a:hover,\n.nav-tabs.control-sidebar-tabs > li:first-of-type > a:focus {\n border-left-width: 0;\n}\n.nav-tabs.control-sidebar-tabs > li > a {\n border-radius: 0;\n}\n.nav-tabs.control-sidebar-tabs > li > a,\n.nav-tabs.control-sidebar-tabs > li > a:hover {\n border-top: none;\n border-right: none;\n border-left: 1px solid transparent;\n border-bottom: 1px solid transparent;\n}\n.nav-tabs.control-sidebar-tabs > li > a .icon {\n font-size: 16px;\n}\n.nav-tabs.control-sidebar-tabs > li.active > a,\n.nav-tabs.control-sidebar-tabs > li.active > a:hover,\n.nav-tabs.control-sidebar-tabs > li.active > a:focus,\n.nav-tabs.control-sidebar-tabs > li.active > a:active {\n border-top: none;\n border-right: none;\n border-bottom: none;\n}\n@media (max-width: 768px) {\n .nav-tabs.control-sidebar-tabs {\n display: table;\n }\n .nav-tabs.control-sidebar-tabs > li {\n display: table-cell;\n }\n}\n.control-sidebar-heading {\n font-weight: 400;\n font-size: 16px;\n padding: 10px 0;\n margin-bottom: 10px;\n}\n.control-sidebar-subheading {\n display: block;\n font-weight: 400;\n font-size: 14px;\n}\n.control-sidebar-menu {\n list-style: none;\n padding: 0;\n margin: 0 -15px;\n}\n.control-sidebar-menu > li > a {\n display: block;\n padding: 10px 15px;\n}\n.control-sidebar-menu > li > a:before,\n.control-sidebar-menu > li > a:after {\n content: \" \";\n display: table;\n}\n.control-sidebar-menu > li > a:after {\n clear: both;\n}\n.control-sidebar-menu > li > a > .control-sidebar-subheading {\n margin-top: 0;\n}\n.control-sidebar-menu .menu-icon {\n float: left;\n width: 35px;\n height: 35px;\n border-radius: 50%;\n text-align: center;\n line-height: 35px;\n}\n.control-sidebar-menu .menu-info {\n margin-left: 45px;\n margin-top: 3px;\n}\n.control-sidebar-menu .menu-info > .control-sidebar-subheading {\n margin: 0;\n}\n.control-sidebar-menu .menu-info > p {\n margin: 0;\n font-size: 11px;\n}\n.control-sidebar-menu .progress {\n margin: 0;\n}\n.control-sidebar-dark {\n color: #b8c7ce;\n}\n.control-sidebar-dark,\n.control-sidebar-dark + .control-sidebar-bg {\n background: #222d32;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs {\n border-bottom: #1c2529;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a {\n background: #181f23;\n color: #b8c7ce;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:hover,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:focus {\n border-left-color: #141a1d;\n border-bottom-color: #141a1d;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:hover,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:focus,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:active {\n background: #1c2529;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li > a:hover {\n color: #fff;\n}\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li.active > a,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li.active > a:hover,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li.active > a:focus,\n.control-sidebar-dark .nav-tabs.control-sidebar-tabs > li.active > a:active {\n background: #222d32;\n color: #fff;\n}\n.control-sidebar-dark .control-sidebar-heading,\n.control-sidebar-dark .control-sidebar-subheading {\n color: #fff;\n}\n.control-sidebar-dark .control-sidebar-menu > li > a:hover {\n background: #1e282c;\n}\n.control-sidebar-dark .control-sidebar-menu > li > a .menu-info > p {\n color: #b8c7ce;\n}\n.control-sidebar-light {\n color: #5e5e5e;\n}\n.control-sidebar-light,\n.control-sidebar-light + .control-sidebar-bg {\n background: #f9fafc;\n border-left: 1px solid #d2d6de;\n}\n.control-sidebar-light .nav-tabs.control-sidebar-tabs {\n border-bottom: #d2d6de;\n}\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a {\n background: #e8ecf4;\n color: #444;\n}\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a:hover,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a:focus {\n border-left-color: #d2d6de;\n border-bottom-color: #d2d6de;\n}\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a:hover,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a:focus,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li > a:active {\n background: #eff1f7;\n}\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li.active > a,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li.active > a:hover,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li.active > a:focus,\n.control-sidebar-light .nav-tabs.control-sidebar-tabs > li.active > a:active {\n background: #f9fafc;\n color: #111;\n}\n.control-sidebar-light .control-sidebar-heading,\n.control-sidebar-light .control-sidebar-subheading {\n color: #111;\n}\n.control-sidebar-light .control-sidebar-menu {\n margin-left: -14px;\n}\n.control-sidebar-light .control-sidebar-menu > li > a:hover {\n background: #f4f4f5;\n}\n.control-sidebar-light .control-sidebar-menu > li > a .menu-info > p {\n color: #5e5e5e;\n}\n/*\n * Component: Dropdown menus\n * -------------------------\n */\n/*Dropdowns in general*/\n.dropdown-menu {\n box-shadow: none;\n border-color: #eee;\n}\n.dropdown-menu > li > a {\n color: #777;\n}\n.dropdown-menu > li > a > .glyphicon,\n.dropdown-menu > li > a > .fa,\n.dropdown-menu > li > a > .ion {\n margin-right: 10px;\n}\n.dropdown-menu > li > a:hover {\n background-color: #e1e3e9;\n color: #333;\n}\n.dropdown-menu > .divider {\n background-color: #eee;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu,\n.navbar-nav > .messages-menu > .dropdown-menu,\n.navbar-nav > .tasks-menu > .dropdown-menu {\n width: 280px;\n padding: 0 0 0 0;\n margin: 0;\n top: 100%;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li,\n.navbar-nav > .messages-menu > .dropdown-menu > li,\n.navbar-nav > .tasks-menu > .dropdown-menu > li {\n position: relative;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li.header,\n.navbar-nav > .messages-menu > .dropdown-menu > li.header,\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n background-color: #ffffff;\n padding: 7px 10px;\n border-bottom: 1px solid #f4f4f4;\n color: #444444;\n font-size: 14px;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li.footer > a,\n.navbar-nav > .messages-menu > .dropdown-menu > li.footer > a,\n.navbar-nav > .tasks-menu > .dropdown-menu > li.footer > a {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n font-size: 12px;\n background-color: #fff;\n padding: 7px 10px;\n border-bottom: 1px solid #eeeeee;\n color: #444 !important;\n text-align: center;\n}\n@media (max-width: 991px) {\n .navbar-nav > .notifications-menu > .dropdown-menu > li.footer > a,\n .navbar-nav > .messages-menu > .dropdown-menu > li.footer > a,\n .navbar-nav > .tasks-menu > .dropdown-menu > li.footer > a {\n background: #fff !important;\n color: #444 !important;\n }\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li.footer > a:hover,\n.navbar-nav > .messages-menu > .dropdown-menu > li.footer > a:hover,\n.navbar-nav > .tasks-menu > .dropdown-menu > li.footer > a:hover {\n text-decoration: none;\n font-weight: normal;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu,\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu,\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu {\n max-height: 200px;\n margin: 0;\n padding: 0;\n list-style: none;\n overflow-x: hidden;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a,\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a,\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu > li > a {\n display: block;\n white-space: nowrap;\n /* Prevent text from breaking */\n border-bottom: 1px solid #f4f4f4;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a:hover,\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a:hover,\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu > li > a:hover {\n background: #f4f4f4;\n text-decoration: none;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a {\n color: #444444;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 10px;\n}\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a > .glyphicon,\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a > .fa,\n.navbar-nav > .notifications-menu > .dropdown-menu > li .menu > li > a > .ion {\n width: 20px;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a {\n margin: 0;\n padding: 10px 10px;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a > div > img {\n margin: auto 10px auto auto;\n width: 40px;\n height: 40px;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a > h4 {\n padding: 0;\n margin: 0 0 0 45px;\n color: #444444;\n font-size: 15px;\n position: relative;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a > h4 > small {\n color: #999999;\n font-size: 10px;\n position: absolute;\n top: 0;\n right: 0;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a > p {\n margin: 0 0 0 45px;\n font-size: 12px;\n color: #888888;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a:before,\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a:after {\n content: \" \";\n display: table;\n}\n.navbar-nav > .messages-menu > .dropdown-menu > li .menu > li > a:after {\n clear: both;\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu > li > a {\n padding: 10px;\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu > li > a > h3 {\n font-size: 14px;\n padding: 0;\n margin: 0 0 10px 0;\n color: #666666;\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li .menu > li > a > .progress {\n padding: 0;\n margin: 0;\n}\n.navbar-nav > .user-menu > .dropdown-menu {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n padding: 1px 0 0 0;\n border-top-width: 0;\n width: 280px;\n}\n.navbar-nav > .user-menu > .dropdown-menu,\n.navbar-nav > .user-menu > .dropdown-menu > .user-body {\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.navbar-nav > .user-menu > .dropdown-menu > li.user-header {\n height: 175px;\n padding: 10px;\n text-align: center;\n}\n.navbar-nav > .user-menu > .dropdown-menu > li.user-header > img {\n z-index: 5;\n height: 90px;\n width: 90px;\n border: 3px solid;\n border-color: transparent;\n border-color: rgba(255, 255, 255, 0.2);\n}\n.navbar-nav > .user-menu > .dropdown-menu > li.user-header > p {\n z-index: 5;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n font-size: 17px;\n margin-top: 10px;\n}\n.navbar-nav > .user-menu > .dropdown-menu > li.user-header > p > small {\n display: block;\n font-size: 12px;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-body {\n padding: 15px;\n border-bottom: 1px solid #f4f4f4;\n border-top: 1px solid #dddddd;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-body:before,\n.navbar-nav > .user-menu > .dropdown-menu > .user-body:after {\n content: \" \";\n display: table;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-body:after {\n clear: both;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-body a {\n color: #444 !important;\n}\n@media (max-width: 991px) {\n .navbar-nav > .user-menu > .dropdown-menu > .user-body a {\n background: #fff !important;\n color: #444 !important;\n }\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-footer {\n background-color: #f9f9f9;\n padding: 10px;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-footer:before,\n.navbar-nav > .user-menu > .dropdown-menu > .user-footer:after {\n content: \" \";\n display: table;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-footer:after {\n clear: both;\n}\n.navbar-nav > .user-menu > .dropdown-menu > .user-footer .btn-default {\n color: #666666;\n}\n@media (max-width: 991px) {\n .navbar-nav > .user-menu > .dropdown-menu > .user-footer .btn-default:hover {\n background-color: #f9f9f9;\n }\n}\n.navbar-nav > .user-menu .user-image {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n margin-top: -2px;\n}\n@media (max-width: 767px) {\n .navbar-nav > .user-menu .user-image {\n float: none;\n margin-right: 0;\n margin-top: -8px;\n line-height: 10px;\n }\n}\n/* Add fade animation to dropdown menus by appending\n the class .animated-dropdown-menu to the .dropdown-menu ul (or ol)*/\n.open:not(.dropup) > .animated-dropdown-menu {\n backface-visibility: visible !important;\n -webkit-animation: flipInX 0.7s both;\n -o-animation: flipInX 0.7s both;\n animation: flipInX 0.7s both;\n}\n@keyframes flipInX {\n 0% {\n transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n transition-timing-function: ease-in;\n opacity: 0;\n }\n 40% {\n transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n transition-timing-function: ease-in;\n }\n 60% {\n transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n 80% {\n transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n 100% {\n transform: perspective(400px);\n }\n}\n@-webkit-keyframes flipInX {\n 0% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n -webkit-transition-timing-function: ease-in;\n opacity: 0;\n }\n 40% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n -webkit-transition-timing-function: ease-in;\n }\n 60% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n 80% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n 100% {\n -webkit-transform: perspective(400px);\n }\n}\n/* Fix dropdown menu in navbars */\n.navbar-custom-menu > .navbar-nav > li {\n position: relative;\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n position: absolute;\n right: 0;\n left: auto;\n}\n@media (max-width: 991px) {\n .navbar-custom-menu > .navbar-nav {\n float: right;\n }\n .navbar-custom-menu > .navbar-nav > li {\n position: static;\n }\n .navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n position: absolute;\n right: 5%;\n left: auto;\n border: 1px solid #ddd;\n background: #fff;\n }\n}\n/*\n * Component: Form\n * ---------------\n */\n.form-control {\n border-radius: 0;\n box-shadow: none;\n border-color: #d2d6de;\n}\n.form-control:focus {\n border-color: #3c8dbc;\n box-shadow: none;\n}\n.form-control::-moz-placeholder,\n.form-control:-ms-input-placeholder,\n.form-control::-webkit-input-placeholder {\n color: #bbb;\n opacity: 1;\n}\n.form-control:not(select) {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.form-group.has-success label {\n color: #00a65a;\n}\n.form-group.has-success .form-control,\n.form-group.has-success .input-group-addon {\n border-color: #00a65a;\n box-shadow: none;\n}\n.form-group.has-success .help-block {\n color: #00a65a;\n}\n.form-group.has-warning label {\n color: #f39c12;\n}\n.form-group.has-warning .form-control,\n.form-group.has-warning .input-group-addon {\n border-color: #f39c12;\n box-shadow: none;\n}\n.form-group.has-warning .help-block {\n color: #f39c12;\n}\n.form-group.has-error label {\n color: #dd4b39;\n}\n.form-group.has-error .form-control,\n.form-group.has-error .input-group-addon {\n border-color: #dd4b39;\n box-shadow: none;\n}\n.form-group.has-error .help-block {\n color: #dd4b39;\n}\n/* Input group */\n.input-group .input-group-addon {\n border-radius: 0;\n border-color: #d2d6de;\n background-color: #fff;\n}\n/* button groups */\n.btn-group-vertical .btn.btn-flat:first-of-type,\n.btn-group-vertical .btn.btn-flat:last-of-type {\n border-radius: 0;\n}\n.icheck > label {\n padding-left: 0;\n}\n/* support Font Awesome icons in form-control */\n.form-control-feedback.fa {\n line-height: 34px;\n}\n.input-lg + .form-control-feedback.fa,\n.input-group-lg + .form-control-feedback.fa,\n.form-group-lg .form-control + .form-control-feedback.fa {\n line-height: 46px;\n}\n.input-sm + .form-control-feedback.fa,\n.input-group-sm + .form-control-feedback.fa,\n.form-group-sm .form-control + .form-control-feedback.fa {\n line-height: 30px;\n}\n/*\n * Component: Progress Bar\n * -----------------------\n */\n.progress,\n.progress > .progress-bar {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.progress,\n.progress > .progress-bar,\n.progress .progress-bar,\n.progress > .progress-bar .progress-bar {\n border-radius: 1px;\n}\n/* size variation */\n.progress.sm,\n.progress-sm {\n height: 10px;\n}\n.progress.sm,\n.progress-sm,\n.progress.sm .progress-bar,\n.progress-sm .progress-bar {\n border-radius: 1px;\n}\n.progress.xs,\n.progress-xs {\n height: 7px;\n}\n.progress.xs,\n.progress-xs,\n.progress.xs .progress-bar,\n.progress-xs .progress-bar {\n border-radius: 1px;\n}\n.progress.xxs,\n.progress-xxs {\n height: 3px;\n}\n.progress.xxs,\n.progress-xxs,\n.progress.xxs .progress-bar,\n.progress-xxs .progress-bar {\n border-radius: 1px;\n}\n/* Vertical bars */\n.progress.vertical {\n position: relative;\n width: 30px;\n height: 200px;\n display: inline-block;\n margin-right: 10px;\n}\n.progress.vertical > .progress-bar {\n width: 100%;\n position: absolute;\n bottom: 0;\n}\n.progress.vertical.sm,\n.progress.vertical.progress-sm {\n width: 20px;\n}\n.progress.vertical.xs,\n.progress.vertical.progress-xs {\n width: 10px;\n}\n.progress.vertical.xxs,\n.progress.vertical.progress-xxs {\n width: 3px;\n}\n.progress-group .progress-text {\n font-weight: 600;\n}\n.progress-group .progress-number {\n float: right;\n}\n/* Remove margins from progress bars when put in a table */\n.table tr > td .progress {\n margin: 0;\n}\n.progress-bar-light-blue,\n.progress-bar-primary {\n background-color: #3c8dbc;\n}\n.progress-striped .progress-bar-light-blue,\n.progress-striped .progress-bar-primary {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-green,\n.progress-bar-success {\n background-color: #00a65a;\n}\n.progress-striped .progress-bar-green,\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-aqua,\n.progress-bar-info {\n background-color: #00c0ef;\n}\n.progress-striped .progress-bar-aqua,\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-yellow,\n.progress-bar-warning {\n background-color: #f39c12;\n}\n.progress-striped .progress-bar-yellow,\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-red,\n.progress-bar-danger {\n background-color: #dd4b39;\n}\n.progress-striped .progress-bar-red,\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n/*\n * Component: Small Box\n * --------------------\n */\n.small-box {\n border-radius: 2px;\n position: relative;\n display: block;\n margin-bottom: 20px;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n}\n.small-box > .inner {\n padding: 10px;\n}\n.small-box > .small-box-footer {\n position: relative;\n text-align: center;\n padding: 3px 0;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n display: block;\n z-index: 10;\n background: rgba(0, 0, 0, 0.1);\n text-decoration: none;\n}\n.small-box > .small-box-footer:hover {\n color: #fff;\n background: rgba(0, 0, 0, 0.15);\n}\n.small-box h3 {\n font-size: 38px;\n font-weight: bold;\n margin: 0 0 10px 0;\n white-space: nowrap;\n padding: 0;\n}\n.small-box p {\n font-size: 15px;\n}\n.small-box p > small {\n display: block;\n color: #f9f9f9;\n font-size: 13px;\n margin-top: 5px;\n}\n.small-box h3,\n.small-box p {\n z-index: 5;\n}\n.small-box .icon {\n -webkit-transition: all 0.3s linear;\n -o-transition: all 0.3s linear;\n transition: all 0.3s linear;\n position: absolute;\n top: -10px;\n right: 10px;\n z-index: 0;\n font-size: 90px;\n color: rgba(0, 0, 0, 0.15);\n}\n.small-box:hover {\n text-decoration: none;\n color: #f9f9f9;\n}\n.small-box:hover .icon {\n font-size: 95px;\n}\n@media (max-width: 767px) {\n .small-box {\n text-align: center;\n }\n .small-box .icon {\n display: none;\n }\n .small-box p {\n font-size: 12px;\n }\n}\n/*\n * Component: Box\n * --------------\n */\n.box {\n position: relative;\n border-radius: 3px;\n background: #ffffff;\n border-top: 3px solid #d2d6de;\n margin-bottom: 20px;\n width: 100%;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n}\n.box.box-primary {\n border-top-color: #3c8dbc;\n}\n.box.box-info {\n border-top-color: #00c0ef;\n}\n.box.box-danger {\n border-top-color: #dd4b39;\n}\n.box.box-warning {\n border-top-color: #f39c12;\n}\n.box.box-success {\n border-top-color: #00a65a;\n}\n.box.box-default {\n border-top-color: #d2d6de;\n}\n.box.collapsed-box .box-body,\n.box.collapsed-box .box-footer {\n display: none;\n}\n.box .nav-stacked > li {\n border-bottom: 1px solid #f4f4f4;\n margin: 0;\n}\n.box .nav-stacked > li:last-of-type {\n border-bottom: none;\n}\n.box.height-control .box-body {\n max-height: 300px;\n overflow: auto;\n}\n.box .border-right {\n border-right: 1px solid #f4f4f4;\n}\n.box .border-left {\n border-left: 1px solid #f4f4f4;\n}\n.box.box-solid {\n border-top: 0;\n}\n.box.box-solid > .box-header .btn.btn-default {\n background: transparent;\n}\n.box.box-solid > .box-header .btn:hover,\n.box.box-solid > .box-header a:hover {\n background: rgba(0, 0, 0, 0.1);\n}\n.box.box-solid.box-default {\n border: 1px solid #d2d6de;\n}\n.box.box-solid.box-default > .box-header {\n color: #444;\n background: #d2d6de;\n background-color: #d2d6de;\n}\n.box.box-solid.box-default > .box-header a,\n.box.box-solid.box-default > .box-header .btn {\n color: #444;\n}\n.box.box-solid.box-primary {\n border: 1px solid #3c8dbc;\n}\n.box.box-solid.box-primary > .box-header {\n color: #fff;\n background: #3c8dbc;\n background-color: #3c8dbc;\n}\n.box.box-solid.box-primary > .box-header a,\n.box.box-solid.box-primary > .box-header .btn {\n color: #fff;\n}\n.box.box-solid.box-info {\n border: 1px solid #00c0ef;\n}\n.box.box-solid.box-info > .box-header {\n color: #fff;\n background: #00c0ef;\n background-color: #00c0ef;\n}\n.box.box-solid.box-info > .box-header a,\n.box.box-solid.box-info > .box-header .btn {\n color: #fff;\n}\n.box.box-solid.box-danger {\n border: 1px solid #dd4b39;\n}\n.box.box-solid.box-danger > .box-header {\n color: #fff;\n background: #dd4b39;\n background-color: #dd4b39;\n}\n.box.box-solid.box-danger > .box-header a,\n.box.box-solid.box-danger > .box-header .btn {\n color: #fff;\n}\n.box.box-solid.box-warning {\n border: 1px solid #f39c12;\n}\n.box.box-solid.box-warning > .box-header {\n color: #fff;\n background: #f39c12;\n background-color: #f39c12;\n}\n.box.box-solid.box-warning > .box-header a,\n.box.box-solid.box-warning > .box-header .btn {\n color: #fff;\n}\n.box.box-solid.box-success {\n border: 1px solid #00a65a;\n}\n.box.box-solid.box-success > .box-header {\n color: #fff;\n background: #00a65a;\n background-color: #00a65a;\n}\n.box.box-solid.box-success > .box-header a,\n.box.box-solid.box-success > .box-header .btn {\n color: #fff;\n}\n.box.box-solid > .box-header > .box-tools .btn {\n border: 0;\n box-shadow: none;\n}\n.box.box-solid[class*='bg'] > .box-header {\n color: #fff;\n}\n.box .box-group > .box {\n margin-bottom: 5px;\n}\n.box .knob-label {\n text-align: center;\n color: #333;\n font-weight: 100;\n font-size: 12px;\n margin-bottom: 0.3em;\n}\n.box > .overlay,\n.overlay-wrapper > .overlay,\n.box > .loading-img,\n.overlay-wrapper > .loading-img {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n.box .overlay,\n.overlay-wrapper .overlay {\n z-index: 50;\n background: rgba(255, 255, 255, 0.7);\n border-radius: 3px;\n}\n.box .overlay > .fa,\n.overlay-wrapper .overlay > .fa {\n position: absolute;\n top: 50%;\n left: 50%;\n margin-left: -15px;\n margin-top: -15px;\n color: #000;\n font-size: 30px;\n}\n.box .overlay.dark,\n.overlay-wrapper .overlay.dark {\n background: rgba(0, 0, 0, 0.5);\n}\n.box-header:before,\n.box-body:before,\n.box-footer:before,\n.box-header:after,\n.box-body:after,\n.box-footer:after {\n content: \" \";\n display: table;\n}\n.box-header:after,\n.box-body:after,\n.box-footer:after {\n clear: both;\n}\n.box-header {\n color: #444;\n display: block;\n padding: 10px;\n position: relative;\n}\n.box-header.with-border {\n border-bottom: 1px solid #f4f4f4;\n}\n.collapsed-box .box-header.with-border {\n border-bottom: none;\n}\n.box-header > .fa,\n.box-header > .glyphicon,\n.box-header > .ion,\n.box-header .box-title {\n display: inline-block;\n font-size: 18px;\n margin: 0;\n line-height: 1;\n}\n.box-header > .fa,\n.box-header > .glyphicon,\n.box-header > .ion {\n margin-right: 5px;\n}\n.box-header > .box-tools {\n float: right;\n margin-top: -5px;\n margin-bottom: -5px;\n}\n.box-header > .box-tools [data-toggle=\"tooltip\"] {\n position: relative;\n}\n.box-header > .box-tools.pull-right .dropdown-menu {\n right: 0;\n left: auto;\n}\n.box-header > .box-tools .dropdown-menu > li > a {\n color: #444 !important;\n}\n.btn-box-tool {\n padding: 5px;\n font-size: 12px;\n background: transparent;\n color: #97a0b3;\n}\n.open .btn-box-tool,\n.btn-box-tool:hover {\n color: #606c84;\n}\n.btn-box-tool.btn:active {\n box-shadow: none;\n}\n.box-body {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n padding: 10px;\n}\n.no-header .box-body {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.box-body > .table {\n margin-bottom: 0;\n}\n.box-body .fc {\n margin-top: 5px;\n}\n.box-body .full-width-chart {\n margin: -19px;\n}\n.box-body.no-padding .full-width-chart {\n margin: -9px;\n}\n.box-body .box-pane {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 3px;\n}\n.box-body .box-pane-right {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 0;\n}\n.box-footer {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n border-top: 1px solid #f4f4f4;\n padding: 10px;\n background-color: #fff;\n}\n.chart-legend {\n margin: 10px 0;\n}\n@media (max-width: 991px) {\n .chart-legend > li {\n float: left;\n margin-right: 10px;\n }\n}\n.box-comments {\n background: #f7f7f7;\n}\n.box-comments .box-comment {\n padding: 8px 0;\n border-bottom: 1px solid #eee;\n}\n.box-comments .box-comment:before,\n.box-comments .box-comment:after {\n content: \" \";\n display: table;\n}\n.box-comments .box-comment:after {\n clear: both;\n}\n.box-comments .box-comment:last-of-type {\n border-bottom: 0;\n}\n.box-comments .box-comment:first-of-type {\n padding-top: 0;\n}\n.box-comments .box-comment img {\n float: left;\n}\n.box-comments .comment-text {\n margin-left: 40px;\n color: #555;\n}\n.box-comments .username {\n color: #444;\n display: block;\n font-weight: 600;\n}\n.box-comments .text-muted {\n font-weight: 400;\n font-size: 12px;\n}\n/* Widget: TODO LIST */\n.todo-list {\n margin: 0;\n padding: 0;\n list-style: none;\n overflow: auto;\n}\n.todo-list > li {\n border-radius: 2px;\n padding: 10px;\n background: #f4f4f4;\n margin-bottom: 2px;\n border-left: 2px solid #e6e7e8;\n color: #444;\n}\n.todo-list > li:last-of-type {\n margin-bottom: 0;\n}\n.todo-list > li > input[type='checkbox'] {\n margin: 0 10px 0 5px;\n}\n.todo-list > li .text {\n display: inline-block;\n margin-left: 5px;\n font-weight: 600;\n}\n.todo-list > li .label {\n margin-left: 10px;\n font-size: 9px;\n}\n.todo-list > li .tools {\n display: none;\n float: right;\n color: #dd4b39;\n}\n.todo-list > li .tools > .fa,\n.todo-list > li .tools > .glyphicon,\n.todo-list > li .tools > .ion {\n margin-right: 5px;\n cursor: pointer;\n}\n.todo-list > li:hover .tools {\n display: inline-block;\n}\n.todo-list > li.done {\n color: #999;\n}\n.todo-list > li.done .text {\n text-decoration: line-through;\n font-weight: 500;\n}\n.todo-list > li.done .label {\n background: #d2d6de !important;\n}\n.todo-list .danger {\n border-left-color: #dd4b39;\n}\n.todo-list .warning {\n border-left-color: #f39c12;\n}\n.todo-list .info {\n border-left-color: #00c0ef;\n}\n.todo-list .success {\n border-left-color: #00a65a;\n}\n.todo-list .primary {\n border-left-color: #3c8dbc;\n}\n.todo-list .handle {\n display: inline-block;\n cursor: move;\n margin: 0 5px;\n}\n/* Chat widget (DEPRECATED - this will be removed in the next major release. Use Direct Chat instead)*/\n.chat {\n padding: 5px 20px 5px 10px;\n}\n.chat .item {\n margin-bottom: 10px;\n}\n.chat .item:before,\n.chat .item:after {\n content: \" \";\n display: table;\n}\n.chat .item:after {\n clear: both;\n}\n.chat .item > img {\n width: 40px;\n height: 40px;\n border: 2px solid transparent;\n border-radius: 50%;\n}\n.chat .item > .online {\n border: 2px solid #00a65a;\n}\n.chat .item > .offline {\n border: 2px solid #dd4b39;\n}\n.chat .item > .message {\n margin-left: 55px;\n margin-top: -40px;\n}\n.chat .item > .message > .name {\n display: block;\n font-weight: 600;\n}\n.chat .item > .attachment {\n border-radius: 3px;\n background: #f4f4f4;\n margin-left: 65px;\n margin-right: 15px;\n padding: 10px;\n}\n.chat .item > .attachment > h4 {\n margin: 0 0 5px 0;\n font-weight: 600;\n font-size: 14px;\n}\n.chat .item > .attachment > p,\n.chat .item > .attachment > .filename {\n font-weight: 600;\n font-size: 13px;\n font-style: italic;\n margin: 0;\n}\n.chat .item > .attachment:before,\n.chat .item > .attachment:after {\n content: \" \";\n display: table;\n}\n.chat .item > .attachment:after {\n clear: both;\n}\n.box-input {\n max-width: 200px;\n}\n.modal .panel-body {\n color: #444;\n}\n/*\n * Component: Info Box\n * -------------------\n */\n.info-box {\n display: block;\n min-height: 90px;\n background: #fff;\n width: 100%;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n border-radius: 2px;\n margin-bottom: 15px;\n}\n.info-box small {\n font-size: 14px;\n}\n.info-box .progress {\n background: rgba(0, 0, 0, 0.2);\n margin: 5px -10px 5px -10px;\n height: 2px;\n}\n.info-box .progress,\n.info-box .progress .progress-bar {\n border-radius: 0;\n}\n.info-box .progress .progress-bar {\n background: #fff;\n}\n.info-box-icon {\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n display: block;\n float: left;\n height: 90px;\n width: 90px;\n text-align: center;\n font-size: 45px;\n line-height: 90px;\n background: rgba(0, 0, 0, 0.2);\n}\n.info-box-icon > img {\n max-width: 100%;\n}\n.info-box-content {\n padding: 5px 10px;\n margin-left: 90px;\n}\n.info-box-number {\n display: block;\n font-weight: bold;\n font-size: 18px;\n}\n.progress-description,\n.info-box-text {\n display: block;\n font-size: 14px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.info-box-text {\n text-transform: uppercase;\n}\n.info-box-more {\n display: block;\n}\n.progress-description {\n margin: 0;\n}\n/*\n * Component: Timeline\n * -------------------\n */\n.timeline {\n position: relative;\n margin: 0 0 30px 0;\n padding: 0;\n list-style: none;\n}\n.timeline:before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n width: 4px;\n background: #ddd;\n left: 31px;\n margin: 0;\n border-radius: 2px;\n}\n.timeline > li {\n position: relative;\n margin-right: 10px;\n margin-bottom: 15px;\n}\n.timeline > li:before,\n.timeline > li:after {\n content: \" \";\n display: table;\n}\n.timeline > li:after {\n clear: both;\n}\n.timeline > li > .timeline-item {\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n border-radius: 3px;\n margin-top: 0;\n background: #fff;\n color: #444;\n margin-left: 60px;\n margin-right: 15px;\n padding: 0;\n position: relative;\n}\n.timeline > li > .timeline-item > .time {\n color: #999;\n float: right;\n padding: 10px;\n font-size: 12px;\n}\n.timeline > li > .timeline-item > .timeline-header {\n margin: 0;\n color: #555;\n border-bottom: 1px solid #f4f4f4;\n padding: 10px;\n font-size: 16px;\n line-height: 1.1;\n}\n.timeline > li > .timeline-item > .timeline-header > a {\n font-weight: 600;\n}\n.timeline > li > .timeline-item > .timeline-body,\n.timeline > li > .timeline-item > .timeline-footer {\n padding: 10px;\n}\n.timeline > li > .fa,\n.timeline > li > .glyphicon,\n.timeline > li > .ion {\n width: 30px;\n height: 30px;\n font-size: 15px;\n line-height: 30px;\n position: absolute;\n color: #666;\n background: #d2d6de;\n border-radius: 50%;\n text-align: center;\n left: 18px;\n top: 0;\n}\n.timeline > .time-label > span {\n font-weight: 600;\n padding: 5px;\n display: inline-block;\n background-color: #fff;\n border-radius: 4px;\n}\n.timeline-inverse > li > .timeline-item {\n background: #f0f0f0;\n border: 1px solid #ddd;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.timeline-inverse > li > .timeline-item > .timeline-header {\n border-bottom-color: #ddd;\n}\n/*\n * Component: Button\n * -----------------\n */\n.btn {\n border-radius: 3px;\n -webkit-box-shadow: none;\n box-shadow: none;\n border: 1px solid transparent;\n}\n.btn.uppercase {\n text-transform: uppercase;\n}\n.btn.btn-flat {\n border-radius: 0;\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n border-width: 1px;\n}\n.btn:active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn:focus {\n outline: none;\n}\n.btn.btn-file {\n position: relative;\n overflow: hidden;\n}\n.btn.btn-file > input[type='file'] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n opacity: 0;\n filter: alpha(opacity=0);\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n}\n.btn-default {\n background-color: #f4f4f4;\n color: #444;\n border-color: #ddd;\n}\n.btn-default:hover,\n.btn-default:active,\n.btn-default.hover {\n background-color: #e7e7e7;\n}\n.btn-primary {\n background-color: #3c8dbc;\n border-color: #367fa9;\n}\n.btn-primary:hover,\n.btn-primary:active,\n.btn-primary.hover {\n background-color: #367fa9;\n}\n.btn-success {\n background-color: #00a65a;\n border-color: #008d4c;\n}\n.btn-success:hover,\n.btn-success:active,\n.btn-success.hover {\n background-color: #008d4c;\n}\n.btn-info {\n background-color: #00c0ef;\n border-color: #00acd6;\n}\n.btn-info:hover,\n.btn-info:active,\n.btn-info.hover {\n background-color: #00acd6;\n}\n.btn-danger {\n background-color: #dd4b39;\n border-color: #d73925;\n}\n.btn-danger:hover,\n.btn-danger:active,\n.btn-danger.hover {\n background-color: #d73925;\n}\n.btn-warning {\n background-color: #f39c12;\n border-color: #e08e0b;\n}\n.btn-warning:hover,\n.btn-warning:active,\n.btn-warning.hover {\n background-color: #e08e0b;\n}\n.btn-outline {\n border: 1px solid #fff;\n background: transparent;\n color: #fff;\n}\n.btn-outline:hover,\n.btn-outline:focus,\n.btn-outline:active {\n color: rgba(255, 255, 255, 0.7);\n border-color: rgba(255, 255, 255, 0.7);\n}\n.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn[class*='bg-']:hover {\n -webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, 0.2);\n box-shadow: inset 0 0 100px rgba(0, 0, 0, 0.2);\n}\n.btn-app {\n border-radius: 3px;\n position: relative;\n padding: 15px 5px;\n margin: 0 0 10px 10px;\n min-width: 80px;\n height: 60px;\n text-align: center;\n color: #666;\n border: 1px solid #ddd;\n background-color: #f4f4f4;\n font-size: 12px;\n}\n.btn-app > .fa,\n.btn-app > .glyphicon,\n.btn-app > .ion {\n font-size: 20px;\n display: block;\n}\n.btn-app:hover {\n background: #f4f4f4;\n color: #444;\n border-color: #aaa;\n}\n.btn-app:active,\n.btn-app:focus {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-app > .badge {\n position: absolute;\n top: -3px;\n right: -10px;\n font-size: 10px;\n font-weight: 400;\n}\n/*\n * Component: Callout\n * ------------------\n */\n.callout {\n border-radius: 3px;\n margin: 0 0 20px 0;\n padding: 15px 30px 15px 15px;\n border-left: 5px solid #eee;\n}\n.callout a {\n color: #fff;\n text-decoration: underline;\n}\n.callout a:hover {\n color: #eee;\n}\n.callout h4 {\n margin-top: 0;\n font-weight: 600;\n}\n.callout p:last-child {\n margin-bottom: 0;\n}\n.callout code,\n.callout .highlight {\n background-color: #fff;\n}\n.callout.callout-danger {\n border-color: #c23321;\n}\n.callout.callout-warning {\n border-color: #c87f0a;\n}\n.callout.callout-info {\n border-color: #0097bc;\n}\n.callout.callout-success {\n border-color: #00733e;\n}\n/*\n * Component: alert\n * ----------------\n */\n.alert {\n border-radius: 3px;\n}\n.alert h4 {\n font-weight: 600;\n}\n.alert .icon {\n margin-right: 10px;\n}\n.alert .close {\n color: #000;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.alert .close:hover {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.alert a {\n color: #fff;\n text-decoration: underline;\n}\n.alert-success {\n border-color: #008d4c;\n}\n.alert-danger,\n.alert-error {\n border-color: #d73925;\n}\n.alert-warning {\n border-color: #e08e0b;\n}\n.alert-info {\n border-color: #00acd6;\n}\n/*\n * Component: Nav\n * --------------\n */\n.nav > li > a:hover,\n.nav > li > a:active,\n.nav > li > a:focus {\n color: #444;\n background: #f7f7f7;\n}\n/* NAV PILLS */\n.nav-pills > li > a {\n border-radius: 0;\n border-top: 3px solid transparent;\n color: #444;\n}\n.nav-pills > li > a > .fa,\n.nav-pills > li > a > .glyphicon,\n.nav-pills > li > a > .ion {\n margin-right: 5px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n border-top-color: #3c8dbc;\n}\n.nav-pills > li.active > a {\n font-weight: 600;\n}\n/* NAV STACKED */\n.nav-stacked > li > a {\n border-radius: 0;\n border-top: 0;\n border-left: 3px solid transparent;\n color: #444;\n}\n.nav-stacked > li.active > a,\n.nav-stacked > li.active > a:hover {\n background: transparent;\n color: #444;\n border-top: 0;\n border-left-color: #3c8dbc;\n}\n.nav-stacked > li.header {\n border-bottom: 1px solid #ddd;\n color: #777;\n margin-bottom: 10px;\n padding: 5px 10px;\n text-transform: uppercase;\n}\n/* NAV TABS */\n.nav-tabs-custom {\n margin-bottom: 20px;\n background: #fff;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n border-radius: 3px;\n}\n.nav-tabs-custom > .nav-tabs {\n margin: 0;\n border-bottom-color: #f4f4f4;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.nav-tabs-custom > .nav-tabs > li {\n border-top: 3px solid transparent;\n margin-bottom: -2px;\n margin-right: 5px;\n}\n.nav-tabs-custom > .nav-tabs > li.disabled > a {\n color: #777;\n}\n.nav-tabs-custom > .nav-tabs > li > a {\n color: #444;\n border-radius: 0;\n}\n.nav-tabs-custom > .nav-tabs > li > a.text-muted {\n color: #999;\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li > a:hover {\n background: transparent;\n margin: 0;\n}\n.nav-tabs-custom > .nav-tabs > li > a:hover {\n color: #999;\n}\n.nav-tabs-custom > .nav-tabs > li:not(.active) > a:hover,\n.nav-tabs-custom > .nav-tabs > li:not(.active) > a:focus,\n.nav-tabs-custom > .nav-tabs > li:not(.active) > a:active {\n border-color: transparent;\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: #3c8dbc;\n}\n.nav-tabs-custom > .nav-tabs > li.active > a,\n.nav-tabs-custom > .nav-tabs > li.active:hover > a {\n background-color: #fff;\n color: #444;\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n border-top-color: transparent;\n border-left-color: #f4f4f4;\n border-right-color: #f4f4f4;\n}\n.nav-tabs-custom > .nav-tabs > li:first-of-type {\n margin-left: 0;\n}\n.nav-tabs-custom > .nav-tabs > li:first-of-type.active > a {\n border-left-color: transparent;\n}\n.nav-tabs-custom > .nav-tabs.pull-right {\n float: none !important;\n}\n.nav-tabs-custom > .nav-tabs.pull-right > li {\n float: right;\n}\n.nav-tabs-custom > .nav-tabs.pull-right > li:first-of-type {\n margin-right: 0;\n}\n.nav-tabs-custom > .nav-tabs.pull-right > li:first-of-type > a {\n border-left-width: 1px;\n}\n.nav-tabs-custom > .nav-tabs.pull-right > li:first-of-type.active > a {\n border-left-color: #f4f4f4;\n border-right-color: transparent;\n}\n.nav-tabs-custom > .nav-tabs > li.header {\n line-height: 35px;\n padding: 0 10px;\n font-size: 20px;\n color: #444;\n}\n.nav-tabs-custom > .nav-tabs > li.header > .fa,\n.nav-tabs-custom > .nav-tabs > li.header > .glyphicon,\n.nav-tabs-custom > .nav-tabs > li.header > .ion {\n margin-right: 5px;\n}\n.nav-tabs-custom > .tab-content {\n background: #fff;\n padding: 10px;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.nav-tabs-custom .dropdown.open > a:active,\n.nav-tabs-custom .dropdown.open > a:focus {\n background: transparent;\n color: #999;\n}\n.nav-tabs-custom.tab-primary > .nav-tabs > li.active {\n border-top-color: #3c8dbc;\n}\n.nav-tabs-custom.tab-info > .nav-tabs > li.active {\n border-top-color: #00c0ef;\n}\n.nav-tabs-custom.tab-danger > .nav-tabs > li.active {\n border-top-color: #dd4b39;\n}\n.nav-tabs-custom.tab-warning > .nav-tabs > li.active {\n border-top-color: #f39c12;\n}\n.nav-tabs-custom.tab-success > .nav-tabs > li.active {\n border-top-color: #00a65a;\n}\n.nav-tabs-custom.tab-default > .nav-tabs > li.active {\n border-top-color: #d2d6de;\n}\n/* PAGINATION */\n.pagination > li > a {\n background: #fafafa;\n color: #666;\n}\n.pagination.pagination-flat > li > a {\n border-radius: 0 !important;\n}\n/*\n * Component: Products List\n * ------------------------\n */\n.products-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.products-list > .item {\n border-radius: 3px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n padding: 10px 0;\n background: #fff;\n}\n.products-list > .item:before,\n.products-list > .item:after {\n content: \" \";\n display: table;\n}\n.products-list > .item:after {\n clear: both;\n}\n.products-list .product-img {\n float: left;\n}\n.products-list .product-img img {\n width: 50px;\n height: 50px;\n}\n.products-list .product-info {\n margin-left: 60px;\n}\n.products-list .product-title {\n font-weight: 600;\n}\n.products-list .product-description {\n display: block;\n color: #999;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.product-list-in-box > .item {\n -webkit-box-shadow: none;\n box-shadow: none;\n border-radius: 0;\n border-bottom: 1px solid #f4f4f4;\n}\n.product-list-in-box > .item:last-of-type {\n border-bottom-width: 0;\n}\n/*\n * Component: Table\n * ----------------\n */\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n border-top: 1px solid #f4f4f4;\n}\n.table > thead > tr > th {\n border-bottom: 2px solid #f4f4f4;\n}\n.table tr td .progress {\n margin-top: 5px;\n}\n.table-bordered {\n border: 1px solid #f4f4f4;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #f4f4f4;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table.no-border,\n.table.no-border td,\n.table.no-border th {\n border: 0;\n}\n/* .text-center in tables */\ntable.text-center,\ntable.text-center td,\ntable.text-center th {\n text-align: center;\n}\n.table.align th {\n text-align: left;\n}\n.table.align td {\n text-align: right;\n}\n/*\n * Component: Label\n * ----------------\n */\n.label-default {\n background-color: #d2d6de;\n color: #444;\n}\n/*\n * Component: Direct Chat\n * ----------------------\n */\n.direct-chat .box-body {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n position: relative;\n overflow-x: hidden;\n padding: 0;\n}\n.direct-chat.chat-pane-open .direct-chat-contacts {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.direct-chat-messages {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n padding: 10px;\n height: 250px;\n overflow: auto;\n}\n.direct-chat-msg,\n.direct-chat-text {\n display: block;\n}\n.direct-chat-msg {\n margin-bottom: 10px;\n}\n.direct-chat-msg:before,\n.direct-chat-msg:after {\n content: \" \";\n display: table;\n}\n.direct-chat-msg:after {\n clear: both;\n}\n.direct-chat-messages,\n.direct-chat-contacts {\n -webkit-transition: -webkit-transform 0.5s ease-in-out;\n -moz-transition: -moz-transform 0.5s ease-in-out;\n -o-transition: -o-transform 0.5s ease-in-out;\n transition: transform 0.5s ease-in-out;\n}\n.direct-chat-text {\n border-radius: 5px;\n position: relative;\n padding: 5px 10px;\n background: #d2d6de;\n border: 1px solid #d2d6de;\n margin: 5px 0 0 50px;\n color: #444;\n}\n.direct-chat-text:after,\n.direct-chat-text:before {\n position: absolute;\n right: 100%;\n top: 15px;\n border: solid transparent;\n border-right-color: #d2d6de;\n content: ' ';\n height: 0;\n width: 0;\n pointer-events: none;\n}\n.direct-chat-text:after {\n border-width: 5px;\n margin-top: -5px;\n}\n.direct-chat-text:before {\n border-width: 6px;\n margin-top: -6px;\n}\n.right .direct-chat-text {\n margin-right: 50px;\n margin-left: 0;\n}\n.right .direct-chat-text:after,\n.right .direct-chat-text:before {\n right: auto;\n left: 100%;\n border-right-color: transparent;\n border-left-color: #d2d6de;\n}\n.direct-chat-img {\n border-radius: 50%;\n float: left;\n width: 40px;\n height: 40px;\n}\n.right .direct-chat-img {\n float: right;\n}\n.direct-chat-info {\n display: block;\n margin-bottom: 2px;\n font-size: 12px;\n}\n.direct-chat-name {\n font-weight: 600;\n}\n.direct-chat-timestamp {\n color: #999;\n}\n.direct-chat-contacts-open .direct-chat-contacts {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.direct-chat-contacts {\n -webkit-transform: translate(101%, 0);\n -ms-transform: translate(101%, 0);\n -o-transform: translate(101%, 0);\n transform: translate(101%, 0);\n position: absolute;\n top: 0;\n bottom: 0;\n height: 250px;\n width: 100%;\n background: #222d32;\n color: #fff;\n overflow: auto;\n}\n.contacts-list > li {\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n padding: 10px;\n margin: 0;\n}\n.contacts-list > li:before,\n.contacts-list > li:after {\n content: \" \";\n display: table;\n}\n.contacts-list > li:after {\n clear: both;\n}\n.contacts-list > li:last-of-type {\n border-bottom: none;\n}\n.contacts-list-img {\n border-radius: 50%;\n width: 40px;\n float: left;\n}\n.contacts-list-info {\n margin-left: 45px;\n color: #fff;\n}\n.contacts-list-name,\n.contacts-list-status {\n display: block;\n}\n.contacts-list-name {\n font-weight: 600;\n}\n.contacts-list-status {\n font-size: 12px;\n}\n.contacts-list-date {\n color: #aaa;\n font-weight: normal;\n}\n.contacts-list-msg {\n color: #999;\n}\n.direct-chat-danger .right > .direct-chat-text {\n background: #dd4b39;\n border-color: #dd4b39;\n color: #fff;\n}\n.direct-chat-danger .right > .direct-chat-text:after,\n.direct-chat-danger .right > .direct-chat-text:before {\n border-left-color: #dd4b39;\n}\n.direct-chat-primary .right > .direct-chat-text {\n background: #3c8dbc;\n border-color: #3c8dbc;\n color: #fff;\n}\n.direct-chat-primary .right > .direct-chat-text:after,\n.direct-chat-primary .right > .direct-chat-text:before {\n border-left-color: #3c8dbc;\n}\n.direct-chat-warning .right > .direct-chat-text {\n background: #f39c12;\n border-color: #f39c12;\n color: #fff;\n}\n.direct-chat-warning .right > .direct-chat-text:after,\n.direct-chat-warning .right > .direct-chat-text:before {\n border-left-color: #f39c12;\n}\n.direct-chat-info .right > .direct-chat-text {\n background: #00c0ef;\n border-color: #00c0ef;\n color: #fff;\n}\n.direct-chat-info .right > .direct-chat-text:after,\n.direct-chat-info .right > .direct-chat-text:before {\n border-left-color: #00c0ef;\n}\n.direct-chat-success .right > .direct-chat-text {\n background: #00a65a;\n border-color: #00a65a;\n color: #fff;\n}\n.direct-chat-success .right > .direct-chat-text:after,\n.direct-chat-success .right > .direct-chat-text:before {\n border-left-color: #00a65a;\n}\n/*\n * Component: Users List\n * ---------------------\n */\n.users-list > li {\n width: 25%;\n float: left;\n padding: 10px;\n text-align: center;\n}\n.users-list > li img {\n border-radius: 50%;\n max-width: 100%;\n height: auto;\n}\n.users-list > li > a:hover,\n.users-list > li > a:hover .users-list-name {\n color: #999;\n}\n.users-list-name,\n.users-list-date {\n display: block;\n}\n.users-list-name {\n font-weight: 600;\n color: #444;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.users-list-date {\n color: #999;\n font-size: 12px;\n}\n/*\n * Component: Carousel\n * -------------------\n */\n.carousel-control.left,\n.carousel-control.right {\n background-image: none;\n}\n.carousel-control > .fa {\n font-size: 40px;\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -20px;\n}\n/*\n * Component: modal\n * ----------------\n */\n.modal {\n background: rgba(0, 0, 0, 0.3);\n}\n.modal-content {\n border-radius: 0;\n -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);\n box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);\n border: 0;\n}\n@media (min-width: 768px) {\n .modal-content {\n -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);\n box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);\n }\n}\n.modal-header {\n border-bottom-color: #f4f4f4;\n}\n.modal-footer {\n border-top-color: #f4f4f4;\n}\n.modal-primary .modal-header,\n.modal-primary .modal-footer {\n border-color: #307095;\n}\n.modal-warning .modal-header,\n.modal-warning .modal-footer {\n border-color: #c87f0a;\n}\n.modal-info .modal-header,\n.modal-info .modal-footer {\n border-color: #0097bc;\n}\n.modal-success .modal-header,\n.modal-success .modal-footer {\n border-color: #00733e;\n}\n.modal-danger .modal-header,\n.modal-danger .modal-footer {\n border-color: #c23321;\n}\n/*\n * Component: Social Widgets\n * -------------------------\n */\n.box-widget {\n border: none;\n position: relative;\n}\n.widget-user .widget-user-header {\n padding: 20px;\n height: 120px;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.widget-user .widget-user-username {\n margin-top: 0;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n}\n.widget-user .widget-user-desc {\n margin-top: 0;\n}\n.widget-user .widget-user-image {\n position: absolute;\n top: 65px;\n left: 50%;\n margin-left: -45px;\n}\n.widget-user .widget-user-image > img {\n width: 90px;\n height: auto;\n border: 3px solid #fff;\n}\n.widget-user .box-footer {\n padding-top: 30px;\n}\n.widget-user-2 .widget-user-header {\n padding: 20px;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.widget-user-2 .widget-user-username {\n margin-top: 5px;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n}\n.widget-user-2 .widget-user-desc {\n margin-top: 0;\n}\n.widget-user-2 .widget-user-username,\n.widget-user-2 .widget-user-desc {\n margin-left: 75px;\n}\n.widget-user-2 .widget-user-image > img {\n width: 65px;\n height: auto;\n float: left;\n}\n.treeview-menu {\n display: none;\n list-style: none;\n padding: 0;\n margin: 0;\n padding-left: 5px;\n}\n.treeview-menu .treeview-menu {\n padding-left: 20px;\n}\n.treeview-menu > li {\n margin: 0;\n}\n.treeview-menu > li > a {\n padding: 5px 5px 5px 15px;\n display: block;\n font-size: 14px;\n}\n.treeview-menu > li > a > .fa,\n.treeview-menu > li > a > .glyphicon,\n.treeview-menu > li > a > .ion {\n width: 20px;\n}\n.treeview-menu > li > a > .pull-right-container > .fa-angle-left,\n.treeview-menu > li > a > .pull-right-container > .fa-angle-down,\n.treeview-menu > li > a > .fa-angle-left,\n.treeview-menu > li > a > .fa-angle-down {\n width: auto;\n}\n.treeview > ul.treeview-menu {\n overflow: hidden;\n height: auto;\n padding-top: 0px !important;\n padding-bottom: 0px !important;\n}\n.treeview.menu-open > ul.treeview-menu {\n overflow: visible;\n height: auto;\n}\n/*\n * Page: Mailbox\n * -------------\n */\n.mailbox-messages > .table {\n margin: 0;\n}\n.mailbox-controls {\n padding: 5px;\n}\n.mailbox-controls.with-border {\n border-bottom: 1px solid #f4f4f4;\n}\n.mailbox-read-info {\n border-bottom: 1px solid #f4f4f4;\n padding: 10px;\n}\n.mailbox-read-info h3 {\n font-size: 20px;\n margin: 0;\n}\n.mailbox-read-info h5 {\n margin: 0;\n padding: 5px 0 0 0;\n}\n.mailbox-read-time {\n color: #999;\n font-size: 13px;\n}\n.mailbox-read-message {\n padding: 10px;\n}\n.mailbox-attachments li {\n float: left;\n width: 200px;\n border: 1px solid #eee;\n margin-bottom: 10px;\n margin-right: 10px;\n}\n.mailbox-attachment-name {\n font-weight: bold;\n color: #666;\n}\n.mailbox-attachment-icon,\n.mailbox-attachment-info,\n.mailbox-attachment-size {\n display: block;\n}\n.mailbox-attachment-info {\n padding: 10px;\n background: #f4f4f4;\n}\n.mailbox-attachment-size {\n color: #999;\n font-size: 12px;\n}\n.mailbox-attachment-icon {\n text-align: center;\n font-size: 65px;\n color: #666;\n padding: 20px 10px;\n}\n.mailbox-attachment-icon.has-img {\n padding: 0;\n}\n.mailbox-attachment-icon.has-img > img {\n max-width: 100%;\n height: auto;\n}\n/*\n * Page: Lock Screen\n * -----------------\n */\n/* ADD THIS CLASS TO THE TAG */\n.lockscreen {\n background: #d2d6de;\n}\n.lockscreen-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n}\n.lockscreen-logo a {\n color: #444;\n}\n.lockscreen-wrapper {\n max-width: 400px;\n margin: 0 auto;\n margin-top: 10%;\n}\n/* User name [optional] */\n.lockscreen .lockscreen-name {\n text-align: center;\n font-weight: 600;\n}\n/* Will contain the image and the sign in form */\n.lockscreen-item {\n border-radius: 4px;\n padding: 0;\n background: #fff;\n position: relative;\n margin: 10px auto 30px auto;\n width: 290px;\n}\n/* User image */\n.lockscreen-image {\n border-radius: 50%;\n position: absolute;\n left: -10px;\n top: -25px;\n background: #fff;\n padding: 5px;\n z-index: 10;\n}\n.lockscreen-image > img {\n border-radius: 50%;\n width: 70px;\n height: 70px;\n}\n/* Contains the password input and the login button */\n.lockscreen-credentials {\n margin-left: 70px;\n}\n.lockscreen-credentials .form-control {\n border: 0;\n}\n.lockscreen-credentials .btn {\n background-color: #fff;\n border: 0;\n padding: 0 10px;\n}\n.lockscreen-footer {\n margin-top: 10px;\n}\n/*\n * Page: Login & Register\n * ----------------------\n */\n.login-logo,\n.register-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n}\n.login-logo a,\n.register-logo a {\n color: #444;\n}\n.login-page,\n.register-page {\n height: auto;\n background: #d2d6de;\n}\n.login-box,\n.register-box {\n width: 360px;\n margin: 7% auto;\n}\n@media (max-width: 768px) {\n .login-box,\n .register-box {\n width: 90%;\n margin-top: 20px;\n }\n}\n.login-box-body,\n.register-box-body {\n background: #fff;\n padding: 20px;\n border-top: 0;\n color: #666;\n}\n.login-box-body .form-control-feedback,\n.register-box-body .form-control-feedback {\n color: #777;\n}\n.login-box-msg,\n.register-box-msg {\n margin: 0;\n text-align: center;\n padding: 0 20px 20px 20px;\n}\n.social-auth-links {\n margin: 10px 0;\n}\n/*\n * Page: 400 and 500 error pages\n * ------------------------------\n */\n.error-page {\n width: 600px;\n margin: 20px auto 0 auto;\n}\n@media (max-width: 991px) {\n .error-page {\n width: 100%;\n }\n}\n.error-page > .headline {\n float: left;\n font-size: 100px;\n font-weight: 300;\n}\n@media (max-width: 991px) {\n .error-page > .headline {\n float: none;\n text-align: center;\n }\n}\n.error-page > .error-content {\n margin-left: 190px;\n display: block;\n}\n@media (max-width: 991px) {\n .error-page > .error-content {\n margin-left: 0;\n }\n}\n.error-page > .error-content > h3 {\n font-weight: 300;\n font-size: 25px;\n}\n@media (max-width: 991px) {\n .error-page > .error-content > h3 {\n text-align: center;\n }\n}\n/*\n * Page: Invoice\n * -------------\n */\n.invoice {\n position: relative;\n background: #fff;\n border: 1px solid #f4f4f4;\n padding: 20px;\n margin: 10px 25px;\n}\n.invoice-title {\n margin-top: 0;\n}\n/*\n * Page: Profile\n * -------------\n */\n.profile-user-img {\n margin: 0 auto;\n width: 100px;\n padding: 3px;\n border: 3px solid #d2d6de;\n}\n.profile-username {\n font-size: 21px;\n margin-top: 5px;\n}\n.post {\n border-bottom: 1px solid #d2d6de;\n margin-bottom: 15px;\n padding-bottom: 15px;\n color: #666;\n}\n.post:last-of-type {\n border-bottom: 0;\n margin-bottom: 0;\n padding-bottom: 0;\n}\n.post .user-block {\n margin-bottom: 15px;\n}\n/*\n * Social Buttons for Bootstrap\n *\n * Copyright 2013-2015 Panayiotis Lipiridis\n * Licensed under the MIT License\n *\n * https://github.com/lipis/bootstrap-social\n */\n.btn-social {\n position: relative;\n padding-left: 44px;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.btn-social > :first-child {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 32px;\n line-height: 34px;\n font-size: 1.6em;\n text-align: center;\n border-right: 1px solid rgba(0, 0, 0, 0.2);\n}\n.btn-social.btn-lg {\n padding-left: 61px;\n}\n.btn-social.btn-lg > :first-child {\n line-height: 45px;\n width: 45px;\n font-size: 1.8em;\n}\n.btn-social.btn-sm {\n padding-left: 38px;\n}\n.btn-social.btn-sm > :first-child {\n line-height: 28px;\n width: 28px;\n font-size: 1.4em;\n}\n.btn-social.btn-xs {\n padding-left: 30px;\n}\n.btn-social.btn-xs > :first-child {\n line-height: 20px;\n width: 20px;\n font-size: 1.2em;\n}\n.btn-social-icon {\n position: relative;\n padding-left: 44px;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n height: 34px;\n width: 34px;\n padding: 0;\n}\n.btn-social-icon > :first-child {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 32px;\n line-height: 34px;\n font-size: 1.6em;\n text-align: center;\n border-right: 1px solid rgba(0, 0, 0, 0.2);\n}\n.btn-social-icon.btn-lg {\n padding-left: 61px;\n}\n.btn-social-icon.btn-lg > :first-child {\n line-height: 45px;\n width: 45px;\n font-size: 1.8em;\n}\n.btn-social-icon.btn-sm {\n padding-left: 38px;\n}\n.btn-social-icon.btn-sm > :first-child {\n line-height: 28px;\n width: 28px;\n font-size: 1.4em;\n}\n.btn-social-icon.btn-xs {\n padding-left: 30px;\n}\n.btn-social-icon.btn-xs > :first-child {\n line-height: 20px;\n width: 20px;\n font-size: 1.2em;\n}\n.btn-social-icon > :first-child {\n border: none;\n text-align: center;\n width: 100%;\n}\n.btn-social-icon.btn-lg {\n height: 45px;\n width: 45px;\n padding-left: 0;\n padding-right: 0;\n}\n.btn-social-icon.btn-sm {\n height: 30px;\n width: 30px;\n padding-left: 0;\n padding-right: 0;\n}\n.btn-social-icon.btn-xs {\n height: 22px;\n width: 22px;\n padding-left: 0;\n padding-right: 0;\n}\n.btn-adn {\n color: #fff;\n background-color: #d87a68;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn:focus,\n.btn-adn.focus {\n color: #fff;\n background-color: #ce563f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn:hover {\n color: #fff;\n background-color: #ce563f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn:active,\n.btn-adn.active,\n.open > .dropdown-toggle.btn-adn {\n color: #fff;\n background-color: #ce563f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn:active:hover,\n.btn-adn.active:hover,\n.open > .dropdown-toggle.btn-adn:hover,\n.btn-adn:active:focus,\n.btn-adn.active:focus,\n.open > .dropdown-toggle.btn-adn:focus,\n.btn-adn:active.focus,\n.btn-adn.active.focus,\n.open > .dropdown-toggle.btn-adn.focus {\n color: #fff;\n background-color: #b94630;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn:active,\n.btn-adn.active,\n.open > .dropdown-toggle.btn-adn {\n background-image: none;\n}\n.btn-adn.disabled:hover,\n.btn-adn[disabled]:hover,\nfieldset[disabled] .btn-adn:hover,\n.btn-adn.disabled:focus,\n.btn-adn[disabled]:focus,\nfieldset[disabled] .btn-adn:focus,\n.btn-adn.disabled.focus,\n.btn-adn[disabled].focus,\nfieldset[disabled] .btn-adn.focus {\n background-color: #d87a68;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-adn .badge {\n color: #d87a68;\n background-color: #fff;\n}\n.btn-bitbucket {\n color: #fff;\n background-color: #205081;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket:focus,\n.btn-bitbucket.focus {\n color: #fff;\n background-color: #163758;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket:hover {\n color: #fff;\n background-color: #163758;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket:active,\n.btn-bitbucket.active,\n.open > .dropdown-toggle.btn-bitbucket {\n color: #fff;\n background-color: #163758;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket:active:hover,\n.btn-bitbucket.active:hover,\n.open > .dropdown-toggle.btn-bitbucket:hover,\n.btn-bitbucket:active:focus,\n.btn-bitbucket.active:focus,\n.open > .dropdown-toggle.btn-bitbucket:focus,\n.btn-bitbucket:active.focus,\n.btn-bitbucket.active.focus,\n.open > .dropdown-toggle.btn-bitbucket.focus {\n color: #fff;\n background-color: #0f253c;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket:active,\n.btn-bitbucket.active,\n.open > .dropdown-toggle.btn-bitbucket {\n background-image: none;\n}\n.btn-bitbucket.disabled:hover,\n.btn-bitbucket[disabled]:hover,\nfieldset[disabled] .btn-bitbucket:hover,\n.btn-bitbucket.disabled:focus,\n.btn-bitbucket[disabled]:focus,\nfieldset[disabled] .btn-bitbucket:focus,\n.btn-bitbucket.disabled.focus,\n.btn-bitbucket[disabled].focus,\nfieldset[disabled] .btn-bitbucket.focus {\n background-color: #205081;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-bitbucket .badge {\n color: #205081;\n background-color: #fff;\n}\n.btn-dropbox {\n color: #fff;\n background-color: #1087dd;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox:focus,\n.btn-dropbox.focus {\n color: #fff;\n background-color: #0d6aad;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox:hover {\n color: #fff;\n background-color: #0d6aad;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox:active,\n.btn-dropbox.active,\n.open > .dropdown-toggle.btn-dropbox {\n color: #fff;\n background-color: #0d6aad;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox:active:hover,\n.btn-dropbox.active:hover,\n.open > .dropdown-toggle.btn-dropbox:hover,\n.btn-dropbox:active:focus,\n.btn-dropbox.active:focus,\n.open > .dropdown-toggle.btn-dropbox:focus,\n.btn-dropbox:active.focus,\n.btn-dropbox.active.focus,\n.open > .dropdown-toggle.btn-dropbox.focus {\n color: #fff;\n background-color: #0a568c;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox:active,\n.btn-dropbox.active,\n.open > .dropdown-toggle.btn-dropbox {\n background-image: none;\n}\n.btn-dropbox.disabled:hover,\n.btn-dropbox[disabled]:hover,\nfieldset[disabled] .btn-dropbox:hover,\n.btn-dropbox.disabled:focus,\n.btn-dropbox[disabled]:focus,\nfieldset[disabled] .btn-dropbox:focus,\n.btn-dropbox.disabled.focus,\n.btn-dropbox[disabled].focus,\nfieldset[disabled] .btn-dropbox.focus {\n background-color: #1087dd;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-dropbox .badge {\n color: #1087dd;\n background-color: #fff;\n}\n.btn-facebook {\n color: #fff;\n background-color: #3b5998;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook:focus,\n.btn-facebook.focus {\n color: #fff;\n background-color: #2d4373;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook:hover {\n color: #fff;\n background-color: #2d4373;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook:active,\n.btn-facebook.active,\n.open > .dropdown-toggle.btn-facebook {\n color: #fff;\n background-color: #2d4373;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook:active:hover,\n.btn-facebook.active:hover,\n.open > .dropdown-toggle.btn-facebook:hover,\n.btn-facebook:active:focus,\n.btn-facebook.active:focus,\n.open > .dropdown-toggle.btn-facebook:focus,\n.btn-facebook:active.focus,\n.btn-facebook.active.focus,\n.open > .dropdown-toggle.btn-facebook.focus {\n color: #fff;\n background-color: #23345a;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook:active,\n.btn-facebook.active,\n.open > .dropdown-toggle.btn-facebook {\n background-image: none;\n}\n.btn-facebook.disabled:hover,\n.btn-facebook[disabled]:hover,\nfieldset[disabled] .btn-facebook:hover,\n.btn-facebook.disabled:focus,\n.btn-facebook[disabled]:focus,\nfieldset[disabled] .btn-facebook:focus,\n.btn-facebook.disabled.focus,\n.btn-facebook[disabled].focus,\nfieldset[disabled] .btn-facebook.focus {\n background-color: #3b5998;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-facebook .badge {\n color: #3b5998;\n background-color: #fff;\n}\n.btn-flickr {\n color: #fff;\n background-color: #ff0084;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr:focus,\n.btn-flickr.focus {\n color: #fff;\n background-color: #cc006a;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr:hover {\n color: #fff;\n background-color: #cc006a;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr:active,\n.btn-flickr.active,\n.open > .dropdown-toggle.btn-flickr {\n color: #fff;\n background-color: #cc006a;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr:active:hover,\n.btn-flickr.active:hover,\n.open > .dropdown-toggle.btn-flickr:hover,\n.btn-flickr:active:focus,\n.btn-flickr.active:focus,\n.open > .dropdown-toggle.btn-flickr:focus,\n.btn-flickr:active.focus,\n.btn-flickr.active.focus,\n.open > .dropdown-toggle.btn-flickr.focus {\n color: #fff;\n background-color: #a80057;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr:active,\n.btn-flickr.active,\n.open > .dropdown-toggle.btn-flickr {\n background-image: none;\n}\n.btn-flickr.disabled:hover,\n.btn-flickr[disabled]:hover,\nfieldset[disabled] .btn-flickr:hover,\n.btn-flickr.disabled:focus,\n.btn-flickr[disabled]:focus,\nfieldset[disabled] .btn-flickr:focus,\n.btn-flickr.disabled.focus,\n.btn-flickr[disabled].focus,\nfieldset[disabled] .btn-flickr.focus {\n background-color: #ff0084;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-flickr .badge {\n color: #ff0084;\n background-color: #fff;\n}\n.btn-foursquare {\n color: #fff;\n background-color: #f94877;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare:focus,\n.btn-foursquare.focus {\n color: #fff;\n background-color: #f71752;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare:hover {\n color: #fff;\n background-color: #f71752;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare:active,\n.btn-foursquare.active,\n.open > .dropdown-toggle.btn-foursquare {\n color: #fff;\n background-color: #f71752;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare:active:hover,\n.btn-foursquare.active:hover,\n.open > .dropdown-toggle.btn-foursquare:hover,\n.btn-foursquare:active:focus,\n.btn-foursquare.active:focus,\n.open > .dropdown-toggle.btn-foursquare:focus,\n.btn-foursquare:active.focus,\n.btn-foursquare.active.focus,\n.open > .dropdown-toggle.btn-foursquare.focus {\n color: #fff;\n background-color: #e30742;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare:active,\n.btn-foursquare.active,\n.open > .dropdown-toggle.btn-foursquare {\n background-image: none;\n}\n.btn-foursquare.disabled:hover,\n.btn-foursquare[disabled]:hover,\nfieldset[disabled] .btn-foursquare:hover,\n.btn-foursquare.disabled:focus,\n.btn-foursquare[disabled]:focus,\nfieldset[disabled] .btn-foursquare:focus,\n.btn-foursquare.disabled.focus,\n.btn-foursquare[disabled].focus,\nfieldset[disabled] .btn-foursquare.focus {\n background-color: #f94877;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-foursquare .badge {\n color: #f94877;\n background-color: #fff;\n}\n.btn-github {\n color: #fff;\n background-color: #444444;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github:focus,\n.btn-github.focus {\n color: #fff;\n background-color: #2b2b2b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github:hover {\n color: #fff;\n background-color: #2b2b2b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github:active,\n.btn-github.active,\n.open > .dropdown-toggle.btn-github {\n color: #fff;\n background-color: #2b2b2b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github:active:hover,\n.btn-github.active:hover,\n.open > .dropdown-toggle.btn-github:hover,\n.btn-github:active:focus,\n.btn-github.active:focus,\n.open > .dropdown-toggle.btn-github:focus,\n.btn-github:active.focus,\n.btn-github.active.focus,\n.open > .dropdown-toggle.btn-github.focus {\n color: #fff;\n background-color: #191919;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github:active,\n.btn-github.active,\n.open > .dropdown-toggle.btn-github {\n background-image: none;\n}\n.btn-github.disabled:hover,\n.btn-github[disabled]:hover,\nfieldset[disabled] .btn-github:hover,\n.btn-github.disabled:focus,\n.btn-github[disabled]:focus,\nfieldset[disabled] .btn-github:focus,\n.btn-github.disabled.focus,\n.btn-github[disabled].focus,\nfieldset[disabled] .btn-github.focus {\n background-color: #444444;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-github .badge {\n color: #444444;\n background-color: #fff;\n}\n.btn-google {\n color: #fff;\n background-color: #dd4b39;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google:focus,\n.btn-google.focus {\n color: #fff;\n background-color: #c23321;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google:hover {\n color: #fff;\n background-color: #c23321;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google:active,\n.btn-google.active,\n.open > .dropdown-toggle.btn-google {\n color: #fff;\n background-color: #c23321;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google:active:hover,\n.btn-google.active:hover,\n.open > .dropdown-toggle.btn-google:hover,\n.btn-google:active:focus,\n.btn-google.active:focus,\n.open > .dropdown-toggle.btn-google:focus,\n.btn-google:active.focus,\n.btn-google.active.focus,\n.open > .dropdown-toggle.btn-google.focus {\n color: #fff;\n background-color: #a32b1c;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google:active,\n.btn-google.active,\n.open > .dropdown-toggle.btn-google {\n background-image: none;\n}\n.btn-google.disabled:hover,\n.btn-google[disabled]:hover,\nfieldset[disabled] .btn-google:hover,\n.btn-google.disabled:focus,\n.btn-google[disabled]:focus,\nfieldset[disabled] .btn-google:focus,\n.btn-google.disabled.focus,\n.btn-google[disabled].focus,\nfieldset[disabled] .btn-google.focus {\n background-color: #dd4b39;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-google .badge {\n color: #dd4b39;\n background-color: #fff;\n}\n.btn-instagram {\n color: #fff;\n background-color: #3f729b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram:focus,\n.btn-instagram.focus {\n color: #fff;\n background-color: #305777;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram:hover {\n color: #fff;\n background-color: #305777;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram:active,\n.btn-instagram.active,\n.open > .dropdown-toggle.btn-instagram {\n color: #fff;\n background-color: #305777;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram:active:hover,\n.btn-instagram.active:hover,\n.open > .dropdown-toggle.btn-instagram:hover,\n.btn-instagram:active:focus,\n.btn-instagram.active:focus,\n.open > .dropdown-toggle.btn-instagram:focus,\n.btn-instagram:active.focus,\n.btn-instagram.active.focus,\n.open > .dropdown-toggle.btn-instagram.focus {\n color: #fff;\n background-color: #26455d;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram:active,\n.btn-instagram.active,\n.open > .dropdown-toggle.btn-instagram {\n background-image: none;\n}\n.btn-instagram.disabled:hover,\n.btn-instagram[disabled]:hover,\nfieldset[disabled] .btn-instagram:hover,\n.btn-instagram.disabled:focus,\n.btn-instagram[disabled]:focus,\nfieldset[disabled] .btn-instagram:focus,\n.btn-instagram.disabled.focus,\n.btn-instagram[disabled].focus,\nfieldset[disabled] .btn-instagram.focus {\n background-color: #3f729b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-instagram .badge {\n color: #3f729b;\n background-color: #fff;\n}\n.btn-linkedin {\n color: #fff;\n background-color: #007bb6;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin:focus,\n.btn-linkedin.focus {\n color: #fff;\n background-color: #005983;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin:hover {\n color: #fff;\n background-color: #005983;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin:active,\n.btn-linkedin.active,\n.open > .dropdown-toggle.btn-linkedin {\n color: #fff;\n background-color: #005983;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin:active:hover,\n.btn-linkedin.active:hover,\n.open > .dropdown-toggle.btn-linkedin:hover,\n.btn-linkedin:active:focus,\n.btn-linkedin.active:focus,\n.open > .dropdown-toggle.btn-linkedin:focus,\n.btn-linkedin:active.focus,\n.btn-linkedin.active.focus,\n.open > .dropdown-toggle.btn-linkedin.focus {\n color: #fff;\n background-color: #00405f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin:active,\n.btn-linkedin.active,\n.open > .dropdown-toggle.btn-linkedin {\n background-image: none;\n}\n.btn-linkedin.disabled:hover,\n.btn-linkedin[disabled]:hover,\nfieldset[disabled] .btn-linkedin:hover,\n.btn-linkedin.disabled:focus,\n.btn-linkedin[disabled]:focus,\nfieldset[disabled] .btn-linkedin:focus,\n.btn-linkedin.disabled.focus,\n.btn-linkedin[disabled].focus,\nfieldset[disabled] .btn-linkedin.focus {\n background-color: #007bb6;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-linkedin .badge {\n color: #007bb6;\n background-color: #fff;\n}\n.btn-microsoft {\n color: #fff;\n background-color: #2672ec;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft:focus,\n.btn-microsoft.focus {\n color: #fff;\n background-color: #125acd;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft:hover {\n color: #fff;\n background-color: #125acd;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft:active,\n.btn-microsoft.active,\n.open > .dropdown-toggle.btn-microsoft {\n color: #fff;\n background-color: #125acd;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft:active:hover,\n.btn-microsoft.active:hover,\n.open > .dropdown-toggle.btn-microsoft:hover,\n.btn-microsoft:active:focus,\n.btn-microsoft.active:focus,\n.open > .dropdown-toggle.btn-microsoft:focus,\n.btn-microsoft:active.focus,\n.btn-microsoft.active.focus,\n.open > .dropdown-toggle.btn-microsoft.focus {\n color: #fff;\n background-color: #0f4bac;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft:active,\n.btn-microsoft.active,\n.open > .dropdown-toggle.btn-microsoft {\n background-image: none;\n}\n.btn-microsoft.disabled:hover,\n.btn-microsoft[disabled]:hover,\nfieldset[disabled] .btn-microsoft:hover,\n.btn-microsoft.disabled:focus,\n.btn-microsoft[disabled]:focus,\nfieldset[disabled] .btn-microsoft:focus,\n.btn-microsoft.disabled.focus,\n.btn-microsoft[disabled].focus,\nfieldset[disabled] .btn-microsoft.focus {\n background-color: #2672ec;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-microsoft .badge {\n color: #2672ec;\n background-color: #fff;\n}\n.btn-openid {\n color: #fff;\n background-color: #f7931e;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid:focus,\n.btn-openid.focus {\n color: #fff;\n background-color: #da7908;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid:hover {\n color: #fff;\n background-color: #da7908;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid:active,\n.btn-openid.active,\n.open > .dropdown-toggle.btn-openid {\n color: #fff;\n background-color: #da7908;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid:active:hover,\n.btn-openid.active:hover,\n.open > .dropdown-toggle.btn-openid:hover,\n.btn-openid:active:focus,\n.btn-openid.active:focus,\n.open > .dropdown-toggle.btn-openid:focus,\n.btn-openid:active.focus,\n.btn-openid.active.focus,\n.open > .dropdown-toggle.btn-openid.focus {\n color: #fff;\n background-color: #b86607;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid:active,\n.btn-openid.active,\n.open > .dropdown-toggle.btn-openid {\n background-image: none;\n}\n.btn-openid.disabled:hover,\n.btn-openid[disabled]:hover,\nfieldset[disabled] .btn-openid:hover,\n.btn-openid.disabled:focus,\n.btn-openid[disabled]:focus,\nfieldset[disabled] .btn-openid:focus,\n.btn-openid.disabled.focus,\n.btn-openid[disabled].focus,\nfieldset[disabled] .btn-openid.focus {\n background-color: #f7931e;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-openid .badge {\n color: #f7931e;\n background-color: #fff;\n}\n.btn-pinterest {\n color: #fff;\n background-color: #cb2027;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest:focus,\n.btn-pinterest.focus {\n color: #fff;\n background-color: #9f191f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest:hover {\n color: #fff;\n background-color: #9f191f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest:active,\n.btn-pinterest.active,\n.open > .dropdown-toggle.btn-pinterest {\n color: #fff;\n background-color: #9f191f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest:active:hover,\n.btn-pinterest.active:hover,\n.open > .dropdown-toggle.btn-pinterest:hover,\n.btn-pinterest:active:focus,\n.btn-pinterest.active:focus,\n.open > .dropdown-toggle.btn-pinterest:focus,\n.btn-pinterest:active.focus,\n.btn-pinterest.active.focus,\n.open > .dropdown-toggle.btn-pinterest.focus {\n color: #fff;\n background-color: #801419;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest:active,\n.btn-pinterest.active,\n.open > .dropdown-toggle.btn-pinterest {\n background-image: none;\n}\n.btn-pinterest.disabled:hover,\n.btn-pinterest[disabled]:hover,\nfieldset[disabled] .btn-pinterest:hover,\n.btn-pinterest.disabled:focus,\n.btn-pinterest[disabled]:focus,\nfieldset[disabled] .btn-pinterest:focus,\n.btn-pinterest.disabled.focus,\n.btn-pinterest[disabled].focus,\nfieldset[disabled] .btn-pinterest.focus {\n background-color: #cb2027;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-pinterest .badge {\n color: #cb2027;\n background-color: #fff;\n}\n.btn-reddit {\n color: #000;\n background-color: #eff7ff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit:focus,\n.btn-reddit.focus {\n color: #000;\n background-color: #bcddff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit:hover {\n color: #000;\n background-color: #bcddff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit:active,\n.btn-reddit.active,\n.open > .dropdown-toggle.btn-reddit {\n color: #000;\n background-color: #bcddff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit:active:hover,\n.btn-reddit.active:hover,\n.open > .dropdown-toggle.btn-reddit:hover,\n.btn-reddit:active:focus,\n.btn-reddit.active:focus,\n.open > .dropdown-toggle.btn-reddit:focus,\n.btn-reddit:active.focus,\n.btn-reddit.active.focus,\n.open > .dropdown-toggle.btn-reddit.focus {\n color: #000;\n background-color: #98ccff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit:active,\n.btn-reddit.active,\n.open > .dropdown-toggle.btn-reddit {\n background-image: none;\n}\n.btn-reddit.disabled:hover,\n.btn-reddit[disabled]:hover,\nfieldset[disabled] .btn-reddit:hover,\n.btn-reddit.disabled:focus,\n.btn-reddit[disabled]:focus,\nfieldset[disabled] .btn-reddit:focus,\n.btn-reddit.disabled.focus,\n.btn-reddit[disabled].focus,\nfieldset[disabled] .btn-reddit.focus {\n background-color: #eff7ff;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-reddit .badge {\n color: #eff7ff;\n background-color: #000;\n}\n.btn-soundcloud {\n color: #fff;\n background-color: #ff5500;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud:focus,\n.btn-soundcloud.focus {\n color: #fff;\n background-color: #cc4400;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud:hover {\n color: #fff;\n background-color: #cc4400;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud:active,\n.btn-soundcloud.active,\n.open > .dropdown-toggle.btn-soundcloud {\n color: #fff;\n background-color: #cc4400;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud:active:hover,\n.btn-soundcloud.active:hover,\n.open > .dropdown-toggle.btn-soundcloud:hover,\n.btn-soundcloud:active:focus,\n.btn-soundcloud.active:focus,\n.open > .dropdown-toggle.btn-soundcloud:focus,\n.btn-soundcloud:active.focus,\n.btn-soundcloud.active.focus,\n.open > .dropdown-toggle.btn-soundcloud.focus {\n color: #fff;\n background-color: #a83800;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud:active,\n.btn-soundcloud.active,\n.open > .dropdown-toggle.btn-soundcloud {\n background-image: none;\n}\n.btn-soundcloud.disabled:hover,\n.btn-soundcloud[disabled]:hover,\nfieldset[disabled] .btn-soundcloud:hover,\n.btn-soundcloud.disabled:focus,\n.btn-soundcloud[disabled]:focus,\nfieldset[disabled] .btn-soundcloud:focus,\n.btn-soundcloud.disabled.focus,\n.btn-soundcloud[disabled].focus,\nfieldset[disabled] .btn-soundcloud.focus {\n background-color: #ff5500;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-soundcloud .badge {\n color: #ff5500;\n background-color: #fff;\n}\n.btn-tumblr {\n color: #fff;\n background-color: #2c4762;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr:focus,\n.btn-tumblr.focus {\n color: #fff;\n background-color: #1c2d3f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr:hover {\n color: #fff;\n background-color: #1c2d3f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr:active,\n.btn-tumblr.active,\n.open > .dropdown-toggle.btn-tumblr {\n color: #fff;\n background-color: #1c2d3f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr:active:hover,\n.btn-tumblr.active:hover,\n.open > .dropdown-toggle.btn-tumblr:hover,\n.btn-tumblr:active:focus,\n.btn-tumblr.active:focus,\n.open > .dropdown-toggle.btn-tumblr:focus,\n.btn-tumblr:active.focus,\n.btn-tumblr.active.focus,\n.open > .dropdown-toggle.btn-tumblr.focus {\n color: #fff;\n background-color: #111c26;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr:active,\n.btn-tumblr.active,\n.open > .dropdown-toggle.btn-tumblr {\n background-image: none;\n}\n.btn-tumblr.disabled:hover,\n.btn-tumblr[disabled]:hover,\nfieldset[disabled] .btn-tumblr:hover,\n.btn-tumblr.disabled:focus,\n.btn-tumblr[disabled]:focus,\nfieldset[disabled] .btn-tumblr:focus,\n.btn-tumblr.disabled.focus,\n.btn-tumblr[disabled].focus,\nfieldset[disabled] .btn-tumblr.focus {\n background-color: #2c4762;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-tumblr .badge {\n color: #2c4762;\n background-color: #fff;\n}\n.btn-twitter {\n color: #fff;\n background-color: #55acee;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter:focus,\n.btn-twitter.focus {\n color: #fff;\n background-color: #2795e9;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter:hover {\n color: #fff;\n background-color: #2795e9;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter:active,\n.btn-twitter.active,\n.open > .dropdown-toggle.btn-twitter {\n color: #fff;\n background-color: #2795e9;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter:active:hover,\n.btn-twitter.active:hover,\n.open > .dropdown-toggle.btn-twitter:hover,\n.btn-twitter:active:focus,\n.btn-twitter.active:focus,\n.open > .dropdown-toggle.btn-twitter:focus,\n.btn-twitter:active.focus,\n.btn-twitter.active.focus,\n.open > .dropdown-toggle.btn-twitter.focus {\n color: #fff;\n background-color: #1583d7;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter:active,\n.btn-twitter.active,\n.open > .dropdown-toggle.btn-twitter {\n background-image: none;\n}\n.btn-twitter.disabled:hover,\n.btn-twitter[disabled]:hover,\nfieldset[disabled] .btn-twitter:hover,\n.btn-twitter.disabled:focus,\n.btn-twitter[disabled]:focus,\nfieldset[disabled] .btn-twitter:focus,\n.btn-twitter.disabled.focus,\n.btn-twitter[disabled].focus,\nfieldset[disabled] .btn-twitter.focus {\n background-color: #55acee;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-twitter .badge {\n color: #55acee;\n background-color: #fff;\n}\n.btn-vimeo {\n color: #fff;\n background-color: #1ab7ea;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo:focus,\n.btn-vimeo.focus {\n color: #fff;\n background-color: #1295bf;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo:hover {\n color: #fff;\n background-color: #1295bf;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo:active,\n.btn-vimeo.active,\n.open > .dropdown-toggle.btn-vimeo {\n color: #fff;\n background-color: #1295bf;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo:active:hover,\n.btn-vimeo.active:hover,\n.open > .dropdown-toggle.btn-vimeo:hover,\n.btn-vimeo:active:focus,\n.btn-vimeo.active:focus,\n.open > .dropdown-toggle.btn-vimeo:focus,\n.btn-vimeo:active.focus,\n.btn-vimeo.active.focus,\n.open > .dropdown-toggle.btn-vimeo.focus {\n color: #fff;\n background-color: #0f7b9f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo:active,\n.btn-vimeo.active,\n.open > .dropdown-toggle.btn-vimeo {\n background-image: none;\n}\n.btn-vimeo.disabled:hover,\n.btn-vimeo[disabled]:hover,\nfieldset[disabled] .btn-vimeo:hover,\n.btn-vimeo.disabled:focus,\n.btn-vimeo[disabled]:focus,\nfieldset[disabled] .btn-vimeo:focus,\n.btn-vimeo.disabled.focus,\n.btn-vimeo[disabled].focus,\nfieldset[disabled] .btn-vimeo.focus {\n background-color: #1ab7ea;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vimeo .badge {\n color: #1ab7ea;\n background-color: #fff;\n}\n.btn-vk {\n color: #fff;\n background-color: #587ea3;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk:focus,\n.btn-vk.focus {\n color: #fff;\n background-color: #466482;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk:hover {\n color: #fff;\n background-color: #466482;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk:active,\n.btn-vk.active,\n.open > .dropdown-toggle.btn-vk {\n color: #fff;\n background-color: #466482;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk:active:hover,\n.btn-vk.active:hover,\n.open > .dropdown-toggle.btn-vk:hover,\n.btn-vk:active:focus,\n.btn-vk.active:focus,\n.open > .dropdown-toggle.btn-vk:focus,\n.btn-vk:active.focus,\n.btn-vk.active.focus,\n.open > .dropdown-toggle.btn-vk.focus {\n color: #fff;\n background-color: #3a526b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk:active,\n.btn-vk.active,\n.open > .dropdown-toggle.btn-vk {\n background-image: none;\n}\n.btn-vk.disabled:hover,\n.btn-vk[disabled]:hover,\nfieldset[disabled] .btn-vk:hover,\n.btn-vk.disabled:focus,\n.btn-vk[disabled]:focus,\nfieldset[disabled] .btn-vk:focus,\n.btn-vk.disabled.focus,\n.btn-vk[disabled].focus,\nfieldset[disabled] .btn-vk.focus {\n background-color: #587ea3;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-vk .badge {\n color: #587ea3;\n background-color: #fff;\n}\n.btn-yahoo {\n color: #fff;\n background-color: #720e9e;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo:focus,\n.btn-yahoo.focus {\n color: #fff;\n background-color: #500a6f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo:hover {\n color: #fff;\n background-color: #500a6f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo:active,\n.btn-yahoo.active,\n.open > .dropdown-toggle.btn-yahoo {\n color: #fff;\n background-color: #500a6f;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo:active:hover,\n.btn-yahoo.active:hover,\n.open > .dropdown-toggle.btn-yahoo:hover,\n.btn-yahoo:active:focus,\n.btn-yahoo.active:focus,\n.open > .dropdown-toggle.btn-yahoo:focus,\n.btn-yahoo:active.focus,\n.btn-yahoo.active.focus,\n.open > .dropdown-toggle.btn-yahoo.focus {\n color: #fff;\n background-color: #39074e;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo:active,\n.btn-yahoo.active,\n.open > .dropdown-toggle.btn-yahoo {\n background-image: none;\n}\n.btn-yahoo.disabled:hover,\n.btn-yahoo[disabled]:hover,\nfieldset[disabled] .btn-yahoo:hover,\n.btn-yahoo.disabled:focus,\n.btn-yahoo[disabled]:focus,\nfieldset[disabled] .btn-yahoo:focus,\n.btn-yahoo.disabled.focus,\n.btn-yahoo[disabled].focus,\nfieldset[disabled] .btn-yahoo.focus {\n background-color: #720e9e;\n border-color: rgba(0, 0, 0, 0.2);\n}\n.btn-yahoo .badge {\n color: #720e9e;\n background-color: #fff;\n}\n/*\n * Plugin: Full Calendar\n * ---------------------\n */\n.fc-button {\n background: #f4f4f4;\n background-image: none;\n color: #444;\n border-color: #ddd;\n border-bottom-color: #ddd;\n}\n.fc-button:hover,\n.fc-button:active,\n.fc-button.hover {\n background-color: #e9e9e9;\n}\n.fc-header-title h2 {\n font-size: 15px;\n line-height: 1.6em;\n color: #666;\n margin-left: 10px;\n}\n.fc-header-right {\n padding-right: 10px;\n}\n.fc-header-left {\n padding-left: 10px;\n}\n.fc-widget-header {\n background: #fafafa;\n}\n.fc-grid {\n width: 100%;\n border: 0;\n}\n.fc-widget-header:first-of-type,\n.fc-widget-content:first-of-type {\n border-left: 0;\n border-right: 0;\n}\n.fc-widget-header:last-of-type,\n.fc-widget-content:last-of-type {\n border-right: 0;\n}\n.fc-toolbar {\n padding: 10px;\n margin: 0;\n}\n.fc-day-number {\n font-size: 20px;\n font-weight: 300;\n padding-right: 10px;\n}\n.fc-color-picker {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.fc-color-picker > li {\n float: left;\n font-size: 30px;\n margin-right: 5px;\n line-height: 30px;\n}\n.fc-color-picker > li .fa {\n -webkit-transition: -webkit-transform linear 0.3s;\n -moz-transition: -moz-transform linear 0.3s;\n -o-transition: -o-transform linear 0.3s;\n transition: transform linear 0.3s;\n}\n.fc-color-picker > li .fa:hover {\n -webkit-transform: rotate(30deg);\n -ms-transform: rotate(30deg);\n -o-transform: rotate(30deg);\n transform: rotate(30deg);\n}\n#add-new-event {\n -webkit-transition: all linear 0.3s;\n -o-transition: all linear 0.3s;\n transition: all linear 0.3s;\n}\n.external-event {\n padding: 5px 10px;\n font-weight: bold;\n margin-bottom: 4px;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n border-radius: 3px;\n cursor: move;\n}\n.external-event:hover {\n box-shadow: inset 0 0 90px rgba(0, 0, 0, 0.2);\n}\n/*\n * Plugin: Select2\n * ---------------\n */\n.select2-container--default.select2-container--focus,\n.select2-selection.select2-container--focus,\n.select2-container--default:focus,\n.select2-selection:focus,\n.select2-container--default:active,\n.select2-selection:active {\n outline: none;\n}\n.select2-container--default .select2-selection--single,\n.select2-selection .select2-selection--single {\n border: 1px solid #d2d6de;\n border-radius: 0;\n padding: 6px 12px;\n height: 34px;\n}\n.select2-container--default.select2-container--open {\n border-color: #3c8dbc;\n}\n.select2-dropdown {\n border: 1px solid #d2d6de;\n border-radius: 0;\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: #3c8dbc;\n color: white;\n}\n.select2-results__option {\n padding: 6px 12px;\n user-select: none;\n -webkit-user-select: none;\n}\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-left: 0;\n padding-right: 0;\n height: auto;\n margin-top: -4px;\n}\n.select2-container[dir=\"rtl\"] .select2-selection--single .select2-selection__rendered {\n padding-right: 6px;\n padding-left: 20px;\n}\n.select2-container--default .select2-selection--single .select2-selection__arrow {\n height: 28px;\n right: 3px;\n}\n.select2-container--default .select2-selection--single .select2-selection__arrow b {\n margin-top: 0;\n}\n.select2-dropdown .select2-search__field,\n.select2-search--inline .select2-search__field {\n border: 1px solid #d2d6de;\n}\n.select2-dropdown .select2-search__field:focus,\n.select2-search--inline .select2-search__field:focus {\n outline: none;\n}\n.select2-container--default.select2-container--focus .select2-selection--multiple,\n.select2-container--default .select2-search--dropdown .select2-search__field {\n border-color: #3c8dbc !important;\n}\n.select2-container--default .select2-results__option[aria-disabled=true] {\n color: #999;\n}\n.select2-container--default .select2-results__option[aria-selected=true] {\n background-color: #ddd;\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n color: #444;\n}\n.select2-container--default .select2-selection--multiple {\n border: 1px solid #d2d6de;\n border-radius: 0;\n}\n.select2-container--default .select2-selection--multiple:focus {\n border-color: #3c8dbc;\n}\n.select2-container--default.select2-container--focus .select2-selection--multiple {\n border-color: #d2d6de;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #3c8dbc;\n border-color: #367fa9;\n padding: 1px 10px;\n color: #fff;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {\n margin-right: 5px;\n color: rgba(255, 255, 255, 0.7);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {\n color: #fff;\n}\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-right: 10px;\n}\n.box .datepicker-inline,\n.box .datepicker-inline .datepicker-days,\n.box .datepicker-inline > table,\n.box .datepicker-inline .datepicker-days > table {\n width: 100%;\n}\n.box .datepicker-inline td:hover,\n.box .datepicker-inline .datepicker-days td:hover,\n.box .datepicker-inline > table td:hover,\n.box .datepicker-inline .datepicker-days > table td:hover {\n background-color: rgba(255, 255, 255, 0.3);\n}\n.box .datepicker-inline td.day.old,\n.box .datepicker-inline .datepicker-days td.day.old,\n.box .datepicker-inline > table td.day.old,\n.box .datepicker-inline .datepicker-days > table td.day.old,\n.box .datepicker-inline td.day.new,\n.box .datepicker-inline .datepicker-days td.day.new,\n.box .datepicker-inline > table td.day.new,\n.box .datepicker-inline .datepicker-days > table td.day.new {\n color: #777;\n}\n/*\n * General: Miscellaneous\n * ----------------------\n */\n.pad {\n padding: 10px;\n}\n.margin {\n margin: 10px;\n}\n.margin-bottom {\n margin-bottom: 20px;\n}\n.margin-bottom-none {\n margin-bottom: 0;\n}\n.margin-r-5 {\n margin-right: 5px;\n}\n.inline {\n display: inline;\n}\n.description-block {\n display: block;\n margin: 10px 0;\n text-align: center;\n}\n.description-block.margin-bottom {\n margin-bottom: 25px;\n}\n.description-block > .description-header {\n margin: 0;\n padding: 0;\n font-weight: 600;\n font-size: 16px;\n}\n.description-block > .description-text {\n text-transform: uppercase;\n}\n.bg-red,\n.bg-yellow,\n.bg-aqua,\n.bg-blue,\n.bg-light-blue,\n.bg-green,\n.bg-navy,\n.bg-teal,\n.bg-olive,\n.bg-lime,\n.bg-orange,\n.bg-fuchsia,\n.bg-purple,\n.bg-maroon,\n.bg-black,\n.bg-red-active,\n.bg-yellow-active,\n.bg-aqua-active,\n.bg-blue-active,\n.bg-light-blue-active,\n.bg-green-active,\n.bg-navy-active,\n.bg-teal-active,\n.bg-olive-active,\n.bg-lime-active,\n.bg-orange-active,\n.bg-fuchsia-active,\n.bg-purple-active,\n.bg-maroon-active,\n.bg-black-active,\n.callout.callout-danger,\n.callout.callout-warning,\n.callout.callout-info,\n.callout.callout-success,\n.alert-success,\n.alert-danger,\n.alert-error,\n.alert-warning,\n.alert-info,\n.label-danger,\n.label-info,\n.label-warning,\n.label-primary,\n.label-success,\n.modal-primary .modal-body,\n.modal-primary .modal-header,\n.modal-primary .modal-footer,\n.modal-warning .modal-body,\n.modal-warning .modal-header,\n.modal-warning .modal-footer,\n.modal-info .modal-body,\n.modal-info .modal-header,\n.modal-info .modal-footer,\n.modal-success .modal-body,\n.modal-success .modal-header,\n.modal-success .modal-footer,\n.modal-danger .modal-body,\n.modal-danger .modal-header,\n.modal-danger .modal-footer {\n color: #fff !important;\n}\n.bg-gray {\n color: #000;\n background-color: #d2d6de !important;\n}\n.bg-gray-light {\n background-color: #f7f7f7;\n}\n.bg-black {\n background-color: #111 !important;\n}\n.bg-red,\n.callout.callout-danger,\n.alert-danger,\n.alert-error,\n.label-danger,\n.modal-danger .modal-body {\n background-color: #dd4b39 !important;\n}\n.bg-yellow,\n.callout.callout-warning,\n.alert-warning,\n.label-warning,\n.modal-warning .modal-body {\n background-color: #f39c12 !important;\n}\n.bg-aqua,\n.callout.callout-info,\n.alert-info,\n.label-info,\n.modal-info .modal-body {\n background-color: #00c0ef !important;\n}\n.bg-blue {\n background-color: #0073b7 !important;\n}\n.bg-light-blue,\n.label-primary,\n.modal-primary .modal-body {\n background-color: #3c8dbc !important;\n}\n.bg-green,\n.callout.callout-success,\n.alert-success,\n.label-success,\n.modal-success .modal-body {\n background-color: #00a65a !important;\n}\n.bg-navy {\n background-color: #001F3F !important;\n}\n.bg-teal {\n background-color: #39CCCC !important;\n}\n.bg-olive {\n background-color: #3D9970 !important;\n}\n.bg-lime {\n background-color: #01FF70 !important;\n}\n.bg-orange {\n background-color: #FF851B !important;\n}\n.bg-fuchsia {\n background-color: #F012BE !important;\n}\n.bg-purple {\n background-color: #605ca8 !important;\n}\n.bg-maroon {\n background-color: #D81B60 !important;\n}\n.bg-gray-active {\n color: #000;\n background-color: #b5bbc8 !important;\n}\n.bg-black-active {\n background-color: #000000 !important;\n}\n.bg-red-active,\n.modal-danger .modal-header,\n.modal-danger .modal-footer {\n background-color: #d33724 !important;\n}\n.bg-yellow-active,\n.modal-warning .modal-header,\n.modal-warning .modal-footer {\n background-color: #db8b0b !important;\n}\n.bg-aqua-active,\n.modal-info .modal-header,\n.modal-info .modal-footer {\n background-color: #00a7d0 !important;\n}\n.bg-blue-active {\n background-color: #005384 !important;\n}\n.bg-light-blue-active,\n.modal-primary .modal-header,\n.modal-primary .modal-footer {\n background-color: #357ca5 !important;\n}\n.bg-green-active,\n.modal-success .modal-header,\n.modal-success .modal-footer {\n background-color: #008d4c !important;\n}\n.bg-navy-active {\n background-color: #001a35 !important;\n}\n.bg-teal-active {\n background-color: #30bbbb !important;\n}\n.bg-olive-active {\n background-color: #368763 !important;\n}\n.bg-lime-active {\n background-color: #00e765 !important;\n}\n.bg-orange-active {\n background-color: #ff7701 !important;\n}\n.bg-fuchsia-active {\n background-color: #db0ead !important;\n}\n.bg-purple-active {\n background-color: #555299 !important;\n}\n.bg-maroon-active {\n background-color: #ca195a !important;\n}\n[class^=\"bg-\"].disabled {\n opacity: 0.65;\n filter: alpha(opacity=65);\n}\n.text-red {\n color: #dd4b39 !important;\n}\n.text-yellow {\n color: #f39c12 !important;\n}\n.text-aqua {\n color: #00c0ef !important;\n}\n.text-blue {\n color: #0073b7 !important;\n}\n.text-black {\n color: #111 !important;\n}\n.text-light-blue {\n color: #3c8dbc !important;\n}\n.text-green {\n color: #00a65a !important;\n}\n.text-gray {\n color: #d2d6de !important;\n}\n.text-navy {\n color: #001F3F !important;\n}\n.text-teal {\n color: #39CCCC !important;\n}\n.text-olive {\n color: #3D9970 !important;\n}\n.text-lime {\n color: #01FF70 !important;\n}\n.text-orange {\n color: #FF851B !important;\n}\n.text-fuchsia {\n color: #F012BE !important;\n}\n.text-purple {\n color: #605ca8 !important;\n}\n.text-maroon {\n color: #D81B60 !important;\n}\n.link-muted {\n color: #7a869d;\n}\n.link-muted:hover,\n.link-muted:focus {\n color: #606c84;\n}\n.link-black {\n color: #666;\n}\n.link-black:hover,\n.link-black:focus {\n color: #999;\n}\n.hide {\n display: none !important;\n}\n.no-border {\n border: 0 !important;\n}\n.no-padding {\n padding: 0 !important;\n}\n.no-margin {\n margin: 0 !important;\n}\n.no-shadow {\n box-shadow: none !important;\n}\n.list-unstyled,\n.chart-legend,\n.contacts-list,\n.users-list,\n.mailbox-attachments {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.list-group-unbordered > .list-group-item {\n border-left: 0;\n border-right: 0;\n border-radius: 0;\n padding-left: 0;\n padding-right: 0;\n}\n.flat {\n border-radius: 0 !important;\n}\n.text-bold,\n.text-bold.table td,\n.text-bold.table th {\n font-weight: 700;\n}\n.text-sm {\n font-size: 12px;\n}\n.jqstooltip {\n padding: 5px !important;\n width: auto !important;\n height: auto !important;\n}\n.bg-teal-gradient {\n background: #39CCCC !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #39CCCC), color-stop(1, #7adddd)) !important;\n background: -ms-linear-gradient(bottom, #39CCCC, #7adddd) !important;\n background: -moz-linear-gradient(center bottom, #39CCCC 0%, #7adddd 100%) !important;\n background: -o-linear-gradient(#7adddd, #39CCCC) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39CCCC', GradientType=0) !important;\n color: #fff;\n}\n.bg-light-blue-gradient {\n background: #3c8dbc !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;\n background: -ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;\n background: -moz-linear-gradient(center bottom, #3c8dbc 0%, #67a8ce 100%) !important;\n background: -o-linear-gradient(#67a8ce, #3c8dbc) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;\n color: #fff;\n}\n.bg-blue-gradient {\n background: #0073b7 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;\n background: -ms-linear-gradient(bottom, #0073b7, #0089db) !important;\n background: -moz-linear-gradient(center bottom, #0073b7 0%, #0089db 100%) !important;\n background: -o-linear-gradient(#0089db, #0073b7) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;\n color: #fff;\n}\n.bg-aqua-gradient {\n background: #00c0ef !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;\n background: -ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;\n background: -moz-linear-gradient(center bottom, #00c0ef 0%, #14d1ff 100%) !important;\n background: -o-linear-gradient(#14d1ff, #00c0ef) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;\n color: #fff;\n}\n.bg-yellow-gradient {\n background: #f39c12 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;\n background: -ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;\n background: -moz-linear-gradient(center bottom, #f39c12 0%, #f7bc60 100%) !important;\n background: -o-linear-gradient(#f7bc60, #f39c12) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;\n color: #fff;\n}\n.bg-purple-gradient {\n background: #605ca8 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;\n background: -ms-linear-gradient(bottom, #605ca8, #9491c4) !important;\n background: -moz-linear-gradient(center bottom, #605ca8 0%, #9491c4 100%) !important;\n background: -o-linear-gradient(#9491c4, #605ca8) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;\n color: #fff;\n}\n.bg-green-gradient {\n background: #00a65a !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;\n background: -ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;\n background: -moz-linear-gradient(center bottom, #00a65a 0%, #00ca6d 100%) !important;\n background: -o-linear-gradient(#00ca6d, #00a65a) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;\n color: #fff;\n}\n.bg-red-gradient {\n background: #dd4b39 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;\n background: -ms-linear-gradient(bottom, #dd4b39, #e47365) !important;\n background: -moz-linear-gradient(center bottom, #dd4b39 0%, #e47365 100%) !important;\n background: -o-linear-gradient(#e47365, #dd4b39) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;\n color: #fff;\n}\n.bg-black-gradient {\n background: #111 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;\n background: -ms-linear-gradient(bottom, #111, #2b2b2b) !important;\n background: -moz-linear-gradient(center bottom, #111 0%, #2b2b2b 100%) !important;\n background: -o-linear-gradient(#2b2b2b, #111) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111', GradientType=0) !important;\n color: #fff;\n}\n.bg-maroon-gradient {\n background: #D81B60 !important;\n background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #D81B60), color-stop(1, #e73f7c)) !important;\n background: -ms-linear-gradient(bottom, #D81B60, #e73f7c) !important;\n background: -moz-linear-gradient(center bottom, #D81B60 0%, #e73f7c 100%) !important;\n background: -o-linear-gradient(#e73f7c, #D81B60) !important;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#D81B60', GradientType=0) !important;\n color: #fff;\n}\n.description-block .description-icon {\n font-size: 16px;\n}\n.no-pad-top {\n padding-top: 0;\n}\n.position-static {\n position: static !important;\n}\n.list-header {\n font-size: 15px;\n padding: 10px 4px;\n font-weight: bold;\n color: #666;\n}\n.list-seperator {\n height: 1px;\n background: #f4f4f4;\n margin: 15px 0 9px 0;\n}\n.list-link > a {\n padding: 4px;\n color: #777;\n}\n.list-link > a:hover {\n color: #222;\n}\n.font-light {\n font-weight: 300;\n}\n.user-block:before,\n.user-block:after {\n content: \" \";\n display: table;\n}\n.user-block:after {\n clear: both;\n}\n.user-block img {\n width: 40px;\n height: 40px;\n float: left;\n}\n.user-block .username,\n.user-block .description,\n.user-block .comment {\n display: block;\n margin-left: 50px;\n}\n.user-block .username {\n font-size: 16px;\n font-weight: 600;\n}\n.user-block .description {\n color: #999;\n font-size: 13px;\n}\n.user-block.user-block-sm .username,\n.user-block.user-block-sm .description,\n.user-block.user-block-sm .comment {\n margin-left: 40px;\n}\n.user-block.user-block-sm .username {\n font-size: 14px;\n}\n.img-sm,\n.img-md,\n.img-lg,\n.box-comments .box-comment img,\n.user-block.user-block-sm img {\n float: left;\n}\n.img-sm,\n.box-comments .box-comment img,\n.user-block.user-block-sm img {\n width: 30px !important;\n height: 30px !important;\n}\n.img-sm + .img-push {\n margin-left: 40px;\n}\n.img-md {\n width: 60px;\n height: 60px;\n}\n.img-md + .img-push {\n margin-left: 70px;\n}\n.img-lg {\n width: 100px;\n height: 100px;\n}\n.img-lg + .img-push {\n margin-left: 110px;\n}\n.img-bordered {\n border: 3px solid #d2d6de;\n padding: 3px;\n}\n.img-bordered-sm {\n border: 2px solid #d2d6de;\n padding: 2px;\n}\n.attachment-block {\n border: 1px solid #f4f4f4;\n padding: 5px;\n margin-bottom: 10px;\n background: #f7f7f7;\n}\n.attachment-block .attachment-img {\n max-width: 100px;\n max-height: 100px;\n height: auto;\n float: left;\n}\n.attachment-block .attachment-pushed {\n margin-left: 110px;\n}\n.attachment-block .attachment-heading {\n margin: 0;\n}\n.attachment-block .attachment-text {\n color: #555;\n}\n.connectedSortable {\n min-height: 100px;\n}\n.ui-helper-hidden-accessible {\n border: 0;\n clip: rect(0 0 0 0);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n}\n.sort-highlight {\n background: #f4f4f4;\n border: 1px dashed #ddd;\n margin-bottom: 10px;\n}\n.full-opacity-hover {\n opacity: 0.65;\n filter: alpha(opacity=65);\n}\n.full-opacity-hover:hover {\n opacity: 1;\n filter: alpha(opacity=100);\n}\n.chart {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.chart svg,\n.chart canvas {\n width: 100% !important;\n}\nhr {\n border-top: 1px solid #555555;\n}\n#red .slider-selection {\n background: #f56954;\n}\n#blue .slider-selection {\n background: #3c8dbc;\n}\n#green .slider-selection {\n background: #00a65a;\n}\n#yellow .slider-selection {\n background: #f39c12;\n}\n#aqua .slider-selection {\n background: #00c0ef;\n}\n#purple .slider-selection {\n background: #932ab6;\n}\n/*\n * Misc: print\n * -----------\n */\n@media print {\n .no-print,\n .main-sidebar,\n .left-side,\n .main-header,\n .content-header {\n display: none !important;\n }\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-left: 0 !important;\n min-height: 0 !important;\n -webkit-transform: translate(0, 0) !important;\n -ms-transform: translate(0, 0) !important;\n -o-transform: translate(0, 0) !important;\n transform: translate(0, 0) !important;\n }\n .fixed .content-wrapper,\n .fixed .right-side {\n padding-top: 0 !important;\n }\n .invoice {\n width: 100%;\n border: 0;\n margin: 0;\n padding: 0;\n }\n .invoice-col {\n float: left;\n width: 33.3333333%;\n }\n .table-responsive {\n overflow: auto;\n }\n .table-responsive > .table tr th,\n .table-responsive > .table tr td {\n white-space: normal !important;\n }\n}\n","/*\n * Core: General Layout Style\n * -------------------------\n */\nhtml,\nbody {\n height: 100%;\n .layout-boxed & {\n height: 100%;\n }\n}\n\nbody {\n font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-weight: 400;\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n/* Layout */\n.wrapper {\n .clearfix();\n height: 100%;\n position: relative;\n overflow-x: hidden;\n overflow-y: auto;\n .layout-boxed & {\n max-width: 1250px;\n margin: 0 auto;\n min-height: 100%;\n box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);\n position: relative;\n }\n}\n\n.layout-boxed {\n background-color: @sidebar-light-bg;\n}\n\n/*\n * Content Wrapper - contains the main content\n */\n.content-wrapper,\n.main-footer {\n // Using disposable variable to join statements with a comma\n @transition-rule: @transition-speed @transition-fn,\n margin @transition-speed @transition-fn;\n .transition-transform(@transition-rule);\n margin-left: @sidebar-width;\n z-index: 820;\n // Top nav layout\n .layout-top-nav & {\n margin-left: 0;\n }\n @media (max-width: @screen-xs-max) {\n margin-left: 0;\n }\n // When opening the sidebar on large screens\n .sidebar-collapse & {\n @media (min-width: @screen-sm) {\n margin-left: 0;\n }\n }\n // When opening the sidebar on small screens\n .sidebar-open & {\n @media (max-width: @screen-xs-max) {\n .translate(@sidebar-width, 0);\n }\n }\n}\n\n.content-wrapper {\n min-height: ~\"calc(100vh - 101px)\";\n background-color: @content-bg;\n z-index: 800;\n}\n\n@media (max-width: @screen-header-collapse) {\n .content-wrapper {\n min-height: ~\"calc(100vh - 151px)\";\n }\n}\n\n.main-footer {\n background: #fff;\n padding: 15px;\n color: #444;\n border-top: 1px solid @gray-lte;\n}\n\n/* Fixed layout */\n.fixed {\n .main-header,\n .main-sidebar,\n .left-side {\n position: fixed;\n }\n .main-header {\n top: 0;\n right: 0;\n left: 0;\n }\n .content-wrapper,\n .right-side {\n padding-top: 50px;\n @media (max-width: @screen-header-collapse) {\n padding-top: 100px;\n }\n }\n &.layout-boxed {\n .wrapper {\n max-width: 100%;\n }\n }\n .wrapper {\n overflow: hidden;\n }\n}\n\n.hold-transition {\n .content-wrapper,\n .right-side,\n .main-footer,\n .main-sidebar,\n .left-side,\n .main-header .navbar,\n .main-header .logo,\n .menu-open .fa-angle-left {\n /* Fix for IE */\n .transition(none);\n }\n}\n\n/* Content */\n.content {\n min-height: 250px;\n padding: 15px;\n .container-fixed(@grid-gutter-width);\n}\n\n/* H1 - H6 font */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: 'Source Sans Pro', sans-serif;\n}\n\n/* General Links */\na {\n color: @link-color;\n}\n\na:hover,\na:active,\na:focus {\n outline: none;\n text-decoration: none;\n color: @link-hover-color;\n}\n\n/* Page Header */\n.page-header {\n margin: 10px 0 20px 0;\n font-size: 22px;\n\n > small {\n color: #666;\n display: block;\n margin-top: 5px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","/*\n * Component: Main Header\n * ----------------------\n */\n\n.main-header {\n position: relative;\n max-height: 100px;\n z-index: 1030;\n //Navbar\n .navbar {\n .transition(margin-left @transition-speed @transition-fn);\n margin-bottom: 0;\n margin-left: @sidebar-width;\n border: none;\n min-height: @navbar-height;\n border-radius: 0;\n .layout-top-nav & {\n margin-left: 0;\n }\n }\n //Navbar search text input\n #navbar-search-input.form-control {\n background: rgba(255, 255, 255, .2);\n border-color: transparent;\n &:focus,\n &:active {\n border-color: rgba(0, 0, 0, .1);\n background: rgba(255, 255, 255, .9);\n }\n &::-moz-placeholder {\n color: #ccc;\n opacity: 1;\n }\n &:-ms-input-placeholder {\n color: #ccc;\n }\n &::-webkit-input-placeholder {\n color: #ccc;\n }\n }\n //Navbar Right Menu\n .navbar-custom-menu,\n .navbar-right {\n float: right;\n @media (max-width: @screen-sm-max) {\n a {\n color: inherit;\n background: transparent;\n }\n }\n }\n .navbar-right {\n @media (max-width: @screen-header-collapse) {\n float: none;\n .navbar-collapse & {\n margin: 7.5px -15px;\n }\n\n > li {\n color: inherit;\n border: 0;\n }\n }\n }\n //Navbar toggle button\n .sidebar-toggle {\n float: left;\n background-color: transparent;\n background-image: none;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n //Add the fontawesome bars icon\n font-family: fontAwesome;\n &:before {\n content: \"\\f0c9\";\n }\n &:hover {\n color: #fff;\n }\n &:focus,\n &:active {\n background: transparent;\n }\n\n &.fa5 {\n font-family: \"Font Awesome\\ 5 Free\";\n &:before {\n content: \"\\f0c9\";\n font-weight: 900;\n }\n }\n }\n .sidebar-toggle .icon-bar {\n display: none;\n }\n //Navbar User Menu\n .navbar .nav > li.user > a {\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n\n //Labels in navbar\n .navbar .nav > li > a > .label {\n position: absolute;\n top: 9px;\n right: 7px;\n text-align: center;\n font-size: 9px;\n padding: 2px 3px;\n line-height: .9;\n }\n\n //Logo bar\n .logo {\n .transition(width @transition-speed @transition-fn);\n display: block;\n float: left;\n height: @navbar-height;\n font-size: 20px;\n line-height: 50px;\n text-align: center;\n width: @sidebar-width;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n padding: 0 15px;\n font-weight: 300;\n overflow: hidden;\n\n img {\n padding: 4px;\n object-fit: contain;\n margin: 0 auto;\n }\n\n //Add support to sidebar mini by allowing the user to create\n //2 logo designs. mini and lg\n .logo-lg {\n //should be visibile when sidebar isn't collapsed\n display: block;\n\n img {\n max-width: 200px;\n max-height: 50px;\n }\n .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: -5px;\n }\n }\n .logo-mini {\n display: none;\n\n img {\n max-width: 50px;\n max-height: 50px;\n }\n .brandlogo-image {\n margin-top: 8px;\n margin-right: 10px;\n margin-left: 10px;\n }\n }\n\n .brandlogo-image {\n float: left;\n height: 34px;\n width: auto;\n }\n }\n //Navbar Brand. Alternative logo with layout-top-nav\n .navbar-brand {\n color: #fff;\n }\n}\n\n// Content Header\n.content-header {\n position: relative;\n padding: 15px 15px 0 15px;\n // Header Text\n > h1 {\n margin: 0;\n font-size: 24px;\n > small {\n font-size: 15px;\n display: inline-block;\n padding-left: 4px;\n font-weight: 300;\n }\n }\n\n > .breadcrumb {\n float: right;\n background: transparent;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 12px;\n padding: 7px 5px;\n position: absolute;\n top: 15px;\n right: 10px;\n .border-radius(2px);\n > li > a {\n color: #444;\n text-decoration: none;\n display: inline-block;\n > .fa, > .glyphicon, > .ion {\n margin-right: 5px;\n }\n }\n > li + li:before {\n content: '>\\00a0';\n }\n }\n\n @media (max-width: @screen-sm-max) {\n > .breadcrumb {\n position: relative;\n margin-top: 5px;\n top: 0;\n right: 0;\n float: none;\n background: @gray-lte;\n padding-left: 10px;\n li:before {\n color: darken(@gray-lte, 20%);\n }\n }\n }\n}\n\n.navbar-toggle {\n color: #fff;\n border: 0;\n margin: 0;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n}\n\n//Control navbar scaffolding on x-small screens\n@media (max-width: @screen-sm-max) {\n .navbar-custom-menu .navbar-nav > li {\n float: left;\n }\n\n //Dont't let links get full width\n .navbar-custom-menu .navbar-nav {\n margin: 0;\n float: left;\n }\n\n .navbar-custom-menu .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n line-height: 20px;\n }\n}\n\n// Collapse header\n@media (max-width: @screen-header-collapse) {\n .main-header {\n position: relative;\n .logo,\n .navbar {\n width: 100%;\n float: none;\n }\n .navbar {\n margin: 0;\n }\n .navbar-custom-menu {\n float: right;\n }\n }\n}\n\n.navbar-collapse.pull-left {\n @media (max-width: @screen-sm-max) {\n float: none !important;\n + .navbar-custom-menu {\n display: block;\n position: absolute;\n top: 0;\n right: 40px;\n }\n }\n}\n","//AdminLTE mixins\n//===============\n\n//Changes the color and the hovering properties of the navbar\n.navbar-variant(@color; @font-color: rgba(255, 255, 255, 0.8); @hover-color: #f6f6f6; @hover-bg: rgba(0, 0, 0, 0.1)) {\n background-color: @color;\n //Navbar links\n .nav > li > a {\n color: @font-color;\n }\n\n .nav > li > a:hover,\n .nav > li > a:active,\n .nav > li > a:focus,\n .nav .open > a,\n .nav .open > a:hover,\n .nav .open > a:focus,\n .nav > .active > a {\n background: @hover-bg;\n color: @hover-color;\n }\n\n //Add color to the sidebar toggle button\n .sidebar-toggle {\n color: @font-color;\n &:hover {\n color: @hover-color;\n background: @hover-bg;\n }\n }\n}\n\n//Logo color variation\n.logo-variant(@bg-color; @color: #fff; @border-bottom-color: transparent; @border-bottom-width: 0) {\n background-color: @bg-color;\n color: @color;\n border-bottom: @border-bottom-width solid @border-bottom-color;\n\n &:hover {\n background-color: darken(@bg-color, 1%);\n }\n}\n\n//Box solid color variantion creator\n.box-solid-variant(@color; @text-color: #fff) {\n border: 1px solid @color;\n > .box-header {\n color: @text-color;\n background: @color;\n background-color: @color;\n a,\n .btn {\n color: @text-color;\n }\n }\n}\n\n//Direct Chat Variant\n.direct-chat-variant(@bg-color; @color: #fff) {\n .right > .direct-chat-text {\n background: @bg-color;\n border-color: @bg-color;\n color: @color;\n &:after,\n &:before {\n border-left-color: @bg-color;\n }\n }\n}\n\n//border radius creator\n.border-radius(@radius) {\n border-radius: @radius;\n}\n\n//Different radius each side\n.border-radius(@top-left, @top-right, @bottom-left, @bottom-right)\n{\n border-top-left-radius: @top-left;\n border-top-right-radius: @top-right;\n border-bottom-right-radius: @bottom-right;\n border-bottom-left-radius: @bottom-left;\n}\n\n//Gradient background\n.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {\n background: @color;\n background: -webkit-gradient(linear,\n left bottom,\n left top,\n color-stop(0, @start),\n color-stop(1, @stop));\n background: -ms-linear-gradient(bottom,\n @start,\n @stop);\n background: -moz-linear-gradient(center bottom,\n @start 0%,\n @stop 100%);\n background: -o-linear-gradient(@stop,\n @start);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",@stop,@start));\n}\n\n//Added 2.1.0\n//Skins Mixins\n\n//Dark Sidebar Mixin\n.skin-dark-sidebar(@link-hover-border-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-dark-bg;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: #fff;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n //Section Headning\n &.header {\n color: lighten(@sidebar-dark-bg, 20%);\n background: darken(@sidebar-dark-bg, 4%);\n }\n //links\n > a {\n border-left: 3px solid transparent;\n }\n //Hover and active states\n &:hover > a,\n &.active > a,\n &.menu-open > a {\n color: @sidebar-dark-hover-color;\n background: @sidebar-dark-hover-bg;\n }\n &.active > a {\n border-left-color: @link-hover-border-color;\n }\n //First Level Submenu\n > .treeview-menu {\n margin: 0 1px;\n background: @sidebar-dark-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-dark-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .sidebar-menu .treeview-menu {\n > li {\n > a {\n color: @sidebar-dark-submenu-color;\n }\n &.active > a, > a:hover {\n color: @sidebar-dark-submenu-hover-color;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid lighten(@sidebar-dark-bg, 10%);\n margin: 10px 10px;\n input[type=\"text\"], .btn {\n box-shadow: none;\n background-color: lighten(@sidebar-dark-bg, 10%);\n border: 1px solid transparent;\n height: 35px;\n //.transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n}\n\n//Light Sidebar Mixin\n.skin-light-sidebar(@icon-active-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-light-bg;\n }\n .content-wrapper,\n .main-footer {\n //border-left: 1px solid @gray-lte;\n }\n .main-sidebar {\n border-right: 1px solid @gray-lte;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: @sidebar-light-color;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n .transition(border-left-color .3s ease);\n //border-left: 3px solid transparent;\n //Section Headning\n &.header {\n color: lighten(@sidebar-light-color, 25%);\n background: @sidebar-light-bg;\n }\n //links\n > a {\n border-left: 3px solid transparent;\n font-weight: 600;\n }\n //Hover and active states\n &:hover > a,\n &.active > a {\n color: @sidebar-light-hover-color;\n background: @sidebar-light-hover-bg;\n }\n &:hover > a {\n\n }\n &.active {\n border-left-color: @icon-active-color;\n > a {\n font-weight: 600;\n }\n }\n //First Level Submenu\n > .treeview-menu {\n background: @sidebar-light-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-light-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .sidebar-menu .treeview-menu {\n > li {\n > a {\n color: @sidebar-light-submenu-color;\n }\n &.active > a,\n > a:hover {\n color: @sidebar-light-submenu-hover-color;\n }\n &.active > a {\n font-weight: 600;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid @gray-lte; //darken(@sidebar-light-bg, 5%);\n margin: 10px 10px;\n input[type=\"text\"],\n .btn {\n box-shadow: none;\n background-color: #fff; //darken(@sidebar-light-bg, 3%);\n border: 1px solid transparent;\n height: 35px;\n //.transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n @media (min-width: @screen-sm-min) {\n &.sidebar-mini.sidebar-collapse {\n .sidebar-menu > li > .treeview-menu {\n border-left: 1px solid @gray-lte;\n }\n }\n }\n}\n","/*\n * Component: Sidebar\n * ------------------\n */\n// Main Sidebar\n.main-sidebar {\n position: absolute;\n top: 0;\n left: 0;\n padding-top: 50px;\n min-height: 100%;\n width: @sidebar-width;\n z-index: 810;\n\n // Using disposable variable to join statements with a comma\n @transition-rule: @transition-speed @transition-fn, width @transition-speed @transition-fn;\n .transition-transform(@transition-rule);\n\n @media (max-width: @screen-header-collapse) {\n padding-top: 100px;\n }\n\n @media (max-width: @screen-xs-max) {\n .translate(-@sidebar-width, 0);\n }\n\n .sidebar-collapse & {\n @media (min-width: @screen-sm) {\n .translate(-@sidebar-width, 0);\n }\n }\n\n .sidebar-open & {\n @media (max-width: @screen-xs-max) {\n .translate(0, 0);\n }\n }\n}\n\n.sidebar {\n padding-bottom: 10px;\n}\n\n// Remove border from form\n.sidebar-form {\n input:focus {\n border-color: transparent;\n }\n}\n\n// Sidebar user panel\n.user-panel {\n position: relative;\n width: 100%;\n padding: 10px;\n overflow: hidden;\n .clearfix();\n > .image > img {\n width: 100%;\n max-width: 45px;\n height: auto;\n }\n > .info {\n padding: 5px 5px 5px 15px;\n line-height: 1;\n position: absolute;\n left: 55px;\n > p {\n font-weight: 600;\n margin-bottom: 9px;\n }\n > a {\n text-decoration: none;\n padding-right: 5px;\n margin-top: 3px;\n font-size: 11px;\n > .fa,\n > .ion,\n > .glyphicon {\n margin-right: 3px;\n }\n }\n }\n}\n\n// Sidebar menu\n.sidebar-menu {\n list-style: none;\n margin: 0;\n padding: 0;\n //First Level\n > li {\n position: relative;\n margin: 0;\n padding: 0;\n > a {\n padding: 12px 5px 12px 15px;\n display: block;\n > .fa,\n > .glyphicon,\n > .ion {\n width: 20px;\n }\n }\n .label,\n .badge {\n margin-right: 5px;\n }\n .badge {\n margin-top: 3px;\n }\n }\n li.header {\n padding: 10px 25px 10px 15px;\n font-size: 12px;\n }\n li > a > .fa-angle-left,\n li > a > .pull-right-container > .fa-angle-left {\n width: auto;\n height: auto;\n padding: 0;\n margin-right: 10px;\n .transition(transform .5s ease);\n }\n li > a > .fa-angle-left {\n position: absolute;\n top: 50%;\n right: 10px;\n margin-top: -8px;\n }\n\n .menu-open {\n > a > .fa-angle-left,\n > a > .pull-right-container > .fa-angle-left {\n .rotate(-90deg);\n }\n }\n .active > .treeview-menu {\n display: block;\n }\n}\n","/*\n * Component: Sidebar Mini\n */\n\n//Add sidebar-mini class to the body tag to activate this feature\n.sidebar-mini {\n //Sidebar mini should work only on devices larger than @screen-sm\n @media (min-width: @screen-sm) {\n //When the sidebar is collapsed...\n &.sidebar-collapse {\n\n //Apply the new margining to the main content and footer\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-left: 50px !important;\n z-index: 840;\n }\n\n //Modify the sidebar to shrink instead of disappearing\n .main-sidebar {\n //Don't go away! Just shrink\n .translate(0, 0);\n width: 50px !important;\n z-index: 850;\n }\n\n .sidebar-menu {\n > li {\n position: relative;\n > a {\n margin-right: 0;\n }\n > a > span {\n border-top-right-radius: 4px;\n }\n\n &:not(.treeview) {\n > a > span {\n border-bottom-right-radius: 4px;\n }\n }\n\n > .treeview-menu {\n // Add some padding to the treeview menu\n padding-top: 5px;\n padding-bottom: 5px;\n border-bottom-right-radius: 4px;\n }\n }\n }\n\n //Make the sidebar links, menus, labels, badges\n //and angle icons disappear\n .main-sidebar .user-panel > .info,\n .sidebar-form,\n .sidebar-menu > li > a > span,\n .sidebar-menu > li > .treeview-menu,\n .sidebar-menu > li > a > .pull-right,\n .sidebar-menu > li > a > span > .pull-right,\n .sidebar-menu li.header {\n display: none !important;\n -webkit-transform: translateZ(0);\n }\n\n .main-header {\n //Let's make the logo also shrink and the mini logo to appear\n .logo {\n width: 50px;\n > .logo-mini {\n display: block;\n margin-left: -15px;\n margin-right: -15px;\n font-size: 18px;\n }\n > .logo-lg {\n display: none;\n }\n }\n\n //Since the logo got smaller, we need to fix the navbar's position\n .navbar {\n margin-left: 50px;\n }\n }\n }\n }\n}\n\n@media (min-width: @screen-sm) {\n // Show menu items on hover\n .sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse {\n .sidebar-menu > li:hover {\n > a {\n //overflow: visible;\n }\n > a > span:not(.pull-right), //:not(.pull-right-container),\n > .treeview-menu {\n display: block !important;\n position: absolute;\n width: @sidebar-width - 50;\n left: 50px;\n }\n\n //position the header & treeview menus\n > a > span {\n top: 0;\n margin-left: -3px;\n padding: 12px 5px 12px 20px;\n background-color: inherit;\n }\n > a > .pull-right-container {\n //display: block!important;\n position: relative !important;\n float: right;\n width: auto !important;\n left: 200px - 20px !important;\n top: -22px !important;\n z-index: 900;\n > .label:not(:first-of-type) {\n display: none;\n }\n }\n > .treeview-menu {\n top: 44px;\n margin-left: 0;\n }\n }\n }\n}\n\n.sidebar-expanded-on-hover {\n .main-footer,\n .content-wrapper {\n margin-left: 50px;\n }\n .main-sidebar {\n box-shadow: @sidebar-expanded-shadow;\n }\n}\n\n//A fix for text overflow while transitioning from sidebar mini to full sidebar\n.sidebar-menu,\n.main-sidebar .user-panel,\n.sidebar-menu > li.header {\n white-space: nowrap;\n overflow: hidden;\n}\n\n.sidebar-menu:hover {\n overflow: visible;\n}\n\n.sidebar-form,\n.sidebar-menu > li.header {\n overflow: hidden;\n text-overflow: clip;\n}\n\n.sidebar-menu li > a {\n position: relative;\n > .pull-right-container {\n position: absolute;\n right: 10px;\n top: 50%;\n margin-top: -7px;\n }\n}\n","/*\n * Component: Control sidebar. By default, this is the right sidebar.\n */\n// The sidebar's background control class\n// This is a hack to make the background visible while scrolling\n.control-sidebar-bg {\n position: fixed;\n z-index: 1000;\n bottom: 0;\n}\n\n// Transitions\n.control-sidebar-bg,\n.control-sidebar {\n top: 0;\n right: -@control-sidebar-width;\n width: @control-sidebar-width;\n .transition(right @transition-speed ease-in-out);\n}\n\n// The sidebar\n.control-sidebar {\n position: absolute;\n padding-top: @navbar-height;\n z-index: 1010;\n // Fix position after header collapse\n @media (max-width: @screen-xs-max) {\n padding-top: @navbar-height + 50;\n }\n // Tab panes\n > .tab-content {\n padding: 10px 15px;\n }\n // Open state with slide over content effect\n &.control-sidebar-open {\n &,\n + .control-sidebar-bg {\n right: 0;\n }\n }\n}\n\n// Open without slide over content\n.control-sidebar-hold-transition {\n .control-sidebar-bg,\n .control-sidebar,\n .content-wrapper {\n transition: none;\n }\n\n}\n.control-sidebar-open {\n .control-sidebar-bg,\n .control-sidebar {\n right: 0;\n }\n @media (min-width: @screen-sm) {\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-right: @control-sidebar-width;\n }\n }\n}\n\n// Fixed Layout\n.fixed {\n .control-sidebar {\n position: fixed;\n height: 100%;\n overflow-y: auto;\n padding-bottom: 50px;\n }\n}\n\n// Control sidebar tabs\n.nav-tabs.control-sidebar-tabs {\n > li {\n &:first-of-type > a {\n &,\n &:hover,\n &:focus {\n border-left-width: 0;\n }\n }\n > a {\n .border-radius(0);\n\n // Hover and active states\n &,\n &:hover {\n border-top: none;\n border-right: none;\n border-left: 1px solid transparent;\n border-bottom: 1px solid transparent;\n }\n .icon {\n font-size: 16px;\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n border-top: none;\n border-right: none;\n border-bottom: none;\n }\n }\n }\n }\n // Remove responsiveness on small screens\n @media (max-width: @screen-sm) {\n display: table;\n > li {\n display: table-cell;\n }\n }\n}\n\n// Headings in the sidebar content\n.control-sidebar-heading {\n font-weight: 400;\n font-size: 16px;\n padding: 10px 0;\n margin-bottom: 10px;\n}\n\n// Subheadings\n.control-sidebar-subheading {\n display: block;\n font-weight: 400;\n font-size: 14px;\n}\n\n// Control Sidebar Menu\n.control-sidebar-menu {\n list-style: none;\n padding: 0;\n margin: 0 -15px;\n > li > a {\n .clearfix();\n display: block;\n padding: 10px 15px;\n > .control-sidebar-subheading {\n margin-top: 0;\n }\n }\n .menu-icon {\n float: left;\n width: 35px;\n height: 35px;\n border-radius: 50%;\n text-align: center;\n line-height: 35px;\n }\n .menu-info {\n margin-left: 45px;\n margin-top: 3px;\n > .control-sidebar-subheading {\n margin: 0;\n }\n > p {\n margin: 0;\n font-size: 11px;\n }\n }\n .progress {\n margin: 0;\n }\n}\n\n// Dark skin\n.control-sidebar-dark {\n color: @sidebar-dark-color;\n // Background\n &,\n + .control-sidebar-bg {\n background: @sidebar-dark-bg;\n }\n // Sidebar tabs\n .nav-tabs.control-sidebar-tabs {\n border-bottom: darken(@sidebar-dark-bg, 3%);\n > li {\n > a {\n background: darken(@sidebar-dark-bg, 5%);\n color: @sidebar-dark-color;\n // Hover and active states\n &,\n &:hover,\n &:focus {\n border-left-color: darken(@sidebar-dark-bg, 7%);\n border-bottom-color: darken(@sidebar-dark-bg, 7%);\n }\n &:hover,\n &:focus,\n &:active {\n background: darken(@sidebar-dark-bg, 3%);\n }\n &:hover {\n color: #fff;\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n background: @sidebar-dark-bg;\n color: #fff;\n }\n }\n }\n }\n }\n // Heading & subheading\n .control-sidebar-heading,\n .control-sidebar-subheading {\n color: #fff;\n }\n // Sidebar list\n .control-sidebar-menu {\n > li {\n > a {\n &:hover {\n background: @sidebar-dark-hover-bg;\n }\n .menu-info {\n > p {\n color: @sidebar-dark-color;\n }\n }\n }\n }\n }\n}\n\n// Light skin\n.control-sidebar-light {\n color: lighten(@sidebar-light-color, 10%);\n // Background\n &,\n + .control-sidebar-bg {\n background: @sidebar-light-bg;\n border-left: 1px solid @gray-lte;\n }\n // Sidebar tabs\n .nav-tabs.control-sidebar-tabs {\n border-bottom: @gray-lte;\n > li {\n > a {\n background: darken(@sidebar-light-bg, 5%);\n color: @sidebar-light-color;\n // Hover and active states\n &,\n &:hover,\n &:focus {\n border-left-color: @gray-lte;\n border-bottom-color: @gray-lte;\n }\n &:hover,\n &:focus,\n &:active {\n background: darken(@sidebar-light-bg, 3%);\n }\n }\n // Active state\n &.active {\n > a {\n &,\n &:hover,\n &:focus,\n &:active {\n background: @sidebar-light-bg;\n color: #111;\n }\n }\n }\n }\n }\n // Heading & subheading\n .control-sidebar-heading,\n .control-sidebar-subheading {\n color: #111;\n }\n // Sidebar list\n .control-sidebar-menu {\n margin-left: -14px;\n > li {\n > a {\n &:hover {\n background: @sidebar-light-hover-bg;\n }\n .menu-info {\n > p {\n color: lighten(@sidebar-light-color, 10%);\n }\n }\n }\n }\n }\n}\n","/*\n * Component: Dropdown menus\n * -------------------------\n */\n\n/*Dropdowns in general*/\n.dropdown-menu {\n box-shadow: none;\n border-color: #eee;\n > li > a {\n color: #777;\n }\n > li > a > .glyphicon,\n > li > a > .fa,\n > li > a > .ion {\n margin-right: 10px;\n }\n > li > a:hover {\n background-color: lighten(@gray-lte, 5%);\n color: #333;\n }\n > .divider {\n background-color: #eee;\n }\n}\n\n//Navbar custom dropdown menu\n.navbar-nav > .notifications-menu,\n.navbar-nav > .messages-menu,\n.navbar-nav > .tasks-menu {\n //fix width and padding\n > .dropdown-menu {\n > li {\n position: relative;\n }\n width: 280px;\n //Remove padding and margins\n padding: 0 0 0 0;\n margin: 0;\n top: 100%;\n }\n //Define header class\n > .dropdown-menu > li.header {\n .border-radius(4px; 4px; 0; 0);\n background-color: #ffffff;\n padding: 7px 10px;\n border-bottom: 1px solid #f4f4f4;\n color: #444444;\n font-size: 14px;\n }\n\n //Define footer class\n > .dropdown-menu > li.footer > a {\n .border-radius(0; 0; 4px; 4px);\n font-size: 12px;\n background-color: #fff;\n padding: 7px 10px;\n border-bottom: 1px solid #eeeeee;\n color: #444 !important;\n @media (max-width: @screen-sm-max) {\n background: #fff !important;\n color: #444 !important;\n }\n text-align: center;\n //Hover state\n &:hover {\n text-decoration: none;\n font-weight: normal;\n }\n }\n\n //Clear inner menu padding and margins\n > .dropdown-menu > li .menu {\n max-height: 200px;\n margin: 0;\n padding: 0;\n list-style: none;\n overflow-x: hidden;\n > li > a {\n display: block;\n white-space: nowrap; /* Prevent text from breaking */\n border-bottom: 1px solid #f4f4f4;\n // Hove state\n &:hover {\n background: #f4f4f4;\n text-decoration: none;\n }\n }\n }\n}\n\n//Notifications menu\n.navbar-nav > .notifications-menu {\n > .dropdown-menu > li .menu {\n // Links inside the menu\n > li > a {\n color: #444444;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 10px;\n // Icons inside the menu\n > .glyphicon,\n > .fa,\n > .ion {\n width: 20px;\n }\n }\n\n }\n}\n\n//Messages menu\n.navbar-nav > .messages-menu {\n //Inner menu\n > .dropdown-menu > li .menu {\n // Messages menu item\n > li > a {\n margin: 0;\n //line-height: 20px;\n padding: 10px 10px;\n // User image\n > div > img {\n margin: auto 10px auto auto;\n width: 40px;\n height: 40px;\n }\n // Message heading\n > h4 {\n padding: 0;\n margin: 0 0 0 45px;\n color: #444444;\n font-size: 15px;\n position: relative;\n // Small for message time display\n > small {\n color: #999999;\n font-size: 10px;\n position: absolute;\n top: 0;\n right: 0;\n }\n }\n\n > p {\n margin: 0 0 0 45px;\n font-size: 12px;\n color: #888888;\n }\n\n .clearfix();\n\n }\n\n }\n}\n\n//Tasks menu\n.navbar-nav > .tasks-menu {\n > .dropdown-menu > li .menu {\n > li > a {\n padding: 10px;\n\n > h3 {\n font-size: 14px;\n padding: 0;\n margin: 0 0 10px 0;\n color: #666666;\n }\n\n > .progress {\n padding: 0;\n margin: 0;\n }\n }\n }\n}\n\n//User menu\n.navbar-nav > .user-menu {\n > .dropdown-menu {\n .border-top-radius(0);\n padding: 1px 0 0 0;\n border-top-width: 0;\n width: 280px;\n\n &,\n > .user-body {\n .border-bottom-radius(4px);\n }\n // Header menu\n > li.user-header {\n height: 175px;\n padding: 10px;\n text-align: center;\n // User image\n > img {\n z-index: 5;\n height: 90px;\n width: 90px;\n border: 3px solid;\n border-color: transparent;\n border-color: rgba(255, 255, 255, 0.2);\n }\n > p {\n z-index: 5;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n font-size: 17px;\n //text-shadow: 2px 2px 3px #333333;\n margin-top: 10px;\n > small {\n display: block;\n font-size: 12px;\n }\n }\n }\n\n // Menu Body\n > .user-body {\n padding: 15px;\n border-bottom: 1px solid #f4f4f4;\n border-top: 1px solid #dddddd;\n .clearfix();\n a {\n color: #444 !important;\n @media (max-width: @screen-sm-max) {\n background: #fff !important;\n color: #444 !important;\n }\n }\n }\n\n // Menu Footer\n > .user-footer {\n background-color: #f9f9f9;\n padding: 10px;\n .clearfix();\n .btn-default {\n color: #666666;\n &:hover {\n @media (max-width: @screen-sm-max) {\n background-color: #f9f9f9;\n }\n }\n }\n }\n }\n .user-image {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n margin-top: -2px;\n @media (max-width: @screen-xs-max) {\n float: none;\n margin-right: 0;\n margin-top: -8px;\n line-height: 10px;\n }\n }\n}\n\n/* Add fade animation to dropdown menus by appending\n the class .animated-dropdown-menu to the .dropdown-menu ul (or ol)*/\n.open:not(.dropup) > .animated-dropdown-menu {\n backface-visibility: visible !important;\n .animation(flipInX .7s both);\n\n}\n\n@keyframes flipInX {\n 0% {\n transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n transition-timing-function: ease-in;\n opacity: 0;\n }\n\n 40% {\n transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n transition-timing-function: ease-in;\n }\n\n 60% {\n transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n\n 80% {\n transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n\n 100% {\n transform: perspective(400px);\n }\n}\n\n@-webkit-keyframes flipInX {\n 0% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n -webkit-transition-timing-function: ease-in;\n opacity: 0;\n }\n\n 40% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n -webkit-transition-timing-function: ease-in;\n }\n\n 60% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n opacity: 1;\n }\n\n 80% {\n -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n }\n\n 100% {\n -webkit-transform: perspective(400px);\n }\n}\n\n/* Fix dropdown menu in navbars */\n.navbar-custom-menu > .navbar-nav {\n > li {\n position: relative;\n > .dropdown-menu {\n position: absolute;\n right: 0;\n left: auto;\n }\n }\n}\n\n@media (max-width: @screen-sm-max) {\n .navbar-custom-menu > .navbar-nav {\n float: right;\n > li {\n position: static;\n > .dropdown-menu {\n position: absolute;\n right: 5%;\n left: auto;\n border: 1px solid #ddd;\n background: #fff;\n }\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","/*\n * Component: Form\n * ---------------\n */\n.form-control {\n .border-radius(@input-radius);\n box-shadow: none;\n border-color: @gray-lte;\n &:focus {\n border-color: @light-blue;\n box-shadow: none;\n }\n &::-moz-placeholder,\n &:-ms-input-placeholder,\n &::-webkit-input-placeholder {\n color: #bbb;\n opacity: 1;\n }\n\n &:not(select) {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n }\n}\n\n.form-group {\n &.has-success {\n label {\n color: @green;\n }\n .form-control,\n .input-group-addon {\n border-color: @green;\n box-shadow: none;\n }\n .help-block {\n color: @green;\n }\n }\n\n &.has-warning {\n label {\n color: @yellow;\n }\n .form-control,\n .input-group-addon {\n border-color: @yellow;\n box-shadow: none;\n }\n .help-block {\n color: @yellow;\n }\n }\n\n &.has-error {\n label {\n color: @red;\n }\n .form-control,\n .input-group-addon {\n border-color: @red;\n box-shadow: none;\n }\n .help-block {\n color: @red;\n }\n }\n}\n\n/* Input group */\n.input-group {\n .input-group-addon {\n .border-radius(@input-radius);\n border-color: @gray-lte;\n background-color: #fff;\n }\n}\n\n/* button groups */\n.btn-group-vertical {\n .btn {\n &.btn-flat:first-of-type, &.btn-flat:last-of-type {\n .border-radius(0);\n }\n }\n}\n\n.icheck > label {\n padding-left: 0;\n}\n\n/* support Font Awesome icons in form-control */\n.form-control-feedback.fa {\n line-height: @input-height-base;\n}\n\n.input-lg + .form-control-feedback.fa,\n.input-group-lg + .form-control-feedback.fa,\n.form-group-lg .form-control + .form-control-feedback.fa {\n line-height: @input-height-large;\n}\n\n.input-sm + .form-control-feedback.fa,\n.input-group-sm + .form-control-feedback.fa,\n.form-group-sm .form-control + .form-control-feedback.fa {\n line-height: @input-height-small;\n}\n","/*\n * Component: Progress Bar\n * -----------------------\n */\n\n//General CSS\n.progress,\n.progress > .progress-bar {\n .box-shadow(none);\n &, .progress-bar {\n .border-radius(@progress-bar-border-radius);\n }\n}\n\n/* size variation */\n.progress.sm,\n.progress-sm {\n height: 10px;\n &, .progress-bar {\n .border-radius(@progress-bar-sm-border-radius);\n }\n}\n\n.progress.xs,\n.progress-xs {\n height: 7px;\n &, .progress-bar {\n .border-radius(@progress-bar-xs-border-radius);\n }\n}\n\n.progress.xxs,\n.progress-xxs {\n height: 3px;\n &, .progress-bar {\n .border-radius(@progress-bar-xs-border-radius);\n }\n}\n\n/* Vertical bars */\n.progress.vertical {\n position: relative;\n width: 30px;\n height: 200px;\n display: inline-block;\n margin-right: 10px;\n > .progress-bar {\n width: 100%;\n position: absolute;\n bottom: 0;\n }\n\n //Sizes\n &.sm,\n &.progress-sm {\n width: 20px;\n }\n\n &.xs,\n &.progress-xs {\n width: 10px;\n }\n &.xxs,\n &.progress-xxs {\n width: 3px;\n }\n}\n\n//Progress Groups\n.progress-group {\n .progress-text {\n font-weight: 600;\n }\n .progress-number {\n float: right;\n }\n}\n\n/* Remove margins from progress bars when put in a table */\n.table {\n tr > td .progress {\n margin: 0;\n }\n}\n\n// Variations\n// -------------------------\n.progress-bar-light-blue,\n.progress-bar-primary {\n .progress-bar-variant(@light-blue);\n}\n\n.progress-bar-green,\n.progress-bar-success {\n .progress-bar-variant(@green);\n}\n\n.progress-bar-aqua,\n.progress-bar-info {\n .progress-bar-variant(@aqua);\n}\n\n.progress-bar-yellow,\n.progress-bar-warning {\n .progress-bar-variant(@yellow);\n}\n\n.progress-bar-red,\n.progress-bar-danger {\n .progress-bar-variant(@red);\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","/*\n * Component: Small Box\n * --------------------\n */\n\n.small-box {\n .border-radius(2px);\n position: relative;\n display: block;\n margin-bottom: 20px;\n box-shadow: @box-boxshadow;\n // content wrapper\n > .inner {\n padding: 10px;\n }\n\n > .small-box-footer {\n position: relative;\n text-align: center;\n padding: 3px 0;\n color: #fff;\n color: rgba(255, 255, 255, 0.8);\n display: block;\n z-index: 10;\n background: rgba(0, 0, 0, 0.1);\n text-decoration: none;\n &:hover {\n color: #fff;\n background: rgba(0, 0, 0, 0.15);\n }\n }\n\n h3 {\n font-size: 38px;\n font-weight: bold;\n margin: 0 0 10px 0;\n white-space: nowrap;\n padding: 0;\n\n }\n\n p {\n font-size: 15px;\n > small {\n display: block;\n color: #f9f9f9;\n font-size: 13px;\n margin-top: 5px;\n }\n }\n\n h3, p {\n z-index: 5;\n }\n\n // the icon\n .icon {\n .transition(all @transition-speed linear);\n position: absolute;\n top: -10px;\n right: 10px;\n z-index: 0;\n font-size: 90px;\n color: rgba(0, 0, 0, 0.15);\n }\n\n // Small box hover state\n &:hover {\n text-decoration: none;\n color: #f9f9f9;\n // Animate icons on small box hover\n .icon {\n font-size: 95px;\n }\n }\n}\n\n@media (max-width: @screen-xs-max) {\n // No need for icons on very small devices\n .small-box {\n text-align: center;\n .icon {\n display: none;\n }\n p {\n font-size: 12px;\n }\n }\n}\n","/*\n * Component: Box\n * --------------\n */\n.box {\n position: relative;\n .border-radius(@box-border-radius);\n background: #ffffff;\n border-top: 3px solid @box-default-border-top-color;\n margin-bottom: 20px;\n width: 100%;\n box-shadow: @box-boxshadow;\n\n // Box color variations\n &.box-primary {\n border-top-color: @light-blue;\n }\n &.box-info {\n border-top-color: @aqua;\n }\n &.box-danger {\n border-top-color: @red;\n }\n &.box-warning {\n border-top-color: @yellow;\n }\n &.box-success {\n border-top-color: @green;\n }\n &.box-default {\n border-top-color: @gray-lte;\n }\n\n // collapsed mode\n &.collapsed-box {\n .box-body,\n .box-footer {\n display: none;\n }\n }\n\n .nav-stacked {\n > li {\n border-bottom: 1px solid @box-border-color;\n margin: 0;\n &:last-of-type {\n border-bottom: none;\n }\n }\n }\n\n // fixed height to 300px\n &.height-control {\n .box-body {\n max-height: 300px;\n overflow: auto;\n }\n }\n\n .border-right {\n border-right: 1px solid @box-border-color;\n }\n .border-left {\n border-left: 1px solid @box-border-color;\n }\n\n //SOLID BOX\n //---------\n //use this class to get a colored header and borders\n\n &.box-solid {\n border-top: 0;\n > .box-header {\n .btn.btn-default {\n background: transparent;\n }\n .btn,\n a {\n &:hover {\n background: rgba(0, 0, 0, 0.1);\n }\n }\n }\n\n // Box color variations\n &.box-default {\n .box-solid-variant(@gray-lte, #444);\n }\n &.box-primary {\n .box-solid-variant(@light-blue);\n }\n &.box-info {\n .box-solid-variant(@aqua);\n }\n &.box-danger {\n .box-solid-variant(@red);\n }\n &.box-warning {\n .box-solid-variant(@yellow);\n }\n &.box-success {\n .box-solid-variant(@green);\n }\n\n > .box-header > .box-tools .btn {\n border: 0;\n box-shadow: none;\n }\n\n // Fix font color for tiles\n &[class*='bg'] {\n > .box-header {\n color: #fff;\n }\n }\n\n }\n\n //BOX GROUP\n .box-group {\n > .box {\n margin-bottom: 5px;\n }\n }\n\n // jQuery Knob in a box\n .knob-label {\n text-align: center;\n color: #333;\n font-weight: 100;\n font-size: 12px;\n margin-bottom: 0.3em;\n }\n}\n\n.box,\n.overlay-wrapper {\n // Box overlay for LOADING STATE effect\n > .overlay,\n > .loading-img {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n\n .overlay {\n z-index: 50;\n background: rgba(255, 255, 255, 0.7);\n .border-radius(@box-border-radius);\n > .fa {\n position: absolute;\n top: 50%;\n left: 50%;\n margin-left: -15px;\n margin-top: -15px;\n color: #000;\n font-size: 30px;\n }\n }\n\n .overlay.dark {\n background: rgba(0, 0, 0, 0.5);\n }\n}\n\n//Add clearfix to header, body and footer\n.box-header,\n.box-body,\n.box-footer {\n .clearfix();\n}\n\n//Box header\n.box-header {\n color: #444;\n display: block;\n padding: @box-padding;\n position: relative;\n\n //Add bottom border\n &.with-border {\n border-bottom: 1px solid @box-border-color;\n .collapsed-box & {\n border-bottom: none;\n }\n }\n\n //Icons and box title\n > .fa,\n > .glyphicon,\n > .ion,\n .box-title {\n display: inline-block;\n font-size: 18px;\n margin: 0;\n line-height: 1;\n }\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n > .box-tools {\n float: right;\n margin-top: -5px;\n margin-bottom: -5px;\n [data-toggle=\"tooltip\"] {\n position: relative;\n }\n\n &.pull-right {\n .dropdown-menu {\n right: 0;\n left: auto;\n }\n }\n\n .dropdown-menu > li > a {\n color: #444!important;\n }\n }\n}\n\n//Box Tools Buttons\n.btn-box-tool {\n padding: 5px;\n font-size: 12px;\n background: transparent;\n color: darken(@box-default-border-top-color, 20%);\n .open &,\n &:hover {\n color: darken(@box-default-border-top-color, 40%);\n }\n &.btn:active {\n box-shadow: none;\n }\n}\n\n//Box Body\n.box-body {\n .border-radius(0; 0; @box-border-radius; @box-border-radius);\n padding: @box-padding;\n .no-header & {\n .border-top-radius(@box-border-radius);\n }\n // Tables within the box body\n > .table {\n margin-bottom: 0;\n }\n\n // Calendar within the box body\n .fc {\n margin-top: 5px;\n }\n\n .full-width-chart {\n margin: -19px;\n }\n &.no-padding .full-width-chart {\n margin: -9px;\n }\n\n .box-pane {\n .border-radius(0; 0; @box-border-radius; 0);\n }\n .box-pane-right {\n .border-radius(0; 0; 0; @box-border-radius);\n }\n}\n\n//Box footer\n.box-footer {\n .border-radius(0; 0; @box-border-radius; @box-border-radius);\n border-top: 1px solid @box-border-color;\n padding: @box-padding;\n background-color: @box-footer-bg;\n}\n\n.chart-legend {\n &:extend(.list-unstyled);\n margin: 10px 0;\n > li {\n @media (max-width: @screen-sm-max) {\n float: left;\n margin-right: 10px;\n }\n }\n}\n\n//Comment Box\n.box-comments {\n background: #f7f7f7;\n .box-comment {\n .clearfix();\n padding: 8px 0;\n border-bottom: 1px solid #eee;\n &:last-of-type {\n border-bottom: 0;\n }\n &:first-of-type {\n padding-top: 0;\n }\n img {\n &:extend(.img-sm);\n float: left;\n }\n }\n .comment-text {\n margin-left: 40px;\n color: #555;\n }\n .username {\n color: #444;\n display: block;\n font-weight: 600;\n }\n .text-muted {\n font-weight: 400;\n font-size: 12px;\n }\n}\n\n//Widgets\n//-----------\n\n/* Widget: TODO LIST */\n\n.todo-list {\n margin: 0;\n padding: 0;\n list-style: none;\n overflow: auto;\n // Todo list element\n > li {\n .border-radius(2px);\n padding: 10px;\n background: #f4f4f4;\n margin-bottom: 2px;\n border-left: 2px solid #e6e7e8;\n color: #444;\n &:last-of-type {\n margin-bottom: 0;\n }\n\n > input[type='checkbox'] {\n margin: 0 10px 0 5px;\n }\n\n .text {\n display: inline-block;\n margin-left: 5px;\n font-weight: 600;\n }\n\n // Time labels\n .label {\n margin-left: 10px;\n font-size: 9px;\n }\n\n // Tools and options box\n .tools {\n display: none;\n float: right;\n color: @red;\n // icons\n > .fa, > .glyphicon, > .ion {\n margin-right: 5px;\n cursor: pointer;\n }\n\n }\n &:hover .tools {\n display: inline-block;\n }\n\n &.done {\n color: #999;\n .text {\n text-decoration: line-through;\n font-weight: 500;\n }\n\n .label {\n background: @gray-lte !important;\n }\n }\n }\n\n // Color varaity\n .danger {\n border-left-color: @red;\n }\n .warning {\n border-left-color: @yellow;\n }\n .info {\n border-left-color: @aqua;\n }\n .success {\n border-left-color: @green;\n }\n .primary {\n border-left-color: @light-blue;\n }\n\n .handle {\n display: inline-block;\n cursor: move;\n margin: 0 5px;\n }\n\n}\n\n// END TODO WIDGET\n\n/* Chat widget (DEPRECATED - this will be removed in the next major release. Use Direct Chat instead)*/\n.chat {\n padding: 5px 20px 5px 10px;\n\n .item {\n .clearfix();\n margin-bottom: 10px;\n // The image\n > img {\n width: 40px;\n height: 40px;\n border: 2px solid transparent;\n .border-radius(50%);\n }\n\n > .online {\n border: 2px solid @green;\n }\n > .offline {\n border: 2px solid @red;\n }\n\n // The message body\n > .message {\n margin-left: 55px;\n margin-top: -40px;\n > .name {\n display: block;\n font-weight: 600;\n }\n }\n\n // The attachment\n > .attachment {\n .border-radius(@attachment-border-radius);\n background: #f4f4f4;\n margin-left: 65px;\n margin-right: 15px;\n padding: 10px;\n > h4 {\n margin: 0 0 5px 0;\n font-weight: 600;\n font-size: 14px;\n }\n > p, > .filename {\n font-weight: 600;\n font-size: 13px;\n font-style: italic;\n margin: 0;\n\n }\n .clearfix();\n }\n }\n\n}\n\n//END CHAT WIDGET\n\n//Input in box\n.box-input {\n max-width: 200px;\n}\n\n//A fix for panels body text color when placed within\n// a modal\n.modal {\n .panel-body {\n color: #444;\n }\n}\n","/*\n * Component: Info Box\n * -------------------\n */\n.info-box {\n display: block;\n min-height: 90px;\n background: #fff;\n width: 100%;\n box-shadow: @box-boxshadow;\n .border-radius(2px);\n margin-bottom: 15px;\n small {\n font-size: 14px;\n }\n .progress {\n background: rgba(0, 0, 0, .2);\n margin: 5px -10px 5px -10px;\n height: 2px;\n &,\n & .progress-bar {\n .border-radius(0);\n }\n .progress-bar {\n background: #fff;\n }\n }\n}\n\n.info-box-icon {\n .border-radius(2px; 0; 2px; 0);\n display: block;\n float: left;\n height: 90px;\n width: 90px;\n text-align: center;\n font-size: 45px;\n line-height: 90px;\n background: rgba(0, 0, 0, 0.2);\n > img {\n max-width: 100%;\n }\n}\n\n.info-box-content {\n padding: 5px 10px;\n margin-left: 90px;\n}\n\n.info-box-number {\n display: block;\n font-weight: bold;\n font-size: 18px;\n}\n\n.progress-description,\n.info-box-text {\n display: block;\n font-size: 14px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.info-box-text {\n text-transform: uppercase;\n}\n\n.info-box-more {\n display: block;\n}\n\n.progress-description {\n margin: 0;\n}\n","/*\n * Component: Timeline\n * -------------------\n */\n\n.timeline {\n position: relative;\n margin: 0 0 30px 0;\n padding: 0;\n list-style: none;\n\n // The line\n &:before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n width: 4px;\n background: #ddd;\n left: 31px;\n margin: 0;\n .border-radius(2px);\n }\n\n > li {\n position: relative;\n margin-right: 10px;\n margin-bottom: 15px;\n .clearfix();\n\n // The content\n > .timeline-item {\n .box-shadow(@box-boxshadow);\n .border-radius(@box-border-radius);\n margin-top: 0;\n background: #fff;\n color: #444;\n margin-left: 60px;\n margin-right: 15px;\n padding: 0;\n position: relative;\n\n // The time and header\n > .time {\n color: #999;\n float: right;\n padding: 10px;\n font-size: 12px;\n }\n > .timeline-header {\n margin: 0;\n color: #555;\n border-bottom: 1px solid @box-border-color;\n padding: 10px;\n font-size: 16px;\n line-height: 1.1;\n > a {\n font-weight: 600;\n }\n }\n // Item body and footer\n > .timeline-body, > .timeline-footer {\n padding: 10px;\n }\n\n }\n\n // The icons\n > .fa,\n > .glyphicon,\n > .ion {\n width: 30px;\n height: 30px;\n font-size: 15px;\n line-height: 30px;\n position: absolute;\n color: #666;\n background: @gray-lte;\n border-radius: 50%;\n text-align: center;\n left: 18px;\n top: 0;\n }\n }\n\n // Time label\n > .time-label {\n > span {\n font-weight: 600;\n padding: 5px;\n display: inline-block;\n background-color: #fff;\n\n .border-radius(4px);\n }\n }\n}\n\n.timeline-inverse {\n > li {\n > .timeline-item {\n background: #f0f0f0;\n border: 1px solid #ddd;\n .box-shadow(none);\n > .timeline-header {\n border-bottom-color: #ddd;\n }\n }\n }\n}\n","/*\n * Component: Button\n * -----------------\n */\n\n.btn {\n .border-radius(@btn-border-radius);\n .box-shadow(@btn-boxshadow);\n border: 1px solid transparent;\n\n &.uppercase {\n text-transform: uppercase\n }\n\n // Flat buttons\n &.btn-flat {\n .border-radius(0);\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n border-width: 1px;\n }\n\n // Active state\n &:active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n }\n\n &:focus {\n outline: none;\n }\n\n // input file btn\n &.btn-file {\n position: relative;\n overflow: hidden;\n > input[type='file'] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n .opacity(0);\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n }\n }\n}\n\n//Button color variations\n.btn-default {\n background-color: #f4f4f4;\n color: #444;\n border-color: #ddd;\n &:hover,\n &:active,\n &.hover {\n background-color: darken(#f4f4f4, 5%);\n }\n}\n\n.btn-primary {\n background-color: @light-blue;\n border-color: darken(@light-blue, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@light-blue, 5%);\n }\n}\n\n.btn-success {\n background-color: @green;\n border-color: darken(@green, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@green, 5%);\n }\n}\n\n.btn-info {\n background-color: @aqua;\n border-color: darken(@aqua, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@aqua, 5%);\n }\n}\n\n.btn-danger {\n background-color: @red;\n border-color: darken(@red, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@red, 5%);\n }\n}\n\n.btn-warning {\n background-color: @yellow;\n border-color: darken(@yellow, 5%);\n &:hover, &:active, &.hover {\n background-color: darken(@yellow, 5%);\n }\n}\n\n.btn-outline {\n border: 1px solid #fff;\n background: transparent;\n color: #fff;\n &:hover,\n &:focus,\n &:active {\n color: rgba(255, 255, 255, .7);\n border-color: rgba(255, 255, 255, .7);\n }\n}\n\n.btn-link {\n .box-shadow(none);\n}\n\n//General .btn with bg class\n.btn[class*='bg-']:hover {\n .box-shadow(inset 0 0 100px rgba(0, 0, 0, 0.2));\n}\n\n// Application buttons\n.btn-app {\n .border-radius(3px);\n position: relative;\n padding: 15px 5px;\n margin: 0 0 10px 10px;\n min-width: 80px;\n height: 60px;\n text-align: center;\n color: #666;\n border: 1px solid #ddd;\n background-color: #f4f4f4;\n font-size: 12px;\n //Icons within the btn\n > .fa, > .glyphicon, > .ion {\n font-size: 20px;\n display: block;\n }\n\n &:hover {\n background: #f4f4f4;\n color: #444;\n border-color: #aaa;\n }\n\n &:active, &:focus {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n }\n\n //The badge\n > .badge {\n position: absolute;\n top: -3px;\n right: -10px;\n font-size: 10px;\n font-weight: 400;\n }\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","/*\n * Component: Callout\n * ------------------\n */\n\n// Base styles (regardless of theme)\n.callout {\n .border-radius(3px);\n margin: 0 0 20px 0;\n padding: 15px 30px 15px 15px;\n border-left: 5px solid #eee;\n a {\n color: #fff;\n text-decoration: underline;\n &:hover {\n color: #eee;\n }\n }\n h4 {\n margin-top: 0;\n font-weight: 600;\n }\n p:last-child {\n margin-bottom: 0;\n }\n code,\n .highlight {\n background-color: #fff;\n }\n\n // Themes for different contexts\n &.callout-danger {\n &:extend(.bg-red);\n border-color: darken(@red, 10%);\n }\n &.callout-warning {\n &:extend(.bg-yellow);\n border-color: darken(@yellow, 10%);\n }\n &.callout-info {\n &:extend(.bg-aqua);\n border-color: darken(@aqua, 10%);\n }\n &.callout-success {\n &:extend(.bg-green);\n border-color: darken(@green, 10%);\n }\n}\n","/*\n * Component: alert\n * ----------------\n */\n\n.alert {\n .border-radius(3px);\n h4 {\n font-weight: 600;\n }\n .icon {\n margin-right: 10px;\n }\n .close {\n color: #000;\n .opacity(.2);\n &:hover {\n .opacity(.5);\n }\n }\n a {\n color: #fff;\n text-decoration: underline;\n }\n}\n\n//Alert Variants\n.alert-success {\n &:extend(.bg-green);\n border-color: darken(@green, 5%);\n}\n\n.alert-danger,\n.alert-error {\n &:extend(.bg-red);\n border-color: darken(@red, 5%);\n}\n\n.alert-warning {\n &:extend(.bg-yellow);\n border-color: darken(@yellow, 5%);\n}\n\n.alert-info {\n &:extend(.bg-aqua);\n border-color: darken(@aqua, 5%);\n}\n","/*\n * Component: Nav\n * --------------\n */\n\n.nav {\n > li > a:hover,\n > li > a:active,\n > li > a:focus {\n color: #444;\n background: #f7f7f7;\n }\n}\n\n/* NAV PILLS */\n.nav-pills {\n > li > a {\n .border-radius(0);\n border-top: 3px solid transparent;\n color: #444;\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n > li.active > a,\n > li.active > a:hover,\n > li.active > a:focus {\n border-top-color: @light-blue;\n }\n > li.active > a {\n font-weight: 600;\n }\n}\n\n/* NAV STACKED */\n.nav-stacked {\n > li > a {\n .border-radius(0);\n border-top: 0;\n border-left: 3px solid transparent;\n color: #444;\n }\n > li.active > a,\n > li.active > a:hover {\n background: transparent;\n color: #444;\n border-top: 0;\n border-left-color: @light-blue;\n }\n\n > li.header {\n border-bottom: 1px solid #ddd;\n color: #777;\n margin-bottom: 10px;\n padding: 5px 10px;\n text-transform: uppercase;\n }\n}\n\n/* NAV TABS */\n.nav-tabs-custom {\n margin-bottom: 20px;\n background: #fff;\n box-shadow: @box-boxshadow;\n border-radius: @box-border-radius;\n > .nav-tabs {\n margin: 0;\n border-bottom-color: #f4f4f4;\n\n .border-top-radius(@box-border-radius);\n > li {\n border-top: 3px solid transparent;\n margin-bottom: -2px;\n\n &.disabled > a {\n color: #777;\n }\n\n > a {\n color: #444;\n .border-radius(0);\n &.text-muted {\n color: #999;\n }\n &,\n &:hover {\n background: transparent;\n margin: 0;\n }\n &:hover {\n color: #999;\n }\n }\n &:not(.active) {\n > a:hover,\n > a:focus,\n > a:active {\n border-color: transparent;\n }\n }\n margin-right: 5px;\n }\n\n > li.active {\n border-top-color: @light-blue;\n & > a,\n &:hover > a {\n background-color: #fff;\n color: #444;\n }\n > a {\n border-top-color: transparent;\n border-left-color: #f4f4f4;\n border-right-color: #f4f4f4;\n }\n\n }\n\n > li:first-of-type {\n margin-left: 0;\n &.active {\n > a {\n border-left-color: transparent;\n }\n }\n }\n\n //Pulled to the right\n &.pull-right {\n float: none !important;\n > li {\n float: right;\n }\n > li:first-of-type {\n margin-right: 0;\n > a {\n border-left-width: 1px;\n }\n &.active {\n > a {\n border-left-color: #f4f4f4;\n border-right-color: transparent;\n }\n }\n }\n }\n\n > li.header {\n line-height: 35px;\n padding: 0 10px;\n font-size: 20px;\n color: #444;\n > .fa,\n > .glyphicon,\n > .ion {\n margin-right: 5px;\n }\n }\n }\n\n > .tab-content {\n background: #fff;\n padding: 10px;\n .border-bottom-radius(@box-border-radius);\n }\n\n .dropdown.open > a {\n &:active,\n &:focus {\n background: transparent;\n color: #999;\n }\n }\n // Tab color variations\n &.tab-primary {\n > .nav-tabs {\n > li.active {\n border-top-color: @light-blue;\n }\n }\n }\n &.tab-info {\n > .nav-tabs {\n > li.active {\n border-top-color: @aqua;\n }\n }\n }\n &.tab-danger {\n > .nav-tabs {\n > li.active {\n border-top-color: @red;\n }\n }\n }\n &.tab-warning {\n > .nav-tabs {\n > li.active {\n border-top-color: @yellow;\n }\n }\n }\n &.tab-success {\n > .nav-tabs {\n > li.active {\n border-top-color: @green;\n }\n }\n }\n &.tab-default {\n > .nav-tabs {\n > li.active {\n border-top-color: @gray-lte;\n }\n }\n }\n}\n\n/* PAGINATION */\n.pagination {\n > li > a {\n background: #fafafa;\n color: #666;\n }\n &.pagination-flat {\n > li > a {\n .border-radius(0) !important;\n }\n }\n}\n","/*\n * Component: Products List\n * ------------------------\n */\n.products-list {\n list-style: none;\n margin: 0;\n padding: 0;\n > .item {\n .border-radius(@box-border-radius);\n .box-shadow(@box-boxshadow);\n .clearfix();\n padding: 10px 0;\n background: #fff;\n }\n .product-img {\n float: left;\n img {\n width: 50px;\n height: 50px;\n }\n }\n .product-info {\n margin-left: 60px;\n }\n .product-title {\n font-weight: 600;\n }\n .product-description {\n display: block;\n color: #999;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n}\n\n.product-list-in-box > .item {\n .box-shadow(none);\n .border-radius(0);\n border-bottom: 1px solid @box-border-color;\n &:last-of-type {\n border-bottom-width: 0;\n }\n}\n","/*\n * Component: Table\n * ----------------\n */\n\n.table {\n //Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border-top: 1px solid @box-border-color;\n }\n }\n }\n //thead cells\n > thead > tr > th {\n border-bottom: 2px solid @box-border-color;\n }\n //progress bars in tables\n tr td .progress {\n margin-top: 5px;\n }\n}\n\n//Bordered Table\n.table-bordered {\n border: 1px solid @box-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @box-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n.table.no-border {\n &,\n td,\n th {\n border: 0;\n }\n}\n\n/* .text-center in tables */\ntable.text-center {\n &, td, th {\n text-align: center;\n }\n}\n\n.table.align {\n th {\n text-align: left;\n }\n td {\n text-align: right;\n }\n}","/*\n * Component: Label\n * ----------------\n */\n.label-default {\n background-color: @gray-lte;\n color: #444;\n}\n\n.label-danger {\n &:extend(.bg-red);\n}\n\n.label-info {\n &:extend(.bg-aqua);\n}\n\n.label-warning {\n &:extend(.bg-yellow);\n}\n\n.label-primary {\n &:extend(.bg-light-blue);\n}\n\n.label-success {\n &:extend(.bg-green);\n}\n","/*\n * Component: Direct Chat\n * ----------------------\n */\n.direct-chat {\n .box-body {\n .border-bottom-radius(0);\n position: relative;\n overflow-x: hidden;\n padding: 0;\n }\n &.chat-pane-open {\n .direct-chat-contacts {\n .translate(0, 0);\n }\n }\n}\n\n.direct-chat-messages {\n .translate(0, 0);\n padding: 10px;\n height: 250px;\n overflow: auto;\n}\n\n.direct-chat-msg,\n.direct-chat-text {\n display: block;\n}\n\n.direct-chat-msg {\n .clearfix();\n margin-bottom: 10px;\n}\n\n.direct-chat-messages,\n.direct-chat-contacts {\n .transition-transform(.5s ease-in-out);\n}\n\n.direct-chat-text {\n .border-radius(5px);\n position: relative;\n padding: 5px 10px;\n background: @direct-chat-default-msg-bg;\n border: 1px solid @direct-chat-default-msg-border-color;\n margin: 5px 0 0 50px;\n color: @direct-chat-default-font-color;\n\n //Create the arrow\n &:after,\n &:before {\n position: absolute;\n right: 100%;\n top: 15px;\n border: solid transparent;\n border-right-color: @direct-chat-default-msg-border-color;\n content: ' ';\n height: 0;\n width: 0;\n pointer-events: none;\n }\n\n &:after {\n border-width: 5px;\n margin-top: -5px;\n }\n &:before {\n border-width: 6px;\n margin-top: -6px;\n }\n .right & {\n margin-right: 50px;\n margin-left: 0;\n &:after,\n &:before {\n right: auto;\n left: 100%;\n border-right-color: transparent;\n border-left-color: @direct-chat-default-msg-border-color;\n }\n }\n}\n\n.direct-chat-img {\n .border-radius(50%);\n float: left;\n width: 40px;\n height: 40px;\n .right & {\n float: right;\n }\n}\n\n.direct-chat-info {\n display: block;\n margin-bottom: 2px;\n font-size: 12px;\n}\n\n.direct-chat-name {\n font-weight: 600;\n}\n\n.direct-chat-timestamp {\n color: #999;\n}\n\n//Direct chat contacts pane\n.direct-chat-contacts-open {\n .direct-chat-contacts {\n .translate(0, 0);\n }\n}\n\n.direct-chat-contacts {\n .translate(101%, 0);\n position: absolute;\n top: 0;\n bottom: 0;\n height: 250px;\n width: 100%;\n background: #222d32;\n color: #fff;\n overflow: auto;\n}\n\n//Contacts list -- for displaying contacts in direct chat contacts pane\n.contacts-list {\n &:extend(.list-unstyled);\n > li {\n .clearfix();\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n padding: 10px;\n margin: 0;\n &:last-of-type {\n border-bottom: none;\n }\n }\n}\n\n.contacts-list-img {\n .border-radius(50%);\n width: 40px;\n float: left;\n}\n\n.contacts-list-info {\n margin-left: 45px;\n color: #fff;\n}\n\n.contacts-list-name,\n.contacts-list-status {\n display: block;\n}\n\n.contacts-list-name {\n font-weight: 600;\n}\n\n.contacts-list-status {\n font-size: 12px;\n}\n\n.contacts-list-date {\n color: #aaa;\n font-weight: normal;\n}\n\n.contacts-list-msg {\n color: #999;\n}\n\n//Direct Chat Variants\n.direct-chat-danger {\n .direct-chat-variant(@red);\n}\n\n.direct-chat-primary {\n .direct-chat-variant(@light-blue);\n}\n\n.direct-chat-warning {\n .direct-chat-variant(@yellow);\n}\n\n.direct-chat-info {\n .direct-chat-variant(@aqua);\n}\n\n.direct-chat-success {\n .direct-chat-variant(@green);\n}\n","/*\n * Component: Users List\n * ---------------------\n */\n.users-list {\n &:extend(.list-unstyled);\n > li {\n width: 25%;\n float: left;\n padding: 10px;\n text-align: center;\n img {\n .border-radius(50%);\n max-width: 100%;\n height: auto;\n }\n > a:hover {\n &,\n .users-list-name {\n color: #999;\n }\n }\n }\n}\n\n.users-list-name,\n.users-list-date {\n display: block;\n}\n\n.users-list-name {\n font-weight: 600;\n color: #444;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.users-list-date {\n color: #999;\n font-size: 12px;\n}\n","/*\n * Component: Carousel\n * -------------------\n */\n.carousel-control {\n &.left,\n &.right {\n background-image: none;\n }\n > .fa {\n font-size: 40px;\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -20px;\n }\n}\n","/*\n * Component: modal\n * ----------------\n */\n.modal {\n background: rgba(0, 0, 0, .3);\n}\n\n.modal-content {\n .border-radius(0);\n .box-shadow(0 2px 3px rgba(0, 0, 0, .125));\n border: 0;\n @media (min-width: @screen-sm-min) {\n .box-shadow(0 2px 3px rgba(0, 0, 0, .125));\n }\n}\n\n.modal-header {\n border-bottom-color: @box-border-color;\n}\n\n.modal-footer {\n border-top-color: @box-border-color;\n}\n\n//Modal variants\n.modal-primary {\n .modal-body {\n &:extend(.bg-light-blue);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-light-blue-active);\n border-color: darken(@light-blue, 10%);\n }\n}\n\n.modal-warning {\n .modal-body {\n &:extend(.bg-yellow);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-yellow-active);\n border-color: darken(@yellow, 10%);\n }\n}\n\n.modal-info {\n .modal-body {\n &:extend(.bg-aqua);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-aqua-active);\n border-color: darken(@aqua, 10%);\n }\n}\n\n.modal-success {\n .modal-body {\n &:extend(.bg-green);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-green-active);\n border-color: darken(@green, 10%);\n }\n}\n\n.modal-danger {\n .modal-body {\n &:extend(.bg-red);\n }\n .modal-header,\n .modal-footer {\n &:extend(.bg-red-active);\n border-color: darken(@red, 10%);\n }\n}\n","/*\n * Component: Social Widgets\n * -------------------------\n */\n//General widget style\n.box-widget {\n border: none;\n position: relative;\n}\n\n//User Widget Style 1\n.widget-user {\n //User name container\n .widget-user-header {\n padding: 20px;\n height: 120px;\n .border-top-radius(@box-border-radius);\n }\n //User name\n .widget-user-username {\n margin-top: 0;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);\n }\n //User single line description\n .widget-user-desc {\n margin-top: 0;\n }\n //User image container\n .widget-user-image {\n position: absolute;\n top: 65px;\n left: 50%;\n margin-left: -45px;\n > img {\n width: 90px;\n height: auto;\n border: 3px solid #fff;\n }\n }\n .box-footer {\n padding-top: 30px;\n }\n}\n\n//User Widget Style 2\n.widget-user-2 {\n //User name container\n .widget-user-header {\n padding: 20px;\n .border-top-radius(@box-border-radius);\n }\n //User name\n .widget-user-username {\n margin-top: 5px;\n margin-bottom: 5px;\n font-size: 25px;\n font-weight: 300;\n }\n //User single line description\n .widget-user-desc {\n margin-top: 0;\n }\n .widget-user-username,\n .widget-user-desc {\n margin-left: 75px;\n }\n //User image container\n .widget-user-image {\n > img {\n width: 65px;\n height: auto;\n float: left;\n }\n }\n}\n","\t// Tree view menu\n.treeview-menu {\n\tdisplay: none;\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tpadding-left: 5px;\n\t.treeview-menu {\n\t padding-left: 20px;\n\t}\n\t> li {\n\t margin: 0;\n\t > a {\n\t padding: 5px 5px 5px 15px;\n\t display: block;\n\t font-size: 14px;\n\t > .fa,\n\t > .glyphicon,\n\t > .ion {\n\t width: 20px;\n\t }\n\t > .pull-right-container > .fa-angle-left,\n\t > .pull-right-container > .fa-angle-down,\n\t > .fa-angle-left,\n\t > .fa-angle-down {\n\t width: auto;\n\t }\n\t }\n\t}\n}\n\n.treeview {\n\t> ul.treeview-menu {\n\t\toverflow: hidden;\n\t\theight:auto;\n\t\tpadding-top:0px !important;\n\t\tpadding-bottom: 0px !important;\n\t}\n}\n.treeview.menu-open {\n\t> ul.treeview-menu {\n\t\t overflow: visible;\n \t\theight:auto;\n\t}\n}","/*\n * Page: Mailbox\n * -------------\n */\n.mailbox-messages {\n > .table {\n margin: 0;\n }\n}\n\n.mailbox-controls {\n padding: 5px;\n &.with-border {\n border-bottom: 1px solid @box-border-color;\n }\n}\n\n.mailbox-read-info {\n border-bottom: 1px solid @box-border-color;\n padding: 10px;\n h3 {\n font-size: 20px;\n margin: 0;\n }\n h5 {\n margin: 0;\n padding: 5px 0 0 0;\n }\n}\n\n.mailbox-read-time {\n color: #999;\n font-size: 13px;\n}\n\n.mailbox-read-message {\n padding: 10px;\n}\n\n.mailbox-attachments {\n &:extend(.list-unstyled);\n li {\n float: left;\n width: 200px;\n border: 1px solid #eee;\n margin-bottom: 10px;\n margin-right: 10px;\n }\n}\n\n.mailbox-attachment-name {\n font-weight: bold;\n color: #666;\n}\n\n.mailbox-attachment-icon,\n.mailbox-attachment-info,\n.mailbox-attachment-size {\n display: block;\n}\n\n.mailbox-attachment-info {\n padding: 10px;\n background: #f4f4f4;\n}\n\n.mailbox-attachment-size {\n color: #999;\n font-size: 12px;\n}\n\n.mailbox-attachment-icon {\n text-align: center;\n font-size: 65px;\n color: #666;\n padding: 20px 10px;\n &.has-img {\n padding: 0;\n > img {\n max-width: 100%;\n height: auto;\n }\n }\n}\n\n.mailbox-attachment-close {\n &:extend(.close);\n}\n","/*\n * Page: Lock Screen\n * -----------------\n */\n/* ADD THIS CLASS TO THE TAG */\n.lockscreen {\n background: @gray-lte;\n}\n\n.lockscreen-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n a {\n color: #444;\n }\n}\n\n.lockscreen-wrapper {\n max-width: 400px;\n margin: 0 auto;\n margin-top: 10%;\n}\n\n/* User name [optional] */\n.lockscreen .lockscreen-name {\n text-align: center;\n font-weight: 600;\n}\n\n/* Will contain the image and the sign in form */\n.lockscreen-item {\n .border-radius(4px);\n padding: 0;\n background: #fff;\n position: relative;\n margin: 10px auto 30px auto;\n width: 290px;\n}\n\n/* User image */\n.lockscreen-image {\n .border-radius(50%);\n position: absolute;\n left: -10px;\n top: -25px;\n background: #fff;\n padding: 5px;\n z-index: 10;\n > img {\n .border-radius(50%);\n width: 70px;\n height: 70px;\n }\n}\n\n/* Contains the password input and the login button */\n.lockscreen-credentials {\n margin-left: 70px;\n .form-control {\n border: 0;\n }\n .btn {\n background-color: #fff;\n border: 0;\n padding: 0 10px;\n }\n}\n\n.lockscreen-footer {\n margin-top: 10px;\n}\n","/*\n * Page: Login & Register\n * ----------------------\n */\n\n.login-logo,\n.register-logo {\n font-size: 35px;\n text-align: center;\n margin-bottom: 25px;\n font-weight: 300;\n a {\n color: #444;\n }\n}\n\n.login-page,\n.register-page {\n height: auto;\n background: @gray-lte;\n}\n\n.login-box,\n.register-box {\n width: 360px;\n margin: 7% auto;\n @media (max-width: @screen-sm) {\n width: 90%;\n margin-top: 20px;\n }\n}\n\n.login-box-body,\n.register-box-body {\n background: #fff;\n padding: 20px;\n border-top: 0;\n color: #666;\n .form-control-feedback {\n color: #777;\n }\n}\n\n.login-box-msg,\n.register-box-msg {\n margin: 0;\n text-align: center;\n padding: 0 20px 20px 20px;\n}\n\n.social-auth-links {\n margin: 10px 0;\n}\n","/*\n * Page: 400 and 500 error pages\n * ------------------------------\n */\n.error-page {\n width: 600px;\n margin: 20px auto 0 auto;\n @media (max-width: @screen-sm-max) {\n width: 100%;\n }\n //For the error number e.g: 404\n > .headline {\n float: left;\n font-size: 100px;\n font-weight: 300;\n @media (max-width: @screen-sm-max) {\n float: none;\n text-align: center;\n }\n }\n //For the message\n > .error-content {\n margin-left: 190px;\n @media (max-width: @screen-sm-max) {\n margin-left: 0;\n }\n > h3 {\n font-weight: 300;\n font-size: 25px;\n @media (max-width: @screen-sm-max) {\n text-align: center;\n }\n }\n display: block;\n }\n}\n","/*\n * Page: Invoice\n * -------------\n */\n\n.invoice {\n position: relative;\n background: #fff;\n border: 1px solid #f4f4f4;\n padding: 20px;\n margin: 10px 25px;\n}\n\n.invoice-title {\n margin-top: 0;\n}\n","/*\n * Page: Profile\n * -------------\n */\n\n.profile-user-img {\n margin: 0 auto;\n width: 100px;\n padding: 3px;\n border: 3px solid @gray-lte;\n}\n\n.profile-username {\n font-size: 21px;\n margin-top: 5px;\n}\n\n.post {\n border-bottom: 1px solid @gray-lte;\n margin-bottom: 15px;\n padding-bottom: 15px;\n color: #666;\n &:last-of-type {\n border-bottom: 0;\n margin-bottom: 0;\n padding-bottom: 0;\n }\n .user-block {\n margin-bottom: 15px;\n }\n}\n","/*\n * Social Buttons for Bootstrap\n *\n * Copyright 2013-2015 Panayiotis Lipiridis\n * Licensed under the MIT License\n *\n * https://github.com/lipis/bootstrap-social\n */\n\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n@bs-height-base: (@line-height-computed + @padding-base-vertical * 2);\n@bs-height-lg: (floor(@font-size-large * @line-height-base) + @padding-large-vertical * 2);\n@bs-height-sm: (floor(@font-size-small * 1.5) + @padding-small-vertical * 2);\n@bs-height-xs: (floor(@font-size-small * 1.2) + @padding-small-vertical + 1);\n\n.btn-social {\n position: relative;\n padding-left: (@bs-height-base + @padding-base-horizontal);\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n > :first-child {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: @bs-height-base;\n line-height: (@bs-height-base + 2);\n font-size: 1.6em;\n text-align: center;\n border-right: 1px solid rgba(0, 0, 0, 0.2);\n }\n &.btn-lg {\n padding-left: (@bs-height-lg + @padding-large-horizontal);\n > :first-child {\n line-height: @bs-height-lg;\n width: @bs-height-lg;\n font-size: 1.8em;\n }\n }\n &.btn-sm {\n padding-left: (@bs-height-sm + @padding-small-horizontal);\n > :first-child {\n line-height: @bs-height-sm;\n width: @bs-height-sm;\n font-size: 1.4em;\n }\n }\n &.btn-xs {\n padding-left: (@bs-height-xs + @padding-small-horizontal);\n > :first-child {\n line-height: @bs-height-xs;\n width: @bs-height-xs;\n font-size: 1.2em;\n }\n }\n}\n\n.btn-social-icon {\n .btn-social;\n height: (@bs-height-base + 2);\n width: (@bs-height-base + 2);\n padding: 0;\n > :first-child {\n border: none;\n text-align: center;\n width: 100%;\n }\n &.btn-lg {\n height: @bs-height-lg;\n width: @bs-height-lg;\n padding-left: 0;\n padding-right: 0;\n }\n &.btn-sm {\n height: (@bs-height-sm + 2);\n width: (@bs-height-sm + 2);\n padding-left: 0;\n padding-right: 0;\n }\n &.btn-xs {\n height: (@bs-height-xs + 2);\n width: (@bs-height-xs + 2);\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n.btn-social(@color-bg, @color: #fff) {\n background-color: @color-bg;\n .button-variant(@color, @color-bg, rgba(0, 0, 0, .2));\n}\n\n.btn-adn {\n .btn-social(#d87a68);\n}\n\n.btn-bitbucket {\n .btn-social(#205081);\n}\n\n.btn-dropbox {\n .btn-social(#1087dd);\n}\n\n.btn-facebook {\n .btn-social(#3b5998);\n}\n\n.btn-flickr {\n .btn-social(#ff0084);\n}\n\n.btn-foursquare {\n .btn-social(#f94877);\n}\n\n.btn-github {\n .btn-social(#444444);\n}\n\n.btn-google {\n .btn-social(#dd4b39);\n}\n\n.btn-instagram {\n .btn-social(#3f729b);\n}\n\n.btn-linkedin {\n .btn-social(#007bb6);\n}\n\n.btn-microsoft {\n .btn-social(#2672ec);\n}\n\n.btn-openid {\n .btn-social(#f7931e);\n}\n\n.btn-pinterest {\n .btn-social(#cb2027);\n}\n\n.btn-reddit {\n .btn-social(#eff7ff, #000);\n}\n\n.btn-soundcloud {\n .btn-social(#ff5500);\n}\n\n.btn-tumblr {\n .btn-social(#2c4762);\n}\n\n.btn-twitter {\n .btn-social(#55acee);\n}\n\n.btn-vimeo {\n .btn-social(#1ab7ea);\n}\n\n.btn-vk {\n .btn-social(#587ea3);\n}\n\n.btn-yahoo {\n .btn-social(#720e9e);\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","/*\n * Plugin: Full Calendar\n * ---------------------\n */\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n// Fullcalendar buttons\n.fc-button {\n background: #f4f4f4;\n background-image: none;\n color: #444;\n border-color: #ddd;\n border-bottom-color: #ddd;\n &:hover,\n &:active,\n &.hover {\n background-color: #e9e9e9;\n }\n}\n\n// Calendar title\n.fc-header-title h2 {\n font-size: 15px;\n line-height: 1.6em;\n color: #666;\n margin-left: 10px;\n}\n\n.fc-header-right {\n padding-right: 10px;\n}\n\n.fc-header-left {\n padding-left: 10px;\n}\n\n// Calendar table header cells\n.fc-widget-header {\n background: #fafafa;\n}\n\n.fc-grid {\n width: 100%;\n border: 0;\n}\n\n.fc-widget-header:first-of-type,\n.fc-widget-content:first-of-type {\n border-left: 0;\n border-right: 0;\n}\n\n.fc-widget-header:last-of-type,\n.fc-widget-content:last-of-type {\n border-right: 0;\n}\n\n.fc-toolbar {\n padding: @box-padding;\n margin: 0;\n}\n\n.fc-day-number {\n font-size: 20px;\n font-weight: 300;\n padding-right: 10px;\n}\n\n.fc-color-picker {\n list-style: none;\n margin: 0;\n padding: 0;\n > li {\n float: left;\n font-size: 30px;\n margin-right: 5px;\n line-height: 30px;\n .fa {\n .transition-transform(linear .3s);\n &:hover {\n .rotate(30deg);\n }\n }\n }\n}\n\n#add-new-event {\n .transition(all linear .3s);\n}\n\n.external-event {\n padding: 5px 10px;\n font-weight: bold;\n margin-bottom: 4px;\n box-shadow: @box-boxshadow;\n text-shadow: @box-boxshadow;\n border-radius: @box-border-radius;\n cursor: move;\n &:hover {\n box-shadow: inset 0 0 90px rgba(0, 0, 0, 0.2);\n }\n}\n","/*\n * Plugin: Select2\n * ---------------\n */\n// Import variables and mixins as a reference for separate plugins version\n@import (reference) \"../bootstrap-less/mixins\";\n@import (reference) \"../bootstrap-less/variables\";\n@import (reference) \"variables\";\n@import (reference) \"mixins\";\n\n//Signle select\n.select2-container--default,\n.select2-selection {\n &.select2-container--focus,\n &:focus,\n &:active {\n outline: none;\n }\n .select2-selection--single {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n padding: 6px 12px;\n height: 34px;\n }\n}\n\n.select2-container--default.select2-container--open {\n border-color: @light-blue;\n}\n\n.select2-dropdown {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n}\n\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: @light-blue;\n color: white;\n}\n\n.select2-results__option {\n padding: 6px 12px;\n user-select: none;\n -webkit-user-select: none;\n}\n\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-left: 0;\n padding-right: 0;\n height: auto;\n margin-top: -4px;\n}\n\n.select2-container[dir=\"rtl\"] .select2-selection--single .select2-selection__rendered {\n padding-right: 6px;\n padding-left: 20px;\n}\n\n.select2-container--default .select2-selection--single .select2-selection__arrow {\n height: 28px;\n right: 3px;\n}\n\n.select2-container--default .select2-selection--single .select2-selection__arrow b {\n margin-top: 0;\n}\n\n.select2-dropdown,\n.select2-search--inline {\n .select2-search__field {\n border: 1px solid @gray-lte;\n &:focus {\n outline: none;\n //border: 1px solid @light-blue;\n }\n }\n}\n\n.select2-container--default.select2-container--focus .select2-selection--multiple,\n.select2-container--default .select2-search--dropdown .select2-search__field {\n border-color: @light-blue !important;\n}\n\n.select2-container--default .select2-results__option[aria-disabled=true] {\n color: #999;\n}\n\n.select2-container--default .select2-results__option[aria-selected=true] {\n background-color: #ddd;\n &,\n &:hover {\n color: #444;\n }\n}\n\n//Multiple select\n.select2-container--default {\n .select2-selection--multiple {\n border: 1px solid @gray-lte;\n border-radius: @input-radius;\n &:focus {\n border-color: @light-blue;\n }\n }\n &.select2-container--focus .select2-selection--multiple {\n border-color: @gray-lte;\n }\n}\n\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @light-blue;\n border-color: darken(@light-blue, 5%);\n padding: 1px 10px;\n color: #fff;\n}\n\n.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {\n margin-right: 5px;\n color: rgba(255, 255, 255, .7);\n &:hover {\n color: #fff;\n }\n}\n\n.select2-container .select2-selection--single .select2-selection__rendered {\n padding-right: 10px;\n}\n",".box {\n .datepicker-inline {\n &,\n .datepicker-days {\n &,\n > table {\n width: 100%;\n td {\n &:hover {\n background-color: rgba(255, 255, 255, .3);\n }\n &.day {\n &.old,\n &.new {\n color: #777;\n }\n }\n }\n }\n }\n }\n}","/*\n * General: Miscellaneous\n * ----------------------\n */\n// 10px padding and margins\n.pad {\n padding: 10px;\n}\n\n.margin {\n margin: 10px;\n}\n\n.margin-bottom {\n margin-bottom: 20px;\n}\n\n.margin-bottom-none {\n margin-bottom: 0;\n}\n\n.margin-r-5 {\n margin-right: 5px;\n}\n\n// Display inline\n.inline {\n display: inline;\n}\n\n// Description Blocks\n.description-block {\n display: block;\n margin: 10px 0;\n text-align: center;\n &.margin-bottom {\n margin-bottom: 25px;\n }\n > .description-header {\n margin: 0;\n padding: 0;\n font-weight: 600;\n font-size: 16px;\n }\n > .description-text {\n text-transform: uppercase;\n }\n}\n\n// Background colors\n.bg-red,\n.bg-yellow,\n.bg-aqua,\n.bg-blue,\n.bg-light-blue,\n.bg-green,\n.bg-navy,\n.bg-teal,\n.bg-olive,\n.bg-lime,\n.bg-orange,\n.bg-fuchsia,\n.bg-purple,\n.bg-maroon,\n.bg-black,\n.bg-red-active,\n.bg-yellow-active,\n.bg-aqua-active,\n.bg-blue-active,\n.bg-light-blue-active,\n.bg-green-active,\n.bg-navy-active,\n.bg-teal-active,\n.bg-olive-active,\n.bg-lime-active,\n.bg-orange-active,\n.bg-fuchsia-active,\n.bg-purple-active,\n.bg-maroon-active,\n.bg-black-active {\n color: #fff !important;\n}\n\n.bg-gray {\n color: #000;\n background-color: @gray-lte !important;\n}\n\n.bg-gray-light {\n background-color: #f7f7f7;\n}\n\n.bg-black {\n background-color: @black !important;\n}\n\n.bg-red {\n background-color: @red !important;\n}\n\n.bg-yellow {\n background-color: @yellow !important;\n}\n\n.bg-aqua {\n background-color: @aqua !important;\n}\n\n.bg-blue {\n background-color: @blue !important;\n}\n\n.bg-light-blue {\n background-color: @light-blue !important;\n}\n\n.bg-green {\n background-color: @green !important;\n}\n\n.bg-navy {\n background-color: @navy !important;\n}\n\n.bg-teal {\n background-color: @teal !important;\n}\n\n.bg-olive {\n background-color: @olive !important;\n}\n\n.bg-lime {\n background-color: @lime !important;\n}\n\n.bg-orange {\n background-color: @orange !important;\n}\n\n.bg-fuchsia {\n background-color: @fuchsia !important;\n}\n\n.bg-purple {\n background-color: @purple !important;\n}\n\n.bg-maroon {\n background-color: @maroon !important;\n}\n\n//Set of Active Background Colors\n.bg-gray-active {\n color: #000;\n background-color: darken(@gray-lte, 10%) !important;\n}\n\n.bg-black-active {\n background-color: darken(@black, 10%) !important;\n}\n\n.bg-red-active {\n background-color: darken(@red , 6%) !important;\n}\n\n.bg-yellow-active {\n background-color: darken(@yellow , 6%) !important;\n}\n\n.bg-aqua-active {\n background-color: darken(@aqua , 6%) !important;\n}\n\n.bg-blue-active {\n background-color: darken(@blue , 10%) !important;\n}\n\n.bg-light-blue-active {\n background-color: darken(@light-blue , 6%) !important;\n}\n\n.bg-green-active {\n background-color: darken(@green , 5%) !important;\n}\n\n.bg-navy-active {\n background-color: darken(@navy , 2%) !important;\n}\n\n.bg-teal-active {\n background-color: darken(@teal , 5%) !important;\n}\n\n.bg-olive-active {\n background-color: darken(@olive , 5%) !important;\n}\n\n.bg-lime-active {\n background-color: darken(@lime , 5%) !important;\n}\n\n.bg-orange-active {\n background-color: darken(@orange , 5%) !important;\n}\n\n.bg-fuchsia-active {\n background-color: darken(@fuchsia , 5%) !important;\n}\n\n.bg-purple-active {\n background-color: darken(@purple , 5%) !important;\n}\n\n.bg-maroon-active {\n background-color: darken(@maroon , 3%) !important;\n}\n\n//Disabled!\n[class^=\"bg-\"].disabled {\n .opacity(.65);\n}\n\n// Text colors\n.text-red {\n color: @red !important;\n}\n\n.text-yellow {\n color: @yellow !important;\n}\n\n.text-aqua {\n color: @aqua !important;\n}\n\n.text-blue {\n color: @blue !important;\n}\n\n.text-black {\n color: @black !important;\n}\n\n.text-light-blue {\n color: @light-blue !important;\n}\n\n.text-green {\n color: @green !important;\n}\n\n.text-gray {\n color: @gray-lte !important;\n}\n\n.text-navy {\n color: @navy !important;\n}\n\n.text-teal {\n color: @teal !important;\n}\n\n.text-olive {\n color: @olive !important;\n}\n\n.text-lime {\n color: @lime !important;\n}\n\n.text-orange {\n color: @orange !important;\n}\n\n.text-fuchsia {\n color: @fuchsia !important;\n}\n\n.text-purple {\n color: @purple !important;\n}\n\n.text-maroon {\n color: @maroon !important;\n}\n\n.link-muted {\n color: darken(@gray-lte, 30%);\n &:hover,\n &:focus {\n color: darken(@gray-lte, 40%);\n }\n}\n\n.link-black {\n color: #666;\n &:hover,\n &:focus {\n color: #999;\n }\n}\n\n// Hide elements by display none only\n.hide {\n display: none !important;\n}\n\n// Remove borders\n.no-border {\n border: 0 !important;\n}\n\n// Remove padding\n.no-padding {\n padding: 0 !important;\n}\n\n// Remove margins\n.no-margin {\n margin: 0 !important;\n}\n\n// Remove box shadow\n.no-shadow {\n box-shadow: none !important;\n}\n\n// Unstyled List\n.list-unstyled {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.list-group-unbordered {\n > .list-group-item {\n border-left: 0;\n border-right: 0;\n border-radius: 0;\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n// Remove border radius\n.flat {\n .border-radius(0) !important;\n}\n\n.text-bold {\n &, &.table td, &.table th {\n font-weight: 700;\n }\n}\n\n.text-sm {\n font-size: 12px;\n}\n\n// _fix for sparkline tooltip\n.jqstooltip {\n padding: 5px !important;\n width: auto !important;\n height: auto !important;\n}\n\n// Gradient Background colors\n.bg-teal-gradient {\n .gradient(@teal; @teal; lighten(@teal, 16%)) !important;\n color: #fff;\n}\n\n.bg-light-blue-gradient {\n .gradient(@light-blue; @light-blue; lighten(@light-blue, 12%)) !important;\n color: #fff;\n}\n\n.bg-blue-gradient {\n .gradient(@blue; @blue; lighten(@blue, 7%)) !important;\n color: #fff;\n}\n\n.bg-aqua-gradient {\n .gradient(@aqua; @aqua; lighten(@aqua, 7%)) !important;\n color: #fff;\n}\n\n.bg-yellow-gradient {\n .gradient(@yellow; @yellow; lighten(@yellow, 16%)) !important;\n color: #fff;\n}\n\n.bg-purple-gradient {\n .gradient(@purple; @purple; lighten(@purple, 16%)) !important;\n color: #fff;\n}\n\n.bg-green-gradient {\n .gradient(@green; @green; lighten(@green, 7%)) !important;\n color: #fff;\n}\n\n.bg-red-gradient {\n .gradient(@red; @red; lighten(@red, 10%)) !important;\n color: #fff;\n}\n\n.bg-black-gradient {\n .gradient(@black; @black; lighten(@black, 10%)) !important;\n color: #fff;\n}\n\n.bg-maroon-gradient {\n .gradient(@maroon; @maroon; lighten(@maroon, 10%)) !important;\n color: #fff;\n}\n\n//Description Block Extension\n.description-block {\n .description-icon {\n font-size: 16px;\n }\n}\n\n//Remove top padding\n.no-pad-top {\n padding-top: 0;\n}\n\n//Make position static\n.position-static {\n position: static !important;\n}\n\n//List utility classes\n.list-header {\n font-size: 15px;\n padding: 10px 4px;\n font-weight: bold;\n color: #666;\n}\n\n.list-seperator {\n height: 1px;\n background: @box-border-color;\n margin: 15px 0 9px 0;\n}\n\n.list-link {\n > a {\n padding: 4px;\n color: #777;\n &:hover {\n color: #222;\n }\n }\n}\n\n//Light font weight\n.font-light {\n font-weight: 300;\n}\n\n//User block\n.user-block {\n .clearfix();\n img {\n width: 40px;\n height: 40px;\n float: left;\n }\n .username,\n .description,\n .comment {\n display: block;\n margin-left: 50px;\n }\n .username {\n font-size: 16px;\n font-weight: 600;\n }\n .description {\n color: #999;\n font-size: 13px;\n }\n &.user-block-sm {\n img {\n &:extend(.img-sm);\n }\n .username,\n .description,\n .comment {\n margin-left: 40px;\n }\n .username {\n font-size: 14px;\n }\n }\n}\n\n//Image sizes\n.img-sm,\n.img-md,\n.img-lg {\n float: left;\n}\n\n.img-sm {\n width: 30px !important;\n height: 30px !important;\n + .img-push {\n margin-left: 40px;\n }\n}\n\n.img-md {\n width: 60px;\n height: 60px;\n + .img-push {\n margin-left: 70px;\n }\n}\n\n.img-lg {\n width: 100px;\n height: 100px;\n + .img-push {\n margin-left: 110px;\n }\n}\n\n// Image bordered\n.img-bordered {\n border: 3px solid @gray-lte;\n padding: 3px;\n}\n\n.img-bordered-sm {\n border: 2px solid @gray-lte;\n padding: 2px;\n}\n\n//General attachemnt block\n.attachment-block {\n border: 1px solid @box-border-color;\n padding: 5px;\n margin-bottom: 10px;\n background: #f7f7f7;\n\n .attachment-img {\n max-width: 100px;\n max-height: 100px;\n height: auto;\n float: left;\n }\n .attachment-pushed {\n margin-left: 110px;\n }\n .attachment-heading {\n margin: 0;\n }\n .attachment-text {\n color: #555;\n }\n}\n\n.connectedSortable {\n min-height: 100px;\n}\n\n.ui-helper-hidden-accessible {\n border: 0;\n clip: rect(0 0 0 0);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n}\n\n.sort-highlight {\n background: #f4f4f4;\n border: 1px dashed #ddd;\n margin-bottom: 10px;\n}\n\n.full-opacity-hover {\n .opacity(.65);\n &:hover {\n .opacity(1);\n }\n}\n\n// Charts\n.chart {\n position: relative;\n overflow: hidden;\n width: 100%;\n svg,\n canvas {\n width: 100% !important;\n }\n}\n\n// Horizontal rules\nhr {\n border-top: 1px solid @hr-border;\n}\n\n// bootstrap slider\n\n#red .slider-selection {\n background: #f56954;\n}\n\n#blue .slider-selection {\n background: #3c8dbc;\n}\n\n#green .slider-selection {\n background: #00a65a;\n}\n\n#yellow .slider-selection {\n background: #f39c12;\n}\n\n#aqua .slider-selection {\n background: #00c0ef;\n}\n\n#purple .slider-selection {\n background: #932ab6;\n}\n","/*\n * Misc: print\n * -----------\n */\n@media print {\n //Add to elements that you do not want to show when printing\n .no-print {\n display: none !important;\n }\n\n //Elements that we want to hide when printing\n .main-sidebar,\n .left-side,\n .main-header,\n .content-header {\n &:extend(.no-print);\n }\n\n //This is the only element that should appear, so let's remove the margins\n .content-wrapper,\n .right-side,\n .main-footer {\n margin-left: 0 !important;\n min-height: 0 !important;\n .translate(0, 0) !important;\n }\n\n .fixed .content-wrapper,\n .fixed .right-side {\n padding-top: 0 !important;\n }\n\n //Invoice printing\n .invoice {\n width: 100%;\n border: 0;\n margin: 0;\n padding: 0;\n }\n\n .invoice-col {\n float: left;\n width: 33.3333333%;\n }\n\n //Make sure table content displays properly\n .table-responsive {\n overflow: auto;\n > .table tr th,\n > .table tr td {\n white-space: normal !important;\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/public/css/build/app.css b/public/css/build/app.css index 3a5e50d53d..fab5f7fbe5 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -306,12 +306,19 @@ a.accordion-header { padding: 0px; /* adjust based on your layout */ } -.skin-blue .main-header .navbar .dropdown-menu li a { - color: #333; -} a.logo.no-hover a:hover { background-color: transparent; } +.index-block { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.index-block:hover { + overflow: visible; + white-space: normal; + height: auto; +} input:required, select:required, textarea:required { @@ -1062,7 +1069,6 @@ th.css-house-laptop > .th-inner::before, th.css-house-user > .th-inner::before, th.css-license > .th-inner::before, th.css-location > .th-inner::before, -th.css-padlock > .th-inner::before, th.css-users > .th-inner::before, th.css-currency > .th-inner::before, th.css-history > .th-inner::before { @@ -1119,7 +1125,8 @@ th.css-component > .th-inner::before { th.css-padlock > .th-inner::before { content: "\f023"; font-family: "Font Awesome 5 Free"; - font-weight: 900; + font-weight: 800; + padding-right: 3px; } th.css-house-user > .th-inner::before { content: "\e1b0"; @@ -1256,7 +1263,6 @@ label.form-control { } label.form-control--disabled { color: #959495; - pointer-events: none; cursor: not-allowed; } /** --------------------------------------- **/ @@ -1419,6 +1425,9 @@ input[type="radio"]:checked::before { .bootstrap-table .fixed-table-container .table tbody tr .card-view { display: table-row !important; } +.form-control-static { + padding-top: 0px; +} td.text-right.text-padding-number-cell { padding-right: 30px !important; white-space: nowrap; @@ -1439,4 +1448,51 @@ p.monospace, span.monospace { font-family: monospace, monospace; } +legend.highlight { + background: repeating-linear-gradient(45deg, #222d32, #222d32 10px, #444 10px, #444 11px); + color: #fff; + font-size: 18px; + padding: 6px 6px 6px 10px; +} +legend.highlight a { + color: #fff; + cursor: pointer; +} +fieldset.bottom-padded { + padding-bottom: 20px; +} +caption.tableCaption { + font-size: 18px; + padding-left: 8px; +} +.sidebar-toggle.btn { + border-radius: 3px; + box-shadow: none; + border-top: 0px solid transparent; + border-bottom: 0px solid transparent; + padding-left: 15px; + padding-right: 15px; + padding-top: 12px; + padding-bottom: 12px; + margin-left: -47px; + margin-top: 2px; +} +.popover.help-popover, +.popover.help-popover .popover-content, +.popover.help-popover .popover-body, +.popover.help-popover .popover-title, +.popover.help-popover .popover-header { + color: #000; +} +.visually-hidden { + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: preserve; + display: inline-block; +} + +/*# sourceMappingURL=app.css.map*/ \ No newline at end of file diff --git a/public/css/build/app.css.map b/public/css/build/app.css.map index 2522e80582..cb6c3b39de 100644 --- a/public/css/build/app.css.map +++ b/public/css/build/app.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./resources/assets/less/app.less","webpack:///./node_modules/bootstrap-less/bootstrap/normalize.less","webpack:///./node_modules/bootstrap-less/bootstrap/print.less","webpack:///./node_modules/bootstrap-less/bootstrap/glyphicons.less","webpack:///./node_modules/bootstrap-less/bootstrap/scaffolding.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/vendor-prefixes.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/tab-focus.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/image.less","webpack:///./node_modules/bootstrap-less/bootstrap/type.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/text-emphasis.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/background-variant.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/text-overflow.less","webpack:///./node_modules/bootstrap-less/bootstrap/code.less","webpack:///./node_modules/bootstrap-less/bootstrap/variables.less","webpack:///./node_modules/bootstrap-less/bootstrap/grid.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/grid.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/grid-framework.less","webpack:///./node_modules/bootstrap-less/bootstrap/tables.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/table-row.less","webpack:///./node_modules/bootstrap-less/bootstrap/forms.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/forms.less","webpack:///./node_modules/bootstrap-less/bootstrap/buttons.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/buttons.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/opacity.less","webpack:///./node_modules/bootstrap-less/bootstrap/component-animations.less","webpack:///./node_modules/bootstrap-less/bootstrap/dropdowns.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/nav-divider.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/reset-filter.less","webpack:///./node_modules/bootstrap-less/bootstrap/button-groups.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/border-radius.less","webpack:///./node_modules/bootstrap-less/bootstrap/input-groups.less","webpack:///./node_modules/bootstrap-less/bootstrap/navs.less","webpack:///./node_modules/bootstrap-less/bootstrap/navbar.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/nav-vertical-align.less","webpack:///./node_modules/bootstrap-less/bootstrap/utilities.less","webpack:///./node_modules/bootstrap-less/bootstrap/breadcrumbs.less","webpack:///./node_modules/bootstrap-less/bootstrap/pagination.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/pagination.less","webpack:///./node_modules/bootstrap-less/bootstrap/pager.less","webpack:///./node_modules/bootstrap-less/bootstrap/labels.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/labels.less","webpack:///./node_modules/bootstrap-less/bootstrap/badges.less","webpack:///./node_modules/bootstrap-less/bootstrap/jumbotron.less","webpack:///./node_modules/bootstrap-less/bootstrap/thumbnails.less","webpack:///./node_modules/bootstrap-less/bootstrap/alerts.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/alerts.less","webpack:///./node_modules/bootstrap-less/bootstrap/progress-bars.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/gradients.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/progress-bar.less","webpack:///./node_modules/bootstrap-less/bootstrap/media.less","webpack:///./node_modules/bootstrap-less/bootstrap/list-group.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/list-group.less","webpack:///./node_modules/bootstrap-less/bootstrap/panels.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/panels.less","webpack:///./node_modules/bootstrap-less/bootstrap/responsive-embed.less","webpack:///./node_modules/bootstrap-less/bootstrap/wells.less","webpack:///./node_modules/bootstrap-less/bootstrap/close.less","webpack:///./node_modules/bootstrap-less/bootstrap/modals.less","webpack:///./node_modules/bootstrap-less/bootstrap/tooltip.less","webpack:///./node_modules/bootstrap-less/bootstrap/popovers.less","webpack:///./node_modules/bootstrap-less/bootstrap/carousel.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/clearfix.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/center-block.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/hide-text.less","webpack:///./node_modules/bootstrap-less/bootstrap/responsive-utilities.less","webpack:///./node_modules/bootstrap-less/bootstrap/mixins/responsive-visibility.less","webpack:///./node_modules/ekko-lightbox/ekko-lightbox.less","webpack:///./node_modules/bootstrap-colorpicker/src/less/colorpicker.less"],"names":[],"mappings":"AAAA,4DCQA,KACE,uBACA,0BACA,8BAOF,KACE,SAaF,2FAaE,cAQF,4BAIE,qBACA,wBAQF,sBACE,aACA,SAQF,kBAEE,aAUF,EACE,6BAOF,iBAEE,UAUF,YACE,yBAOF,SAEE,gBAOF,IACE,kBAQF,GACE,cACA,eAOF,KACE,gBACA,WAOF,MACE,cAOF,QAEE,cACA,cACA,kBACA,wBAGF,IACE,UAGF,IACE,cAUF,IACE,SAOF,eACE,gBAUF,OACE,gBAOF,GAEE,sDACA,SAOF,IACE,cAOF,kBAIE,gCACA,cAkBF,sCAKE,cACA,aACA,SAOF,OACE,iBAUF,cAEE,oBAWF,oEAIE,0BACA,eAOF,sCAEE,eAOF,iDAEE,SACA,UAQF,MACE,mBAWF,uCAEE,oDACA,UASF,4FAEE,YASF,mBACE,6BACA,+BAEA,uBASF,+FAEE,wBAOF,SACE,wBACA,aACA,2BAiBF,SACE,cAQF,SACE,gBAUF,MACE,yBACA,iBAGF,MAEE,+FClaF,aACI,iBAGI,iCACA,qBACA,4DACA,2BAGJ,YAEI,0BAGJ,cACI,2BAAyB,CAG7B,kBACI,4BAA0B,CAK9B,gDAEI,UAAS,CAGb,eAEI,sBACA,wBAGJ,MACI,2BAGJ,OAEI,wBAGJ,IACI,yBAGJ,QAGI,UACA,SAGJ,MAEI,uBAOJ,OACI,0BAIJ,QACI,aAEJ,gCAGQ,gCAGR,OACI,sBAGJ,OACI,mCADJ,oBAKQ,gCAGR,sCAGQ,iCC3FZ,WACE,iCACA,uFACA,oiBAI8E,CAIhF,WACE,kBACA,QACA,qBACA,iCACA,kBACA,gBACA,cACA,mCACA,kCAIkC,2BAAW,WAAS,CACpB,uBAAW,WAAS,CAEpB,6CAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,qBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,qBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,mCAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,kCAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,qCAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,kCAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,mCAAW,eAAS,CACpB,uCAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,qCAAW,eAAS,CACpB,yCAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,qBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CASpB,wBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,yBAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,uBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,2BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,sBAAW,aAAS,CACpB,wBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,mCAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,kCAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,sBAAW,eAAS,CACpB,wBAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,kCAAW,eAAS,CACpB,mCAAW,eAAS,CACpB,sCAAW,eAAS,CACpB,0CAAW,eAAS,CACpB,oCAAW,eAAS,CACpB,wCAAW,eAAS,CACpB,qCAAW,eAAS,CACpB,iCAAW,eAAS,CACpB,gCAAW,eAAS,CACpB,kCAAW,eAAS,CACpB,+BAAW,eAAS,CACpB,0BAAW,eAAS,CACpB,8BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,6BAAW,eAAS,CACpB,4BAAW,eAAS,CACpB,0BAAW,eAAS,CC/RxD,iBC6DE,8BACG,qBACK,CDvDV,KACE,eACA,0CAGF,KACE,sDACA,eACA,uBACA,WACA,sBAIF,6BAIE,oBACA,kBACA,oBAMF,EACE,cACA,qBAEA,gBAEE,cACA,0BAGF,QErDA,oBAEA,0CACA,oBF6DF,OACE,SAMF,IACE,sBAIF,sGGvEE,cACA,eACA,YH0EF,aACE,kBAMF,eACE,YACA,uBACA,sBACA,sBACA,kBC6FA,uCACK,+BEtLL,qBACA,eACA,YH8FF,YACE,kBAMF,GACE,gBACA,mBACA,SACA,0BAQF,SACE,kBACA,UACA,WACA,YACA,UACA,gBACA,mBACA,SAQA,mDAEE,gBACA,WACA,YACA,SACA,iBACA,UI3IJ,0CAEE,oBACA,gBACA,gBACA,cALF,gPASI,gBACA,cACA,WAIJ,qBAGE,gBACA,mBAJF,wHAQI,cAGJ,qBAGE,gBACA,mBAJF,wHAQI,cAIJ,OAAU,eACV,OAAU,eACV,OAAU,eACV,OAAU,eACV,OAAU,eACV,OAAU,eAMV,EACE,gBAGF,MACE,mBACA,eACA,gBACA,gBAEA,+BACE,gBASJ,aAEE,cAGF,WAEE,yBACA,aAIF,WAAuB,gBACvB,YAAuB,iBACvB,aAAuB,kBACvB,cAAuB,mBACvB,aAAuB,mBAGvB,gBAAuB,yBACvB,gBAAuB,yBACvB,iBAAuB,0BAGvB,YACE,WAEF,cCrGE,cACA,qBACE,cDsGJ,cCxGE,cACA,qBACE,cDyGJ,WC3GE,cACA,kBACE,cD4GJ,cC9GE,cACA,qBACE,cD+GJ,aCjHE,cACA,oBACE,cDsHJ,YAGE,WE3HA,yBACA,mBACE,yBF4HJ,YE9HE,yBACA,mBACE,yBF+HJ,SEjIE,yBACA,gBACE,yBFkIJ,YEpIE,yBACA,mBACE,yBFqIJ,WEvIE,yBACA,kBACE,yBF6IJ,aACE,mBACA,mBACA,6BAQF,MAEE,aACA,mBAHF,wBAMI,gBAaJ,4BALE,eACA,gBAIF,aAEE,iBAFF,gBAKI,qBACA,iBACA,kBAKJ,GACE,aACA,mBAEF,MAEE,uBAEF,GACE,gBAEF,GACE,cAaA,2CAEI,WACA,YACA,WACA,iBGtNJ,gBACA,uBACA,mBH+MA,kBASI,mBAUN,sCAGE,YACA,8BAEF,YACE,cACA,yBAIF,WACE,kBACA,gBACA,iBACA,2BAKE,0EACE,gBAVN,qDAmBI,cACA,cACA,uBACA,WAEA,0EACE,qBAAS,CAQf,0CAEE,mBACA,eACA,4BACA,cACA,iBAME,gNAAW,UAAS,CACpB,0MACE,qBAAS,CAMf,QACE,mBACA,kBACA,uBItSF,kBAIE,uDCqCiD,CDjCnD,KAGE,cACA,yBACA,kBAIF,SARE,gBACA,aACA,CAMF,IAGE,WACA,sBACA,kBACA,4FANF,QASI,UACA,eACA,gBACA,wCAKJ,IACE,cACA,cACA,gBACA,eACA,uBACA,qBACA,qBACA,WACA,yBACA,sBACA,kBAXF,SAeI,UACA,kBACA,cACA,qBACA,6BACA,gBAKJ,gBACE,iBACA,kBE1DF,WCHE,kBACA,iBACA,kBACA,mBDGA,oCACE,aAEF,oCACE,aAEF,qCACE,cAUJ,iBCvBE,kBACA,iBACA,kBACA,mBD6BF,KCvBE,kBACA,mBCAE,4eACE,kBAEA,eAEA,kBACA,mBAgBF,2HACE,WAOJ,WACE,WADF,WACE,mBADF,WACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,kBAcF,gBACE,WADF,gBACE,mBADF,gBACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,kBAIF,eACE,WAhBF,gBACE,UADF,gBACE,kBADF,gBACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,iBAIF,eACE,UAcF,kBACE,iBADF,kBACE,yBADF,kBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,wBADF,iBACE,cFTJ,yBEzBI,2HACE,WAOJ,WACE,WADF,WACE,mBADF,WACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,kBAcF,gBACE,WADF,gBACE,mBADF,gBACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,kBAIF,eACE,WAhBF,gBACE,UADF,gBACE,kBADF,gBACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,iBAIF,eACE,UAcF,kBACE,iBADF,kBACE,yBADF,kBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,wBADF,iBACE,eFAJ,yBElCI,2HACE,WAOJ,WACE,WADF,WACE,mBADF,WACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,kBAcF,gBACE,WADF,gBACE,mBADF,gBACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,kBAIF,eACE,WAhBF,gBACE,UADF,gBACE,kBADF,gBACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,iBAIF,eACE,UAcF,kBACE,iBADF,kBACE,yBADF,kBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,wBADF,iBACE,eFSJ,0BE3CI,2HACE,WAOJ,WACE,WADF,WACE,mBADF,WACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,mBADF,UACE,UADF,UACE,mBADF,UACE,kBAcF,gBACE,WADF,gBACE,mBADF,gBACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,mBADF,eACE,UADF,eACE,mBADF,eACE,kBAIF,eACE,WAhBF,gBACE,UADF,gBACE,kBADF,gBACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,kBADF,eACE,SADF,eACE,kBADF,eACE,iBAIF,eACE,UAcF,kBACE,iBADF,kBACE,yBADF,kBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,yBADF,iBACE,gBADF,iBACE,yBADF,iBACE,wBADF,iBACE,eCnEJ,MACE,6BAEF,QACE,gBACA,mBACA,UACA,CAEF,WAFE,gBASF,OACE,WACA,eACA,mBAHF,kHAWQ,YACA,uBACA,mBACA,0BAdR,mBAoBI,sBACA,6BArBJ,oPA8BQ,aA9BR,mBAoCI,0BApCJ,cAyCI,sBAOJ,8KAOQ,YAWR,wLAQQ,sBARR,wDAeM,wBAUN,yCAEI,yBASJ,4BAEI,yBASJ,uBACE,gBACA,WACA,qBAKE,4CACE,gBACA,WACA,mBC5IJ,wTAOI,yBAMJ,4LAMI,yBAnBJ,oUAOI,yBAMJ,iMAMI,yBAnBJ,gSAOI,yBAMJ,kLAMI,yBAnBJ,oUAOI,yBAMJ,iMAMI,yBAnBJ,wTAOI,yBAMJ,4LAMI,yBDkJN,kBACE,gBACA,gBAEA,sDACE,WACA,mBACA,kBACA,4CACA,sBALF,yBASI,gBATJ,8NAkBU,mBAlBV,kCA0BI,SA1BJ,4VAmCU,cAnCV,sVAuCU,eAvCV,oOAoDU,iBEzNZ,SAEE,SAKA,YAGF,gBATE,UAEA,QAIA,CAeF,OAXE,cACA,WAEA,mBACA,eACA,oBACA,WAEA,gCAGF,MACE,qBACA,eACA,kBACA,gBAWF,mBd4BE,8BACG,qBACK,CczBV,uCAEE,eACA,iBACA,mBAIF,iBACE,cAIF,kBACE,cACA,WAIF,8BAEE,YAIF,0EbxEE,oBAEA,0CACA,oBa4EF,OAEE,eACA,CA4BF,qBA9BE,cAEA,eACA,uBACA,WChCA,cD4DA,WACA,YACA,iBAIA,sBACA,sBACA,sBACA,kBdzDA,oDACQ,4CAyHR,6EACK,qFACG,2PexIR,oBACE,qBACA,UfUF,iFACQ,yEAiCR,gCACE,WACA,UAEF,oCAA0B,WAC1B,yCAAgC,WciChC,iFAGE,mBACA,sBACA,UAIF,sBACE,YAYJ,mBACE,wBAWF,qDACE,+EAIE,iBAEA,kQAEE,iBAGF,kQAEE,kBAWN,YACE,mBAQF,iBAEE,kBACA,cACA,gBACA,mBALF,6BAQI,gBACA,kBACA,gBACA,gBACA,eAGJ,8HAIE,kBACA,kBACA,iBAGF,kCAEE,gBAIF,+BAEE,qBACA,kBACA,gBACA,sBACA,gBACA,eAEF,8DAEE,aACA,iBA0BA,yaAGI,mBAWN,qBAEE,gBACA,mBAEA,gBAEA,4DAEE,eACA,gBAaJ,UCpPE,YACA,iBACA,eACA,gBACA,kBAEA,gBACE,YACA,iBAGF,4CAEE,YD0OJ,6BCvPE,YACA,iBACA,eACA,gBACA,kBAEA,mCACE,YACA,iBAGF,kFAEE,YD0OJ,oCAKI,YACA,iBACA,eACA,gBAIJ,UCnQE,YACA,kBACA,eACA,sBACA,kBAEA,gBACE,YACA,iBAGF,4CAEE,YDyPJ,6BCtQE,YACA,kBACA,eACA,sBACA,kBAEA,mCACE,YACA,iBAGF,kFAEE,YDyPJ,oCAKI,YACA,kBACA,eACA,sBASJ,cAEE,kBAFF,4BAMI,qBAIJ,uBACE,kBACA,MACA,QACA,UACA,cACA,WACA,YACA,iBACA,kBACA,oBAEF,iCACE,WACA,YACA,iBAEF,iCACE,WACA,YACA,iBAIF,iRC7WI,cD6WJ,2BCzWI,qBf+CF,oDACQ,4Ce9CN,iCACE,qBf4CJ,oEACQ,4DcyTV,gCC/VI,cACA,qBACA,yBD6VJ,oCCzVI,cD4VJ,iRChXI,cDgXJ,2BC5WI,qBf+CF,oDACQ,4Ce9CN,iCACE,qBf4CJ,oEACQ,4Dc4TV,gCClWI,cACA,qBACA,yBDgWJ,oCC5VI,cD+VJ,6PCnXI,cDmXJ,yBC/WI,qBf+CF,oDACQ,4Ce9CN,+BACE,qBf4CJ,oEACQ,4Dc+TV,8BCrWI,cACA,qBACA,yBDmWJ,kCC/VI,cDsWF,2CACG,SAEH,mDACG,MAUL,YACE,cACA,eACA,mBACA,cAkBA,kDAGI,qBACA,gBACA,sBALJ,2BAUI,qBACA,WACA,sBAZJ,kCAiBI,qBAjBJ,0BAqBI,qBACA,sBAtBJ,gIA2BM,WA3BN,wCAiCI,WAjCJ,4BAqCI,gBACA,sBAtCJ,2CA6CI,qBACA,aACA,gBACA,sBAhDJ,uDAmDM,eAnDN,kFAwDI,kBACA,cAzDJ,kDA8DI,OAWN,oHASI,aACA,gBACA,gBAXJ,mDAiBI,gBAjBJ,6BJzeE,kBACA,mBImgBA,yDAEI,iBACA,gBACA,iBA/BN,sDAwCI,WAQA,wEAEI,yBAKJ,wEAEI,iBEziBR,KACE,qBACA,gBACA,gBACA,kBACA,sBACA,wDACA,eACA,sBACA,6BACA,mBC6BA,iBACA,eACA,uBACA,kBjB4KA,yBACG,sBACC,qBACI,iBgBxMN,8FfrBF,oBAEA,0CACA,oBewBA,iCAGE,WACA,qBAGF,wBAEE,UACA,sBhB2BF,oDACQ,4CgBxBR,qDAGE,mBACA,oBE9CF,YAGA,yBlB8DA,wBACQ,gBgBVV,aCrDE,WACA,sBACA,kBAEA,oIAME,WACA,yBACI,qBAEN,2EAGE,sBAKA,8iBAME,sBACI,kBDwBV,oBCnBI,WACA,sBDqBJ,aCxDE,WACA,yBACA,qBAEA,oIAME,WACA,yBACI,qBAEN,2EAGE,sBAKA,8iBAME,yBACI,qBD2BV,oBCtBI,cACA,sBDyBJ,aC5DE,WACA,yBACA,qBAEA,oIAME,WACA,yBACI,qBAEN,2EAGE,sBAKA,8iBAME,yBACI,qBD+BV,oBC1BI,cACA,sBD6BJ,UChEE,WACA,yBACA,qBAEA,kHAME,WACA,yBACI,qBAEN,kEAGE,sBAKA,wfAME,yBACI,qBDmCV,iBC9BI,cACA,sBDiCJ,aCpEE,WACA,yBACA,qBAEA,oIAME,WACA,yBACI,qBAEN,2EAGE,sBAKA,8iBAME,yBACI,qBDuCV,oBClCI,cACA,sBDqCJ,YCxEE,WACA,yBACA,qBAEA,8HAME,WACA,yBACI,qBAEN,wEAGE,sBAKA,4hBAME,yBACI,qBD2CV,mBCtCI,cACA,sBD8CJ,UACE,cACA,gBACA,gBAEA,6FAKE,6BhB7BF,wBACQ,gBgB+BR,2DAIE,yBAEF,gCAEE,cACA,0BACA,6BAIA,0HAEE,WACA,qBASN,2BC/EE,kBACA,eACA,sBACA,kBDgFF,2BCnFE,iBACA,eACA,gBACA,kBDoFF,2BCvFE,gBACA,eACA,gBACA,kBD4FF,WACE,cACA,WAIF,sBACE,eAOA,sFACE,WGpJJ,MACE,UnBoLA,uCACK,8BACG,CmBpLR,SACE,UAIJ,UACE,aACA,kBAEA,aAAY,cAAgB,mBAC5B,eAAY,kBACZ,kBAAY,wBAGd,YACE,kBACA,SACA,gBnBsKA,8CACQ,sCAOR,iCACQ,yBAGR,wCACQ,gCoB1MV,OACE,qBACA,QACA,SACA,gBACA,sBACA,qBACA,mCACA,kCAIF,kBAEE,kBAIF,uBACE,UAIF,eACE,kBACA,SACA,OACA,aACA,aACA,WACA,gBACA,cACA,eACA,gBACA,eACA,gBACA,sBACA,sBACA,iCACA,kBpBuBA,+CACQ,uCoBtBR,4BAKA,0BACE,QACA,UAzBJ,wBCxBE,WACA,aACA,gBACA,yBDqBF,oBAmCI,cACA,iBACA,WACA,gBACA,uBACA,WACA,mBAMF,oDAEE,qBACA,cACA,yBAMF,uFAGE,WACA,qBACA,UACA,yBASF,6FAGE,WAIF,kEAEE,qBACA,6BACA,sBE1GF,mEF4GE,mBAKJ,qBAGI,cAHJ,QAQI,UAQJ,qBACE,UACA,QAQF,oBACE,OACA,WAIF,iBACE,cACA,iBACA,eACA,uBACA,WACA,mBAIF,mBACE,eACA,OACA,QACA,SACA,MACA,YAIF,2BACE,QACA,UAQF,qDAII,aACA,wBACA,UAAS,CANb,qEAUI,SACA,YACA,kBASJ,yBACE,6BAnEA,UACA,QAkEA,kCAzDA,OACA,YG9IF,+BAEE,kBACA,qBACA,sBAJF,yCAMI,kBACA,WAEA,wNAIE,UAMN,4GAKI,iBAKJ,aACE,iBADF,kDAMI,WANJ,oEAWI,gBAIJ,yEACE,gBAIF,4BACE,cACA,mECjDA,6BACG,0BDqDL,2FC9CE,4BACG,yBDmDL,sBACE,WAEF,8DACE,gBAEF,uIClEE,6BACG,0BDuEL,oEChEE,4BACG,yBDoEL,oEAEE,UAiBF,iCACE,iBACA,kBAEF,oCACE,kBACA,mBAKF,iCvB9CE,oDACQ,4CuBiDR,0CvBlDA,wBACQ,gBuBwDV,YACE,cAGF,eACE,uBACA,sBAGF,uBACE,uBAOF,4FAII,cACA,WACA,WACA,eAPJ,oCAcM,WAdN,gJAsBI,gBACA,cAKF,4DACE,gBAEF,sDACE,4BCnKF,6BACC,4BDqKD,sDACE,8BC/KF,0BACC,yBDkLH,uEACE,gBAEF,yJC9KE,6BACC,4BDmLH,6EC5LE,0BACC,yBDmMH,qBACE,cACA,WACA,mBACA,yBAJF,0DAOI,WACA,mBACA,SATJ,qCAYI,WAZJ,+CAgBI,UAiBJ,gNAKM,kBACA,mBACA,oBEzON,aACE,kBACA,cACA,yBAGA,0BACE,WACA,eACA,gBATJ,2BAeI,kBACA,UAKA,WAEA,WACA,gBASJ,uGV8BE,YACA,kBACA,eACA,sBACA,kBAEA,yHACE,YACA,iBAGF,sRAEE,YUtCJ,uGVyBE,YACA,iBACA,eACA,gBACA,kBAEA,yHACE,YACA,iBAGF,sRAEE,YU7BJ,+DAGE,mBAEA,wKACE,gBAIJ,oCAEE,SACA,mBACA,sBAKF,mBACE,iBACA,eACA,gBACA,cACA,WACA,kBACA,sBACA,sBACA,kBAGA,4BACE,iBACA,eACA,kBAEF,4BACE,kBACA,eACA,kBApBJ,6EA0BI,aAKJ,wUDhGE,6BACG,0BCwGL,+BACE,eAEF,iTDpGE,4BACG,yBC4GL,8BACE,cAKF,iBAIE,YACA,mBALF,uCACE,iBAGA,CAJF,2BAYM,iBAGF,qFAGE,UAKJ,0EAGI,kBAGJ,wEAGI,iBC1JN,KACE,gBACA,eACA,gBAHF,kBAOI,kBACA,cAME,UADA,kBACA,gCAEE,qBACA,sBAKJ,mBACE,WAEA,kDAEE,WACA,qBACA,6BACA,mBAOJ,mDAGE,sBACA,qBAzCN,kBLHE,WACA,aACA,gBACA,yBKAF,cA0DI,eASJ,UACE,6BADF,aAGI,WAEA,mBALJ,eASM,iBACA,uBACA,6BACA,0BACA,qBACE,4BAMF,8EAGE,WACA,sBACA,sBACA,gCACA,eAKN,wBAqDA,WA8BA,gBAnFA,2BAwDE,WAxDF,6BA0DI,kBACA,kBA3DJ,iDAgEE,SACA,UAGF,oDAEI,mBACA,SAHJ,6BAKM,iBAzEN,6BAuFE,eACA,kBAxFF,kHA8FE,sBAGF,sDAEI,6BACA,0BAHJ,kHAQI,0BAhGN,cAEI,WAFJ,gBAMM,kBANN,iBASM,gBAKA,iFAGE,WACA,yBAQR,gBAEI,WAFJ,mBAIM,eACA,cAYN,eACE,WADF,kBAII,WAJJ,oBAMM,kBACA,kBAPN,wCAYI,SACA,UAGF,2CAEI,mBACA,SAHJ,oBAKM,iBASR,oBACE,gBADF,yBAKI,eACA,kBANJ,sGAYI,sBAGF,kDAEI,6BACA,0BAHJ,sGAQI,0BAUN,uBAEI,aACA,kBAHJ,qBAMI,cACA,mBASJ,yBAEE,gBF7OA,0BACC,yBGMH,QACE,kBACA,gBACA,mBACA,6BAKA,iCACE,mBAaF,wCACE,YAeJ,iBACE,mBACA,mBACA,kBACA,iCACA,gGAEA,iCAEA,oBACE,gBAGF,0CACE,WACA,aACA,wCAEA,0BACE,wBACA,6BACA,sBACA,iBACA,2BAGF,oBACE,mBAKF,6GAGE,eACA,iBAKN,yEAGI,iBAEA,qIACE,kBAUN,wHAII,mBACA,kBAEA,iJACE,eACA,eAaN,mBACE,aACA,qBAEA,4CACE,iBAKJ,uCAEE,eACA,QACA,OACA,aAGA,gEACE,iBAGJ,kBACE,MACA,qBAEF,qBACE,SACA,gBACA,qBAMF,cACE,WACA,aACA,eACA,iBACA,YAEA,wCAEE,qBATJ,kBAaI,cAGF,yBACE,wEAEE,mBAWN,eACE,kBACA,YACA,kBACA,iBC/LA,eACA,kBDgMA,6BACA,sBACA,6BACA,kBAIA,qBACE,UAdJ,yBAmBI,cACA,WACA,WACA,kBAtBJ,mCAyBI,eAGF,wCACE,cAUJ,YACE,mBADF,iBAII,iBACA,oBACA,iBAGF,0DAGI,gBACA,WACA,WACA,aACA,6BACA,SACA,wCATJ,wFAYM,0BAZN,sCAeM,iBACA,wFAEE,uBAOR,qCACE,WACA,SAFF,eAKI,WALJ,iBAOM,iBACA,qBAYR,aACE,iBAEA,kBACA,iCACA,oC3B/NA,+EACQ,sE4B/DR,CdyaA,kDAGI,qBACA,gBACA,sBALJ,2BAUI,qBACA,WACA,sBAZJ,kCAiBI,qBAjBJ,0BAqBI,qBACA,sBAtBJ,gIA2BM,WA3BN,wCAiCI,WAjCJ,4BAqCI,gBACA,sBAtCJ,2CA6CI,qBACA,aACA,gBACA,sBAhDJ,uDAmDM,eAnDN,kFAwDI,kBACA,cAzDJ,kDA8DI,OalMF,kDACE,kBAEA,oCACE,iBASN,sCACE,WACA,SACA,cACA,eACA,cACA,iB3B1PF,wBACQ,iB2BkQV,8BACE,aHrUA,0BACC,yBGwUH,mDACE,gBH1UA,4BACC,2BAOD,6BACC,4BG2UH,YCjVE,eACA,kBDmVA,mBCpVA,gBACA,mBDsVA,mBCvVA,gBACA,mBDgWF,aCjWE,gBACA,mBDmWA,sCACE,WACA,iBACA,mBAaJ,yBACE,aEzWA,qBF0WA,cE7WA,sBF+WE,mBAFF,4BAKI,gBAUN,gBACE,yBACA,qBAFF,8BAKI,WACA,wEAEE,cACA,6BATN,8DAmBM,WAEA,8EAEE,WACA,6BAIF,8HAGE,WACA,yBAIF,oIAGE,WACA,6BAxCR,+BA8CI,kBACA,0EAEE,sBAjDN,yCAoDM,sBApDN,8DA0DI,qBAOE,wHAGE,yBACA,WAIJ,+EAIM,WACA,wHAEE,WACA,6BAIF,6LAGE,WACA,yBAIF,mMAGE,WACA,8BAjGZ,6BA8GI,WACA,mCACE,WAhHN,0BAqHI,WACA,gEAEE,WAIA,0LAEE,WAQR,gBACE,sBACA,qBAFF,8BAKI,cACA,wEAEE,WACA,6BATN,8DAmBM,cAEA,8EAEE,WACA,6BAIF,8HAGE,WACA,yBAIF,oIAGE,WACA,6BAxCR,+BA+CI,kBACA,0EAEE,sBAlDN,yCAqDM,sBArDN,8DA2DI,qBAME,wHAGE,yBACA,WAIJ,2FAIM,qBAJN,0DAOM,yBAPN,sDAUM,cACA,wHAEE,WACA,6BAIF,6LAGE,WACA,yBAIF,mMAGE,WACA,8BAvGZ,6BA+GI,cACA,mCACE,WAjHN,0BAsHI,cACA,gEAEE,WAIA,0LAEE,WG3oBR,YACE,iBACA,mBACA,gBACA,yBACA,kBALF,eAQI,qBARJ,yBAWM,eACA,cACA,WAbN,oBAkBI,WCpBJ,YACE,qBACA,eACA,cACA,kBAJF,eAOI,eAPJ,qCAUM,kBACA,WACA,iBACA,uBACA,qBACA,cACA,sBACA,sBACA,iBAEF,6DAGI,cPXN,8BACG,2BOcD,2DPvBF,+BACG,4BOgCD,kGAEE,cACA,sBACA,kBAMF,qKAGE,UACA,WACA,yBACA,qBACA,eAtDN,iLAiEM,WACA,sBACA,kBACA,mBASN,2CC1EM,kBACA,eAEF,mERMF,8BACG,2BQDD,iERRF,+BACG,4BO6EL,2CC/EM,iBACA,eAEF,mERMF,8BACG,2BQDD,iERRF,+BACG,4BSHL,OACE,eACA,cACA,gBACA,kBAJF,UAOI,eAPJ,2BAUM,qBACA,iBACA,sBACA,sBACA,mBAdN,oCAmBM,qBACA,sBApBN,iCA2BM,YA3BN,yCAkCM,WAlCN,2FA2CM,WACA,sBACA,mBC9CN,OACE,eACA,uBACA,cACA,gBACA,cACA,WACA,kBACA,mBACA,wBACA,oBAIE,4BAEE,WACA,qBACA,eAKJ,aACE,aAIF,YACE,kBACA,SAOJ,eCtCE,sBAGE,sDAEE,yBDqCN,eC1CE,yBAGE,sDAEE,yBDyCN,eC9CE,yBAGE,sDAEE,yBD6CN,YClDE,yBAGE,gDAEE,yBDiDN,eCtDE,yBAGE,sDAEE,yBDqDN,cC1DE,yBAGE,oDAEE,yBCFN,OACE,qBACA,eACA,gBACA,eACA,gBACA,WACA,cACA,wBACA,mBACA,kBACA,sBACA,mBAGA,aACE,aAIF,YACE,kBACA,SAGF,eACE,MACA,gBAKA,4BAEE,WACA,qBACA,eAKJ,2DAEE,cACA,sBAGF,wBACE,YAGF,+BACE,iBAGF,uBACE,gBCzDJ,WACE,kBACA,mBAEA,sBAJF,wCAGE,aACA,CAJF,aAYI,mBACA,eACA,gBAdJ,cAkBI,yBAGF,kDAEE,kBAvBJ,sBA2BI,eAGF,+CACE,eAEA,kDAEE,kBACA,mBANJ,6BAWI,gBCxCN,WACE,cACA,YACA,mBACA,uBACA,sBACA,sBACA,kBtCiLA,0CACK,iCACG,CsC1LV,gCAaI,iBACA,kBAIF,uDAGE,qBArBJ,oBA0BI,YACA,WCzBJ,OACE,aACA,mBACA,6BACA,kBAJF,UAQI,aAEA,cAVJ,mBAeI,gBAfJ,mBAqBI,gBArBJ,WAyBI,eAQJ,sCAEE,mBAFF,oDAMI,kBACA,SACA,YACA,cAQJ,eCvDE,yBACA,qBACA,cDqDF,kBClDI,yBDkDJ,2BC/CI,cDmDJ,YC3DE,yBACA,qBACA,cDyDF,eCtDI,yBDsDJ,wBCnDI,cDuDJ,eC/DE,yBACA,qBACA,cD6DF,kBC1DI,yBD0DJ,2BCvDI,cD2DJ,cCnEE,yBACA,qBACA,cDiEF,iBC9DI,yBD8DJ,0BC3DI,cCFJ,wCACE,GAAQ,2BACR,GAAQ,yBAIV,gCACE,GAAQ,2BACR,GAAQ,yBAQV,UACE,gBACA,YACA,mBACA,yBACA,kBzCsCA,kDACQ,0CyClCV,cACE,WACA,QACA,YACA,eACA,iBACA,WACA,kBACA,yBzCyBA,kDACQ,0CAyHR,kCACK,yBACG,CyC3IV,sDCGI,sKDAF,0BAOF,oDzC5CE,0DACK,iDACG,CyCmDV,sBErEE,yBAGA,wCDkDE,qKAAkB,CDoBtB,mBEzEE,yBAGA,qCDkDE,qKAAkB,CDwBtB,sBE7EE,yBAGA,wCDkDE,qKAAkB,CD4BtB,qBEjFE,yBAGA,uCDkDE,qKAAkB,CExDtB,OAEE,gBAEA,mBACE,aAIJ,mBAEE,OACA,gBAGF,YACE,cAGF,cACE,cAGF,gCAEE,kBAGF,8BAEE,mBAGF,qCAGE,mBACA,mBAGF,cACE,sBAGF,cACE,sBAIF,eACE,aACA,kBAMF,YACE,eACA,gBClDF,YAEE,mBACA,eAQF,iBACE,kBACA,cACA,kBAEA,mBACA,sBACA,sBAGA,6BrB3BA,4BACC,2BqB6BD,4BACE,gBrBvBF,+BACC,8BqBiCH,kBACE,WADF,2CAII,WAIF,gDAEE,qBACA,WACA,yBAMF,0FAGE,sBACA,WACA,mBALF,qKASI,cATJ,4JAYI,WAKJ,oFAGE,UACA,WACA,yBACA,qBANF,ogBAYI,cAZJ,sJAeI,cC5FJ,yBACE,cACA,yBAEA,0BACE,cADF,mDAII,cAGF,gEAEE,cACA,yBAEF,+GAGE,WACA,yBACA,qBArBN,sBACE,cACA,yBAEA,uBACE,cADF,gDAII,cAGF,0DAEE,cACA,yBAEF,sGAGE,WACA,yBACA,qBArBN,yBACE,cACA,yBAEA,0BACE,cADF,mDAII,cAGF,gEAEE,cACA,yBAEF,+GAGE,WACA,yBACA,qBArBN,wBACE,cACA,yBAEA,yBACE,cADF,kDAII,cAGF,8DAEE,cACA,yBAEF,4GAGE,WACA,yBACA,qBD4FR,yBACE,aACA,kBAEF,sBACE,gBACA,gBEpHF,OACE,mBACA,sBACA,6BACA,kB/C0DA,6CACQ,qC+CtDV,YACE,aAKF,eACE,kBACA,oCvBpBA,4BACC,2BuB4BH,uDALI,cAKJ,aACE,aACA,gBACA,cACA,CAJF,iGAWI,cAKJ,cACE,kBACA,yBACA,0BvBxCA,+BACC,8BuBiDH,sDAGI,gBAHJ,wFAMM,mBACA,gBAIF,wIAEI,avBvEN,4BACC,2BuB2EC,oIAEI,gBvBtEN,+BACC,8BuBiFH,kFACE,mBAQF,4EAII,gBAJJ,oGAOM,kBACA,mBARN,0XvBnGE,4BACC,2BuBkGH,wsBAwBU,2BAxBV,gsBA4BU,4BA5BV,yWvB3FE,+BACC,8BuB0FH,4qBA8CU,8BA9CV,oqBAkDU,+BAlDV,8HA2DI,0BA3DJ,oGA+DI,aA/DJ,gEAmEI,SAnEJ,gqBA0EU,cA1EV,opBA8EU,eA9EV,w3BAgGU,gBAhGV,yBAsGI,SACA,gBAUJ,aACE,mBADF,oBAKI,gBACA,kBANJ,2BASM,eATN,4BAcI,gBAdJ,gHAkBM,0BAlBN,2BAuBI,aAvBJ,uDAyBM,6BAON,eCpPE,kBAEA,8BACE,WACA,yBACA,kBAHF,0DAMI,sBANJ,qCASI,cACA,sBAGJ,yDAEI,yBDsON,eCvPE,qBAEA,8BACE,WACA,yBACA,qBAHF,0DAMI,yBANJ,qCASI,cACA,sBAGJ,yDAEI,4BDyON,eC1PE,qBAEA,8BACE,cACA,yBACA,qBAHF,0DAMI,yBANJ,qCASI,cACA,yBAGJ,yDAEI,4BD4ON,YC7PE,qBAEA,2BACE,cACA,yBACA,qBAHF,uDAMI,yBANJ,kCASI,cACA,yBAGJ,sDAEI,4BD+ON,eChQE,qBAEA,8BACE,cACA,yBACA,qBAHF,0DAMI,yBANJ,qCASI,cACA,yBAGJ,yDAEI,4BDkPN,cCnQE,qBAEA,6BACE,cACA,yBACA,qBAHF,yDAMI,yBANJ,oCASI,cACA,yBAGJ,wDAEI,4BChBN,kBACE,kBACA,cACA,SACA,UACA,gBALF,2IAYI,kBACA,MACA,OACA,SACA,YACA,WACA,SAIF,yCACE,sBAIF,wCACE,mBC1BJ,MACE,gBACA,aACA,mBACA,yBACA,yBACA,kBlDwDA,mDACQ,2CkD/DV,iBASI,kBACA,6BAKJ,SACE,aACA,kBAEF,SACE,YACA,kBCtBF,OACE,YACA,eACA,gBACA,cACA,WACA,yBjCRA,WAGA,yBiCQA,0BAEE,WACA,qBACA,ejCfF,WAGA,yBiCoBA,aACE,UACA,eACA,uBACA,SACA,wBChBJ,mBAJE,gBAoBA,OAfA,aAEA,eACA,MACA,QACA,SACA,OACA,aACA,iCAIA,UAGA,0BpD+GA,mCACI,2BAoEJ,kDACG,0CAEK,mGoDnLR,wBpD2GA,+BACI,sBAEe,CoD5GrB,mBACE,kBACA,gBAIF,cACE,kBACA,WACA,YAIF,eACE,kBACA,sBACA,sBACA,gCACA,kBpDaA,4CACQ,oCoDZR,4BAEA,UAIF,gBACE,kBACA,MACA,QACA,OACA,sBAEA,qBlCnEA,UAGA,wBkCiEA,mBlCpEA,WAGA,yBkCsEF,cACE,aACA,gCACA,yBAGF,qBACE,gBAIF,aACE,SACA,uBAKF,YACE,kBACA,aAIF,cACE,aACA,iBACA,6BAHF,wBAQI,gBACA,gBATJ,mCAaI,iBAbJ,oCAiBI,cAKJ,yBACE,kBACA,YACA,WACA,YACA,gBAIF,yBAEE,cACE,YACA,iBAEF,epDrEA,6CACQ,qCoDyER,UAAY,aAGd,yBACE,UAAY,aC5Id,SACE,kBACA,aACA,cACA,mBAEA,sDACA,eACA,gBACA,gBnCZA,UAGA,wBmCYA,YnCfA,WAGA,yBmCaA,aAAW,gBAAmB,cAC9B,eAAW,gBAAmB,cAC9B,gBAAW,eAAmB,cAC9B,cAAW,iBAAmB,cAIhC,eACE,gBACA,gBACA,WACA,kBACA,qBACA,sBACA,kBAIF,eACE,kBACA,QACA,SACA,yBACA,mBAIA,4BACE,SACA,SACA,iBACA,uBACA,sBAEF,iCAEE,SACA,CAIF,mEANE,SAEA,mBACA,uBACA,sBASF,kCALE,QACA,CAIF,8BACE,QACA,OACA,gBACA,2BACA,wBAEF,6BACE,QACA,QACA,gBACA,2BACA,uBAEF,+BACE,MACA,SACA,iBACA,uBACA,yBAEF,oCACE,MACA,UACA,gBACA,uBACA,yBAEF,qCACE,MACA,SACA,gBACA,uBACA,yBC/FJ,SACE,kBACA,MACA,OACA,aACA,aACA,gBACA,YAEA,sDACA,eACA,gBACA,uBACA,gBACA,sBACA,4BACA,sBACA,gCACA,kBtD6CA,6CACQ,qCsD1CR,mBAGA,aAAY,iBACZ,eAAY,iBACZ,gBAAY,gBACZ,cAAY,kBAGd,eACE,SACA,iBACA,eACA,yBACA,gCACA,0BAGF,iBACE,iBAQA,sCAEE,kBACA,cACA,QACA,SACA,yBACA,mBAGJ,gBACE,kBAEF,sBACE,kBACA,UAAS,CAIT,oBACE,SACA,kBACA,sBACA,sBACA,iCACA,aACA,0BACE,YACA,WACA,kBACA,sBACA,sBAGJ,sBACE,QACA,WACA,iBACA,oBACA,wBACA,mCACA,4BACE,YACA,SACA,aACA,oBACA,wBAGJ,uBACE,SACA,kBACA,mBACA,yBACA,oCACA,UACA,6BACE,YACA,QACA,kBACA,mBACA,yBAIJ,qBACE,QACA,YACA,iBACA,qBACA,uBACA,kCACA,2BACE,YACA,UACA,qBACA,uBACA,aCzHN,0BAHE,kBAGF,gBAEE,gBACA,WAHF,sBAMI,aACA,kBvD6KF,wCACK,+BACG,CuDtLV,sDAcM,cAIF,mEvDuLF,qDACG,6CA3JH,+IACG,2BAgHH,yBACG,gBACK,CuD3IJ,8DvDmHJ,wCACQ,gCuDjHF,OAEF,6DvD8GJ,yCACQ,iCuD5GF,OAEF,8FvDyGJ,gCACQ,wBuDtGF,QArCR,oEA6CI,cA7CJ,wBAiDI,OAjDJ,4CAsDI,kBACA,MACA,WAxDJ,sBA4DI,UA5DJ,sBA+DI,WA/DJ,uDAmEI,OAnEJ,6BAuEI,WAvEJ,8BA0EI,UAQJ,kBACE,kBACA,MACA,OACA,SACA,UrC9FA,WAGA,yBqC6FA,eACA,WACA,kBACA,qCAKA,uBbhGE,2LACA,2BACA,mHAAQ,CaiGV,wBACE,UACA,QbvGA,gHAEA,2EACA,2BACA,mHAAQ,CawGV,gDAEE,UACA,WACA,qBrCtHF,WAGA,yBqCsFF,+IAsCI,kBACA,QACA,UACA,qBAzCJ,uEA6CI,SACA,kBA9CJ,wEAkDI,UACA,mBAnDJ,0DAuDI,WACA,YACA,iBACA,cACA,kBAKA,oCACE,eAAS,CAIX,oCACE,eAAS,CAUf,qBACE,kBACA,YACA,SACA,WACA,UACA,iBACA,eACA,gBACA,kBATF,wBAYI,qBACA,WACA,YACA,WACA,mBACA,sBACA,mBACA,eAWA,wBACA,6BA/BJ,6BAkCI,SACA,WACA,YACA,sBAOJ,kBACE,kBACA,SACA,UACA,YACA,WACA,iBACA,oBACA,WACA,kBACA,qCACA,uBACE,iBAMJ,oCAGE,+IAKI,WACA,YACA,iBACA,eARJ,uEAYI,kBAZJ,wEAgBI,mBAKJ,kBACE,SACA,UACA,oBAIF,qBACE,aC7PF,2mBAEE,YACA,cAEF,8SACE,W3BRJ,c4BRE,cACA,iBACA,kB5BSF,YACE,sBAEF,WACE,qBAQF,MACE,uBAEF,MACE,wBAEF,WACE,kBAEF,W6BzBE,WACA,kBACA,iBACA,6BACA,S7B8BF,QACE,uBACA,4BAOF,OACE,e8BlCF,cACE,mBAaF,wSAYE,uBAIA,qCC7CA,wBACA,iBAAU,cACV,cAAU,4BACV,4BACU,8BD8CV,2CACE,yBAIF,4CACE,0BAIF,kDACE,gCAKF,2DClEA,wBACA,iBAAU,cACV,cAAU,4BACV,4BACU,8BDmEV,iEACE,yBAIF,kEACE,0BAIF,wEACE,gCAKF,4DCvFA,wBACA,iBAAU,cACV,cAAU,4BACV,4BACU,8BDwFV,kEACE,yBAIF,mEACE,0BAIF,yEACE,gCAKF,sCC5GA,wBACA,iBAAU,cACV,cAAU,4BACV,4BACU,8BD6GV,4CACE,yBAIF,6CACE,0BAIF,mDACE,gCAKF,oCCzHA,wBD8HA,0DC9HA,wBDmIA,2DCnIA,wBDwIA,qCCxIA,wBDmJF,eCnJE,uBDsJA,4BC9JA,wBACA,oBAAU,cACV,iBAAU,4BACV,kCACU,8BD8JZ,qBACE,uBAEA,kCACE,yBAGJ,sBACE,uBAEA,mCACE,0BAGJ,4BACE,uBAEA,yCACE,gCAKF,2BCjLA,wBCbF,eACE,mFACA,kEACA,oEACA,0BAEA,yBACE,kBADF,gDAGI,kBACA,MACA,OACA,SACA,QACA,WAdN,sBAmBI,WACA,YAGF,2BAEE,kBACA,MACA,OACA,WACA,WACA,CAPF,wDACE,YAMA,qDAPF,6BAUI,qCAEA,kEAEA,UACA,sDACA,WACA,cACA,CAlBJ,+BAqBM,mDACA,oCACE,aAvBR,kCA2BM,eAEF,6CACE,iBAEF,mCACE,qBAEF,mCACE,aAEF,sCACE,eACA,kBA/DR,uBAqEI,UACA,qBAtEJ,6BAyEI,aAzEJ,6BA4EI,gBAIF,sBACE,kBACA,MACA,OACA,SACA,QACA,WAEA,qDACA,yGACA,oEACA,kEAXF,0BAcI,WACA,YAEA,kBACA,kBAlBJ,8BAqBM,WACA,YACA,kBACA,sBACA,WACA,kBACA,MACA,OAEA,gGACA,yCACE,gDAMR,4CAEI,sBAIJ,6BACE,MAAW,0BAAmB,CAC9B,IAAM,0BAAmB,EAG3B,qBACE,MACE,mBACA,0BAAmB,CACnB,IACE,mBACA,0BAAmB;;;;;;;;GCnH3B,wBACE,YACA,aAXA,m8KAaA,iBACA,WALF,0BAOI,cACA,WACA,UACA,sBAfF,kBAiBE,kBACA,MACA,OACA,qBAfJ,4BAiBM,cACA,WACA,UACA,sBAzBJ,iBAEA,CA6BF,oCAEE,WACA,aACA,WACA,kBACA,gBACA,kBAGF,wCAEE,cACA,WACA,gBACA,0BACA,kBACA,MACA,OACA,WACA,gBAGF,iBA1DE,8sBAAsB,CA8DxB,mBA9DE,2rBAgEA,aAGF,4DAGE,wBAGF,aACE,YACA,gBACA,eAxEA,kBA0EA,aAGF,uCAEE,cACA,WACA,cAGF,mBACE,WAGF,oBAGE,kCACA,mCACA,6BACA,mCAEA,SACA,SAGF,uCAXE,WACA,qBAKA,iBACA,CAeF,mBARE,kCACA,mCACA,6BAEA,SACA,SAGF,iBACE,kBAGF,oCACE,gBAGF,uDACE,cAGF,mBACE,YACA,eACA,WAlIA,2rBAoIA,2BAGF,uBACE,YAGF,uBACE,aACA,YACA,eACA,WAGF,yBACE,eACA,WACA,YACA,WAGF,2BACE,gBAGF,yEAEE,qBACA,eACA,YACA,wBACA,WAGF,gCACE,kBACA,qBACA,WACA,aAGF,oCACE,YACA,gBACA,YAGF,4DACE,kBAGF,uDACE,YAGF,4GAEE,YACA,YACA,WACA,kBACA,cACA,kBAGF,gHAEE,cACA,YACA,gBACA,kBACA,MACA,OACA,UACA,YACA,aAGF,qDAlNE,8qBAAsB,CAsNxB,uDAtNE,sxBAAsB,CA0NxB,0BACE,UACA,UAGF,yBACE,UACA,UAQF,yDACE,eACA,cAQA,oMACE,cASF,+LACE,aAIJ,wCACE","file":"css/build/app.css","sourcesContent":["/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=\"button\"],input[type=\"reset\"],input[type=\"submit\"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=\"checkbox\"],input[type=\"radio\"]{box-sizing:border-box;padding:0}input[type=\"number\"]::-webkit-inner-spin-button,input[type=\"number\"]::-webkit-outer-spin-button{height:auto}input[type=\"search\"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=\"search\"]::-webkit-search-cancel-button,input[type=\"search\"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:\" (\" attr(href) \")\"}abbr[title]:after{content:\" (\" attr(title) \")\"}a[href^=\"#\"]:after,a[href^=\"javascript:\"]:after{content:\"\"}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.eot');src:url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.woff') format('woff'),url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../../../node_modules/bootstrap-less/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:\"\\2a\"}.glyphicon-plus:before{content:\"\\2b\"}.glyphicon-euro:before,.glyphicon-eur:before{content:\"\\20ac\"}.glyphicon-minus:before{content:\"\\2212\"}.glyphicon-cloud:before{content:\"\\2601\"}.glyphicon-envelope:before{content:\"\\2709\"}.glyphicon-pencil:before{content:\"\\270f\"}.glyphicon-glass:before{content:\"\\e001\"}.glyphicon-music:before{content:\"\\e002\"}.glyphicon-search:before{content:\"\\e003\"}.glyphicon-heart:before{content:\"\\e005\"}.glyphicon-star:before{content:\"\\e006\"}.glyphicon-star-empty:before{content:\"\\e007\"}.glyphicon-user:before{content:\"\\e008\"}.glyphicon-film:before{content:\"\\e009\"}.glyphicon-th-large:before{content:\"\\e010\"}.glyphicon-th:before{content:\"\\e011\"}.glyphicon-th-list:before{content:\"\\e012\"}.glyphicon-ok:before{content:\"\\e013\"}.glyphicon-remove:before{content:\"\\e014\"}.glyphicon-zoom-in:before{content:\"\\e015\"}.glyphicon-zoom-out:before{content:\"\\e016\"}.glyphicon-off:before{content:\"\\e017\"}.glyphicon-signal:before{content:\"\\e018\"}.glyphicon-cog:before{content:\"\\e019\"}.glyphicon-trash:before{content:\"\\e020\"}.glyphicon-home:before{content:\"\\e021\"}.glyphicon-file:before{content:\"\\e022\"}.glyphicon-time:before{content:\"\\e023\"}.glyphicon-road:before{content:\"\\e024\"}.glyphicon-download-alt:before{content:\"\\e025\"}.glyphicon-download:before{content:\"\\e026\"}.glyphicon-upload:before{content:\"\\e027\"}.glyphicon-inbox:before{content:\"\\e028\"}.glyphicon-play-circle:before{content:\"\\e029\"}.glyphicon-repeat:before{content:\"\\e030\"}.glyphicon-refresh:before{content:\"\\e031\"}.glyphicon-list-alt:before{content:\"\\e032\"}.glyphicon-lock:before{content:\"\\e033\"}.glyphicon-flag:before{content:\"\\e034\"}.glyphicon-headphones:before{content:\"\\e035\"}.glyphicon-volume-off:before{content:\"\\e036\"}.glyphicon-volume-down:before{content:\"\\e037\"}.glyphicon-volume-up:before{content:\"\\e038\"}.glyphicon-qrcode:before{content:\"\\e039\"}.glyphicon-barcode:before{content:\"\\e040\"}.glyphicon-tag:before{content:\"\\e041\"}.glyphicon-tags:before{content:\"\\e042\"}.glyphicon-book:before{content:\"\\e043\"}.glyphicon-bookmark:before{content:\"\\e044\"}.glyphicon-print:before{content:\"\\e045\"}.glyphicon-camera:before{content:\"\\e046\"}.glyphicon-font:before{content:\"\\e047\"}.glyphicon-bold:before{content:\"\\e048\"}.glyphicon-italic:before{content:\"\\e049\"}.glyphicon-text-height:before{content:\"\\e050\"}.glyphicon-text-width:before{content:\"\\e051\"}.glyphicon-align-left:before{content:\"\\e052\"}.glyphicon-align-center:before{content:\"\\e053\"}.glyphicon-align-right:before{content:\"\\e054\"}.glyphicon-align-justify:before{content:\"\\e055\"}.glyphicon-list:before{content:\"\\e056\"}.glyphicon-indent-left:before{content:\"\\e057\"}.glyphicon-indent-right:before{content:\"\\e058\"}.glyphicon-facetime-video:before{content:\"\\e059\"}.glyphicon-picture:before{content:\"\\e060\"}.glyphicon-map-marker:before{content:\"\\e062\"}.glyphicon-adjust:before{content:\"\\e063\"}.glyphicon-tint:before{content:\"\\e064\"}.glyphicon-edit:before{content:\"\\e065\"}.glyphicon-share:before{content:\"\\e066\"}.glyphicon-check:before{content:\"\\e067\"}.glyphicon-move:before{content:\"\\e068\"}.glyphicon-step-backward:before{content:\"\\e069\"}.glyphicon-fast-backward:before{content:\"\\e070\"}.glyphicon-backward:before{content:\"\\e071\"}.glyphicon-play:before{content:\"\\e072\"}.glyphicon-pause:before{content:\"\\e073\"}.glyphicon-stop:before{content:\"\\e074\"}.glyphicon-forward:before{content:\"\\e075\"}.glyphicon-fast-forward:before{content:\"\\e076\"}.glyphicon-step-forward:before{content:\"\\e077\"}.glyphicon-eject:before{content:\"\\e078\"}.glyphicon-chevron-left:before{content:\"\\e079\"}.glyphicon-chevron-right:before{content:\"\\e080\"}.glyphicon-plus-sign:before{content:\"\\e081\"}.glyphicon-minus-sign:before{content:\"\\e082\"}.glyphicon-remove-sign:before{content:\"\\e083\"}.glyphicon-ok-sign:before{content:\"\\e084\"}.glyphicon-question-sign:before{content:\"\\e085\"}.glyphicon-info-sign:before{content:\"\\e086\"}.glyphicon-screenshot:before{content:\"\\e087\"}.glyphicon-remove-circle:before{content:\"\\e088\"}.glyphicon-ok-circle:before{content:\"\\e089\"}.glyphicon-ban-circle:before{content:\"\\e090\"}.glyphicon-arrow-left:before{content:\"\\e091\"}.glyphicon-arrow-right:before{content:\"\\e092\"}.glyphicon-arrow-up:before{content:\"\\e093\"}.glyphicon-arrow-down:before{content:\"\\e094\"}.glyphicon-share-alt:before{content:\"\\e095\"}.glyphicon-resize-full:before{content:\"\\e096\"}.glyphicon-resize-small:before{content:\"\\e097\"}.glyphicon-exclamation-sign:before{content:\"\\e101\"}.glyphicon-gift:before{content:\"\\e102\"}.glyphicon-leaf:before{content:\"\\e103\"}.glyphicon-fire:before{content:\"\\e104\"}.glyphicon-eye-open:before{content:\"\\e105\"}.glyphicon-eye-close:before{content:\"\\e106\"}.glyphicon-warning-sign:before{content:\"\\e107\"}.glyphicon-plane:before{content:\"\\e108\"}.glyphicon-calendar:before{content:\"\\e109\"}.glyphicon-random:before{content:\"\\e110\"}.glyphicon-comment:before{content:\"\\e111\"}.glyphicon-magnet:before{content:\"\\e112\"}.glyphicon-chevron-up:before{content:\"\\e113\"}.glyphicon-chevron-down:before{content:\"\\e114\"}.glyphicon-retweet:before{content:\"\\e115\"}.glyphicon-shopping-cart:before{content:\"\\e116\"}.glyphicon-folder-close:before{content:\"\\e117\"}.glyphicon-folder-open:before{content:\"\\e118\"}.glyphicon-resize-vertical:before{content:\"\\e119\"}.glyphicon-resize-horizontal:before{content:\"\\e120\"}.glyphicon-hdd:before{content:\"\\e121\"}.glyphicon-bullhorn:before{content:\"\\e122\"}.glyphicon-bell:before{content:\"\\e123\"}.glyphicon-certificate:before{content:\"\\e124\"}.glyphicon-thumbs-up:before{content:\"\\e125\"}.glyphicon-thumbs-down:before{content:\"\\e126\"}.glyphicon-hand-right:before{content:\"\\e127\"}.glyphicon-hand-left:before{content:\"\\e128\"}.glyphicon-hand-up:before{content:\"\\e129\"}.glyphicon-hand-down:before{content:\"\\e130\"}.glyphicon-circle-arrow-right:before{content:\"\\e131\"}.glyphicon-circle-arrow-left:before{content:\"\\e132\"}.glyphicon-circle-arrow-up:before{content:\"\\e133\"}.glyphicon-circle-arrow-down:before{content:\"\\e134\"}.glyphicon-globe:before{content:\"\\e135\"}.glyphicon-wrench:before{content:\"\\e136\"}.glyphicon-tasks:before{content:\"\\e137\"}.glyphicon-filter:before{content:\"\\e138\"}.glyphicon-briefcase:before{content:\"\\e139\"}.glyphicon-fullscreen:before{content:\"\\e140\"}.glyphicon-dashboard:before{content:\"\\e141\"}.glyphicon-paperclip:before{content:\"\\e142\"}.glyphicon-heart-empty:before{content:\"\\e143\"}.glyphicon-link:before{content:\"\\e144\"}.glyphicon-phone:before{content:\"\\e145\"}.glyphicon-pushpin:before{content:\"\\e146\"}.glyphicon-usd:before{content:\"\\e148\"}.glyphicon-gbp:before{content:\"\\e149\"}.glyphicon-sort:before{content:\"\\e150\"}.glyphicon-sort-by-alphabet:before{content:\"\\e151\"}.glyphicon-sort-by-alphabet-alt:before{content:\"\\e152\"}.glyphicon-sort-by-order:before{content:\"\\e153\"}.glyphicon-sort-by-order-alt:before{content:\"\\e154\"}.glyphicon-sort-by-attributes:before{content:\"\\e155\"}.glyphicon-sort-by-attributes-alt:before{content:\"\\e156\"}.glyphicon-unchecked:before{content:\"\\e157\"}.glyphicon-expand:before{content:\"\\e158\"}.glyphicon-collapse-down:before{content:\"\\e159\"}.glyphicon-collapse-up:before{content:\"\\e160\"}.glyphicon-log-in:before{content:\"\\e161\"}.glyphicon-flash:before{content:\"\\e162\"}.glyphicon-log-out:before{content:\"\\e163\"}.glyphicon-new-window:before{content:\"\\e164\"}.glyphicon-record:before{content:\"\\e165\"}.glyphicon-save:before{content:\"\\e166\"}.glyphicon-open:before{content:\"\\e167\"}.glyphicon-saved:before{content:\"\\e168\"}.glyphicon-import:before{content:\"\\e169\"}.glyphicon-export:before{content:\"\\e170\"}.glyphicon-send:before{content:\"\\e171\"}.glyphicon-floppy-disk:before{content:\"\\e172\"}.glyphicon-floppy-saved:before{content:\"\\e173\"}.glyphicon-floppy-remove:before{content:\"\\e174\"}.glyphicon-floppy-save:before{content:\"\\e175\"}.glyphicon-floppy-open:before{content:\"\\e176\"}.glyphicon-credit-card:before{content:\"\\e177\"}.glyphicon-transfer:before{content:\"\\e178\"}.glyphicon-cutlery:before{content:\"\\e179\"}.glyphicon-header:before{content:\"\\e180\"}.glyphicon-compressed:before{content:\"\\e181\"}.glyphicon-earphone:before{content:\"\\e182\"}.glyphicon-phone-alt:before{content:\"\\e183\"}.glyphicon-tower:before{content:\"\\e184\"}.glyphicon-stats:before{content:\"\\e185\"}.glyphicon-sd-video:before{content:\"\\e186\"}.glyphicon-hd-video:before{content:\"\\e187\"}.glyphicon-subtitles:before{content:\"\\e188\"}.glyphicon-sound-stereo:before{content:\"\\e189\"}.glyphicon-sound-dolby:before{content:\"\\e190\"}.glyphicon-sound-5-1:before{content:\"\\e191\"}.glyphicon-sound-6-1:before{content:\"\\e192\"}.glyphicon-sound-7-1:before{content:\"\\e193\"}.glyphicon-copyright-mark:before{content:\"\\e194\"}.glyphicon-registration-mark:before{content:\"\\e195\"}.glyphicon-cloud-download:before{content:\"\\e197\"}.glyphicon-cloud-upload:before{content:\"\\e198\"}.glyphicon-tree-conifer:before{content:\"\\e199\"}.glyphicon-tree-deciduous:before{content:\"\\e200\"}.glyphicon-cd:before{content:\"\\e201\"}.glyphicon-save-file:before{content:\"\\e202\"}.glyphicon-open-file:before{content:\"\\e203\"}.glyphicon-level-up:before{content:\"\\e204\"}.glyphicon-copy:before{content:\"\\e205\"}.glyphicon-paste:before{content:\"\\e206\"}.glyphicon-alert:before{content:\"\\e209\"}.glyphicon-equalizer:before{content:\"\\e210\"}.glyphicon-king:before{content:\"\\e211\"}.glyphicon-queen:before{content:\"\\e212\"}.glyphicon-pawn:before{content:\"\\e213\"}.glyphicon-bishop:before{content:\"\\e214\"}.glyphicon-knight:before{content:\"\\e215\"}.glyphicon-baby-formula:before{content:\"\\e216\"}.glyphicon-tent:before{content:\"\\26fa\"}.glyphicon-blackboard:before{content:\"\\e218\"}.glyphicon-bed:before{content:\"\\e219\"}.glyphicon-apple:before{content:\"\\f8ff\"}.glyphicon-erase:before{content:\"\\e221\"}.glyphicon-hourglass:before{content:\"\\231b\"}.glyphicon-lamp:before{content:\"\\e223\"}.glyphicon-duplicate:before{content:\"\\e224\"}.glyphicon-piggy-bank:before{content:\"\\e225\"}.glyphicon-scissors:before{content:\"\\e226\"}.glyphicon-bitcoin:before{content:\"\\e227\"}.glyphicon-yen:before{content:\"\\00a5\"}.glyphicon-ruble:before{content:\"\\20bd\"}.glyphicon-scale:before{content:\"\\e230\"}.glyphicon-ice-lolly:before{content:\"\\e231\"}.glyphicon-ice-lolly-tasted:before{content:\"\\e232\"}.glyphicon-education:before{content:\"\\e233\"}.glyphicon-option-horizontal:before{content:\"\\e234\"}.glyphicon-option-vertical:before{content:\"\\e235\"}.glyphicon-menu-hamburger:before{content:\"\\e236\"}.glyphicon-modal-window:before{content:\"\\e237\"}.glyphicon-oil:before{content:\"\\e238\"}.glyphicon-grain:before{content:\"\\e239\"}.glyphicon-sunglasses:before{content:\"\\e240\"}.glyphicon-text-size:before{content:\"\\e241\"}.glyphicon-text-color:before{content:\"\\e242\"}.glyphicon-text-background:before{content:\"\\e243\"}.glyphicon-object-align-top:before{content:\"\\e244\"}.glyphicon-object-align-bottom:before{content:\"\\e245\"}.glyphicon-object-align-horizontal:before{content:\"\\e246\"}.glyphicon-object-align-left:before{content:\"\\e247\"}.glyphicon-object-align-vertical:before{content:\"\\e248\"}.glyphicon-object-align-right:before{content:\"\\e249\"}.glyphicon-triangle-right:before{content:\"\\e250\"}.glyphicon-triangle-left:before{content:\"\\e251\"}.glyphicon-triangle-bottom:before{content:\"\\e252\"}.glyphicon-triangle-top:before{content:\"\\e253\"}.glyphicon-console:before{content:\"\\e254\"}.glyphicon-superscript:before{content:\"\\e255\"}.glyphicon-subscript:before{content:\"\\e256\"}.glyphicon-menu-left:before{content:\"\\e257\"}.glyphicon-menu-right:before{content:\"\\e258\"}.glyphicon-menu-down:before{content:\"\\e259\"}.glyphicon-menu-up:before{content:\"\\e260\"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\\2014 \\00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\\00A0 \\2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,\"Courier New\",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=\"col-\"]{position:static;float:none;display:table-column}table td[class*=\"col-\"],table th[class*=\"col-\"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type=\"search\"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=\"radio\"],input[type=\"checkbox\"]{margin:4px 0 0;margin-top:1px \\9;line-height:normal}input[type=\"file\"]{display:block}input[type=\"range\"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=\"file\"]:focus,input[type=\"radio\"]:focus,input[type=\"checkbox\"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=\"search\"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=\"date\"],input[type=\"time\"],input[type=\"datetime-local\"],input[type=\"month\"]{line-height:34px}input[type=\"date\"].input-sm,input[type=\"time\"].input-sm,input[type=\"datetime-local\"].input-sm,input[type=\"month\"].input-sm,.input-group-sm input[type=\"date\"],.input-group-sm input[type=\"time\"],.input-group-sm input[type=\"datetime-local\"],.input-group-sm input[type=\"month\"]{line-height:30px}input[type=\"date\"].input-lg,input[type=\"time\"].input-lg,input[type=\"datetime-local\"].input-lg,input[type=\"month\"].input-lg,.input-group-lg input[type=\"date\"],.input-group-lg input[type=\"time\"],.input-group-lg input[type=\"datetime-local\"],.input-group-lg input[type=\"month\"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type=\"radio\"],.radio-inline input[type=\"radio\"],.checkbox input[type=\"checkbox\"],.checkbox-inline input[type=\"checkbox\"]{position:absolute;margin-left:-20px;margin-top:4px \\9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=\"radio\"][disabled],input[type=\"checkbox\"][disabled],input[type=\"radio\"].disabled,input[type=\"checkbox\"].disabled,fieldset[disabled] input[type=\"radio\"],fieldset[disabled] input[type=\"checkbox\"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}textarea.form-group-sm .form-control,select[multiple].form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}textarea.form-group-lg .form-control,select[multiple].form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=\"radio\"],.form-inline .checkbox input[type=\"checkbox\"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.333333px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover,.btn-primary:focus,.btn-primary.focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success.focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info.focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning.focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger.focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=\"submit\"].btn-block,input[type=\"reset\"].btn-block,input[type=\"button\"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#337ab7}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:\"\"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=\"buttons\"]>.btn input[type=\"radio\"],[data-toggle=\"buttons\"]>.btn-group>.btn input[type=\"radio\"],[data-toggle=\"buttons\"]>.btn input[type=\"checkbox\"],[data-toggle=\"buttons\"]>.btn-group>.btn input[type=\"checkbox\"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=\"col-\"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=\"radio\"],.input-group-addon input[type=\"checkbox\"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;visibility:visible !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=\"radio\"],.navbar-form .checkbox input[type=\"checkbox\"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:\"/\\00a0\";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;visibility:visible;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857143;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:\"\"}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:\" \";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:\" \";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:\" \";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:\" \";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;-moz-perspective:1000;perspective:1000}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\\2039'}.carousel-control .icon-next:before{content:'\\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \\9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:\" \";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.ekko-lightbox{display:flex !important;align-items:center;justify-content:center;padding-right:0px!important}.ekko-lightbox-container{position:relative}.ekko-lightbox-container>div.ekko-lightbox-item{position:absolute;top:0;left:0;bottom:0;right:0;width:100%}.ekko-lightbox iframe{width:100%;height:100%}.ekko-lightbox-nav-overlay{z-index:100;position:absolute;top:0;left:0;width:100%;height:100%;display:flex}.ekko-lightbox-nav-overlay a{flex:1;display:flex;align-items:center;opacity:0;transition:opacity 0.5s;color:#fff;font-size:30px;z-index:100}.ekko-lightbox-nav-overlay a>*{flex-grow:1}.ekko-lightbox-nav-overlay a>*:focus{outline:none}.ekko-lightbox-nav-overlay a span{padding:0 30px}.ekko-lightbox-nav-overlay a:last-child span{text-align:right}.ekko-lightbox-nav-overlay a:hover{text-decoration:none}.ekko-lightbox-nav-overlay a:focus{outline:none}.ekko-lightbox-nav-overlay a.disabled{cursor:default;visibility:hidden}.ekko-lightbox a:hover{opacity:1;text-decoration:none}.ekko-lightbox .modal-dialog{display:none}.ekko-lightbox .modal-footer{text-align:left}.ekko-lightbox-loader{position:absolute;top:0;left:0;bottom:0;right:0;width:100%;display:flex;flex-direction:column;justify-content:center;align-items:center}.ekko-lightbox-loader>div{width:40px;height:40px;position:relative;text-align:center}.ekko-lightbox-loader>div>div{width:100%;height:100%;border-radius:50%;background-color:#fff;opacity:0.6;position:absolute;top:0;left:0;animation:sk-bounce 2s infinite ease-in-out}.ekko-lightbox-loader>div>div:last-child{animation-delay:-1s}.modal-dialog .ekko-lightbox-loader>div>div{background-color:#333}@-webkit-keyframes sk-bounce{0%,100%{-webkit-transform:scale(0)}50%{-webkit-transform:scale(1)}}@keyframes sk-bounce{0%,100%{transform:scale(0);-webkit-transform:scale(0)}50%{transform:scale(1);-webkit-transform:scale(1)}}/*!\n * Bootstrap Colorpicker v2.5.2\n * https://itsjavi.com/bootstrap-colorpicker/\n *\n * Originally written by (c) 2012 Stefan Petre\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0.txt\n *\n */.colorpicker-saturation{width:100px;height:100px;background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAQAAADa613fAAAP9klEQVR4XnRWC47rNgwcKjlA0bv2VL1Qi/YELRav7203iS1ppqZoiXCAhuBHVLI74xFtG3/Hz2joIOjRGuR5eMYuRn9YA1fds859KX8ZvczLr9/pImiR3Rqky9/wlajRIdVE/1Rufeu/0No3/ASgBZAJUkwgi0iCaEatekJJoEqiTQncd67/gyOfRCZshTed0Nl8LbLj8D6qxtoq9/7kJz/aH/3Xfu8VwI5+AUH8DxE7gUyiIpZ5LwiGzUqE3CScJsCDQHAsvBnxWpkbC0QMHmBp6latWS0bnvrCN/x1+xPfce+Ij0GAyeAGGz15sOiax2UylPhKrFaMPnVWClwepKh07hdhkVDsK2uoyEIySergjdbY2VBtV8VLr8Mf9mF/4wMb7kR8FOhzFWZZe7HIZD9JRIbee28eJKBweTB6TwjYkAgWaUmtDveGw1Wx3zZ76YlPPfQd/+gTTUFkiGiJ+NQAszU1EPT/QJEgufolAMPkNU4CVOyUIBLg4xglEZHGQnTFOFV0VaulYddBhA986ge/7N/yQi/3flFgwfQq2ibLnTDBRl9TmUHyJASPV/eoN0UISIr+ICQKIFV4EpljSjV1uFVUq9hRtet5e9gXvuyHPW0zMhQxWaoBBa9Tg8vsCEhww23Smd0CKjIkmPIoxWrUBDgJqFCyESF43ctQxLUoHN7Q1KyVhqrNNm3cy2vMyQNPVKjc29Rh5SSU+giWdRJHkLnQG71FQEuNyNGBTDdBQQAKCuGiEUS/jcyGbkMPq931OIzb/dUPGuVlG7f+slqkO5NAAlzTMdcq0NkzmsEBmAQkbI+pSHbiqnuWIA6lijhvqwIxMyWxMGZiPU669XJE1tADDTs2HWpwKxuqdnTpOiOR42xlzLtm3pXGel3xd8/oTs8Xy0MV8GM1RlsC2Y3Wy3wut3M+2mEVux0Gt9fhzTWyLvGiiJYaqY5DWRFIwAiQ5r6gB9GpQihJw4I9j5Mkscj3BnzGjBhv8xna5P1Jo428o6IOPY5KFZtVOkEKqUjqQY9Gi+jrIOFwJUDzRtA9xyoIrGGmkNRmxVAnZoK+TkUIeUYni5wEzgOG5iZX5HCr2JyQNqdk++G0rgb1ochSIGutTj4P7F0PuRUAolmh5sCzAHn1BYyaADh6bgFeoBx6vst091CEvcSLWBBpqGq384jZ5llVHSwEShLx+D4d0mU3D5eEAJQ9KEhOZUYnDENV2qKgmIlQhWfdvcoXYaegPp/n1oKIOgYFqxrzQSciqNhv/5FqPpy6b0UcX2vf13DfWySRSEgkEYlEJJGQSyKJSEQSCYlEEpHexIVO3XOevffze2a+PfPv9x1rne1c3b3Mmlmz9mE++zuzngfnw/E+Dlc4LL4NwHdFy7u3KGPVmZ6/4eeMoDyre3i/KHADIHYO04w9zO0mAotuKnrc7XaPjvu66bNe5cDT7RlPepEnfS2X8dF1/utDvD+OwGDBxEgQywLCvIMYWBY+DShwAAORAdv9PswhDAqOUCi5+71AbFcDMR4xBDNfhySKXPXZ1+Vub+Q1Ltf5z7eC0AjVldHI26rIFdKIAyYBJCFVUhVDwttAnM52B3Ect1TFQXzJ0z33lOuib/QO8g+CuO0gKBRU80A8hkeJ0b1KRQWmFQVSh8mf3lpUpNaRulzN5NArrmKKGMijXgzk7w5ijdFVgT8f1IdFNjVWjDWicUYWEEMmSFDtILdzHW5XueHp7p+yuS54ep5/c5BE2Gw/gWPNYU4/PZaak2VGEsFjSbOf8irea6KQgojGCk0KxZY31tWWgzwayF8N5KYyo3VADVicWWrhwzr3ZqIOa5xW5zbqMPPMiyDURHDIHQTeWq7KFXcQPOqzPOL5Ov/iIDEDy7DHEwx0PTgjO8SS0fOEHcZNMt+XKEFMj8Q4QUSvPu6HPuvd4N9/x12RPwcIVRCAakSOUzHgsUSMFWYzDQ+PiOJqAOuYc9jh5TecnA+xHfFyOYhebeTH89P80wrCJzUjlsx7euIV0g4zQFUSiBPioIWBACFC7GgDj8P91ZSJOQmQP74MAnQo8H5RIe8kZ0kBcQCMAlEpRDiKROBxbR0ksdhWFq0gR9q9uQzkDzuIFQSPqAgRCAsCaVNF2ZAAhxvtzcqcnDk6tpXxSsayqXLIgSOb6zqeH+fvO0i9XEu5EVV+OZehRZJ6BGTeaRhCkTzVIZeAzaWGAFfErIPogQI5CuR3HQQx7DzBB16R3s7e0MBUPedjWutgG/JUTPqMeAQNEiytJRnJearWUgdwFNxN7rtBoECuj/O3BMHaTIxQ0a4GctireElTJHJvLTaalih5kvBCGMvkdESUMAdCFaI4yG8SpDfRWAptqkAJUwCG6B7lOREFSZBqKs57MEHqVJEBwHa2lp0OiKtiQ18gx9P89QrSXyc0vObBM4vPmBADqJZLAo/yzK7qPSZstCy+fDSZlhrm+Zkyjsf5q2otdC14zkLjHLf0me9wjNqQo0B1a6wBJRaIEgC2Qw9oby/cRHA+xHCQy/xlB1HVSV3Y/5yVhsc7dBi2UoIWCMcbELZWgxNCGUZ5y4ceBaLlE8dAfrEosrYT+z8ya3sxXndFBxuQivNGEHFCbLGBlBLKGYHZoeoQpcjtMn/uICPefcxecpuDOEemg9S/44cflZPIlWolyHkLrEpgbS9IQRlAgZgi0WDjsEiPh+PN/Fkogq4GdzPtarlRGW2tJwEK1RMTEvdVdmhAKHO1pdUuGQsVcX+rSfGzDbwGyE8NRPQc83HCaOkTZwPqABZBdFq8zAN1gue0FPO8wYUFBE1WkMwVzM1iQ4BItFh+H36Qy/yJg0DRQICmBl+tbKUC5cCj3yXI+SUFBS78ZAcBtHt+e9lBuiqpTNh9zTvIjzuIWxVYGQJpAZY+VWS3QKh84iSZbwuIdiDpc4KztQa/sjhMaDJEJDSZ8mZ+kCBdC0JpKVNQzZdKu+EsOeFCosrngVAkDS/uy6iGnW7UxmMpkB8FyFKo6iQW8z1HuBdMu1pdkZdB8jWTjlFtNaiJRYniIDcD+eECMqFLS9ED6DgxzCMKnRD3HYYA2uMCJUh70OK8G0EUnJV8lqe8nj84QdqLhdoJskNlEw1ivajM8LtPBhIeN99LESXI9xcQIHFQudHngZjUhXOQeGlUYmAddh5pxMhzV0M1vMAtMFIVmfp6fq+DgEWefjQVenstaqUy3bJQAiVlEihDghCDINFQg8oUhoQPkO8SBEM7SFQ72VYBwPuE7k8uYF5LNwg/TEd2zkuKjIIhTiJRlYrDfNS1QL7DYUcbcCyKJNwOwucVCVSwBBj/DwghXA2hQtACgCBBPprfXkAIFIYRXhONQARFU00Tsh6LEmmQUbkTImMi9me5qaHDIeBgHeRbdxAIqAJBCDSoCNVQglrciqX/ZCD9RRP6rgpBvhmKAFhg2ForBLXBYPtUjj7vCHPe8SXbYAY47gHB9mKeqjjIg/53fmMD0fR9Bug7SFcHI6EA1OC/E8QTL4NgBSGiCiyTChnI1zcQxmyfRZGM6w701KRybDvsIK3LWDx6mxGkcglEZQLkawnCdppZ6sgCh8trWWBUQaUWCEOlOs7HAenFE45QSu9RQQDAqchXNxDq4orQR44qRIFUQvM+mRJuB6GDEixgCbSBQGXghEEbdn1P/zO/QhAWCsWsmRhLa2VFkSZIgSVKmgEQhvk6K8YKMRZl7Dwg4amOUYvFBfLlE4RasOCB5S9PXKq0AqGDMiYIReXF0mYctITWBmqR5F38X5Y7yJfeCtKBzNbWYm5XpsMpf3dRZD3jPDesvdVCOs6KYQXIFw1E4fcE8dHWOepZBXpLJcACWUZVMRZbfvgXR4Ak8A7VVSKSVuu9p6/mFxyE7cOWavtLp952O8huK83+gmHzHaAsVXLgAvl8gPCvHzAFsM8GNXGKPH5cmN02sXTLa8QdKRXMzHv67/k5A9k1UIx36UH/VlWWtuKssNiRapB6BaLXl6MA+ayDcNS3v/sYXgCL620F1kk8QhKAEOvKu4DvajDO5zkHc4fBg76anyEIIcamBPex5EK8AoVHhMW7QAqWrYD1204CJB1hCfOAV/PTBPH0zBmJmsZZKCEaAmdqm4zMcYxYLN0JuHThIAjirAnp3px7TRgD+ZSD/K92M1CNIgbC8Ex7FkSEIlQEEUQEQQQBRBABEUQQEQTx3X0Evap9AhP39jL5OvuzAWuvbDaTTDIzX2aypUCJ0i7nAigoQAk9gUIUSxXEoCFyyVIuL9ZQcMZoArnwr4D0OLS8jGNGTgGnsZQWMYrcOARoIReAALBeWhf+RUCAIEsECFQHLkwR5zj4JW3t5WOUU5djvgQIawD53EDsctmYz8xGaZGPBUR3qNkiGwqDICUYIFpqBgRaayCfFiAWR2wWvoobmzxdF8N5kyxXmvap/sgGcLF/aoBosbG+lE395R8zCA4BqUYgOgYq+HtvBrT0LK15X8lZwx5f9klCX0rdgXzIIGbdhXMqZtHzJhuptEjmsFc4KzmN5IFPtfM7gWw2kPczSIqQSPUDYKYBMamsBCpKphW0iA5H8AbMDPJOQYjLZg1Vk4G49GlCYNYAkdOd0kwRQ8FCyAHydgLZ6Z2AqrVtjDUQ7hCEmrkEooDAsB2YnBCvkBpZ6yBvJpCd7Mn5zJ6C4QF2BUQPgHEIGUrGnHzQ8rlMekBeTyAzwDJksxwM4+w3BY02B8mIl0CmFRm+ZscxAuSnvwqQsECTIGSV6FEoJFTygVuzB5xAsKqBvAQE3+nkVoJDI1BJIaPBWik7ZSu5NIp5A3mRQaTFvLgkO9fVgEgMqqeVfb+p55tijWH+Kea71ubq4v8Sl8089sZKbKEZNq+VUfISJJF7j79WrbYgS994ZEf+nIz0pNFRWqapSmK6P45i3OQuItIiPDyg6RnxZ4D0g+CFPxAzluoRsWsaA6I6JOqVWCisDvJ0BgHTzMSRgMi0vmi8R+sR6tg/XUh7kCc7kMRqSNkTBDx0OkAUegFcMazciBXNpm798R6klXap/WZz49TQwBHqEcj4oCToUPjUuP9lfxcbyKMAwT6bTf1qqIIQDl3i5oCERNmVm0wgW4A8BGRxMX3hWh8bEV5Rvfp4DS5F3djWH2ztDNWKW7OBjgjIwsDWaKRknJjqMsh9QCa1p608lLovFkBE969DYtYelSzwSRcg535vAsFeNU9SzRCYZb4LDmxmFQKkwYGM+5y/G7b1uxMIylLdyE5yxIyYsoXWhQIpzQhYPi3JkJoKkB9+BxD0OMuyOEBe36DgyPSrxscmATldgKj8PxrkA/kA5PYMgkrocwIQ6GSRGmF0VaNqBKQZ5FYDEZSDzFTzq9mBQjAayE1A+ryDTzcQZe0Ibbxj7EwpAmTrJwEimZR9CCPtODhzxuNtY19Zd2Lf/fjCTnEiDAOg62j1utb/dv9mZ/aHCj4AyOHbsW3/As0BTzIgeJU7AAAAAElFTkSuQmCC\");cursor:crosshair;float:left}.colorpicker-saturation i{display:block;height:5px;width:5px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;position:absolute;top:0;left:0;margin:-4px 0 0 -4px}.colorpicker-saturation i b{display:block;height:5px;width:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{width:15px;height:100px;float:left;cursor:row-resize;margin-left:4px;margin-bottom:4px}.colorpicker-hue i,.colorpicker-alpha i{display:block;height:1px;background:#000;border-top:1px solid #fff;position:absolute;top:0;left:0;width:100%;margin-top:-1px}.colorpicker-hue{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAMAAABw8qpSAAABLFBMVEXqFBb/ABH/ACL/ADH/AEH/AFD/AGD/AG7/AH7/AI3/AJ3/AKz/ALz/AMr/ANv/AOr/APr2AP/mAP/XAP/HAP+4AP+oAP+aAP+JAP97AP9rAP9cAP9MAP8+AP8tAP8fAP8PAP8BAv8AEP8AH/8AL/8APv8ATv8AXP8Abf8Ae/8Ai/8Amv8Aqv8AuP8Ayf8A1/8A5/8A9/8A//gA/+kA/9kA/8oA/7oA/6wA/5sA/40A/30A/24A/14A/1AA/z8A/zEA/yEA/xEB/wMN/wAd/wAs/wA8/wBK/wBb/wBp/wB5/wCI/wCY/wCm/wC3/wDF/wDV/wDk/wD1/wD/+gD/7AD/3AD/zAD/vgD/rQD/nwD/jgD/gAD/cAD/YgD/UQD/QwD/MgD/JAD/FAD4Eg42qAedAAAAh0lEQVR4XgXAg3EDAAAAwI9to7Zt27a1/w49BASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTS1tHXo1KVbj159+g0YNGTYiFFjxk2YNGXajFlz5i1YtGTZilVr1m3YtGXbjl179h04dOTYiVNnzl24dOXajVt37j149OTZi1dv3n349OXbj19//wOxE1dQ8reGAAAAAElFTkSuQmCC\")}.colorpicker-alpha{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAABkCAQAAAAVxWkcAAABr0lEQVR4Xo2VwU0DQQxF7dmRuNIFlzlSAR3QAaXQQdIBJVABFXDcOVAAd67cjJLR07dkhcSrkZKfb/t7bG88rFo3B5gZPMNycItu2xloGV7MWHzM9zuzFWCkmA0nK6AszCUJDW6+mG6R03ncw5v8EMTEvZ2O3AliYjpslblc0RF9LmZYWxURU6aKytWZYsoWCAe+xwOZp1GsEukGiIkYxcQCHck99+gRgB7JncyIB5SGEhP3Yh5P6JwX+u6AnYot104d8DJT7uH7M9JH6OZbimj0vfMVaYnJIZFJDBW9kHlerL2C6JV4mSt7uuo2N57RxnZ+usQjn0R1jwBJBrNO3evJpVYUWsJ/E3UiXRlv24/7YZ04xmEdWlzcKS+B/eapeyMvFd2k0+hRk/T0AmTW8h69s2sjYMsdPntECiILhAeIMZAeH4QvUwfn6ijC0tTV+fT9ky8jM9nK2g7Ly1VjSpKYq6IvsAm7MtNu1orEqa/K3KNvgMFdhfquPfJmp2dbh0/8Gzb6Y22ViaNr6n5410zXdngVhbu6XqdOtWOuin5hjABGp4a2uotZ71MVCfwDBt2/v37yo6AAAAAASUVORK5CYII=\");display:none}.colorpicker-saturation,.colorpicker-hue,.colorpicker-alpha{background-size:contain}.colorpicker{padding:4px;min-width:130px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;z-index:2500}.colorpicker:before,.colorpicker:after{display:table;content:\"\";line-height:0}.colorpicker:after{clear:both}.colorpicker:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute;top:-7px;left:6px}.colorpicker:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:7px}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAABkCAQAAAAVxWkcAAABr0lEQVR4Xo2VwU0DQQxF7dmRuNIFlzlSAR3QAaXQQdIBJVABFXDcOVAAd67cjJLR07dkhcSrkZKfb/t7bG88rFo3B5gZPMNycItu2xloGV7MWHzM9zuzFWCkmA0nK6AszCUJDW6+mG6R03ncw5v8EMTEvZ2O3AliYjpslblc0RF9LmZYWxURU6aKytWZYsoWCAe+xwOZp1GsEukGiIkYxcQCHck99+gRgB7JncyIB5SGEhP3Yh5P6JwX+u6AnYot104d8DJT7uH7M9JH6OZbimj0vfMVaYnJIZFJDBW9kHlerL2C6JV4mSt7uuo2N57RxnZ+usQjn0R1jwBJBrNO3evJpVYUWsJ/E3UiXRlv24/7YZ04xmEdWlzcKS+B/eapeyMvFd2k0+hRk/T0AmTW8h69s2sjYMsdPntECiILhAeIMZAeH4QvUwfn6ijC0tTV+fT9ky8jM9nK2g7Ly1VjSpKYq6IvsAm7MtNu1orEqa/K3KNvgMFdhfquPfJmp2dbh0/8Gzb6Y22ViaNr6n5410zXdngVhbu6XqdOtWOuin5hjABGp4a2uotZ71MVCfwDBt2/v37yo6AAAAAASUVORK5CYII=\");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{cursor:pointer;float:left;height:10px;width:10px}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .input-group-addon i,.colorpicker-element .add-on i{display:inline-block;cursor:pointer;height:16px;vertical-align:text-top;width:16px}.colorpicker.colorpicker-inline{position:relative;display:inline-block;float:none;z-index:auto}.colorpicker.colorpicker-horizontal{width:110px;min-width:110px;height:auto}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{width:100px;height:15px;float:left;cursor:col-resize;margin-left:0px;margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{display:block;height:15px;background:#ffffff;position:absolute;top:0;left:0;width:1px;border:none;margin-top:0px}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAABCAMAAAAfBfuPAAABLFBMVEXqFBb/ABH/ACL/ADH/AEH/AFD/AGD/AG7/AH7/AI3/AJ3/AKz/ALz/AMr/ANv/AOr/APr2AP/mAP/XAP/HAP+4AP+oAP+aAP+JAP97AP9rAP9cAP9MAP8+AP8tAP8fAP8PAP8BAv8AEP8AH/8AL/8APv8ATv8AXP8Abf8Ae/8Ai/8Amv8Aqv8AuP8Ayf8A1/8A5/8A9/8A//gA/+kA/9kA/8oA/7oA/6wA/5sA/40A/30A/24A/14A/1AA/z8A/zEA/yEA/xEB/wMN/wAd/wAs/wA8/wBK/wBb/wBp/wB5/wCI/wCY/wCm/wC3/wDF/wDV/wDk/wD1/wD/+gD/7AD/3AD/zAD/vgD/rQD/nwD/jgD/gAD/cAD/YgD/UQD/QwD/MgD/JAD/FAD4Eg42qAedAAAAbUlEQVR4XgXAghEDsbxtlrZt27ax/w49ACAYQTGcICmaYTleECVZUTXdMC1Wm93hdLk9Xp8/EAyFI9FYPJFMpTPZXL5QLJUr1Vq90Wy1O91efzAcjSfT2XyxXK03293+cDydL9fb/fF8vT/f3x+LfRNXARMbCAAAAABJRU5ErkJggg==\")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAQAAADoFTP1AAAB9ElEQVR4XoWTQW4VMRBEu9qWEimL7DhEMp8NF+ASnJJLcAQgE1bcgBUSkYKUuHCrZ9pjeqSU5Yn9LPu7umJQBIIv+k7vIOrtK66L4lmr3pVOrOv3otp619KZ0/KjdNI79L52Uo09FBQWrU0vfe5trezU+hLsoUKd3Repovte+0vbq/7Lj5XbaHECKasR9G4MPlbp+gzZxd6koPEJCkAYC5SjcOTAIIOK90Dja1IfIZ8Z+zAY9jm3b5Ia+MT5sFcqRJrR2AYYA8Kua5BzYRrFPNmD4PQMegGJMOffJJUsWiI3nCHZZjInNdffLWOufzbc3JaboCAVxwmnRHbhLSPwRJ4wU0BRSc6HkECYYVw95nMKgJOcylxrJttE5Ibzf9Xq9GPvP+WX3MiV/MGHfRu/SentRQrfG1GzsIrytdNXucSRKxQNIGHM9YhGFQJcdjNcBZvfJayuYe4Sia1CzwW+19mWOhe37HsxJWKwbu/jluEU15QzAQjAqCEbhMJc78GYV2E0kooHDubUImWkTOhGpgv8PoT8DJG/bzxna4BZ0eOFSOaLADGeSpFsg5AzeaDZIDQQXjZ4y/8ryfzUXBwdELRjTjCNvOeT0rNlrJz90vwy6N9pXXQEluX0inElpPWokSdiLCfiNJJjMKQ8Qsh8GEKQKMo/eiHrNbI9UksAAAAASUVORK5CYII=\")}.colorpicker-right:before{left:auto;right:6px}.colorpicker-right:after{left:auto;right:7px}.colorpicker-no-arrow:before{border-right:0;border-left:0}.colorpicker-no-arrow:after{border-right:0;border-left:0}.colorpicker.colorpicker-visible,.colorpicker-alpha.colorpicker-visible,.colorpicker-saturation.colorpicker-visible,.colorpicker-hue.colorpicker-visible,.colorpicker-selectors.colorpicker-visible{display:block}.colorpicker.colorpicker-hidden,.colorpicker-alpha.colorpicker-hidden,.colorpicker-saturation.colorpicker-hidden,.colorpicker-hue.colorpicker-hidden,.colorpicker-selectors.colorpicker-hidden{display:none}.colorpicker-inline.colorpicker-visible{display:inline-block}\n\n\n// WEBPACK FOOTER //\n// ./resources/assets/less/app.less","/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/normalize.less","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n //\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/print.less","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/glyphicons.less","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/scaffolding.less","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/vendor-prefixes.less","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/tab-focus.less","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/image.less","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/type.less","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/text-emphasis.less","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/background-variant.less","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/text-overflow.less","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/code.less","//\n// Variables\n// --------------------------------------------------\n\n\n//== Colors\n//\n//## Gray and brand colors for use across Bootstrap.\n\n@gray-base: #000;\n@gray-darker: lighten(@gray-base, 13.5%); // #222\n@gray-dark: lighten(@gray-base, 20%); // #333\n@gray: lighten(@gray-base, 33.5%); // #555\n@gray-light: lighten(@gray-base, 46.7%); // #777\n@gray-lighter: lighten(@gray-base, 93.5%); // #eee\n\n@brand-primary: darken(#428bca, 6.5%); // #337ab7\n@brand-success: #5cb85c;\n@brand-info: #5bc0de;\n@brand-warning: #f0ad4e;\n@brand-danger: #d9534f;\n\n\n//== Scaffolding\n//\n//## Settings for some of the most global styles.\n\n//** Background color for ``.\n@body-bg: #fff;\n//** Global text color on ``.\n@text-color: @gray-dark;\n\n//** Global textual link color.\n@link-color: @brand-primary;\n//** Link hover color set via `darken()` function.\n@link-hover-color: darken(@link-color, 15%);\n//** Link hover decoration.\n@link-hover-decoration: underline;\n\n\n//== Typography\n//\n//## Font, line-height, and color for body text, headings, and more.\n\n@font-family-sans-serif: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n@font-family-serif: Georgia, \"Times New Roman\", Times, serif;\n//** Default monospace fonts for ``, ``, and `
`.\n@font-family-monospace:   Menlo, Monaco, Consolas, \"Courier New\", monospace;\n@font-family-base:        @font-family-sans-serif;\n\n@font-size-base:          14px;\n@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px\n@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px\n\n@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px\n@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px\n@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px\n@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px\n@font-size-h5:            @font-size-base;\n@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px\n\n//** Unit-less `line-height` for use in components like buttons.\n@line-height-base:        1.428571429; // 20/14\n//** Computed \"line-height\" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.\n@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px\n\n//** By default, this inherits from the ``.\n@headings-font-family:    inherit;\n@headings-font-weight:    500;\n@headings-line-height:    1.1;\n@headings-color:          inherit;\n\n\n//== Iconography\n//\n//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.\n\n//** Load fonts from this directory.\n@icon-font-path:          \"../fonts/\";\n//** File name for all font files.\n@icon-font-name:          \"glyphicons-halflings-regular\";\n//** Element ID within SVG icon file.\n@icon-font-svg-id:        \"glyphicons_halflingsregular\";\n\n\n//== Components\n//\n//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).\n\n@padding-base-vertical:     6px;\n@padding-base-horizontal:   12px;\n\n@padding-large-vertical:    10px;\n@padding-large-horizontal:  16px;\n\n@padding-small-vertical:    5px;\n@padding-small-horizontal:  10px;\n\n@padding-xs-vertical:       1px;\n@padding-xs-horizontal:     5px;\n\n@line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome\n@line-height-small:         1.5;\n\n@border-radius-base:        4px;\n@border-radius-large:       6px;\n@border-radius-small:       3px;\n\n//** Global color for active items (e.g., navs or dropdowns).\n@component-active-color:    #fff;\n//** Global background color for active items (e.g., navs or dropdowns).\n@component-active-bg:       @brand-primary;\n\n//** Width of the `border` for generating carets that indicator dropdowns.\n@caret-width-base:          4px;\n//** Carets increase slightly in size for larger components.\n@caret-width-large:         5px;\n\n\n//== Tables\n//\n//## Customizes the `.table` component with basic values, each used across all table variations.\n\n//** Padding for ``s and ``s.\n@table-cell-padding:            8px;\n//** Padding for cells in `.table-condensed`.\n@table-condensed-cell-padding:  5px;\n\n//** Default background color used for all tables.\n@table-bg:                      transparent;\n//** Background color used for `.table-striped`.\n@table-bg-accent:               #f9f9f9;\n//** Background color used for `.table-hover`.\n@table-bg-hover:                #f5f5f5;\n@table-bg-active:               @table-bg-hover;\n\n//** Border color for table and cell borders.\n@table-border-color:            #ddd;\n\n\n//== Buttons\n//\n//## For each of Bootstrap's buttons, define text, background and border color.\n\n@btn-font-weight:                normal;\n\n@btn-default-color:              #333;\n@btn-default-bg:                 #fff;\n@btn-default-border:             #ccc;\n\n@btn-primary-color:              #fff;\n@btn-primary-bg:                 @brand-primary;\n@btn-primary-border:             darken(@btn-primary-bg, 5%);\n\n@btn-success-color:              #fff;\n@btn-success-bg:                 @brand-success;\n@btn-success-border:             darken(@btn-success-bg, 5%);\n\n@btn-info-color:                 #fff;\n@btn-info-bg:                    @brand-info;\n@btn-info-border:                darken(@btn-info-bg, 5%);\n\n@btn-warning-color:              #fff;\n@btn-warning-bg:                 @brand-warning;\n@btn-warning-border:             darken(@btn-warning-bg, 5%);\n\n@btn-danger-color:               #fff;\n@btn-danger-bg:                  @brand-danger;\n@btn-danger-border:              darken(@btn-danger-bg, 5%);\n\n@btn-link-disabled-color:        @gray-light;\n\n\n//== Forms\n//\n//##\n\n//** `` background color\n@input-bg:                       #fff;\n//** `` background color\n@input-bg-disabled:              @gray-lighter;\n\n//** Text color for ``s\n@input-color:                    @gray;\n//** `` border color\n@input-border:                   #ccc;\n\n// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4\n//** Default `.form-control` border radius\n// This has no effect on ``s in CSS.\n@input-border-radius:            @border-radius-base;\n//** Large `.form-control` border radius\n@input-border-radius-large:      @border-radius-large;\n//** Small `.form-control` border radius\n@input-border-radius-small:      @border-radius-small;\n\n//** Border color for inputs on focus\n@input-border-focus:             #66afe9;\n\n//** Placeholder text color\n@input-color-placeholder:        #999;\n\n//** Default `.form-control` height\n@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);\n//** Large `.form-control` height\n@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);\n//** Small `.form-control` height\n@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);\n\n@legend-color:                   @gray-dark;\n@legend-border-color:            #e5e5e5;\n\n//** Background color for textual input addons\n@input-group-addon-bg:           @gray-lighter;\n//** Border color for textual input addons\n@input-group-addon-border-color: @input-border;\n\n//** Disabled cursor for form controls and buttons.\n@cursor-disabled:                not-allowed;\n\n\n//== Dropdowns\n//\n//## Dropdown menu container and contents.\n\n//** Background for the dropdown menu.\n@dropdown-bg:                    #fff;\n//** Dropdown menu `border-color`.\n@dropdown-border:                rgba(0,0,0,.15);\n//** Dropdown menu `border-color` **for IE8**.\n@dropdown-fallback-border:       #ccc;\n//** Divider color for between dropdown items.\n@dropdown-divider-bg:            #e5e5e5;\n\n//** Dropdown link text color.\n@dropdown-link-color:            @gray-dark;\n//** Hover color for dropdown links.\n@dropdown-link-hover-color:      darken(@gray-dark, 5%);\n//** Hover background for dropdown links.\n@dropdown-link-hover-bg:         #f5f5f5;\n\n//** Active dropdown menu item text color.\n@dropdown-link-active-color:     @component-active-color;\n//** Active dropdown menu item background color.\n@dropdown-link-active-bg:        @component-active-bg;\n\n//** Disabled dropdown menu item background color.\n@dropdown-link-disabled-color:   @gray-light;\n\n//** Text color for headers within dropdown menus.\n@dropdown-header-color:          @gray-light;\n\n//** Deprecated `@dropdown-caret-color` as of v3.1.0\n@dropdown-caret-color:           #000;\n\n\n//-- Z-index master list\n//\n// Warning: Avoid customizing these values. They're used for a bird's eye view\n// of components dependent on the z-axis and are designed to all work together.\n//\n// Note: These variables are not generated into the Customizer.\n\n@zindex-navbar:            1000;\n@zindex-dropdown:          1000;\n@zindex-popover:           1060;\n@zindex-tooltip:           1070;\n@zindex-navbar-fixed:      1030;\n@zindex-modal:             1040;\n\n\n//== Media queries breakpoints\n//\n//## Define the breakpoints at which your layout will change, adapting to different screen sizes.\n\n// Extra small screen / phone\n//** Deprecated `@screen-xs` as of v3.0.1\n@screen-xs:                  480px;\n//** Deprecated `@screen-xs-min` as of v3.2.0\n@screen-xs-min:              @screen-xs;\n//** Deprecated `@screen-phone` as of v3.0.1\n@screen-phone:               @screen-xs-min;\n\n// Small screen / tablet\n//** Deprecated `@screen-sm` as of v3.0.1\n@screen-sm:                  768px;\n@screen-sm-min:              @screen-sm;\n//** Deprecated `@screen-tablet` as of v3.0.1\n@screen-tablet:              @screen-sm-min;\n\n// Medium screen / desktop\n//** Deprecated `@screen-md` as of v3.0.1\n@screen-md:                  992px;\n@screen-md-min:              @screen-md;\n//** Deprecated `@screen-desktop` as of v3.0.1\n@screen-desktop:             @screen-md-min;\n\n// Large screen / wide desktop\n//** Deprecated `@screen-lg` as of v3.0.1\n@screen-lg:                  1200px;\n@screen-lg-min:              @screen-lg;\n//** Deprecated `@screen-lg-desktop` as of v3.0.1\n@screen-lg-desktop:          @screen-lg-min;\n\n// So media queries don't overlap when required, provide a maximum\n@screen-xs-max:              (@screen-sm-min - 1);\n@screen-sm-max:              (@screen-md-min - 1);\n@screen-md-max:              (@screen-lg-min - 1);\n\n\n//== Grid system\n//\n//## Define your custom responsive grid.\n\n//** Number of columns in the grid.\n@grid-columns:              12;\n//** Padding between columns. Gets divided in half for the left and right.\n@grid-gutter-width:         30px;\n// Navbar collapse\n//** Point at which the navbar becomes uncollapsed.\n@grid-float-breakpoint:     @screen-sm-min;\n//** Point at which the navbar begins collapsing.\n@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);\n\n\n//== Container sizes\n//\n//## Define the maximum width of `.container` for different screen sizes.\n\n// Small screen / tablet\n@container-tablet:             (720px + @grid-gutter-width);\n//** For `@screen-sm-min` and up.\n@container-sm:                 @container-tablet;\n\n// Medium screen / desktop\n@container-desktop:            (940px + @grid-gutter-width);\n//** For `@screen-md-min` and up.\n@container-md:                 @container-desktop;\n\n// Large screen / wide desktop\n@container-large-desktop:      (1140px + @grid-gutter-width);\n//** For `@screen-lg-min` and up.\n@container-lg:                 @container-large-desktop;\n\n\n//== Navbar\n//\n//##\n\n// Basics of a navbar\n@navbar-height:                    50px;\n@navbar-margin-bottom:             @line-height-computed;\n@navbar-border-radius:             @border-radius-base;\n@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));\n@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);\n@navbar-collapse-max-height:       340px;\n\n@navbar-default-color:             #777;\n@navbar-default-bg:                #f8f8f8;\n@navbar-default-border:            darken(@navbar-default-bg, 6.5%);\n\n// Navbar links\n@navbar-default-link-color:                #777;\n@navbar-default-link-hover-color:          #333;\n@navbar-default-link-hover-bg:             transparent;\n@navbar-default-link-active-color:         #555;\n@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);\n@navbar-default-link-disabled-color:       #ccc;\n@navbar-default-link-disabled-bg:          transparent;\n\n// Navbar brand label\n@navbar-default-brand-color:               @navbar-default-link-color;\n@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);\n@navbar-default-brand-hover-bg:            transparent;\n\n// Navbar toggle\n@navbar-default-toggle-hover-bg:           #ddd;\n@navbar-default-toggle-icon-bar-bg:        #888;\n@navbar-default-toggle-border-color:       #ddd;\n\n\n// Inverted navbar\n// Reset inverted navbar basics\n@navbar-inverse-color:                      lighten(@gray-light, 15%);\n@navbar-inverse-bg:                         #222;\n@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);\n\n// Inverted navbar links\n@navbar-inverse-link-color:                 lighten(@gray-light, 15%);\n@navbar-inverse-link-hover-color:           #fff;\n@navbar-inverse-link-hover-bg:              transparent;\n@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;\n@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);\n@navbar-inverse-link-disabled-color:        #444;\n@navbar-inverse-link-disabled-bg:           transparent;\n\n// Inverted navbar brand label\n@navbar-inverse-brand-color:                @navbar-inverse-link-color;\n@navbar-inverse-brand-hover-color:          #fff;\n@navbar-inverse-brand-hover-bg:             transparent;\n\n// Inverted navbar toggle\n@navbar-inverse-toggle-hover-bg:            #333;\n@navbar-inverse-toggle-icon-bar-bg:         #fff;\n@navbar-inverse-toggle-border-color:        #333;\n\n\n//== Navs\n//\n//##\n\n//=== Shared nav styles\n@nav-link-padding:                          10px 15px;\n@nav-link-hover-bg:                         @gray-lighter;\n\n@nav-disabled-link-color:                   @gray-light;\n@nav-disabled-link-hover-color:             @gray-light;\n\n//== Tabs\n@nav-tabs-border-color:                     #ddd;\n\n@nav-tabs-link-hover-border-color:          @gray-lighter;\n\n@nav-tabs-active-link-hover-bg:             @body-bg;\n@nav-tabs-active-link-hover-color:          @gray;\n@nav-tabs-active-link-hover-border-color:   #ddd;\n\n@nav-tabs-justified-link-border-color:            #ddd;\n@nav-tabs-justified-active-link-border-color:     @body-bg;\n\n//== Pills\n@nav-pills-border-radius:                   @border-radius-base;\n@nav-pills-active-link-hover-bg:            @component-active-bg;\n@nav-pills-active-link-hover-color:         @component-active-color;\n\n\n//== Pagination\n//\n//##\n\n@pagination-color:                     @link-color;\n@pagination-bg:                        #fff;\n@pagination-border:                    #ddd;\n\n@pagination-hover-color:               @link-hover-color;\n@pagination-hover-bg:                  @gray-lighter;\n@pagination-hover-border:              #ddd;\n\n@pagination-active-color:              #fff;\n@pagination-active-bg:                 @brand-primary;\n@pagination-active-border:             @brand-primary;\n\n@pagination-disabled-color:            @gray-light;\n@pagination-disabled-bg:               #fff;\n@pagination-disabled-border:           #ddd;\n\n\n//== Pager\n//\n//##\n\n@pager-bg:                             @pagination-bg;\n@pager-border:                         @pagination-border;\n@pager-border-radius:                  15px;\n\n@pager-hover-bg:                       @pagination-hover-bg;\n\n@pager-active-bg:                      @pagination-active-bg;\n@pager-active-color:                   @pagination-active-color;\n\n@pager-disabled-color:                 @pagination-disabled-color;\n\n\n//== Jumbotron\n//\n//##\n\n@jumbotron-padding:              30px;\n@jumbotron-color:                inherit;\n@jumbotron-bg:                   @gray-lighter;\n@jumbotron-heading-color:        inherit;\n@jumbotron-font-size:            ceil((@font-size-base * 1.5));\n\n\n//== Form states and alerts\n//\n//## Define colors for form feedback states and, by default, alerts.\n\n@state-success-text:             #3c763d;\n@state-success-bg:               #dff0d8;\n@state-success-border:           darken(spin(@state-success-bg, -10), 5%);\n\n@state-info-text:                #31708f;\n@state-info-bg:                  #d9edf7;\n@state-info-border:              darken(spin(@state-info-bg, -10), 7%);\n\n@state-warning-text:             #8a6d3b;\n@state-warning-bg:               #fcf8e3;\n@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);\n\n@state-danger-text:              #a94442;\n@state-danger-bg:                #f2dede;\n@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);\n\n\n//== Tooltips\n//\n//##\n\n//** Tooltip max width\n@tooltip-max-width:           200px;\n//** Tooltip text color\n@tooltip-color:               #fff;\n//** Tooltip background color\n@tooltip-bg:                  #000;\n@tooltip-opacity:             .9;\n\n//** Tooltip arrow width\n@tooltip-arrow-width:         5px;\n//** Tooltip arrow color\n@tooltip-arrow-color:         @tooltip-bg;\n\n\n//== Popovers\n//\n//##\n\n//** Popover body background color\n@popover-bg:                          #fff;\n//** Popover maximum width\n@popover-max-width:                   276px;\n//** Popover border color\n@popover-border-color:                rgba(0,0,0,.2);\n//** Popover fallback border color\n@popover-fallback-border-color:       #ccc;\n\n//** Popover title background color\n@popover-title-bg:                    darken(@popover-bg, 3%);\n\n//** Popover arrow width\n@popover-arrow-width:                 10px;\n//** Popover arrow color\n@popover-arrow-color:                 @popover-bg;\n\n//** Popover outer arrow width\n@popover-arrow-outer-width:           (@popover-arrow-width + 1);\n//** Popover outer arrow color\n@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);\n//** Popover outer arrow fallback color\n@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);\n\n\n//== Labels\n//\n//##\n\n//** Default label background color\n@label-default-bg:            @gray-light;\n//** Primary label background color\n@label-primary-bg:            @brand-primary;\n//** Success label background color\n@label-success-bg:            @brand-success;\n//** Info label background color\n@label-info-bg:               @brand-info;\n//** Warning label background color\n@label-warning-bg:            @brand-warning;\n//** Danger label background color\n@label-danger-bg:             @brand-danger;\n\n//** Default label text color\n@label-color:                 #fff;\n//** Default text color of a linked label\n@label-link-hover-color:      #fff;\n\n\n//== Modals\n//\n//##\n\n//** Padding applied to the modal body\n@modal-inner-padding:         15px;\n\n//** Padding applied to the modal title\n@modal-title-padding:         15px;\n//** Modal title line-height\n@modal-title-line-height:     @line-height-base;\n\n//** Background color of modal content area\n@modal-content-bg:                             #fff;\n//** Modal content border color\n@modal-content-border-color:                   rgba(0,0,0,.2);\n//** Modal content border color **for IE8**\n@modal-content-fallback-border-color:          #999;\n\n//** Modal backdrop background color\n@modal-backdrop-bg:           #000;\n//** Modal backdrop opacity\n@modal-backdrop-opacity:      .5;\n//** Modal header border color\n@modal-header-border-color:   #e5e5e5;\n//** Modal footer border color\n@modal-footer-border-color:   @modal-header-border-color;\n\n@modal-lg:                    900px;\n@modal-md:                    600px;\n@modal-sm:                    300px;\n\n\n//== Alerts\n//\n//## Define alert colors, border radius, and padding.\n\n@alert-padding:               15px;\n@alert-border-radius:         @border-radius-base;\n@alert-link-font-weight:      bold;\n\n@alert-success-bg:            @state-success-bg;\n@alert-success-text:          @state-success-text;\n@alert-success-border:        @state-success-border;\n\n@alert-info-bg:               @state-info-bg;\n@alert-info-text:             @state-info-text;\n@alert-info-border:           @state-info-border;\n\n@alert-warning-bg:            @state-warning-bg;\n@alert-warning-text:          @state-warning-text;\n@alert-warning-border:        @state-warning-border;\n\n@alert-danger-bg:             @state-danger-bg;\n@alert-danger-text:           @state-danger-text;\n@alert-danger-border:         @state-danger-border;\n\n\n//== Progress bars\n//\n//##\n\n//** Background color of the whole progress component\n@progress-bg:                 #f5f5f5;\n//** Progress bar text color\n@progress-bar-color:          #fff;\n//** Variable for setting rounded corners on progress bar.\n@progress-border-radius:      @border-radius-base;\n\n//** Default progress bar color\n@progress-bar-bg:             @brand-primary;\n//** Success progress bar color\n@progress-bar-success-bg:     @brand-success;\n//** Warning progress bar color\n@progress-bar-warning-bg:     @brand-warning;\n//** Danger progress bar color\n@progress-bar-danger-bg:      @brand-danger;\n//** Info progress bar color\n@progress-bar-info-bg:        @brand-info;\n\n\n//== List group\n//\n//##\n\n//** Background color on `.list-group-item`\n@list-group-bg:                 #fff;\n//** `.list-group-item` border color\n@list-group-border:             #ddd;\n//** List group border radius\n@list-group-border-radius:      @border-radius-base;\n\n//** Background color of single list items on hover\n@list-group-hover-bg:           #f5f5f5;\n//** Text color of active list items\n@list-group-active-color:       @component-active-color;\n//** Background color of active list items\n@list-group-active-bg:          @component-active-bg;\n//** Border color of active list elements\n@list-group-active-border:      @list-group-active-bg;\n//** Text color for content within active list items\n@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);\n\n//** Text color of disabled list items\n@list-group-disabled-color:      @gray-light;\n//** Background color of disabled list items\n@list-group-disabled-bg:         @gray-lighter;\n//** Text color for content within disabled list items\n@list-group-disabled-text-color: @list-group-disabled-color;\n\n@list-group-link-color:         #555;\n@list-group-link-hover-color:   @list-group-link-color;\n@list-group-link-heading-color: #333;\n\n\n//== Panels\n//\n//##\n\n@panel-bg:                    #fff;\n@panel-body-padding:          15px;\n@panel-heading-padding:       10px 15px;\n@panel-footer-padding:        @panel-heading-padding;\n@panel-border-radius:         @border-radius-base;\n\n//** Border color for elements within panels\n@panel-inner-border:          #ddd;\n@panel-footer-bg:             #f5f5f5;\n\n@panel-default-text:          @gray-dark;\n@panel-default-border:        #ddd;\n@panel-default-heading-bg:    #f5f5f5;\n\n@panel-primary-text:          #fff;\n@panel-primary-border:        @brand-primary;\n@panel-primary-heading-bg:    @brand-primary;\n\n@panel-success-text:          @state-success-text;\n@panel-success-border:        @state-success-border;\n@panel-success-heading-bg:    @state-success-bg;\n\n@panel-info-text:             @state-info-text;\n@panel-info-border:           @state-info-border;\n@panel-info-heading-bg:       @state-info-bg;\n\n@panel-warning-text:          @state-warning-text;\n@panel-warning-border:        @state-warning-border;\n@panel-warning-heading-bg:    @state-warning-bg;\n\n@panel-danger-text:           @state-danger-text;\n@panel-danger-border:         @state-danger-border;\n@panel-danger-heading-bg:     @state-danger-bg;\n\n\n//== Thumbnails\n//\n//##\n\n//** Padding around the thumbnail image\n@thumbnail-padding:           4px;\n//** Thumbnail background color\n@thumbnail-bg:                @body-bg;\n//** Thumbnail border color\n@thumbnail-border:            #ddd;\n//** Thumbnail border radius\n@thumbnail-border-radius:     @border-radius-base;\n\n//** Custom text color for thumbnail captions\n@thumbnail-caption-color:     @text-color;\n//** Padding around the thumbnail caption\n@thumbnail-caption-padding:   9px;\n\n\n//== Wells\n//\n//##\n\n@well-bg:                     #f5f5f5;\n@well-border:                 darken(@well-bg, 7%);\n\n\n//== Badges\n//\n//##\n\n@badge-color:                 #fff;\n//** Linked badge text color on hover\n@badge-link-hover-color:      #fff;\n@badge-bg:                    @gray-light;\n\n//** Badge text color in active nav link\n@badge-active-color:          @link-color;\n//** Badge background color in active nav link\n@badge-active-bg:             #fff;\n\n@badge-font-weight:           bold;\n@badge-line-height:           1;\n@badge-border-radius:         10px;\n\n\n//== Breadcrumbs\n//\n//##\n\n@breadcrumb-padding-vertical:   8px;\n@breadcrumb-padding-horizontal: 15px;\n//** Breadcrumb background color\n@breadcrumb-bg:                 #f5f5f5;\n//** Breadcrumb text color\n@breadcrumb-color:              #ccc;\n//** Text color of current page in the breadcrumb\n@breadcrumb-active-color:       @gray-light;\n//** Textual separator for between breadcrumb elements\n@breadcrumb-separator:          \"/\";\n\n\n//== Carousel\n//\n//##\n\n@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);\n\n@carousel-control-color:                      #fff;\n@carousel-control-width:                      15%;\n@carousel-control-opacity:                    .5;\n@carousel-control-font-size:                  20px;\n\n@carousel-indicator-active-bg:                #fff;\n@carousel-indicator-border-color:             #fff;\n\n@carousel-caption-color:                      #fff;\n\n\n//== Close\n//\n//##\n\n@close-font-weight:           bold;\n@close-color:                 #000;\n@close-text-shadow:           0 1px 0 #fff;\n\n\n//== Code\n//\n//##\n\n@code-color:                  #c7254e;\n@code-bg:                     #f9f2f4;\n\n@kbd-color:                   #fff;\n@kbd-bg:                      #333;\n\n@pre-bg:                      #f5f5f5;\n@pre-color:                   @gray-dark;\n@pre-border-color:            #ccc;\n@pre-scrollable-max-height:   340px;\n\n\n//== Type\n//\n//##\n\n//** Horizontal offset for forms and lists.\n@component-offset-horizontal: 180px;\n//** Text muted color\n@text-muted:                  @gray-light;\n//** Abbreviations and acronyms border color\n@abbr-border-color:           @gray-light;\n//** Headings small color\n@headings-small-color:        @gray-light;\n//** Blockquote small color\n@blockquote-small-color:      @gray-light;\n//** Blockquote font size\n@blockquote-font-size:        (@font-size-base * 1.25);\n//** Blockquote border color\n@blockquote-border-color:     @gray-lighter;\n//** Page header border color\n@page-header-border-color:    @gray-lighter;\n//** Width of horizontal description list titles\n@dl-horizontal-offset:        @component-offset-horizontal;\n//** Horizontal line color.\n@hr-border:                   @gray-lighter;\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/variables.less","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n  .container-fixed();\n\n  @media (min-width: @screen-sm-min) {\n    width: @container-sm;\n  }\n  @media (min-width: @screen-md-min) {\n    width: @container-md;\n  }\n  @media (min-width: @screen-lg-min) {\n    width: @container-lg;\n  }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n  .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n  .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n  .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n  .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n  .make-grid(lg);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/grid.less","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n  margin-right: auto;\n  margin-left: auto;\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n  &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n  margin-left:  (@gutter / -2);\n  margin-right: (@gutter / -2);\n  &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  float: left;\n  width: percentage((@columns / @grid-columns));\n  min-height: 1px;\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n  margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n  left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n  right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  min-height: 1px;\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  @media (min-width: @screen-sm-min) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n.make-sm-column-offset(@columns) {\n  @media (min-width: @screen-sm-min) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-sm-column-push(@columns) {\n  @media (min-width: @screen-sm-min) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-sm-column-pull(@columns) {\n  @media (min-width: @screen-sm-min) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  min-height: 1px;\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  @media (min-width: @screen-md-min) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n.make-md-column-offset(@columns) {\n  @media (min-width: @screen-md-min) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-md-column-push(@columns) {\n  @media (min-width: @screen-md-min) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-md-column-pull(@columns) {\n  @media (min-width: @screen-md-min) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n  position: relative;\n  min-height: 1px;\n  padding-left:  (@gutter / 2);\n  padding-right: (@gutter / 2);\n\n  @media (min-width: @screen-lg-min) {\n    float: left;\n    width: percentage((@columns / @grid-columns));\n  }\n}\n.make-lg-column-offset(@columns) {\n  @media (min-width: @screen-lg-min) {\n    margin-left: percentage((@columns / @grid-columns));\n  }\n}\n.make-lg-column-push(@columns) {\n  @media (min-width: @screen-lg-min) {\n    left: percentage((@columns / @grid-columns));\n  }\n}\n.make-lg-column-pull(@columns) {\n  @media (min-width: @screen-lg-min) {\n    right: percentage((@columns / @grid-columns));\n  }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/grid.less","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n  // Common styles for all sizes of grid columns, widths 1-12\n  .col(@index) { // initial\n    @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n    .col((@index + 1), @item);\n  }\n  .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n    @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n    .col((@index + 1), ~\"@{list}, @{item}\");\n  }\n  .col(@index, @list) when (@index > @grid-columns) { // terminal\n    @{list} {\n      position: relative;\n      // Prevent columns from collapsing when empty\n      min-height: 1px;\n      // Inner gutter via padding\n      padding-left:  (@grid-gutter-width / 2);\n      padding-right: (@grid-gutter-width / 2);\n    }\n  }\n  .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n  .col(@index) { // initial\n    @item: ~\".col-@{class}-@{index}\";\n    .col((@index + 1), @item);\n  }\n  .col(@index, @list) when (@index =< @grid-columns) { // general\n    @item: ~\".col-@{class}-@{index}\";\n    .col((@index + 1), ~\"@{list}, @{item}\");\n  }\n  .col(@index, @list) when (@index > @grid-columns) { // terminal\n    @{list} {\n      float: left;\n    }\n  }\n  .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n  .col-@{class}-@{index} {\n    width: percentage((@index / @grid-columns));\n  }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n  .col-@{class}-push-@{index} {\n    left: percentage((@index / @grid-columns));\n  }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n  .col-@{class}-push-0 {\n    left: auto;\n  }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n  .col-@{class}-pull-@{index} {\n    right: percentage((@index / @grid-columns));\n  }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n  .col-@{class}-pull-0 {\n    right: auto;\n  }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n  .col-@{class}-offset-@{index} {\n    margin-left: percentage((@index / @grid-columns));\n  }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n  .calc-grid-column(@index, @class, @type);\n  // next iteration\n  .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n  .float-grid-columns(@class);\n  .loop-grid-columns(@grid-columns, @class, width);\n  .loop-grid-columns(@grid-columns, @class, pull);\n  .loop-grid-columns(@grid-columns, @class, push);\n  .loop-grid-columns(@grid-columns, @class, offset);\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/grid-framework.less","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n  background-color: @table-bg;\n}\ncaption {\n  padding-top: @table-cell-padding;\n  padding-bottom: @table-cell-padding;\n  color: @text-muted;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: @line-height-computed;\n  // Cells\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-cell-padding;\n        line-height: @line-height-base;\n        vertical-align: top;\n        border-top: 1px solid @table-border-color;\n      }\n    }\n  }\n  // Bottom align for column headings\n  > thead > tr > th {\n    vertical-align: bottom;\n    border-bottom: 2px solid @table-border-color;\n  }\n  // Remove top border from thead by default\n  > caption + thead,\n  > colgroup + thead,\n  > thead:first-child {\n    > tr:first-child {\n      > th,\n      > td {\n        border-top: 0;\n      }\n    }\n  }\n  // Account for multiple tbody instances\n  > tbody + tbody {\n    border-top: 2px solid @table-border-color;\n  }\n\n  // Nesting\n  .table {\n    background-color: @body-bg;\n  }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-condensed-cell-padding;\n      }\n    }\n  }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n  border: 1px solid @table-border-color;\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        border: 1px solid @table-border-color;\n      }\n    }\n  }\n  > thead > tr {\n    > th,\n    > td {\n      border-bottom-width: 2px;\n    }\n  }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n  > tbody > tr:nth-of-type(odd) {\n    background-color: @table-bg-accent;\n  }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n  > tbody > tr:hover {\n    background-color: @table-bg-hover;\n  }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n  position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n  float: none;\n  display: table-column;\n}\ntable {\n  td,\n  th {\n    &[class*=\"col-\"] {\n      position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n      float: none;\n      display: table-cell;\n    }\n  }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n  overflow-x: auto;\n  min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n  @media screen and (max-width: @screen-xs-max) {\n    width: 100%;\n    margin-bottom: (@line-height-computed * 0.75);\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid @table-border-color;\n\n    // Tighten up spacing\n    > .table {\n      margin-bottom: 0;\n\n      // Ensure the content doesn't wrap\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th,\n          > td {\n            white-space: nowrap;\n          }\n        }\n      }\n    }\n\n    // Special overrides for the bordered tables\n    > .table-bordered {\n      border: 0;\n\n      // Nuke the appropriate borders so that the parent can handle them\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th:first-child,\n          > td:first-child {\n            border-left: 0;\n          }\n          > th:last-child,\n          > td:last-child {\n            border-right: 0;\n          }\n        }\n      }\n\n      // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n      // chances are there will be only one `tr` in a `thead` and that would\n      // remove the border altogether.\n      > tbody,\n      > tfoot {\n        > tr:last-child {\n          > th,\n          > td {\n            border-bottom: 0;\n          }\n        }\n      }\n\n    }\n  }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/tables.less","// Tables\n\n.table-row-variant(@state; @background) {\n  // Exact selectors below required to override `.table-striped` and prevent\n  // inheritance to nested tables.\n  .table > thead > tr,\n  .table > tbody > tr,\n  .table > tfoot > tr {\n    > td.@{state},\n    > th.@{state},\n    &.@{state} > td,\n    &.@{state} > th {\n      background-color: @background;\n    }\n  }\n\n  // Hover states for `.table-hover`\n  // Note: this is not available for cells or rows within `thead` or `tfoot`.\n  .table-hover > tbody > tr {\n    > td.@{state}:hover,\n    > th.@{state}:hover,\n    &.@{state}:hover > td,\n    &:hover > .@{state},\n    &.@{state}:hover > th {\n      background-color: darken(@background, 5%);\n    }\n  }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./node_modules/bootstrap-less/bootstrap/mixins/table-row.less","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n  padding: 0;\n  margin: 0;\n  border: 0;\n  // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n  // so we reset that to ensure it behaves more like a standard block element.\n  // See https://github.com/twbs/bootstrap/issues/12359.\n  min-width: 0;\n}\n\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: @line-height-computed;\n  font-size: (@font-size-base * 1.5);\n  line-height: inherit;\n  color: @legend-color;\n  border: 0;\n  border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n  display: inline-block;\n  max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n  margin-bottom: 5px;\n  font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n  .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9; // IE8-9\n  line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n  display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n  height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  .tab-focus();\n}\n\n// Adjust output element\noutput {\n  display: block;\n  padding-top: (@padding-base-vertical + 1);\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n  display: block;\n  width: 100%;\n  height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n  padding: @padding-base-vertical @padding-base-horizontal;\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @input-color;\n  background-color: @input-bg;\n  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n  border: 1px solid @input-border;\n  border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n  .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n  .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n  // Customize the `:focus` state to imitate native WebKit styles.\n  .form-control-focus();\n\n  // Placeholder\n  .placeholder();\n\n  // Disabled and read-only inputs\n  //\n  // HTML5 says that controls under a fieldset > legend:first-child won't be\n  // disabled if the fieldset is disabled. Due to implementation difficulty, we\n  // don't honor that edge case; we style them as disabled anyway.\n  &[disabled],\n  &[readonly],\n  fieldset[disabled] & {\n    cursor: @cursor-disabled;\n    background-color: @input-bg-disabled;\n    opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n  }\n\n  // Reset height for `textarea`s\n  textarea& {\n    height: auto;\n  }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"],\n  input[type=\"time\"],\n  input[type=\"datetime-local\"],\n  input[type=\"month\"] {\n    line-height: @input-height-base;\n\n    &.input-sm,\n    .input-group-sm & {\n      line-height: @input-height-small;\n    }\n\n    &.input-lg,\n    .input-group-lg & {\n      line-height: @input-height-large;\n    }\n  }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n  margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n\n  label {\n    min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n    padding-left: 20px;\n    margin-bottom: 0;\n    font-weight: normal;\n    cursor: pointer;\n  }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-left: -20px;\n  margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  vertical-align: middle;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because