Compare commits
1581 Commits
use-transf
...
v8.3.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a7c7fdebf | ||
|
|
65ff3d414a | ||
|
|
9fa38b70c8 | ||
|
|
470172f53f | ||
|
|
81261d9e36 | ||
|
|
3ab2e20119 | ||
|
|
b773d576ea | ||
|
|
23feb64b5a | ||
|
|
87fe9d9d3d | ||
|
|
b1ef3f51cb | ||
|
|
e1b6488f8e | ||
|
|
a472dede2b | ||
|
|
263cc3f7a1 | ||
|
|
d355812433 | ||
|
|
1afc14f5ab | ||
|
|
8947b667ae | ||
|
|
6b41796d44 | ||
|
|
6efe8eb55b | ||
|
|
4c8f8918e8 | ||
|
|
4cb5bb1855 | ||
|
|
eb007e025a | ||
|
|
18aefa9dee | ||
|
|
2ec540bd36 | ||
|
|
1374ec408a | ||
|
|
8f8fed2b79 | ||
|
|
a1e6f01fe9 | ||
|
|
d7496f22e5 | ||
|
|
7de25a1c37 | ||
|
|
78da89340c | ||
|
|
60606115fe | ||
|
|
57b49fc31c | ||
|
|
6e90c8f6e6 | ||
|
|
a7ff2595a5 | ||
|
|
1edea6abef | ||
|
|
3339d1999f | ||
|
|
df3e8ec0f3 | ||
|
|
2dbec867d9 | ||
|
|
b91d23023d | ||
|
|
cc234c60b8 | ||
|
|
f1883c8004 | ||
|
|
79f6ddc8ee | ||
|
|
89f439a18d | ||
|
|
79eb5bfad9 | ||
|
|
3a36b7dafd | ||
|
|
1fe2fd9891 | ||
|
|
5fdb999ece | ||
|
|
5a38d9c2b6 | ||
|
|
b1c7dc6cbb | ||
|
|
b21b2ac41c | ||
|
|
1396c597ef | ||
|
|
46af72ee4e | ||
|
|
e3e8f553c8 | ||
|
|
549928d3d1 | ||
|
|
abefec9628 | ||
|
|
b483eeded4 | ||
|
|
fab85dafa8 | ||
|
|
955faed919 | ||
|
|
3ff516180d | ||
|
|
0c8ca6d6b0 | ||
|
|
a5de077e04 | ||
|
|
c0a99d6b52 | ||
|
|
a1e65cd897 | ||
|
|
5d65f1ffc5 | ||
|
|
b7193a06fd | ||
|
|
cba090f8eb | ||
|
|
0d6baa1081 | ||
|
|
bc60d796a3 | ||
|
|
85a208526b | ||
|
|
c3ac0a750d | ||
|
|
0f111127a5 | ||
|
|
cd266a6bef | ||
|
|
0f4945621c | ||
|
|
9a477f227b | ||
|
|
3c81257325 | ||
|
|
218fe9ebdc | ||
|
|
24bb45ab97 | ||
|
|
85f39de540 | ||
|
|
5cc3277e2d | ||
|
|
d5ef7f3204 | ||
|
|
cb47d01f51 | ||
|
|
ee499c1385 | ||
|
|
4ae4af5c10 | ||
|
|
b0f5fe7e25 | ||
|
|
fd32585efc | ||
|
|
c675bb7252 | ||
|
|
e65d11d71e | ||
|
|
c2680334f1 | ||
|
|
1217a02ec1 | ||
|
|
143a091f45 | ||
|
|
49ecc93dbe | ||
|
|
1735fb6bed | ||
|
|
8fefc11b4d | ||
|
|
1a3b22171c | ||
|
|
5e773be260 | ||
|
|
c317a1dc8b | ||
|
|
9401ffc83c | ||
|
|
479a852446 | ||
|
|
9188fb03f0 | ||
|
|
36bb91cbc3 | ||
|
|
56e5ab8bc6 | ||
|
|
607eb6ca03 | ||
|
|
ad745cc84b | ||
|
|
5a13d5ea6f | ||
|
|
0b065eb7fe | ||
|
|
e2c0e4bc66 | ||
|
|
655b0e6778 | ||
|
|
cd785c9fc3 | ||
|
|
23885f5166 | ||
|
|
44ef39e419 | ||
|
|
e05a0ef565 | ||
|
|
0ea9c0647f | ||
|
|
00b9ba2b75 | ||
|
|
f1e3bc9531 | ||
|
|
7360e093b2 | ||
|
|
5f639bc24f | ||
|
|
884d2a9552 | ||
|
|
fd8a8b29b1 | ||
|
|
4e1ef40c05 | ||
|
|
00af0ddff5 | ||
|
|
2467e823a5 | ||
|
|
224642fcb5 | ||
|
|
59518ca2c5 | ||
|
|
dfb7c73069 | ||
|
|
70eccceab3 | ||
|
|
3f69b70367 | ||
|
|
94a0a2f8be | ||
|
|
7e23596ab8 | ||
|
|
3c58a5dd3d | ||
|
|
51789ccbf3 | ||
|
|
6ae4a9aa1a | ||
|
|
0aebd669b2 | ||
|
|
2a92c4899d | ||
|
|
530089895a | ||
|
|
ae8289fc8c | ||
|
|
90f4dfb48b | ||
|
|
ec2eddf538 | ||
|
|
44d31d4b39 | ||
|
|
4934e7666c | ||
|
|
35bf0d020e | ||
|
|
4934dc85ac | ||
|
|
5ceb50d7e5 | ||
|
|
ae7ccbb7bd | ||
|
|
1cd9fc47aa | ||
|
|
013c50607a | ||
|
|
d7bf9b7f2e | ||
|
|
4702fdddc6 | ||
|
|
dd06a530c0 | ||
|
|
c36125dc95 | ||
|
|
ae43f93d0a | ||
|
|
8918b17f77 | ||
|
|
dfd05e8b5b | ||
|
|
3daa6dd051 | ||
|
|
14e43192e6 | ||
|
|
6cf88b1792 | ||
|
|
6b9839367f | ||
|
|
34fcf5d616 | ||
|
|
1cf3c74e67 | ||
|
|
16b57b931e | ||
|
|
3457e7d617 | ||
|
|
edbe8001e6 | ||
|
|
71644c1cbe | ||
|
|
03fd8df8bd | ||
|
|
71d622b6dd | ||
|
|
689d5a2d58 | ||
|
|
b2e9eb866c | ||
|
|
c8b7782d1d | ||
|
|
673f936689 | ||
|
|
2ca0d39e51 | ||
|
|
908c8bc397 | ||
|
|
93082e1e87 | ||
|
|
ef0a6aa25e | ||
|
|
b9f4dc1e9d | ||
|
|
90afec864e | ||
|
|
b082fb6692 | ||
|
|
4bbbd786cd | ||
|
|
0c1b2a54e7 | ||
|
|
a6ded20ede | ||
|
|
9b96314371 | ||
|
|
85e16ecd51 | ||
|
|
2ac36cdfd6 | ||
|
|
e40c532354 | ||
|
|
9404dff79c | ||
|
|
55b324d8d6 | ||
|
|
3ed2e2d79e | ||
|
|
3152d9eadd | ||
|
|
f1266ab5d6 | ||
|
|
e70f1408aa | ||
|
|
664e3984e3 | ||
|
|
665c13e238 | ||
|
|
8a667b20c2 | ||
|
|
3693241292 | ||
|
|
a384245368 | ||
|
|
3c3acff79b | ||
|
|
e15de83a95 | ||
|
|
636fccbf97 | ||
|
|
7d8ed399a8 | ||
|
|
272385db6c | ||
|
|
9334b8df47 | ||
|
|
e0bc2ae86f | ||
|
|
2f019bb033 | ||
|
|
291be64aa0 | ||
|
|
75c83236ff | ||
|
|
72be171917 | ||
|
|
4ddee4ac40 | ||
|
|
43cd0d7eb3 | ||
|
|
6b693e2644 | ||
|
|
eeea69d8f2 | ||
|
|
72cf921a4b | ||
|
|
32882f81e7 | ||
|
|
36f5099932 | ||
|
|
bec88a0441 | ||
|
|
6e67e3a8a0 | ||
|
|
4dc3c30354 | ||
|
|
947ccf911d | ||
|
|
06f313febe | ||
|
|
b387136b8f | ||
|
|
31614c5da1 | ||
|
|
146b5a3085 | ||
|
|
397cc1754a | ||
|
|
ff1297cac5 | ||
|
|
8af3cf4056 | ||
|
|
9edec9e212 | ||
|
|
be4362c59a | ||
|
|
8461b147de | ||
|
|
82bdd43168 | ||
|
|
533d82d4d8 | ||
|
|
6f990dd1de | ||
|
|
be848598e3 | ||
|
|
62a58fa23b | ||
|
|
7d0742054f | ||
|
|
dcf7e83507 | ||
|
|
407c2bf0c8 | ||
|
|
c46227ee94 | ||
|
|
d8171eb056 | ||
|
|
c67ca500db | ||
|
|
0081e7b731 | ||
|
|
4e6483d3ed | ||
|
|
0ddf0002c4 | ||
|
|
ad0daf33b9 | ||
|
|
c614c44d4c | ||
|
|
8a46579588 | ||
|
|
fb9fb9c097 | ||
|
|
d9399534ce | ||
|
|
17a749bbed | ||
|
|
0b60c6a939 | ||
|
|
25ce63f00b | ||
|
|
2462bc05b3 | ||
|
|
c3748da0b1 | ||
|
|
90c242a441 | ||
|
|
52239a88b5 | ||
|
|
7a3596c86d | ||
|
|
ac8a9e38f0 | ||
|
|
5c08f3a27e | ||
|
|
2dc11a84bf | ||
|
|
2960ea15f5 | ||
|
|
17aab4c490 | ||
|
|
59d0f0d292 | ||
|
|
27d13a113a | ||
|
|
c58e999fbb | ||
|
|
a02a96d5c4 | ||
|
|
47e9e4704d | ||
|
|
b2ad9d404e | ||
|
|
925d48640d | ||
|
|
5216dd75bf | ||
|
|
028b4e7b79 | ||
|
|
b8b45d2d81 | ||
|
|
4b2b2cb68e | ||
|
|
625a46a2c2 | ||
|
|
be4ace293e | ||
|
|
ebc1e27c22 | ||
|
|
764b363bbc | ||
|
|
705474dc14 | ||
|
|
e639d7726b | ||
|
|
357e85d358 | ||
|
|
9da9166442 | ||
|
|
8ea339f0ef | ||
|
|
e29b0aa6a4 | ||
|
|
d2157868f2 | ||
|
|
89b36ba63f | ||
|
|
1d3dfa1fa4 | ||
|
|
89cfafd933 | ||
|
|
ca567eec8a | ||
|
|
75cfcb83aa | ||
|
|
41da31c379 | ||
|
|
e81f63f46b | ||
|
|
ade03e4827 | ||
|
|
63a4d1ad33 | ||
|
|
33a4c88c3a | ||
|
|
69c5dbfc23 | ||
|
|
cf1bccfd65 | ||
|
|
99acf018f1 | ||
|
|
f04d6f37e5 | ||
|
|
1f79776b8f | ||
|
|
11e5f851f0 | ||
|
|
4ca1db8a1b | ||
|
|
14b829aa30 | ||
|
|
384652b3df | ||
|
|
469069b471 | ||
|
|
9db65c6ae9 | ||
|
|
1346e33e99 | ||
|
|
ab9cc447aa | ||
|
|
cb63c12d2f | ||
|
|
fe9e0444b4 | ||
|
|
6ce0fd20ce | ||
|
|
a18957dbe9 | ||
|
|
13d5b724ee | ||
|
|
b383cd9493 | ||
|
|
c7d8203da9 | ||
|
|
96b5c1d8e1 | ||
|
|
882ee80424 | ||
|
|
e977771fe4 | ||
|
|
4339e4552e | ||
|
|
9bca5912d9 | ||
|
|
b54d222943 | ||
|
|
23756ba1c7 | ||
|
|
e4e613550a | ||
|
|
d1207444db | ||
|
|
06f060161d | ||
|
|
73e0628124 | ||
|
|
7393c4170b | ||
|
|
73e185bf9d | ||
|
|
0bad75b263 | ||
|
|
74b98083e2 | ||
|
|
9034b5ec11 | ||
|
|
77153c3e78 | ||
|
|
927f557672 | ||
|
|
86fb089901 | ||
|
|
630ea05e17 | ||
|
|
7df5196083 | ||
|
|
01f7b5d709 | ||
|
|
07227887f6 | ||
|
|
ec47ee3573 | ||
|
|
13d3b103f1 | ||
|
|
7062962cc8 | ||
|
|
fde447846a | ||
|
|
319cb1bd1e | ||
|
|
58cda5ae6d | ||
|
|
251a3db880 | ||
|
|
30b6dcd767 | ||
|
|
05f6622912 | ||
|
|
36183ac19d | ||
|
|
f6a823e0a8 | ||
|
|
312353551d | ||
|
|
4bdfd0e115 | ||
|
|
fd5c9cee38 | ||
|
|
84bf71802c | ||
|
|
786b20708e | ||
|
|
35739c2eef | ||
|
|
1914a71623 | ||
|
|
dcc53886d9 | ||
|
|
21ef87ef09 | ||
|
|
0e957cad84 | ||
|
|
b67f808da9 | ||
|
|
ad69447b53 | ||
|
|
b4614df88c | ||
|
|
7171247cdc | ||
|
|
6d0084f108 | ||
|
|
29359f42ae | ||
|
|
cf875bf872 | ||
|
|
13c0d335d3 | ||
|
|
ceb33409b5 | ||
|
|
83597d4a8b | ||
|
|
81eefc5448 | ||
|
|
082bc3ece4 | ||
|
|
b2406b61fb | ||
|
|
15698d7694 | ||
|
|
990cd82f97 | ||
|
|
c87829b3e8 | ||
|
|
6799c41d65 | ||
|
|
80c059be58 | ||
|
|
aa3f896538 | ||
|
|
85c728f313 | ||
|
|
d8c17a8a5e | ||
|
|
50e210b2db | ||
|
|
850939367c | ||
|
|
bf4fef9bf7 | ||
|
|
d5175961a4 | ||
|
|
ba3fb8cd66 | ||
|
|
e7e1d6a232 | ||
|
|
712345f3a0 | ||
|
|
54c9bc3dcb | ||
|
|
e796c0da4a | ||
|
|
cf8ff0f43e | ||
|
|
e67ce23a7c | ||
|
|
d9fb7dc754 | ||
|
|
a66bb95a81 | ||
|
|
2249dad9d7 | ||
|
|
c66fa33b2e | ||
|
|
5c4fa630ae | ||
|
|
56eebb9db4 | ||
|
|
ede74ad24e | ||
|
|
d9773f107e | ||
|
|
27542a8f91 | ||
|
|
5dc07b94aa | ||
|
|
e09112f46a | ||
|
|
d7acf721ae | ||
|
|
50a17a82b6 | ||
|
|
eff5232828 | ||
|
|
7eb032d646 | ||
|
|
3eb29b1cdb | ||
|
|
e065f22f8e | ||
|
|
c1b4ba1f85 | ||
|
|
eeaec471f0 | ||
|
|
0d3c8678d8 | ||
|
|
bbddf5f95b | ||
|
|
3b8c8b3af9 | ||
|
|
84753aa13f | ||
|
|
90b84451d8 | ||
|
|
54c8ae41cc | ||
|
|
7d32b1a724 | ||
|
|
69ffd63ca6 | ||
|
|
4857c19eb6 | ||
|
|
d535e23da0 | ||
|
|
30e02544ab | ||
|
|
ee53925bd2 | ||
|
|
c9961f63b4 | ||
|
|
40495b8a17 | ||
|
|
b1de98f05d | ||
|
|
6bc9a82a7a | ||
|
|
09e843a800 | ||
|
|
6504ee37bd | ||
|
|
082bff2fa8 | ||
|
|
ab7bd86336 | ||
|
|
eada0b0bb5 | ||
|
|
f221f9f22a | ||
|
|
6731e44a0d | ||
|
|
acc37045e4 | ||
|
|
a7c5899c16 | ||
|
|
80b02635a9 | ||
|
|
f90de5ec67 | ||
|
|
9a3e046530 | ||
|
|
7f56e461fe | ||
|
|
1da37e0d38 | ||
|
|
0004d4936c | ||
|
|
7a6fdc4e0a | ||
|
|
2eb727bd0c | ||
|
|
57af507170 | ||
|
|
e37f87465c | ||
|
|
324070f345 | ||
|
|
e1aa843b6d | ||
|
|
e652a7fd61 | ||
|
|
2397bfbad0 | ||
|
|
7e2bc8e452 | ||
|
|
00e8fd0483 | ||
|
|
6d8bf2c665 | ||
|
|
77b79dbd95 | ||
|
|
72466f1aab | ||
|
|
dafc6c5136 | ||
|
|
6901deccbf | ||
|
|
5a9c906eb9 | ||
|
|
b95b60b49e | ||
|
|
14408ef18f | ||
|
|
c790147a5c | ||
|
|
6b87c90e02 | ||
|
|
80c39c5ef3 | ||
|
|
2b4d5222eb | ||
|
|
9604ecebad | ||
|
|
9a3e84d84c | ||
|
|
0d67970a45 | ||
|
|
913b9f0c40 | ||
|
|
610a5745f0 | ||
|
|
dff12324c6 | ||
|
|
f340390fc8 | ||
|
|
643960c829 | ||
|
|
be81e74921 | ||
|
|
1737018325 | ||
|
|
2bee8729e4 | ||
|
|
5d03038734 | ||
|
|
75b11de0f4 | ||
|
|
484d5ba76e | ||
|
|
c5bede8594 | ||
|
|
798685d0b8 | ||
|
|
cd9ea6ae3b | ||
|
|
cb7654ae90 | ||
|
|
113b762ec7 | ||
|
|
78704d8b85 | ||
|
|
1109db76fe | ||
|
|
b1b390febf | ||
|
|
be451fa0c0 | ||
|
|
1fa553c785 | ||
|
|
905f61371d | ||
|
|
7da5210a01 | ||
|
|
18172d3896 | ||
|
|
c28e78b9e2 | ||
|
|
e7827a3847 | ||
|
|
039564e74c | ||
|
|
e164595a0f | ||
|
|
d29e09a3ff | ||
|
|
db9f85e9da | ||
|
|
27022954b1 | ||
|
|
30362c924f | ||
|
|
bf63b15b46 | ||
|
|
19aea4bd6c | ||
|
|
090890e9c6 | ||
|
|
00c394345a | ||
|
|
605022a9e3 | ||
|
|
b06c58fe7b | ||
|
|
f5c8b3eb04 | ||
|
|
739980aa09 | ||
|
|
afde5943e3 | ||
|
|
32300cb42c | ||
|
|
dffcb62fa1 | ||
|
|
de3b1697c8 | ||
|
|
8c668b72b7 | ||
|
|
a18fb10b5a | ||
|
|
52140dbe06 | ||
|
|
db5bb1928e | ||
|
|
65b66beb07 | ||
|
|
c83504b4e7 | ||
|
|
cd2e7ee31d | ||
|
|
c3a0a0415a | ||
|
|
709f4672b7 | ||
|
|
b54ecd4da0 | ||
|
|
e6c030b050 | ||
|
|
7bd3a791a1 | ||
|
|
87a7e3501b | ||
|
|
b9cfc03b4f | ||
|
|
daefec3013 | ||
|
|
131327a64d | ||
|
|
183a9742c4 | ||
|
|
77d002a158 | ||
|
|
94699893ac | ||
|
|
9f81989bdd | ||
|
|
15abe36c53 | ||
|
|
3094e007ee | ||
|
|
eb259aee22 | ||
|
|
04b83f8176 | ||
|
|
c05c8defb9 | ||
|
|
bf5668a42e | ||
|
|
335ab3f064 | ||
|
|
ec310bc8fb | ||
|
|
db477421b2 | ||
|
|
30a9496cf5 | ||
|
|
6cefa0d0b3 | ||
|
|
9284984265 | ||
|
|
53b96168a9 | ||
|
|
eadce51f10 | ||
|
|
7dd493da35 | ||
|
|
b3c583b6dc | ||
|
|
560bd6da92 | ||
|
|
28abeab31d | ||
|
|
a5824ccc5f | ||
|
|
830a7964a4 | ||
|
|
12a649ec4b | ||
|
|
35b79e4d14 | ||
|
|
751dad7f2e | ||
|
|
b08d86220a | ||
|
|
3a27ecc475 | ||
|
|
da6fab5d43 | ||
|
|
ca95b29cd6 | ||
|
|
c5c68e9dd5 | ||
|
|
44fbde26fa | ||
|
|
6e2bcd6aa9 | ||
|
|
b1359c3277 | ||
|
|
9c0202e5ce | ||
|
|
39ef353073 | ||
|
|
7b5d90dd81 | ||
|
|
0ba8f5cc5a | ||
|
|
d1129081df | ||
|
|
315a812df5 | ||
|
|
6fb9e2c38e | ||
|
|
cfc979acf0 | ||
|
|
d7407d70a3 | ||
|
|
8ccd2e97a8 | ||
|
|
988204619f | ||
|
|
cad6cc3007 | ||
|
|
eebc2ab8be | ||
|
|
b303875f1d | ||
|
|
d5cc61f378 | ||
|
|
0d7ec43262 | ||
|
|
d3747f4daa | ||
|
|
af695e7dc8 | ||
|
|
1edbfd87df | ||
|
|
454be01a6c | ||
|
|
b65b3151ee | ||
|
|
745fc515f1 | ||
|
|
715b9c1182 | ||
|
|
1d24b7985b | ||
|
|
526bb2c650 | ||
|
|
c450c0ddb8 | ||
|
|
95be847d87 | ||
|
|
c1a6546eba | ||
|
|
648c25a0a7 | ||
|
|
f2ec7f2975 | ||
|
|
f518af6d61 | ||
|
|
13a0f49f5f | ||
|
|
b11c6a5c06 | ||
|
|
5822e4e692 | ||
|
|
e4f06b0ca8 | ||
|
|
2f093c0e82 | ||
|
|
5d9dc0e74d | ||
|
|
199eefafa1 | ||
|
|
adc3a34929 | ||
|
|
cb2ffe6b3f | ||
|
|
c5b58f9ecc | ||
|
|
b3e3d01672 | ||
|
|
6b68fe4de6 | ||
|
|
4a6520fc78 | ||
|
|
75ab6c9b13 | ||
|
|
2f77fcb526 | ||
|
|
3461bbfdb3 | ||
|
|
60604c3481 | ||
|
|
51f6927076 | ||
|
|
671c113cd2 | ||
|
|
8a74d21ede | ||
|
|
75995b2109 | ||
|
|
4298aad008 | ||
|
|
823c67400d | ||
|
|
3160d1064d | ||
|
|
d1eefc3fea | ||
|
|
16795382fc | ||
|
|
eb17974adc | ||
|
|
22852c27f8 | ||
|
|
f4a94d975d | ||
|
|
7a36bbbd1e | ||
|
|
2b401b965b | ||
|
|
314bc5b44f | ||
|
|
76374f0d5a | ||
|
|
264efb015e | ||
|
|
e74460aefc | ||
|
|
55a5a12b30 | ||
|
|
58944a38eb | ||
|
|
469e3bd475 | ||
|
|
17650c5735 | ||
|
|
15e64155b5 | ||
|
|
39955ac760 | ||
|
|
855a176ca9 | ||
|
|
47b2b30455 | ||
|
|
b702e3e2de | ||
|
|
a6b74d56c6 | ||
|
|
a4222bcaef | ||
|
|
ecf24511cd | ||
|
|
abb097a391 | ||
|
|
1d88cf443f | ||
|
|
7b6c0c3a40 | ||
|
|
fdb0651bf4 | ||
|
|
dd742a2e4a | ||
|
|
c39d484611 | ||
|
|
c42996429f | ||
|
|
a091baf5a6 | ||
|
|
128bdf500a | ||
|
|
73ac00bc51 | ||
|
|
3524e23e38 | ||
|
|
be0f3910bb | ||
|
|
07dbc6842c | ||
|
|
5a16b59462 | ||
|
|
13cd7071b8 | ||
|
|
40108b196c | ||
|
|
c8e79aa5ca | ||
|
|
e60f2b2332 | ||
|
|
b6d397bcca | ||
|
|
918426a2fa | ||
|
|
c076b37c9b | ||
|
|
7c58bfa282 | ||
|
|
0caaba156d | ||
|
|
c22575812d | ||
|
|
0ede4da816 | ||
|
|
98e23ff92e | ||
|
|
6503f9c667 | ||
|
|
4770e469b4 | ||
|
|
29a18c7c8b | ||
|
|
6db0003e3f | ||
|
|
c538c460fa | ||
|
|
822339fe42 | ||
|
|
b84d9282ca | ||
|
|
00a17cd55e | ||
|
|
952b6f33bb | ||
|
|
c57c4b8ff2 | ||
|
|
39e6223ff2 | ||
|
|
d8dd274c08 | ||
|
|
15f97b6cb9 | ||
|
|
fc091c1174 | ||
|
|
c07ef4d87f | ||
|
|
643d44af22 | ||
|
|
b934f43db0 | ||
|
|
c7bdad649a | ||
|
|
18c2508d2f | ||
|
|
4dcfd8b353 | ||
|
|
726116574d | ||
|
|
27f02014ca | ||
|
|
f80f1acaa7 | ||
|
|
39d5ffeceb | ||
|
|
48ba7eed3e | ||
|
|
f39afe5a65 | ||
|
|
11eee833bb | ||
|
|
35b358d336 | ||
|
|
ae109be631 | ||
|
|
3f7ed73395 | ||
|
|
7612ee6b08 | ||
|
|
fec9d716ee | ||
|
|
da5b1afd19 | ||
|
|
618106c103 | ||
|
|
2ed2b0101a | ||
|
|
312be98132 | ||
|
|
e0bb77a6d6 | ||
|
|
5ca9d31964 | ||
|
|
855922c21a | ||
|
|
bc645d2621 | ||
|
|
2fcd8cd261 | ||
|
|
9c06ff3899 | ||
|
|
2a37aa3b49 | ||
|
|
0ffa47a2c6 | ||
|
|
bf591320af | ||
|
|
56e687bed2 | ||
|
|
9caa240fdb | ||
|
|
3ffb73a516 | ||
|
|
cc1132be87 | ||
|
|
1c31f126ef | ||
|
|
d8eaf2676f | ||
|
|
07b25fe376 | ||
|
|
c2ecd20b7d | ||
|
|
1b42abcc98 | ||
|
|
9efb49d510 | ||
|
|
2d6270c697 | ||
|
|
0823c23a6e | ||
|
|
b3f0ce4b2a | ||
|
|
8b83584b67 | ||
|
|
9eb686fe08 | ||
|
|
765051ce88 | ||
|
|
ed402e0122 | ||
|
|
e203d4dee3 | ||
|
|
1488271a83 | ||
|
|
b47d773e13 | ||
|
|
48bbf8d005 | ||
|
|
e97b969d66 | ||
|
|
a8d0a4a95d | ||
|
|
cdd12df81a | ||
|
|
3fb0804cef | ||
|
|
050a3afc74 | ||
|
|
270401c693 | ||
|
|
551822ce7d | ||
|
|
4b8c371097 | ||
|
|
90fbf6da46 | ||
|
|
0c3103e3d2 | ||
|
|
6a8e1566fe | ||
|
|
ced30082a6 | ||
|
|
f6c64abc1a | ||
|
|
6811ebcd52 | ||
|
|
7f9939a896 | ||
|
|
1c99f2dfdd | ||
|
|
1974fccac3 | ||
|
|
911552035e | ||
|
|
ff25d275ee | ||
|
|
1fcf5e03e7 | ||
|
|
4fe7bfb851 | ||
|
|
9b4101855f | ||
|
|
9253d894d3 | ||
|
|
fb60985d03 | ||
|
|
ebd79f22c7 | ||
|
|
c1b139fb9a | ||
|
|
8f575923cf | ||
|
|
a88bcea8ca | ||
|
|
21566560a7 | ||
|
|
0ecfd02649 | ||
|
|
e3ca43bf40 | ||
|
|
420aaf4f61 | ||
|
|
61abb8d5cb | ||
|
|
0c35f213e1 | ||
|
|
ecad656551 | ||
|
|
615e6d6e4f | ||
|
|
f68813af13 | ||
|
|
6dceefb96e | ||
|
|
69eff394fd | ||
|
|
a9da3aca81 | ||
|
|
91f3556375 | ||
|
|
aab7c3a840 | ||
|
|
9c823119e3 | ||
|
|
f5128833f6 | ||
|
|
2bc144354a | ||
|
|
e6fec6ec34 | ||
|
|
37a90d0ce9 | ||
|
|
53389875bf | ||
|
|
3b243b38c8 | ||
|
|
02f1291e8f | ||
|
|
3d9580808b | ||
|
|
2141ee71d4 | ||
|
|
01dd07083e | ||
|
|
42a28ea06b | ||
|
|
92e4f6b5d9 | ||
|
|
180cb6ba8e | ||
|
|
a78762e40b | ||
|
|
7b7738fbcc | ||
|
|
9797bb19e2 | ||
|
|
3101212c49 | ||
|
|
3f0ac103a1 | ||
|
|
59bd6ca360 | ||
|
|
22fe9a786e | ||
|
|
08a9554b3c | ||
|
|
d79bd825ee | ||
|
|
fe3d225cfa | ||
|
|
31197604a3 | ||
|
|
d2ee8de9ac | ||
|
|
03d3fb6a5f | ||
|
|
376e0db66e | ||
|
|
5fdabc1a62 | ||
|
|
dfe2a75d72 | ||
|
|
ba85af11aa | ||
|
|
db58b80d27 | ||
|
|
5cb8aae383 | ||
|
|
817530429b | ||
|
|
e33b1b6c90 | ||
|
|
30520297e8 | ||
|
|
78ca1d1335 | ||
|
|
6159ee8c2c | ||
|
|
5cd5392958 | ||
|
|
0dcdfc5d14 | ||
|
|
d0e068f1c0 | ||
|
|
4a7b7183d2 | ||
|
|
f42a2d7457 | ||
|
|
94bd39cf23 | ||
|
|
4038a22093 | ||
|
|
d29619b67c | ||
|
|
682baec0c9 | ||
|
|
ff91be491d | ||
|
|
ef35a0f2f1 | ||
|
|
f5235cb835 | ||
|
|
f12a3bb08b | ||
|
|
ee830e0cb4 | ||
|
|
c8a5065ffa | ||
|
|
23da5573f3 | ||
|
|
b08f985776 | ||
|
|
9b968baaa7 | ||
|
|
07edbe6f1c | ||
|
|
0cd3be003d | ||
|
|
1f55a8b6e3 | ||
|
|
f6b9e11810 | ||
|
|
c93e35ec77 | ||
|
|
c18a3e4266 | ||
|
|
9538a76232 | ||
|
|
5840ef1c6f | ||
|
|
7974baddf5 | ||
|
|
05876bb124 | ||
|
|
4bf569758f | ||
|
|
8bcd5a6d2a | ||
|
|
f56fd9bb0b | ||
|
|
a36afbcb25 | ||
|
|
357ee5fc45 | ||
|
|
ebd8d085cf | ||
|
|
c6dea085b2 | ||
|
|
505148b024 | ||
|
|
8782c3ecec | ||
|
|
b636cf2ef0 | ||
|
|
6dee2b8601 | ||
|
|
bcf301ac17 | ||
|
|
bf2120fb31 | ||
|
|
de56b74f3e | ||
|
|
2f146abe91 | ||
|
|
543d41b6ff | ||
|
|
8da0dd7563 | ||
|
|
a2217d7dbc | ||
|
|
ea84728a3f | ||
|
|
b2d10f7ccf | ||
|
|
b6af25ce99 | ||
|
|
7a9d2454d4 | ||
|
|
a9254cff02 | ||
|
|
d14b34141c | ||
|
|
14bc2cc1ba | ||
|
|
a91b54b97a | ||
|
|
ead655e1db | ||
|
|
c5f28748f7 | ||
|
|
ee4831cb30 | ||
|
|
deb1afd28b | ||
|
|
9e8eead71e | ||
|
|
3f96f7cbd7 | ||
|
|
dde2e88332 | ||
|
|
ff25015595 | ||
|
|
7d0c695808 | ||
|
|
906385def9 | ||
|
|
a6c6c7eae9 | ||
|
|
205725c767 | ||
|
|
c207efbb35 | ||
|
|
c0211e59b3 | ||
|
|
dd2678cbb9 | ||
|
|
e2c87b664e | ||
|
|
29d4b4dd53 | ||
|
|
3fba307e55 | ||
|
|
7171fa36d8 | ||
|
|
c570f656bf | ||
|
|
a5e37519f5 | ||
|
|
0f88d6eec3 | ||
|
|
651c51bb01 | ||
|
|
3eefeec4ce | ||
|
|
b61419c1ce | ||
|
|
f590fcffbc | ||
|
|
0fdbdfd5c2 | ||
|
|
31056ff858 | ||
|
|
8d2643696b | ||
|
|
e7488d19e9 | ||
|
|
2bb3b6d64c | ||
|
|
5744e48ae8 | ||
|
|
82d0a21440 | ||
|
|
58133cffac | ||
|
|
bfd8c2f310 | ||
|
|
30d447c023 | ||
|
|
9a0846b8a6 | ||
|
|
e87e924ac2 | ||
|
|
3667fcddd7 | ||
|
|
90f261bab6 | ||
|
|
906741d662 | ||
|
|
f7dfb09a4d | ||
|
|
12be088c4f | ||
|
|
6737ba80cd | ||
|
|
862a3d938e | ||
|
|
09e82377a5 | ||
|
|
59470864e7 | ||
|
|
c95aeb3730 | ||
|
|
5c55c90d68 | ||
|
|
e47972731b | ||
|
|
5851cc9e41 | ||
|
|
6f615230e9 | ||
|
|
d91598a25e | ||
|
|
9e416778d9 | ||
|
|
860a117567 | ||
|
|
b8fe3b18d4 | ||
|
|
40269a724b | ||
|
|
ec828318d8 | ||
|
|
d31e7ed534 | ||
|
|
5c2dbe438b | ||
|
|
10857635ac | ||
|
|
df2545ef80 | ||
|
|
f6ff729316 | ||
|
|
38678803eb | ||
|
|
67c931f196 | ||
|
|
3135917127 | ||
|
|
1c23092d0e | ||
|
|
a90ff21cbf | ||
|
|
0ce0cee81f | ||
|
|
f4be5ffb5d | ||
|
|
19958748bf | ||
|
|
d6ca8468e3 | ||
|
|
7bccb7718b | ||
|
|
f6b63b5e44 | ||
|
|
9a2c5ff195 | ||
|
|
3597f759da | ||
|
|
3ed3b21286 | ||
|
|
b89b636474 | ||
|
|
2afc595452 | ||
|
|
52afa3d36d | ||
|
|
c7262f2885 | ||
|
|
8662aa2277 | ||
|
|
242aa60e04 | ||
|
|
8095e0ab72 | ||
|
|
7a3c2c27ff | ||
|
|
be3c8ddd5c | ||
|
|
ec5b9ce903 | ||
|
|
bd2acefecc | ||
|
|
18e49e9067 | ||
|
|
5d124360c2 | ||
|
|
a0d65520a3 | ||
|
|
365d7448d5 | ||
|
|
a35731d9d5 | ||
|
|
9a0102c723 | ||
|
|
9d3623cca6 | ||
|
|
2fe08a721f | ||
|
|
7abc3a7d7d | ||
|
|
d4a34f1a3c | ||
|
|
2f77f2cb2b | ||
|
|
ddda4848d3 | ||
|
|
528e3a2106 | ||
|
|
8516856d37 | ||
|
|
032a664d4c | ||
|
|
132327594b | ||
|
|
d2a2c63070 | ||
|
|
aac1864c9b | ||
|
|
170a5158fa | ||
|
|
1d8493d388 | ||
|
|
ff39e8bd2c | ||
|
|
c3442033da | ||
|
|
f1dd84edba | ||
|
|
e3477f3306 | ||
|
|
06b040a337 | ||
|
|
6620a4f87b | ||
|
|
fa546ddc5b | ||
|
|
f811352c79 | ||
|
|
7ed8963b9f | ||
|
|
c0e9dff5bf | ||
|
|
a9fc8b79fd | ||
|
|
afd794b4c7 | ||
|
|
c4a28f0ec4 | ||
|
|
db343bf795 | ||
|
|
0157043dc5 | ||
|
|
a947f9bd32 | ||
|
|
2a4181c7c3 | ||
|
|
30192f5b14 | ||
|
|
c41b5e8844 | ||
|
|
b27928807b | ||
|
|
16f1b5e23e | ||
|
|
ed651b6869 | ||
|
|
b9d925c7aa | ||
|
|
3650a29381 | ||
|
|
de84ee3693 | ||
|
|
42ba31591d | ||
|
|
a78a243e20 | ||
|
|
38924ced4a | ||
|
|
5e8cc66f5c | ||
|
|
1353837584 | ||
|
|
7cb5a89523 | ||
|
|
1db09a7953 | ||
|
|
bc6aa12dd0 | ||
|
|
c3bea88979 | ||
|
|
6e85e466b0 | ||
|
|
3327cc70c9 | ||
|
|
c9eac66a93 | ||
|
|
53e9bd6e48 | ||
|
|
eaa18e1efb | ||
|
|
afa3dacc31 | ||
|
|
c803c4a57a | ||
|
|
2d3a53e449 | ||
|
|
2d961c435a | ||
|
|
5e076754ce | ||
|
|
927e217961 | ||
|
|
80b48101aa | ||
|
|
08530e6133 | ||
|
|
97130ef6c1 | ||
|
|
da37feae6d | ||
|
|
f96172e61f | ||
|
|
e35477b8db | ||
|
|
cea5560a67 | ||
|
|
311bd5e67e | ||
|
|
1cfddf2a4c | ||
|
|
abe58117fe | ||
|
|
ee5f89f70d | ||
|
|
4f545ed101 | ||
|
|
136de4208e | ||
|
|
7650a2c2a7 | ||
|
|
c3d1987fac | ||
|
|
12ef78bb1c | ||
|
|
16c4241a6e | ||
|
|
4992c77818 | ||
|
|
3a0b1de136 | ||
|
|
1c3ef02c7b | ||
|
|
f268fe9e80 | ||
|
|
2ed98c17d4 | ||
|
|
571ae4fbfd | ||
|
|
c94a8c42f4 | ||
|
|
16fdb16a56 | ||
|
|
822f9a6f28 | ||
|
|
b264bbf69f | ||
|
|
6e61e94e02 | ||
|
|
6a7972c5a1 | ||
|
|
db4fbe315a | ||
|
|
f3613d7103 | ||
|
|
cbbed36428 | ||
|
|
e86e9697b3 | ||
|
|
fd6b2d5715 | ||
|
|
fbb36d1665 | ||
|
|
07be1b8192 | ||
|
|
33880393ac | ||
|
|
5123fe7838 | ||
|
|
cbe26a365d | ||
|
|
f1bb72b2a6 | ||
|
|
2c33654395 | ||
|
|
7c95f03166 | ||
|
|
dd86de017e | ||
|
|
3eabde9630 | ||
|
|
640c51af31 | ||
|
|
7167b17d25 | ||
|
|
8a35948678 | ||
|
|
0fe63d3fb9 | ||
|
|
e4302c3e88 | ||
|
|
a7df6fb465 | ||
|
|
133e7598e0 | ||
|
|
c1a52ffa75 | ||
|
|
4f46313388 | ||
|
|
03b2cc9cd2 | ||
|
|
1a2bf8dc95 | ||
|
|
dd63fbeb84 | ||
|
|
59e435c418 | ||
|
|
f89f0a19b5 | ||
|
|
cbc6ef95cb | ||
|
|
0ceecc9e1d | ||
|
|
c816902025 | ||
|
|
cfb03cdca0 | ||
|
|
266f77b08c | ||
|
|
31e5c13b50 | ||
|
|
257d58c236 | ||
|
|
4a9fe4f981 | ||
|
|
015f3d936c | ||
|
|
18d2a0ffd7 | ||
|
|
4fcc5587ee | ||
|
|
24afde0e46 | ||
|
|
6ca49a20ce | ||
|
|
8499faa55a | ||
|
|
28f293fdc1 | ||
|
|
c60dd809b8 | ||
|
|
297b8e33f2 | ||
|
|
d0593c6b8d | ||
|
|
8a40d7e35c | ||
|
|
b670b2014c | ||
|
|
440e969f52 | ||
|
|
14b79f2f1c | ||
|
|
b3e7619adc | ||
|
|
00cf49a61f | ||
|
|
4f534e0e84 | ||
|
|
6e56d56137 | ||
|
|
83a19fbbbf | ||
|
|
610cb884fc | ||
|
|
2528f6a07b | ||
|
|
ba92cec62b | ||
|
|
d92e961a52 | ||
|
|
b13e74756a | ||
|
|
4ef3072766 | ||
|
|
423d07c919 | ||
|
|
e96e2461d3 | ||
|
|
7a2e2be169 | ||
|
|
cc608de4bf | ||
|
|
8d2a5a7e4a | ||
|
|
b7b0e4fab5 | ||
|
|
a624a79b30 | ||
|
|
313135da6f | ||
|
|
58d27d1247 | ||
|
|
f999a68608 | ||
|
|
edfb28168f | ||
|
|
8d0e03bb06 | ||
|
|
855f6f77cf | ||
|
|
6236cffe14 | ||
|
|
322a71fbb8 | ||
|
|
4d9f8476f3 | ||
|
|
d7d93b14b2 | ||
|
|
db78a9f18f | ||
|
|
d1af3ece6e | ||
|
|
816039f48e | ||
|
|
8153b20984 | ||
|
|
ae240bae6d | ||
|
|
9e30c69e6d | ||
|
|
a50f605c29 | ||
|
|
daf23edd10 | ||
|
|
43c7de9049 | ||
|
|
7e51c5db81 | ||
|
|
2eaaeb8259 | ||
|
|
a02c62d62c | ||
|
|
e0232a8e84 | ||
|
|
6ea5693b2f | ||
|
|
0ee3c45e7b | ||
|
|
030c2114d1 | ||
|
|
2cb18e3668 | ||
|
|
cd9f8be563 | ||
|
|
a02792e9bf | ||
|
|
41bb422244 | ||
|
|
54663d3342 | ||
|
|
2529f7369f | ||
|
|
909c33dccf | ||
|
|
1adc9f1aa9 | ||
|
|
e9948f0718 | ||
|
|
49da9e58fd | ||
|
|
2f74a8afe1 | ||
|
|
f3e288d078 | ||
|
|
988000952e | ||
|
|
981e69929c | ||
|
|
6537f3794b | ||
|
|
d31718ba8a | ||
|
|
9dd4bc5fa8 | ||
|
|
df5f1bd522 | ||
|
|
ddffab9169 | ||
|
|
553ab8851a | ||
|
|
1eae5d12fc | ||
|
|
0c34073582 | ||
|
|
14674947cb | ||
|
|
51bccdbd66 | ||
|
|
f0fbb3cf36 | ||
|
|
0cc47aacbe | ||
|
|
fafd592290 | ||
|
|
40e754b8c3 | ||
|
|
483301db7a | ||
|
|
218606fbd6 | ||
|
|
c601b8e62c | ||
|
|
2bd68ec991 | ||
|
|
66842648ed | ||
|
|
ce54b9a7b5 | ||
|
|
8a5f6d2a5d | ||
|
|
1d86a5476f | ||
|
|
ca4d3f6bce | ||
|
|
2812f2ce92 | ||
|
|
5c623db798 | ||
|
|
edaf005fe1 | ||
|
|
4f6e407247 | ||
|
|
e30881239c | ||
|
|
bbde2cc4b2 | ||
|
|
16d18c79d7 | ||
|
|
a0d2cb8a03 | ||
|
|
1bb5dc7e69 | ||
|
|
58759acfe4 | ||
|
|
0cd5136052 | ||
|
|
b3c6fe5369 | ||
|
|
599718f84e | ||
|
|
d9a5452388 | ||
|
|
0fe49e04bf | ||
|
|
a98d3fb4dc | ||
|
|
8c670d1832 | ||
|
|
c232f490bc | ||
|
|
c7280953dd | ||
|
|
8f4c606c64 | ||
|
|
6740afab42 | ||
|
|
5df22b3e6a | ||
|
|
3d9d18a0d5 | ||
|
|
0102599708 | ||
|
|
960edd4adf | ||
|
|
3547fa723c | ||
|
|
7a456185c6 | ||
|
|
dd79c3f2d6 | ||
|
|
35682d11f0 | ||
|
|
d04b3f0907 | ||
|
|
c926358e04 | ||
|
|
856ba52f36 | ||
|
|
a5bea31154 | ||
|
|
2afcc1e384 | ||
|
|
fc469707a3 | ||
|
|
8863208333 | ||
|
|
77fdc370c7 | ||
|
|
5f38a74a72 | ||
|
|
301290fb6d | ||
|
|
fe15dacb1f | ||
|
|
07fffe2f79 | ||
|
|
0227a63fa5 | ||
|
|
27764b863c | ||
|
|
032fd75f9e | ||
|
|
0bf4f861f3 | ||
|
|
fd8f90cb52 | ||
|
|
b6c6b025c8 | ||
|
|
3d89e98d1f | ||
|
|
7c5110ed5d | ||
|
|
0a474f48ad | ||
|
|
c409bfd5be | ||
|
|
39d5d5b2e0 | ||
|
|
c2d44cf2f2 | ||
|
|
8a80d9009d | ||
|
|
f62b5df566 | ||
|
|
826521f053 | ||
|
|
f9b05bc8de | ||
|
|
b8239e8ed9 | ||
|
|
214757ab0b | ||
|
|
f130186b37 | ||
|
|
2244eebc3b | ||
|
|
4176792f2d | ||
|
|
1e6cef52c9 | ||
|
|
a0f4f30a50 | ||
|
|
4cbf6ac393 | ||
|
|
af7425d8e6 | ||
|
|
3fea909d3f | ||
|
|
7c37d40677 | ||
|
|
3a97c27350 | ||
|
|
e0516a52a8 | ||
|
|
a85ec6efb3 | ||
|
|
3795c74814 | ||
|
|
27954dc6d3 | ||
|
|
68c4187a09 | ||
|
|
b9834231f3 | ||
|
|
2be343ea1c | ||
|
|
109fe1b62c | ||
|
|
63d691a63c | ||
|
|
7f1bdb6f34 | ||
|
|
6f57d6b876 | ||
|
|
e0bad99ea1 | ||
|
|
7cdfaa93ec | ||
|
|
59ccc70303 | ||
|
|
f1584b722d | ||
|
|
e39eb09cfb | ||
|
|
64d397c3f3 | ||
|
|
465ac1d1e1 | ||
|
|
18d6becebc | ||
|
|
b0305e12d2 | ||
|
|
4d8c5a86a4 | ||
|
|
3bbd0fdbcd | ||
|
|
58f76b5c99 | ||
|
|
7c4ee632cf | ||
|
|
b6b0f716eb | ||
|
|
8214b11da5 | ||
|
|
36090bf83e | ||
|
|
bffb2fe82f | ||
|
|
500cbf5d92 | ||
|
|
bd0e04ed15 | ||
|
|
8599981d44 | ||
|
|
6fc6e95c67 | ||
|
|
43b585bde8 | ||
|
|
710f89291f | ||
|
|
5f835aa009 | ||
|
|
d5ca543719 | ||
|
|
4c6249eb9e | ||
|
|
016900bad8 | ||
|
|
2e8ae33761 | ||
|
|
2b8ea9a233 | ||
|
|
0d325060da | ||
|
|
1a6e98e18f | ||
|
|
97e34595f6 | ||
|
|
a179d5234b | ||
|
|
64c6121fdb | ||
|
|
08d8954a85 | ||
|
|
4f20955d0d | ||
|
|
3a703c8bcf | ||
|
|
ccbffa086b | ||
|
|
07ee4be840 | ||
|
|
4cc9b2d312 | ||
|
|
24dddae1d1 | ||
|
|
ad0165d085 | ||
|
|
39dc38c5d1 | ||
|
|
046ce19dbb | ||
|
|
33263f5a93 | ||
|
|
8ef8e76300 | ||
|
|
73cfdae9e7 | ||
|
|
54a3e41281 | ||
|
|
d59ba6da84 | ||
|
|
1b28b06934 | ||
|
|
c8b213c190 | ||
|
|
942de9dce5 | ||
|
|
ff3e69a56c | ||
|
|
6b975a5fb4 | ||
|
|
3a02b15124 | ||
|
|
5f73d81935 | ||
|
|
002b5c0f6f | ||
|
|
8086842570 | ||
|
|
51f2d5a664 | ||
|
|
db63ad1cf4 | ||
|
|
b74b76de75 | ||
|
|
a1b1498106 | ||
|
|
1158851ea7 | ||
|
|
e1ab9e959e | ||
|
|
2bbac3ae9d | ||
|
|
e889b1d5e5 | ||
|
|
233bf856f4 | ||
|
|
bca843e06c | ||
|
|
30a79a1278 | ||
|
|
0f92dee2c4 | ||
|
|
f04efede15 | ||
|
|
f0dfdf6720 | ||
|
|
e26d731382 | ||
|
|
d684d3e559 | ||
|
|
47c54cb998 | ||
|
|
592cb2b3ec | ||
|
|
f5a7871a2e | ||
|
|
ec411fa0db | ||
|
|
5da79cd5ca | ||
|
|
a850a9bb83 | ||
|
|
13c971b171 | ||
|
|
1cee7e43ed | ||
|
|
5e81c63d6e | ||
|
|
479b7a3f94 | ||
|
|
f7cfee77c9 | ||
|
|
65a8126a13 | ||
|
|
a39bc102d5 | ||
|
|
81d930c4d2 | ||
|
|
6839623061 | ||
|
|
7de2809d42 | ||
|
|
98ec6b6886 | ||
|
|
04827f00cc | ||
|
|
660bfc6578 | ||
|
|
1152cd5537 | ||
|
|
17456482d6 | ||
|
|
a0431e1912 | ||
|
|
f30e8497b2 | ||
|
|
06495bc45d | ||
|
|
26067916b3 | ||
|
|
c36ee4852b | ||
|
|
2cb992ad44 | ||
|
|
ee5aac8008 | ||
|
|
083b7be6c0 | ||
|
|
e24854558f | ||
|
|
e4314cf426 | ||
|
|
69b9b0bbc0 | ||
|
|
3c1088f030 | ||
|
|
4106e4e45c | ||
|
|
05f143db2b | ||
|
|
64aeaeeeea | ||
|
|
61db37ab0d | ||
|
|
f9c4d921e7 | ||
|
|
ca099df573 | ||
|
|
28b584b8bc | ||
|
|
70449e694d | ||
|
|
8395ea552d | ||
|
|
dc66452633 | ||
|
|
53ce44ac91 | ||
|
|
c7c3243bbc | ||
|
|
8bdd77d33d | ||
|
|
acd7d0db3a | ||
|
|
2bfadb8a3c | ||
|
|
0912e4af7b | ||
|
|
5aa5c48018 | ||
|
|
8cdd998f79 | ||
|
|
050d4d6b25 | ||
|
|
366cd11238 | ||
|
|
58d6443331 | ||
|
|
101b8afb56 | ||
|
|
5df5c47945 | ||
|
|
a04740ba86 | ||
|
|
425ad93ac5 | ||
|
|
b0067fee51 | ||
|
|
732c3dae89 | ||
|
|
d45bd67cae | ||
|
|
9200de5032 | ||
|
|
8a44144c20 | ||
|
|
ee82c70582 | ||
|
|
c87e8e606b | ||
|
|
37a50dd953 | ||
|
|
a2669a3084 | ||
|
|
77da22f4dd | ||
|
|
7830ffe202 | ||
|
|
1c9e20d59f | ||
|
|
3fbbff5a47 | ||
|
|
c22efc2c3d | ||
|
|
8c0281bf70 | ||
|
|
320edac286 | ||
|
|
d49878371d | ||
|
|
d2575a5d9b | ||
|
|
ea6cf72580 | ||
|
|
2118155b37 | ||
|
|
ba4f5bb71f | ||
|
|
d5a74a5a8b | ||
|
|
23be1df360 | ||
|
|
b5c1a1da4c | ||
|
|
c11e784f51 | ||
|
|
06f51c8f9c | ||
|
|
181bcbbda6 | ||
|
|
d008ead6a4 | ||
|
|
75924be958 | ||
|
|
b1a6e3f8a2 | ||
|
|
06712a6041 | ||
|
|
720a4bc4a2 | ||
|
|
7fd93645b3 | ||
|
|
62b16339a9 | ||
|
|
9a2f1a36ba | ||
|
|
fcbfbca6d0 | ||
|
|
f2bca9491c | ||
|
|
b48f309ab6 | ||
|
|
0b1be3e63b | ||
|
|
95cc4d3a73 | ||
|
|
497eeeb2e0 | ||
|
|
4be21ca249 | ||
|
|
e8598e214e | ||
|
|
54b1d65e3c | ||
|
|
f7648496d3 | ||
|
|
59a57c7197 | ||
|
|
5659b26827 | ||
|
|
ee4443aaf0 | ||
|
|
839dcad358 | ||
|
|
d67933ab49 | ||
|
|
0eb3f6b952 | ||
|
|
68b0f80fce | ||
|
|
93489529a3 | ||
|
|
511be74e74 | ||
|
|
bdee067803 | ||
|
|
32156cace3 | ||
|
|
30688114be | ||
|
|
34088bcc17 | ||
|
|
07835766cc | ||
|
|
8c129c10af | ||
|
|
251851ec6a | ||
|
|
049a669186 | ||
|
|
d29f13bae9 | ||
|
|
c758355df9 | ||
|
|
79d97a83af | ||
|
|
85bd47c240 | ||
|
|
473ead9616 | ||
|
|
cf2850933c | ||
|
|
ff2564c57a | ||
|
|
91d3848246 | ||
|
|
c031f0b45e | ||
|
|
fdbb9568ae | ||
|
|
d817883459 | ||
|
|
12255979ac | ||
|
|
366b61850b | ||
|
|
89be6bd183 | ||
|
|
e120331a2c | ||
|
|
cb8a212d96 | ||
|
|
7aec431ac5 | ||
|
|
d19681dea1 | ||
|
|
63ce2a14fe | ||
|
|
f435ebb110 | ||
|
|
843f001bf6 | ||
|
|
bf2299daf8 | ||
|
|
164930d0dd | ||
|
|
387dbac809 | ||
|
|
3b661e5a99 | ||
|
|
90c1c0e655 | ||
|
|
21d8e7695b | ||
|
|
0d28165c04 | ||
|
|
ee31bfbcd4 | ||
|
|
1acc452cfe | ||
|
|
1375e1feee | ||
|
|
2187adf59a | ||
|
|
0dcb315d9d | ||
|
|
327ccbd0c9 | ||
|
|
f571d400e6 | ||
|
|
293aa52335 | ||
|
|
ca178ae9a7 | ||
|
|
d0c810e418 | ||
|
|
d496d2caeb | ||
|
|
e70b75c350 | ||
|
|
a50befeda5 | ||
|
|
e2616e8039 | ||
|
|
904266debe | ||
|
|
72d5783795 | ||
|
|
d699fb1473 | ||
|
|
fab1a6c33a | ||
|
|
be73c30194 | ||
|
|
451646fe4f | ||
|
|
decc919991 | ||
|
|
e0b4005921 | ||
|
|
3ef36e7534 | ||
|
|
1949e1e1e9 | ||
|
|
3358382358 | ||
|
|
3eca3ecd75 | ||
|
|
140c6b91b0 | ||
|
|
a5315ec240 | ||
|
|
93f1656e0b | ||
|
|
f1d006c236 | ||
|
|
b0b5a96694 | ||
|
|
7dbe9a85f4 | ||
|
|
d2c39528d5 | ||
|
|
0420543c94 | ||
|
|
aae0db902b | ||
|
|
88dae7cef7 | ||
|
|
e5cb17e934 | ||
|
|
9d609805f2 | ||
|
|
e2b9ca8254 | ||
|
|
e16a2fe8af | ||
|
|
22d61a533d | ||
|
|
af408bb45f | ||
|
|
24bfbc06f0 | ||
|
|
5eb9f353b5 | ||
|
|
473ce15f47 | ||
|
|
eb9cfbaed6 | ||
|
|
faeb037ff9 | ||
|
|
07602f697d | ||
|
|
11abb0fdb1 | ||
|
|
deeb2fa543 | ||
|
|
ef8d5ff11e | ||
|
|
91f3e07b83 | ||
|
|
c29bdbdacb | ||
|
|
a20d104d2f | ||
|
|
a61dd8ac17 | ||
|
|
7ee9a690ea | ||
|
|
5ba94c6c41 | ||
|
|
9fa855c837 | ||
|
|
9251007574 | ||
|
|
cc73b984cb | ||
|
|
548ef97c32 | ||
|
|
ed8a486726 | ||
|
|
1ab0911fc8 | ||
|
|
bdbaea7294 | ||
|
|
5cfd1f6fb2 | ||
|
|
5eda67381f | ||
|
|
2c8b8bfaf2 | ||
|
|
8f3159751a | ||
|
|
4b05e55b29 | ||
|
|
3d3c13fcd0 | ||
|
|
88e1d8a8cf | ||
|
|
e007db34e2 | ||
|
|
f9f06d2c02 | ||
|
|
234f7d00c8 | ||
|
|
9924553da5 | ||
|
|
df38d7e3ed | ||
|
|
44dd061619 | ||
|
|
7603a932b1 | ||
|
|
138e7acc13 | ||
|
|
e863d3e7e5 | ||
|
|
c8e401f5ed | ||
|
|
3ba20a8e28 | ||
|
|
ebae63752f | ||
|
|
8bc73901cf | ||
|
|
b4f70d9244 | ||
|
|
21e9f2bba3 | ||
|
|
881f4e3d6a | ||
|
|
b141945add | ||
|
|
1c67d6802d | ||
|
|
5da8c86ec7 | ||
|
|
10a2d59ec1 | ||
|
|
34e8360b10 | ||
|
|
ca259ee4c3 | ||
|
|
2143952a1e | ||
|
|
6bab6e7151 | ||
|
|
d217c2e295 | ||
|
|
52bf0faaa5 | ||
|
|
3f3f2bfc61 | ||
|
|
f050864fb4 | ||
|
|
db11fc35f4 | ||
|
|
f47a2b10c0 | ||
|
|
344b4e7d60 | ||
|
|
7a23372489 | ||
|
|
9da15a8e58 | ||
|
|
50e0e4a07b | ||
|
|
5e1562ae4c | ||
|
|
8a0ed49623 |
@@ -3206,7 +3206,8 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
|
||||||
"profile": "https://github.com/swift2512",
|
"profile": "https://github.com/swift2512",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug",
|
||||||
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4189,6 +4190,51 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
|||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
|
DB_SOCKET=null
|
||||||
DB_PORT='3306'
|
DB_PORT='3306'
|
||||||
DB_DATABASE=snipeit
|
DB_DATABASE=snipeit
|
||||||
DB_USERNAME=snipeit
|
DB_USERNAME=snipeit
|
||||||
@@ -168,6 +169,7 @@ AWS_DEFAULT_REGION=null
|
|||||||
LOGIN_MAX_ATTEMPTS=5
|
LOGIN_MAX_ATTEMPTS=5
|
||||||
LOGIN_LOCKOUT_DURATION=60
|
LOGIN_LOCKOUT_DURATION=60
|
||||||
RESET_PASSWORD_LINK_EXPIRES=900
|
RESET_PASSWORD_LINK_EXPIRES=900
|
||||||
|
INVITE_PASSWORD_LINK_EXPIRES=1500
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# OPTIONAL: MISC
|
# OPTIONAL: MISC
|
||||||
|
|||||||
10
.env.example
10
.env.example
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
|||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
|
DB_SOCKET=null
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_DATABASE=null
|
DB_DATABASE=null
|
||||||
DB_USERNAME=null
|
DB_USERNAME=null
|
||||||
@@ -174,6 +175,7 @@ LOGIN_AUTOCOMPLETE=false
|
|||||||
RESET_PASSWORD_LINK_EXPIRES=15
|
RESET_PASSWORD_LINK_EXPIRES=15
|
||||||
PASSWORD_CONFIRM_TIMEOUT=10800
|
PASSWORD_CONFIRM_TIMEOUT=10800
|
||||||
PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
||||||
|
INVITE_PASSWORD_LINK_EXPIRES=1500
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# OPTIONAL: MISC
|
# OPTIONAL: MISC
|
||||||
@@ -191,11 +193,17 @@ LDAP_TIME_LIM=600
|
|||||||
IMPORT_TIME_LIMIT=600
|
IMPORT_TIME_LIMIT=600
|
||||||
IMPORT_MEMORY_LIMIT=500M
|
IMPORT_MEMORY_LIMIT=500M
|
||||||
REPORT_TIME_LIMIT=12000
|
REPORT_TIME_LIMIT=12000
|
||||||
REQUIRE_SAML=false
|
|
||||||
API_THROTTLE_PER_MINUTE=120
|
API_THROTTLE_PER_MINUTE=120
|
||||||
CSV_ESCAPE_FORMULAS=true
|
CSV_ESCAPE_FORMULAS=true
|
||||||
LIVEWIRE_URL_PREFIX=null
|
LIVEWIRE_URL_PREFIX=null
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# OPTIONAL: SAML SETTINGS
|
||||||
|
# --------------------------------------------
|
||||||
|
REQUIRE_SAML=false
|
||||||
|
SAML_KEY_SIZE=2048
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# OPTIONAL: HASHING
|
# OPTIONAL: HASHING
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|||||||
163
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
163
.github/ISSUE_TEMPLATE/Bug-Report.yml
vendored
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: File a bug report.
|
||||||
|
title: "[Bug]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Most issues are documented in the [Snipe-IT repository's issues](https://github.com/grokability/snipe-it/issues) or in the official [Common Issues section of the Documentation](https://snipe-it.readme.io/docs/common-issues#/) and are due to the following:
|
||||||
|
|
||||||
|
- `.env` misconfiguration
|
||||||
|
- [Server Permissions](https://snipe-it.readme.io/docs/debugging-permissions#/)
|
||||||
|
- [Database Migrations](https://snipe-it.readme.io/docs/database-issues#run-migrations)
|
||||||
|
|
||||||
|
Please make sure you've checked these resources before submitting a new issue. If you find an existing issue, please add your context to it instead of opening a new issue. If your issue is more of a question, consider [opening a new discussion](https://github.com/grokability/snipe-it/discussions) or [pop by our Discord](https://discord.gg/yZFtShAcKk) instead of creating an issue.
|
||||||
|
|
||||||
|
**Please write your bug report in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you seeing this issue on? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.2 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: php-version
|
||||||
|
attributes:
|
||||||
|
label: PHP Version
|
||||||
|
description: What version of PHP are you running? You can find the version of PHP your webserver is running in the `Admin Settings` section in the footer, and the cli version by running `php -v` via command line .
|
||||||
|
placeholder: ex. v8.3.1 (web), PHP 8.4.12 (cli)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: composer-version
|
||||||
|
attributes:
|
||||||
|
label: Composer Version
|
||||||
|
description: What version of composer are you running? You can find the version number by running `composer --version`.
|
||||||
|
placeholder: ex. 2.8.10
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: db-version
|
||||||
|
attributes:
|
||||||
|
label: MySQL/MariaDB version
|
||||||
|
description: What database are you using, and what version?
|
||||||
|
placeholder: ex. MySQL 5.7
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: How did you install Snipe-IT?
|
||||||
|
options:
|
||||||
|
- Git install
|
||||||
|
- Manual install (downloading zip/tar.gz)
|
||||||
|
- Docker
|
||||||
|
- install.sh
|
||||||
|
- Hosted by Grokability
|
||||||
|
- Other
|
||||||
|
- Not sure
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: upgrade-or-fresh
|
||||||
|
attributes:
|
||||||
|
label: Is this a fresh install or an upgrade?
|
||||||
|
options:
|
||||||
|
- Fresh install
|
||||||
|
- Upgrade
|
||||||
|
- NA
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: Tell us what you see! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox
|
||||||
|
- Chrome
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
|
- Other
|
||||||
|
- type: dropdown
|
||||||
|
id: on-demo
|
||||||
|
attributes:
|
||||||
|
label: Can you reproduce this on the public demo?
|
||||||
|
description: You can check this at https://demo.snipeitapp.com.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- N/A
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs
|
||||||
|
attributes:
|
||||||
|
label: Do you have full multiple company support enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: fmcs-location
|
||||||
|
attributes:
|
||||||
|
label: If you have full multiple company support enabled, do you have location scoping to company enabled?
|
||||||
|
description: You can check this in your Snipe-IT installation at `Admin Settings > General Settings > Scoping`.
|
||||||
|
options:
|
||||||
|
- 'Yes'
|
||||||
|
- 'No'
|
||||||
|
- I do not have full multiple company support enabled
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: server-logs
|
||||||
|
attributes:
|
||||||
|
label: Application log output
|
||||||
|
description: Please copy and paste any relevant log output from `storage/logs/laravel.log`. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: browser-logs
|
||||||
|
attributes:
|
||||||
|
label: Browser console output
|
||||||
|
description: Please copy and paste any relevant log output from your browser console. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: checkboxes
|
||||||
|
id: common-issues
|
||||||
|
attributes:
|
||||||
|
label: Common Issues
|
||||||
|
description: Please make sure you have done the following before submitting your issue.
|
||||||
|
options:
|
||||||
|
- label: I have searched this repo for existing issues related to my issue (including closed issues)
|
||||||
|
required: true
|
||||||
|
- label: My APP_URL is set correctly in my .env file (including http or https and no trailing slash)
|
||||||
|
required: true
|
||||||
|
- label: I have searched the official Snipe-IT documentation and have checked the Common Issues documentation (where applicable)
|
||||||
|
required: true
|
||||||
|
- label: I have run database migrations (where applicable).
|
||||||
|
required: true
|
||||||
|
- label: I have attached screenshots and/or videos of the issue (where applicable)
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/Feature-Request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Request a new feature.
|
||||||
|
title: "[Feature]: "
|
||||||
|
projects: ["grokability/snipe-it"]
|
||||||
|
type: feature
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request! Please make sure to search the existing issues in this repository to see if your feature has already been requested, and feel free to add your context to any existing requests.
|
||||||
|
|
||||||
|
**Please write your issue in English.** You can use tools like [DeepL](https://www.deepl.com) or [Google Translate](https://translate.google.com/) to translate if necessary.
|
||||||
|
|
||||||
|
**If you choose to upload screenshots or videos (which we always encourage), please make sure they do not contain any sensitive information.**
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Snipe-IT Version
|
||||||
|
description: What version of Snipe-IT are you currently running? You can find the version number in the footer of any page in Snipe-IT.
|
||||||
|
placeholder: ex. v8.3.1 - build 19577 (master)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: How can we help?
|
||||||
|
description: Let us know in detail what feature you'd like to see added. While we can't promise to implement every feature request, we do read every one and take them into consideration when planning future releases.
|
||||||
|
placeholder: Tell us what you'd like to see in Snipe-IT! (Be nice!)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/grokability/snipe-it/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
8
.github/workflows/SA-codeql.yml
vendored
8
.github/workflows/SA-codeql.yml
vendored
@@ -26,14 +26,14 @@ jobs:
|
|||||||
language: [ 'javascript' ]
|
language: [ 'javascript' ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
6
.github/workflows/codacy-analysis.yml
vendored
6
.github/workflows/codacy-analysis.yml
vendored
@@ -32,11 +32,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout the repository to the GitHub Actions runner
|
# Checkout the repository to the GitHub Actions runner
|
||||||
- name: Checkout code
|
- 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
|
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||||
- name: Run Codacy Analysis CLI
|
- name: Run Codacy Analysis CLI
|
||||||
uses: codacy/codacy-analysis-cli-action@v4.4.5
|
uses: codacy/codacy-analysis-cli-action@v4.4.7
|
||||||
with:
|
with:
|
||||||
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
# 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
|
# You can also omit the token and run the tools that support default configurations
|
||||||
@@ -52,6 +52,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the SARIF file generated in the previous step
|
# Upload the SARIF file generated in the previous step
|
||||||
- name: Upload SARIF results file
|
- name: Upload SARIF results file
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
2
.github/workflows/crowdin-upload.yml
vendored
2
.github/workflows/crowdin-upload.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Crowdin push
|
- name: Crowdin push
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
|
|||||||
2
.github/workflows/docker-alpine.yml
vendored
2
.github/workflows/docker-alpine.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|||||||
2
.github/workflows/docker-ubuntu.yml
vendored
2
.github/workflows/docker-ubuntu.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|||||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
# pull-requests: write
|
# pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
debug-only: true
|
debug-only: true
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
14
.github/workflows/tests-mysql.yml
vendored
14
.github/workflows/tests-mysql.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -76,4 +76,16 @@ jobs:
|
|||||||
DB_DATABASE: snipeit
|
DB_DATABASE: snipeit
|
||||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
14
.github/workflows/tests-postgres.yml
vendored
14
.github/workflows/tests-postgres.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -75,4 +75,16 @@ jobs:
|
|||||||
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
||||||
DB_USERNAME: snipeit
|
DB_USERNAME: snipeit
|
||||||
DB_PASSWORD: password
|
DB_PASSWORD: password
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
14
.github/workflows/tests-sqlite.yml
vendored
14
.github/workflows/tests-sqlite.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
@@ -61,4 +61,16 @@ jobs:
|
|||||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION: sqlite
|
DB_CONNECTION: sqlite
|
||||||
|
LOG_CHANNEL: single
|
||||||
|
LOG_LEVEL: debug
|
||||||
run: php artisan test
|
run: php artisan test
|
||||||
|
|
||||||
|
- name: Upload Laravel logs as artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
storage/logs/*.log
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||||||
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=swift2512 "Code") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
|
||||||
@@ -68,6 +68,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||||||
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
||||||
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
|
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
|
||||||
|
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") |
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
|||||||
59
app/Actions/Categories/DestroyCategoryAction.php
Normal file
59
app/Actions/Categories/DestroyCategoryAction.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Categories;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DestroyCategoryAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasAssetModels
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
*/
|
||||||
|
static function run(Category $category): bool
|
||||||
|
{
|
||||||
|
$category->loadCount([
|
||||||
|
'assets as assets_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
'models as models_count'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($category->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($category);
|
||||||
|
}
|
||||||
|
if ($category->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($category);
|
||||||
|
}
|
||||||
|
if ($category->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($category);
|
||||||
|
}
|
||||||
|
if ($category->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($category);
|
||||||
|
}
|
||||||
|
if ($category->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($category);
|
||||||
|
}
|
||||||
|
if ($category->models_count > 0) {
|
||||||
|
throw new ItemStillHasAssetModels($category);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
||||||
|
$category->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
app/Actions/Manufacturers/DeleteManufacturerAction.php
Normal file
63
app/Actions/Manufacturers/DeleteManufacturerAction.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Manufacturers;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DeleteManufacturerAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
*/
|
||||||
|
static function run(Manufacturer $manufacturer): bool
|
||||||
|
{
|
||||||
|
$manufacturer->loadCount([
|
||||||
|
'assets as assets_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($manufacturer->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($manufacturer);
|
||||||
|
}
|
||||||
|
if ($manufacturer->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($manufacturer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manufacturer->image) {
|
||||||
|
try {
|
||||||
|
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::info($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$manufacturer->delete();
|
||||||
|
//dd($manufacturer);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
app/Actions/Suppliers/DestroySupplierAction.php
Normal file
72
app/Actions/Suppliers/DestroySupplierAction.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Suppliers;
|
||||||
|
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class DestroySupplierAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws ItemStillHasLicenses
|
||||||
|
* @throws ItemStillHasAssets
|
||||||
|
* @throws ItemStillHasMaintenances
|
||||||
|
* @throws ItemStillHasAccessories
|
||||||
|
* @throws ItemStillHasConsumables
|
||||||
|
* @throws ItemStillHasComponents
|
||||||
|
*/
|
||||||
|
static function run(Supplier $supplier): bool
|
||||||
|
{
|
||||||
|
$supplier->loadCount([
|
||||||
|
'maintenances as maintenances_count',
|
||||||
|
'assets as assets_count',
|
||||||
|
'licenses as licenses_count',
|
||||||
|
'accessories as accessories_count',
|
||||||
|
'consumables as consumables_count',
|
||||||
|
'components as components_count',
|
||||||
|
]);
|
||||||
|
if ($supplier->assets_count > 0) {
|
||||||
|
throw new ItemStillHasAssets($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->maintenances_count > 0) {
|
||||||
|
throw new ItemStillHasMaintenances($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->licenses_count > 0) {
|
||||||
|
throw new ItemStillHasLicenses($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->accessories_count > 0) {
|
||||||
|
throw new ItemStillHasAccessories($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->consumables_count > 0) {
|
||||||
|
throw new ItemStillHasConsumables($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->components_count > 0) {
|
||||||
|
throw new ItemStillHasComponents($supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supplier->image) {
|
||||||
|
try {
|
||||||
|
Storage::disk('public')->delete('suppliers/'.$supplier->image);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::info($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$supplier->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
app/Console/Commands/CleanIncorrectCheckoutAcceptances.php
Normal file
68
app/Console/Commands/CleanIncorrectCheckoutAcceptances.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\CheckoutAcceptance;
|
||||||
|
use App\Models\LicenseSeat;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanIncorrectCheckoutAcceptances extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'snipeit:clean-checkout-acceptances';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = "Delete checkout acceptances for checkouts to non-users";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$deletions = 0;
|
||||||
|
$skips = 0;
|
||||||
|
|
||||||
|
// This walks *every* checkoutacceptance. That's gnarly. But necessary
|
||||||
|
$this->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
74
app/Console/Commands/CleanOldCheckoutRequests.php
Normal file
74
app/Console/Commands/CleanOldCheckoutRequests.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\CheckoutRequest;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanOldCheckoutRequests extends Command
|
||||||
|
{
|
||||||
|
private int $deletions = 0;
|
||||||
|
private int $skips = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'snipeit:clean-old-checkout-requests';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Removes checkout requests that reference deleted assets or users.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$requests = CheckoutRequest::with([
|
||||||
|
'user' => 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,6 +55,8 @@ class LdapSync extends Command
|
|||||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
||||||
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||||
|
|
||||||
|
|
||||||
|
// Map the LDAP attributes to the Snipe-IT user fields.
|
||||||
$ldap_map = [
|
$ldap_map = [
|
||||||
"username" => Setting::getSettings()->ldap_username_field,
|
"username" => Setting::getSettings()->ldap_username_field,
|
||||||
"last_name" => Setting::getSettings()->ldap_lname_field,
|
"last_name" => Setting::getSettings()->ldap_lname_field,
|
||||||
@@ -63,11 +65,17 @@ class LdapSync extends Command
|
|||||||
"emp_num" => Setting::getSettings()->ldap_emp_num,
|
"emp_num" => Setting::getSettings()->ldap_emp_num,
|
||||||
"email" => Setting::getSettings()->ldap_email,
|
"email" => Setting::getSettings()->ldap_email,
|
||||||
"phone" => Setting::getSettings()->ldap_phone_field,
|
"phone" => Setting::getSettings()->ldap_phone_field,
|
||||||
|
"mobile" => Setting::getSettings()->ldap_mobile,
|
||||||
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
|
"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,
|
"country" => Setting::getSettings()->ldap_country,
|
||||||
"location" => Setting::getSettings()->ldap_location,
|
"location" => Setting::getSettings()->ldap_location,
|
||||||
"dept" => Setting::getSettings()->ldap_dept,
|
"dept" => Setting::getSettings()->ldap_dept,
|
||||||
"manager" => Setting::getSettings()->ldap_manager,
|
"manager" => Setting::getSettings()->ldap_manager,
|
||||||
|
"display_name" => Setting::getSettings()->ldap_display_name,
|
||||||
];
|
];
|
||||||
|
|
||||||
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
||||||
@@ -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++) {
|
for ($i = 0; $i < $results['count']; $i++) {
|
||||||
$item = [];
|
$item = [];
|
||||||
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
|
$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['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
|
||||||
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
|
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
|
||||||
$item['firstname'] = $results[$i][$ldap_map["first_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['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
|
||||||
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
||||||
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
|
$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['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['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
|
||||||
|
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? '';
|
||||||
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
|
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
|
||||||
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
|
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
|
||||||
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
|
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
|
||||||
@@ -278,6 +293,9 @@ class LdapSync extends Command
|
|||||||
if($ldap_map["username"] != null){
|
if($ldap_map["username"] != null){
|
||||||
$user->username = $item['username'];
|
$user->username = $item['username'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["display_name"] != null){
|
||||||
|
$user->display_name = $item['display_name'];
|
||||||
|
}
|
||||||
if($ldap_map["last_name"] != null){
|
if($ldap_map["last_name"] != null){
|
||||||
$user->last_name = $item['lastname'];
|
$user->last_name = $item['lastname'];
|
||||||
}
|
}
|
||||||
@@ -293,12 +311,27 @@ class LdapSync extends Command
|
|||||||
if($ldap_map["phone"] != null){
|
if($ldap_map["phone"] != null){
|
||||||
$user->phone = $item['telephone'];
|
$user->phone = $item['telephone'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["mobile"] != null){
|
||||||
|
$user->mobile = $item['mobile'];
|
||||||
|
}
|
||||||
if($ldap_map["jobtitle"] != null){
|
if($ldap_map["jobtitle"] != null){
|
||||||
$user->jobtitle = $item['jobtitle'];
|
$user->jobtitle = $item['jobtitle'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["address"] != null){
|
||||||
|
$user->address = $item['address'];
|
||||||
|
}
|
||||||
|
if($ldap_map["city"] != null){
|
||||||
|
$user->city = $item['city'];
|
||||||
|
}
|
||||||
|
if($ldap_map["state"] != null){
|
||||||
|
$user->state = $item['state'];
|
||||||
|
}
|
||||||
if($ldap_map["country"] != null){
|
if($ldap_map["country"] != null){
|
||||||
$user->country = $item['country'];
|
$user->country = $item['country'];
|
||||||
}
|
}
|
||||||
|
if($ldap_map["zip"] != null){
|
||||||
|
$user->zip = $item['zip'];
|
||||||
|
}
|
||||||
if($ldap_map["dept"] != null){
|
if($ldap_map["dept"] != null){
|
||||||
$user->department_id = $department->id;
|
$user->department_id = $department->id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
|||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use App\Models\Ldap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a given ip is in a network
|
* Check if a given ip is in a network
|
||||||
@@ -160,7 +161,15 @@ class LdapTroubleshooter extends Command
|
|||||||
$output[] = "-x";
|
$output[] = "-x";
|
||||||
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
||||||
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
|
$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));
|
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
|
||||||
if($settings->ldap_tls) {
|
if($settings->ldap_tls) {
|
||||||
$this->line("# adding STARTTLS option");
|
$this->line("# adding STARTTLS option");
|
||||||
@@ -171,6 +180,23 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->line(implode(" \\\n",$output));
|
$this->line(implode(" \\\n",$output));
|
||||||
exit(0);
|
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')) {
|
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?');
|
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
|
||||||
if(!$confirmation) {
|
if(!$confirmation) {
|
||||||
@@ -179,7 +205,7 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//$this->line(print_r($settings,true));
|
//$this->line(print_r($settings,true));
|
||||||
$this->info("STAGE 1: Checking settings");
|
$this->line("STAGE 1: Checking settings");
|
||||||
if(!$settings->ldap_enabled) {
|
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)");
|
$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("Determined LDAP hostname to be: ".$parsed['host']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("Performing DNS lookup of: ".$parsed['host']);
|
|
||||||
$ips = dns_get_record($parsed['host']);
|
|
||||||
$raw_ips = [];
|
$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->info("Host IP is: ".print_r($ips,true));
|
||||||
$this->error("ERROR: DNS lookup of host: ".$parsed['host']." has failed. ABORTING.");
|
|
||||||
exit(-1);
|
if (!$ips || count($ips) == 0) {
|
||||||
}
|
$this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING.");
|
||||||
$this->debugout("IP's? ".print_r($ips,true));
|
exit(-1);
|
||||||
foreach($ips as $ip) {
|
|
||||||
if(!isset($ip['ip'])) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
$raw_ips[]=$ip['ip'];
|
$this->debugout("IP's? " . print_r($ips, true));
|
||||||
if($ip['ip'] == "127.0.0.1") {
|
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");
|
$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->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");
|
$this->line("STAGE 2: Checking basic network connectivity");
|
||||||
$ports = [389,636];
|
$ports = [636, 389];
|
||||||
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
|
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
|
||||||
$ports[] = $parsed['port'];
|
$ports[] = $parsed['port'];
|
||||||
}
|
}
|
||||||
@@ -246,7 +280,7 @@ class LdapTroubleshooter extends Command
|
|||||||
$errstr = '';
|
$errstr = '';
|
||||||
$timeout = 30.0;
|
$timeout = 30.0;
|
||||||
$result = '';
|
$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 {
|
try {
|
||||||
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
|
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
@@ -265,9 +299,9 @@ class LdapTroubleshooter extends Command
|
|||||||
exit(-1);
|
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 = [];
|
$pretty_ldap_urls = [];
|
||||||
foreach($open_ports as $port) {
|
foreach($open_ports as $port) {
|
||||||
$this->line("Trying TLS first for port $port");
|
$this->line("Trying TLS first for port $port");
|
||||||
@@ -275,35 +309,46 @@ class LdapTroubleshooter extends Command
|
|||||||
if($this->test_anonymous_bind($ldap_url)) {
|
if($this->test_anonymous_bind($ldap_url)) {
|
||||||
$this->info("Anonymous bind succesful to $ldap_url!");
|
$this->info("Anonymous bind succesful to $ldap_url!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
$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...
|
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
|
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->test_anonymous_bind($ldap_url, false)) {
|
if($this->test_anonymous_bind($ldap_url, false)) {
|
||||||
$this->info("Anonymous bind succesful to $ldap_url with certifcate-checks disabled");
|
$this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled");
|
||||||
$ldap_urls[] = [ $ldap_url, false, false ];
|
$ldap_urls[] = [$ldap_url, false, false];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "no", "no" ];
|
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
|
$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";
|
$ldap_url = "ldap://".$parsed['host'].":$port";
|
||||||
|
|
||||||
if($this->test_anonymous_bind($ldap_url, true, true)) {
|
if($this->test_anonymous_bind($ldap_url, true, true)) {
|
||||||
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
|
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, true ];
|
$ldap_urls[] = [ $ldap_url, true, true ];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "YES" ];
|
$pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} 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)) {
|
if($this->test_anonymous_bind($ldap_url)) {
|
||||||
$this->info("Plain connection to $ldap_url succesful!");
|
$this->info("Plain connection to $ldap_url succesful!");
|
||||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
$ldap_urls[] = [ $ldap_url, true, false ];
|
||||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
|
$pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"];
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
|
$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));
|
$this->debugout(print_r($ldap_urls,true));
|
||||||
|
|
||||||
if(count($ldap_urls) > 0 ) {
|
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?
|
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
|
||||||
$this->info("LDAP URL: ".$ldap_url[0]);
|
$this->debugout("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[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 {
|
} else {
|
||||||
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
|
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
|
||||||
exit(1);
|
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) {
|
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)
|
//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();
|
$all_defined_constants = get_defined_constants();
|
||||||
$ldap_constants = [];
|
$ldap_constants = [];
|
||||||
@@ -341,16 +392,23 @@ class LdapTroubleshooter extends Command
|
|||||||
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
||||||
|
|
||||||
foreach($ldap_urls AS $ldap_url) {
|
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!");
|
$this->info("Success getting informational bind!");
|
||||||
} else {
|
} else {
|
||||||
$this->error("Unable to get information from bind.");
|
$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) {
|
foreach($ldap_urls AS $ldap_url) {
|
||||||
$this->info("Starting auth to ".$ldap_url[0]);
|
$this->line("Starting auth to " . $ldap_url[0]);
|
||||||
while(true) {
|
while(true) {
|
||||||
$with_tls = $ldap_url[1] ? "with": "without";
|
$with_tls = $ldap_url[1] ? "with": "without";
|
||||||
$with_startssl = $ldap_url[2] ? "using": "not using";
|
$with_startssl = $ldap_url[2] ? "using": "not using";
|
||||||
@@ -359,7 +417,12 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
$username = $this->ask("Username");
|
$username = $this->ask("Username");
|
||||||
$password = $this->secret("Password");
|
$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)
|
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);
|
$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?
|
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
|
// 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) {
|
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
|
||||||
// client-side TLS certificate support for LDAP (Google Secure LDAP)
|
// client-side TLS certificate support for LDAP (Google Secure LDAP)
|
||||||
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
|
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) {
|
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
|
||||||
try {
|
try {
|
||||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
$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);
|
$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;
|
return (bool)$bind_results;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
|
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
|
||||||
@@ -421,6 +488,7 @@ class LdapTroubleshooter extends Command
|
|||||||
try {
|
try {
|
||||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||||
$bind_results = ldap_bind($lconn, $username, $password);
|
$bind_results = ldap_bind($lconn, $username, $password);
|
||||||
|
ldap_close($lconn);
|
||||||
if(!$bind_results) {
|
if(!$bind_results) {
|
||||||
$this->error("WARNING: Failed to bind to $ldap_url as $username");
|
$this->error("WARNING: Failed to bind to $ldap_url as $username");
|
||||||
return false;
|
return false;
|
||||||
@@ -446,22 +514,62 @@ class LdapTroubleshooter extends Command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
||||||
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
$cleaned_results = [];
|
||||||
$results = ldap_get_entries($conn, $result);
|
try {
|
||||||
$cleaned_results = $this->ldap_results_cleaner($results);
|
// This _may_ only work for Active Directory?
|
||||||
$this->line(print_r($cleaned_results,true));
|
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
||||||
//okay, great - now how do we display those results? I have no idea.
|
$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?
|
// 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));
|
$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));
|
$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: ");
|
$this->info("Printing first 10 results: ");
|
||||||
for($i=0;$i<10;$i++) {
|
$pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10);
|
||||||
$this->info($search_results[$i]);
|
//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) {
|
} catch (\Exception $e) {
|
||||||
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
|
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
|
||||||
return false;
|
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'))) {
|
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)
|
// 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();
|
return $function();
|
||||||
} else {
|
} else {
|
||||||
$parent_pid = posix_getpid();
|
$parent_pid = posix_getpid();
|
||||||
@@ -514,4 +622,6 @@ class LdapTroubleshooter extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class MoveUploadsToNewDisk extends Command
|
|||||||
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
|
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
|
||||||
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
|
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
|
||||||
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
|
$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['imports'] = glob('storage/private_uploads/imports'."/*.*");
|
||||||
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
|
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
|
||||||
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
|
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ class PaveIt extends Command
|
|||||||
'migrations',
|
'migrations',
|
||||||
'settings',
|
'settings',
|
||||||
'users',
|
'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.
|
// We only need to find out what these are so we can nuke these columns on the assets table.
|
||||||
|
|||||||
@@ -62,19 +62,19 @@ class Purge extends Command
|
|||||||
$assetcount = $assets->count();
|
$assetcount = $assets->count();
|
||||||
$this->info($assets->count().' assets purged.');
|
$this->info($assets->count().' assets purged.');
|
||||||
$asset_assoc = 0;
|
$asset_assoc = 0;
|
||||||
$asset_maintenances = 0;
|
$maintenances = 0;
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
$this->info('- Asset "'.$asset->present()->name().'" deleted.');
|
$this->info('- Asset "'.$asset->display_name.'" deleted.');
|
||||||
$asset_assoc += $asset->assetlog()->count();
|
$asset_assoc += $asset->assetlog()->count();
|
||||||
$asset->assetlog()->forceDelete();
|
$asset->assetlog()->forceDelete();
|
||||||
$asset_maintenances += $asset->assetmaintenances()->count();
|
$maintenances += $asset->maintenances()->count();
|
||||||
$asset->assetmaintenances()->forceDelete();
|
$asset->maintenances()->forceDelete();
|
||||||
$asset->forceDelete();
|
$asset->forceDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info($asset_assoc.' corresponding log records purged.');
|
$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();
|
$locations = Location::whereNotNull('deleted_at')->withTrashed()->get();
|
||||||
$this->info($locations->count().' locations purged.');
|
$this->info($locations->count().' locations purged.');
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RemoveInvalidUploadDeleteActionLogItems extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'snipeit:remove-invalid-upload-delete-action-log-items';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Permanently remove invalid "upload deleted" action log items that have a null filename. This command can potentially result in deleted files being "resurrected" in the UI.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$invalidLogs = Actionlog::query()
|
||||||
|
->where('action_type', 'upload deleted')
|
||||||
|
->whereNull('filename')
|
||||||
|
->withTrashed()
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->info("{$invalidLogs->count()} invalid log items found.");
|
||||||
|
|
||||||
|
if ($invalidLogs->count() === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn($log) => [
|
||||||
|
$log->id,
|
||||||
|
$log->action_type,
|
||||||
|
$log->item_type,
|
||||||
|
$log->item_id,
|
||||||
|
$log->created_at,
|
||||||
|
$log->deleted_at,
|
||||||
|
])->toArray());
|
||||||
|
|
||||||
|
if ($this->confirm("Do you wish to remove {$invalidLogs->count()} log items?")) {
|
||||||
|
$invalidLogs->each(fn($log) => $log->forceDelete());
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use enshrined\svgSanitize\Sanitizer;
|
||||||
|
|
||||||
class SQLStreamer {
|
class SQLStreamer {
|
||||||
private $input;
|
private $input;
|
||||||
@@ -242,7 +243,10 @@ class RestoreFromBackup extends Command
|
|||||||
|
|
||||||
$private_dirs = [
|
$private_dirs = [
|
||||||
'storage/private_uploads/accessories',
|
'storage/private_uploads/accessories',
|
||||||
'storage/private_uploads/assetmodels',
|
'storage/private_uploads/assetmodels' => 'storage/private_uploads/models', //this was changed from assetmodels => models Aug 10 2025
|
||||||
|
'storage/private_uploads/asset_maintenances' => 'storage/private_uploads/maintenances', //this was changed from asset_maintenances => maintenances Aug 10 2025
|
||||||
|
'storage/private_uploads/maintenances', //but let 'maintenances' take precedence
|
||||||
|
'storage/private_uploads/models', //and let 'models' take precedence
|
||||||
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
||||||
'storage/private_uploads/audits',
|
'storage/private_uploads/audits',
|
||||||
'storage/private_uploads/components',
|
'storage/private_uploads/components',
|
||||||
@@ -260,9 +264,10 @@ class RestoreFromBackup extends Command
|
|||||||
];
|
];
|
||||||
$public_dirs = [
|
$public_dirs = [
|
||||||
'public/uploads/accessories',
|
'public/uploads/accessories',
|
||||||
|
// 'public/uploads/assetmodels' => 'public/uploads/models', //according to git, this was _never_ a thing... (see below)
|
||||||
|
'public/uploads/maintenances',
|
||||||
'public/uploads/assets', // these are asset _pictures_, not asset files
|
'public/uploads/assets', // these are asset _pictures_, not asset files
|
||||||
'public/uploads/avatars',
|
'public/uploads/avatars',
|
||||||
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
|
|
||||||
'public/uploads/categories',
|
'public/uploads/categories',
|
||||||
'public/uploads/companies',
|
'public/uploads/companies',
|
||||||
'public/uploads/components',
|
'public/uploads/components',
|
||||||
@@ -270,7 +275,7 @@ class RestoreFromBackup extends Command
|
|||||||
'public/uploads/departments',
|
'public/uploads/departments',
|
||||||
'public/uploads/locations',
|
'public/uploads/locations',
|
||||||
'public/uploads/manufacturers',
|
'public/uploads/manufacturers',
|
||||||
'public/uploads/models',
|
'public/uploads/models', // ...it's been this way for 9 years (as of late 2025)
|
||||||
'public/uploads/suppliers',
|
'public/uploads/suppliers',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -283,8 +288,6 @@ class RestoreFromBackup extends Command
|
|||||||
'public/uploads/favicon-uploaded.*',
|
'public/uploads/favicon-uploaded.*',
|
||||||
];
|
];
|
||||||
|
|
||||||
$all_files = $private_dirs + $public_dirs;
|
|
||||||
|
|
||||||
$sqlfiles = [];
|
$sqlfiles = [];
|
||||||
$sqlfile_indices = [];
|
$sqlfile_indices = [];
|
||||||
|
|
||||||
@@ -292,6 +295,20 @@ class RestoreFromBackup extends Command
|
|||||||
$boring_files = [];
|
$boring_files = [];
|
||||||
$unsafe_files = [];
|
$unsafe_files = [];
|
||||||
|
|
||||||
|
$good_extensions = config('filesystems.allowed_upload_extensions_array');
|
||||||
|
|
||||||
|
$private_extensions = array_merge($good_extensions, ["csv", "key"]); //add csv, and 'key'
|
||||||
|
$public_extensions = array_diff($good_extensions, ["xml"]); //remove xml
|
||||||
|
|
||||||
|
$sanitizer = new Sanitizer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: I _hate_ the "continue 3" thing we keep doing here
|
||||||
|
* I think a better approach might be to have the "each file" stuff be in a method on this class, and the
|
||||||
|
* boring_files and interesting_files be properties on it that we fill out. Then, in that method, we could
|
||||||
|
* just do a 'return' once the file is actually handled (yay or nay). We could also start to break out some of
|
||||||
|
* the _other_ things that we do into their own methods too? But I don't care about that as much.
|
||||||
|
*/
|
||||||
for ($i = 0; $i < $za->numFiles; $i++) {
|
for ($i = 0; $i < $za->numFiles; $i++) {
|
||||||
$stat_results = $za->statIndex($i);
|
$stat_results = $za->statIndex($i);
|
||||||
// echo "index: $i\n";
|
// echo "index: $i\n";
|
||||||
@@ -306,7 +323,7 @@ class RestoreFromBackup extends Command
|
|||||||
// skip macOS resource fork files (?!?!?!)
|
// skip macOS resource fork files (?!?!?!)
|
||||||
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
|
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
|
||||||
//print "SKIPPING macOS Resource fork file: $raw_path\n";
|
//print "SKIPPING macOS Resource fork file: $raw_path\n";
|
||||||
$boring_files[] = $raw_path;
|
// $boring_files[] = $raw_path; //stop adding this to the boring files list; it's just confusing
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
||||||
@@ -315,44 +332,70 @@ class RestoreFromBackup extends Command
|
|||||||
$sqlfile_indices[] = $i;
|
$sqlfile_indices[] = $i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($raw_path[-1] == '/') {
|
||||||
|
//last character is '/' - this is a directory, and we don't need it, and we don't need to warn about it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_array(basename($raw_path), [".gitkeep", ".gitignore", ".DS_Store"])) {
|
||||||
|
//skip these boring files silently without reporting on them; they're stupid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
|
foreach (['public' => $public_dirs, 'private' => $private_dirs] as $purpose => $dirs) {
|
||||||
$last_pos = strrpos($raw_path, $dir . '/');
|
$allowed_extensions = match ($purpose) {
|
||||||
if ($last_pos !== false) {
|
'public' => $public_extensions,
|
||||||
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
'private' => $private_extensions,
|
||||||
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
|
};
|
||||||
$interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
|
foreach ($dirs as $dir => $destdir) {
|
||||||
continue 2;
|
if (is_int($dir)) {
|
||||||
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
|
$dir = $destdir;
|
||||||
// we don't care about that; we just want files with the appropriate prefix
|
}
|
||||||
//print("FOUND THE EXACT DIRECTORY: $dir AT: $raw_path!!!\n");
|
$last_pos = strrpos($raw_path, $dir . '/');
|
||||||
|
if ($last_pos !== false) {
|
||||||
|
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
||||||
|
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
|
||||||
|
//the CSV bit, below, is because we store CSV files as "blahcsv" - without an extension
|
||||||
|
if (!in_array($extension, $allowed_extensions) && !($dir == "storage/private_uploads/imports" && substr($raw_path, -3) == "csv" && $extension == "")) {
|
||||||
|
$unsafe_files[] = $raw_path;
|
||||||
|
Log::debug($raw_path . ' from directory ' . $dir . ' is being skipped');
|
||||||
|
} else {
|
||||||
|
if ($dir != $destdir) {
|
||||||
|
Log::debug("Getting ready to save file $raw_path to new directory $destdir");
|
||||||
|
}
|
||||||
|
$interesting_files[$raw_path] = ['dest' => $destdir, 'index' => $i];
|
||||||
|
}
|
||||||
|
continue 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$good_extensions = config('filesystems.allowed_upload_extensions_array');
|
foreach (['public' => $public_files, 'private' => $private_files] as $purpose => $files) {
|
||||||
|
$allowed_extensions = match ($purpose) {
|
||||||
foreach (array_merge($private_files, $public_files) as $file) {
|
'public' => $public_extensions,
|
||||||
$has_wildcard = (strpos($file, '*') !== false);
|
'private' => $private_extensions,
|
||||||
if ($has_wildcard) {
|
};
|
||||||
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
|
foreach ($files as $file) {
|
||||||
}
|
$has_wildcard = (strpos($file, '*') !== false);
|
||||||
$last_pos = strrpos($raw_path, $file); // no trailing slash!
|
if ($has_wildcard) {
|
||||||
if ($last_pos !== false) {
|
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
|
||||||
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
|
||||||
if (!in_array($extension, $good_extensions)) {
|
|
||||||
// gathering potentially unsafe files here to return at exit
|
|
||||||
$unsafe_files[] = $raw_path;
|
|
||||||
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
|
|
||||||
$boring_files[] = $raw_path;
|
|
||||||
continue 2;
|
|
||||||
}
|
}
|
||||||
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
$last_pos = strrpos($raw_path, $file); // no trailing slash!
|
||||||
//no wildcards found in $file, process 'normally'
|
if ($last_pos !== false) {
|
||||||
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
|
if (!in_array($extension, $allowed_extensions)) {
|
||||||
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
|
// gathering potentially unsafe files here to return at exit
|
||||||
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
|
$unsafe_files[] = $raw_path;
|
||||||
continue 2;
|
Log::debug('Potentially unsafe file ' . $raw_path . ' is being skipped');
|
||||||
|
$boring_files[] = $raw_path;
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
||||||
|
//no wildcards found in $file, process 'normally'
|
||||||
|
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
|
||||||
|
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
|
||||||
|
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,18 +532,25 @@ class RestoreFromBackup extends Command
|
|||||||
}
|
}
|
||||||
foreach ($interesting_files as $pretty_file_name => $file_details) {
|
foreach ($interesting_files as $pretty_file_name => $file_details) {
|
||||||
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
|
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
|
||||||
$fp = $za->getStream($ugly_file_name);
|
$migrated_file_name = $file_details['dest'] . '/' . basename($pretty_file_name);
|
||||||
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
|
if (strcasecmp(substr($pretty_file_name, -4), ".svg") === 0) {
|
||||||
if (!is_dir($file_details['dest'])) {
|
$svg_contents = $za->getFromIndex($file_details['index']);
|
||||||
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
|
$cleaned_svg = $sanitizer->sanitize($svg_contents);
|
||||||
|
file_put_contents($migrated_file_name, $cleaned_svg);
|
||||||
|
} else {
|
||||||
|
$fp = $za->getStream($ugly_file_name);
|
||||||
|
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
|
||||||
|
if (!is_dir($file_details['dest'])) {
|
||||||
|
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
|
||||||
|
}
|
||||||
|
$migrated_file = fopen($migrated_file_name, 'w');
|
||||||
|
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
||||||
|
fwrite($migrated_file, $buffer);
|
||||||
|
}
|
||||||
|
fclose($migrated_file);
|
||||||
|
fclose($fp);
|
||||||
|
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
|
||||||
}
|
}
|
||||||
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
|
|
||||||
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
|
||||||
fwrite($migrated_file, $buffer);
|
|
||||||
}
|
|
||||||
fclose($migrated_file);
|
|
||||||
fclose($fp);
|
|
||||||
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
|
|
||||||
if ($bar) {
|
if ($bar) {
|
||||||
$bar->advance();
|
$bar->advance();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class SendAcceptanceReminder extends Command
|
|||||||
if(!$email){
|
if(!$email){
|
||||||
$no_email_list[] = [
|
$no_email_list[] = [
|
||||||
'id' => $acceptance->assignedTo?->id,
|
'id' => $acceptance->assignedTo?->id,
|
||||||
'name' => $acceptance->assignedTo?->present()->fullName(),
|
'name' => $acceptance->assignedTo?->display_name,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$count++;
|
$count++;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Mail\ExpiringAssetsMail;
|
use App\Mail\ExpiringAssetsMail;
|
||||||
use App\Mail\ExpiringLicenseMail;
|
use App\Mail\ExpiringLicenseMail;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
@@ -52,19 +53,73 @@ class SendExpirationAlerts extends Command
|
|||||||
->filter(fn($item) => !empty($item))
|
->filter(fn($item) => !empty($item))
|
||||||
->all();
|
->all();
|
||||||
// Expiring Assets
|
// Expiring Assets
|
||||||
$assets = Asset::getExpiringWarrantee($alert_interval);
|
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
|
||||||
|
|
||||||
if ($assets->count() > 0) {
|
if ($assets->count() > 0) {
|
||||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('admin/hardware/form.tag'),
|
||||||
|
trans('admin/hardware/form.model'),
|
||||||
|
trans('general.model_no'),
|
||||||
|
trans('general.purchase_date'),
|
||||||
|
trans('admin/hardware/form.eol_rate'),
|
||||||
|
trans('admin/hardware/form.eol_date'),
|
||||||
|
trans('admin/hardware/form.warranty_expires'),
|
||||||
|
],
|
||||||
|
$assets->map(fn($item) =>
|
||||||
|
[
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('admin/hardware/form.tag') => $item->asset_tag,
|
||||||
|
trans('admin/hardware/form.model') => $item->model->name,
|
||||||
|
trans('general.model_no') => $item->model->model_number,
|
||||||
|
trans('general.purchase_date') => $item->purchase_date_formatted,
|
||||||
|
trans('admin/hardware/form.eol_rate') => $item->model->eol,
|
||||||
|
trans('admin/hardware/form.eol_date') => $item->eol_date ? $item->eol_formatted_date .' ('.$item->eol_diff_for_humans.')' : '',
|
||||||
|
trans('admin/hardware/form.warranty_expires') => $item->warranty_expires ? $item->warranty_expires_formatted_date .' ('.$item->warranty_expires_diff_for_humans.')' : '',
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expiring licenses
|
// Expiring licenses
|
||||||
$licenses = License::getExpiringLicenses($alert_interval);
|
$licenses = License::query()->ExpiringLicenses($alert_interval)
|
||||||
|
->with('manufacturer','category')
|
||||||
|
->orderBy('expiration_date', 'ASC')
|
||||||
|
->orderBy('termination_date', 'ASC')
|
||||||
|
->get();
|
||||||
if ($licenses->count() > 0) {
|
if ($licenses->count() > 0) {
|
||||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
|
||||||
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('general.name'),
|
||||||
|
trans('general.purchase_date'),
|
||||||
|
trans('admin/licenses/form.expiration'),
|
||||||
|
trans('mail.expires'),
|
||||||
|
trans('admin/licenses/form.termination_date'),
|
||||||
|
trans('mail.terminates')],
|
||||||
|
$licenses->map(fn($item) => [
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('general.name') => $item->name,
|
||||||
|
trans('general.purchase_date') => $item->purchase_date_formatted,
|
||||||
|
trans('admin/licenses/form.expiration') => $item->expires_formatted_date,
|
||||||
|
trans('mail.expires') => $item->expires_formatted_date ? $item->expires_diff_for_humans : '',
|
||||||
|
trans('admin/licenses/form.termination_date') => $item->terminates_formatted_date,
|
||||||
|
trans('mail.terminates') => $item->terminates_diff_for_humans
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a message even if the count is 0
|
||||||
|
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
|
||||||
|
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ($settings->alert_email == '') {
|
if ($settings->alert_email == '') {
|
||||||
$this->error('Could not send email. No alert email configured in settings');
|
$this->error('Could not send email. No alert email configured in settings');
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class SendUpcomingAuditReport extends Command
|
|||||||
$today = Carbon::now();
|
$today = Carbon::now();
|
||||||
$interval_date = $today->copy()->addDays($interval);
|
$interval_date = $today->copy()->addDays($interval);
|
||||||
|
|
||||||
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
|
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'asc')->get();
|
||||||
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
|
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
|
||||||
|
|
||||||
|
|
||||||
@@ -61,6 +61,28 @@ class SendUpcomingAuditReport extends Command
|
|||||||
|
|
||||||
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
|
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
|
||||||
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
|
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
[
|
||||||
|
trans('general.id'),
|
||||||
|
trans('general.name'),
|
||||||
|
trans('general.last_audit'),
|
||||||
|
trans('general.next_audit_date'),
|
||||||
|
trans('mail.Days'),
|
||||||
|
trans('mail.supplier'),
|
||||||
|
trans('mail.assigned_to'),
|
||||||
|
|
||||||
|
],
|
||||||
|
$assets->map(fn($item) => [
|
||||||
|
trans('general.id') => $item->id,
|
||||||
|
trans('general.name') => $item->display_name,
|
||||||
|
trans('general.last_audit') => $item->last_audit_formatted_date,
|
||||||
|
trans('general.next_audit_date') => $item->next_audit_formatted_date,
|
||||||
|
trans('mail.Days') => round($item->next_audit_diff_in_days),
|
||||||
|
trans('mail.supplier') => $item->supplier ? $item->supplier->name : '',
|
||||||
|
trans('mail.assigned_to') => $item->assignedTo ? $item->assignedTo->display_name : '',
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,9 +133,18 @@ class Handler extends ExceptionHandler
|
|||||||
// This is traaaaash but it handles models that are not found while using route model binding :(
|
// 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
|
// The only alternative is to set that at *each* route, which is crazypants
|
||||||
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
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
|
// 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';
|
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
|
||||||
|
|
||||||
// Sigh.
|
// Sigh.
|
||||||
@@ -151,9 +160,7 @@ class Handler extends ExceptionHandler
|
|||||||
$route = 'maintenances.index';
|
$route = 'maintenances.index';
|
||||||
} elseif ($route === 'licenseseats.index') {
|
} elseif ($route === 'licenseseats.index') {
|
||||||
$route = 'licenses.index';
|
$route = 'licenses.index';
|
||||||
} elseif ($route === 'customfields.index') {
|
} elseif (($route === 'customfieldsets.index') || ($route === 'customfields.index')) {
|
||||||
$route = 'fields.index';
|
|
||||||
} elseif ($route === 'customfieldsets.index') {
|
|
||||||
$route = 'fields.index';
|
$route = 'fields.index';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
app/Exceptions/ItemStillHasAccessories.php
Normal file
10
app/Exceptions/ItemStillHasAccessories.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAccessories extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasAssetModels.php
Normal file
10
app/Exceptions/ItemStillHasAssetModels.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAssetModels extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
9
app/Exceptions/ItemStillHasAssets.php
Normal file
9
app/Exceptions/ItemStillHasAssets.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasAssets extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
}
|
||||||
14
app/Exceptions/ItemStillHasChildren.php
Normal file
14
app/Exceptions/ItemStillHasChildren.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasChildren extends Exception
|
||||||
|
{
|
||||||
|
//public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
|
||||||
|
//{
|
||||||
|
// trans()
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasComponents.php
Normal file
10
app/Exceptions/ItemStillHasComponents.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasComponents extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasConsumables.php
Normal file
10
app/Exceptions/ItemStillHasConsumables.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasConsumables extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasLicenses.php
Normal file
10
app/Exceptions/ItemStillHasLicenses.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasLicenses extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
10
app/Exceptions/ItemStillHasMaintenances.php
Normal file
10
app/Exceptions/ItemStillHasMaintenances.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ItemStillHasMaintenances extends ItemStillHasChildren
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -95,7 +95,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->text($str);
|
return $Parsedown->text(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class Helper
|
|||||||
$Parsedown->setSafeMode(true);
|
$Parsedown->setSafeMode(true);
|
||||||
|
|
||||||
if ($str) {
|
if ($str) {
|
||||||
return $Parsedown->line($str);
|
return $Parsedown->line(strip_tags($str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +435,34 @@ class Helper
|
|||||||
return $colors[$index];
|
return $colors[$index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string has any RTL characters
|
||||||
|
* @param $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function hasRtl($string) {
|
||||||
|
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
|
||||||
|
return preg_match($rtlChar, $string) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is chinese, japanese or korean language
|
||||||
|
public static function isCjk($string) {
|
||||||
|
return Helper::isChinese($string) || Helper::isJapanese($string) || Helper::isKorean($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isChinese($string) {
|
||||||
|
return preg_match("/\p{Han}+/u", $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isJapanese($string) {
|
||||||
|
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isKorean($string) {
|
||||||
|
return preg_match('/[\x{3130}-\x{318F}\x{AC00}-\x{D7AF}]/u', $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||||
*
|
*
|
||||||
@@ -1197,19 +1225,30 @@ class Helper
|
|||||||
'webp' => 'far fa-image',
|
'webp' => 'far fa-image',
|
||||||
'avif' => 'far fa-image',
|
'avif' => 'far fa-image',
|
||||||
'svg' => 'fas fa-vector-square',
|
'svg' => 'fas fa-vector-square',
|
||||||
|
|
||||||
// word
|
// word
|
||||||
'doc' => 'far fa-file-word',
|
'doc' => 'far fa-file-word',
|
||||||
'docx' => 'far fa-file-word',
|
'docx' => 'far fa-file-word',
|
||||||
|
|
||||||
// Excel
|
// Excel
|
||||||
'xls' => 'far fa-file-excel',
|
'xls' => 'far fa-file-excel',
|
||||||
'xlsx' => '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
|
// archive
|
||||||
'zip' => 'fas fa-file-archive',
|
'zip' => 'fas fa-file-archive',
|
||||||
'rar' => 'fas fa-file-archive',
|
'rar' => 'fas fa-file-archive',
|
||||||
|
|
||||||
//Text
|
//Text
|
||||||
|
'odt' => 'far fa-file-alt',
|
||||||
'txt' => 'far fa-file-alt',
|
'txt' => 'far fa-file-alt',
|
||||||
'rtf' => 'far fa-file-alt',
|
'rtf' => 'far fa-file-alt',
|
||||||
'xml' => 'fas fa-code',
|
'xml' => 'fas fa-code',
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
'pdf' => 'far fa-file-pdf',
|
'pdf' => 'far fa-file-pdf',
|
||||||
'lic' => 'far fa-save',
|
'lic' => 'far fa-save',
|
||||||
@@ -1543,11 +1582,6 @@ class Helper
|
|||||||
|
|
||||||
// return to previous page
|
// return to previous page
|
||||||
if ($redirect_option === 'back') {
|
if ($redirect_option === 'back') {
|
||||||
if ($backUrl === route('home')) {
|
|
||||||
return redirect()->to($backUrl)
|
|
||||||
->with('warning', trans('general.page_error'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->to($backUrl);
|
return redirect()->to($backUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1700,5 +1734,5 @@ class Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $mismatched;
|
return $mismatched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class IconHelper
|
|||||||
case 'clone':
|
case 'clone':
|
||||||
return 'far fa-clone';
|
return 'far fa-clone';
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
case 'upload deleted':
|
||||||
return 'fas fa-trash';
|
return 'fas fa-trash';
|
||||||
case 'create':
|
case 'create':
|
||||||
return 'fa-solid fa-plus';
|
return 'fa-solid fa-plus';
|
||||||
@@ -43,6 +44,8 @@ class IconHelper
|
|||||||
return 'fa-regular fa-envelope';
|
return 'fa-regular fa-envelope';
|
||||||
case 'phone':
|
case 'phone':
|
||||||
return 'fa-solid fa-phone';
|
return 'fa-solid fa-phone';
|
||||||
|
case 'mobile':
|
||||||
|
return 'fas fa-mobile-screen-button';
|
||||||
case 'long-arrow-right':
|
case 'long-arrow-right':
|
||||||
return 'fas fa-long-arrow-alt-right';
|
return 'fas fa-long-arrow-alt-right';
|
||||||
case 'download':
|
case 'download':
|
||||||
@@ -151,6 +154,7 @@ class IconHelper
|
|||||||
case 'location':
|
case 'location':
|
||||||
return 'fas fa-map-marker-alt';
|
return 'fas fa-map-marker-alt';
|
||||||
case 'superadmin':
|
case 'superadmin':
|
||||||
|
case 'admin':
|
||||||
return 'fas fa-crown';
|
return 'fas fa-crown';
|
||||||
case 'print':
|
case 'print':
|
||||||
return 'fa-solid fa-print';
|
return 'fa-solid fa-print';
|
||||||
@@ -191,6 +195,10 @@ class IconHelper
|
|||||||
case 'note':
|
case 'note':
|
||||||
case 'notes':
|
case 'notes':
|
||||||
return 'fas fa-sticky-note';
|
return 'fas fa-sticky-note';
|
||||||
|
case 'tip':
|
||||||
|
return 'fa-solid fa-lightbulb';
|
||||||
|
case 'highlight':
|
||||||
|
return 'fa-solid fa-highlighter';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,45 @@ class StorageHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* This determines the file types that should be allowed inline and checks their fileinfo extension
|
||||||
@@ -52,7 +91,6 @@ class StorageHelper
|
|||||||
'pdf',
|
'pdf',
|
||||||
'png',
|
'png',
|
||||||
'svg',
|
'svg',
|
||||||
'svg',
|
|
||||||
'wav',
|
'wav',
|
||||||
'webm',
|
'webm',
|
||||||
'webp',
|
'webp',
|
||||||
|
|||||||
@@ -77,9 +77,25 @@ class AccessoriesController extends Controller
|
|||||||
$accessory->supplier_id = request('supplier_id');
|
$accessory->supplier_id = request('supplier_id');
|
||||||
$accessory->notes = request('notes');
|
$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?
|
// Was the accessory created?
|
||||||
if ($accessory->save()) {
|
if ($accessory->save()) {
|
||||||
// Redirect to the new accessory page
|
// Redirect to the new accessory page
|
||||||
@@ -114,11 +130,12 @@ class AccessoriesController extends Controller
|
|||||||
|
|
||||||
$this->authorize('create', Accessory::class);
|
$this->authorize('create', Accessory::class);
|
||||||
$cloned = clone $accessory;
|
$cloned = clone $accessory;
|
||||||
|
$accessory_to_clone = $accessory;
|
||||||
$cloned->id = null;
|
$cloned->id = null;
|
||||||
$cloned->deleted_at = '';
|
$cloned->deleted_at = '';
|
||||||
$cloned->location_id = null;
|
|
||||||
|
|
||||||
return view('accessories/edit')
|
return view('accessories/edit')
|
||||||
|
->with('cloned_model', $accessory_to_clone)
|
||||||
->with('item', $cloned);
|
->with('item', $cloned);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Accessories;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Accessory;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
|
||||||
use \Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Support\Facades\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
||||||
|
|
||||||
class AccessoriesFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Validates and stores files associated with a accessory.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $accessoryId
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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'));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -71,6 +71,7 @@ class AccessoryCheckoutController extends Controller
|
|||||||
$this->authorize('checkout', $accessory);
|
$this->authorize('checkout', $accessory);
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -8,33 +8,23 @@ use App\Events\ItemAccepted;
|
|||||||
use App\Events\ItemDeclined;
|
use App\Events\ItemDeclined;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Asset;
|
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Contracts\Acceptable;
|
use App\Models\Contracts\Acceptable;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\AssetModel;
|
|
||||||
use App\Models\Accessory;
|
|
||||||
use App\Models\License;
|
|
||||||
use App\Models\Component;
|
|
||||||
use App\Models\Consumable;
|
|
||||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||||
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
||||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Http\Controllers\SettingsController;
|
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
use \Illuminate\Http\RedirectResponse;
|
use \Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
|
||||||
class AcceptanceController extends Controller
|
class AcceptanceController extends Controller
|
||||||
{
|
{
|
||||||
@@ -85,6 +75,10 @@ class AcceptanceController extends Controller
|
|||||||
public function store(Request $request, $id) : RedirectResponse
|
public function store(Request $request, $id) : RedirectResponse
|
||||||
{
|
{
|
||||||
$acceptance = CheckoutAcceptance::find($id);
|
$acceptance = CheckoutAcceptance::find($id);
|
||||||
|
$assigned_user = User::find($acceptance->assigned_to_id);
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
$sig_filename='';
|
||||||
|
|
||||||
|
|
||||||
if (is_null($acceptance)) {
|
if (is_null($acceptance)) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||||
@@ -107,139 +101,84 @@ class AcceptanceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the signature and save it
|
* Check for the signature directory
|
||||||
*/
|
*/
|
||||||
if (! Storage::exists('private_uploads/signatures')) {
|
if (! Storage::exists('private_uploads/signatures')) {
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for the eula-pdfs directory
|
||||||
|
*/
|
||||||
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||||
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||||
|
}
|
||||||
|
|
||||||
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
||||||
$display_model = '';
|
|
||||||
$pdf_view_route = '';
|
|
||||||
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
|
||||||
$sig_filename='';
|
// If signatures are required, make sure we have one
|
||||||
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
||||||
|
|
||||||
|
// The item was accepted, check for a signature
|
||||||
|
if ($request->filled('signature_output')) {
|
||||||
|
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
||||||
|
$data_uri = $request->input('signature_output');
|
||||||
|
$encoded_image = explode(',', $data_uri);
|
||||||
|
$decoded_image = base64_decode($encoded_image[1]);
|
||||||
|
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
||||||
|
|
||||||
|
// No image data is present, kick them back.
|
||||||
|
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
||||||
|
} else {
|
||||||
|
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert PDF logo to base64 for TCPDF
|
||||||
|
// This is needed for TCPDF to properly embed the image if it's a png and the cache isn't writable
|
||||||
|
$encoded_logo = null;
|
||||||
|
if ($settings->acceptance_pdf_logo) {
|
||||||
|
$encoded_logo = base64_encode(file_get_contents(public_path() . '/uploads/' . $settings->acceptance_pdf_logo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data array ready for the notifications and PDF generation
|
||||||
|
$data = [
|
||||||
|
'item_tag' => $item->asset_tag,
|
||||||
|
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
|
||||||
|
'item_model' => $item->model?->name,
|
||||||
|
'item_serial' => $item->serial,
|
||||||
|
'item_status' => $item->assetstatus?->name,
|
||||||
|
'eula' => $item->getEula(),
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'check_out_date' => Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false),
|
||||||
|
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
|
||||||
|
'assigned_to' => $assigned_user->display_name,
|
||||||
|
'email' => $assigned_user->email,
|
||||||
|
'employee_num' => $assigned_user->employee_num,
|
||||||
|
'site_name' => $settings->site_name,
|
||||||
|
'company_name' => $item->company?->name?? $settings->site_name,
|
||||||
|
'signature' => (($sig_filename && array_key_exists('1', $encoded_image))) ? $encoded_image[1] : null,
|
||||||
|
'logo' => ($encoded_logo) ?? null,
|
||||||
|
'date_settings' => $settings->date_display_format,
|
||||||
|
'admin' => auth()->user()->present()?->fullName,
|
||||||
|
'qty' => $acceptance->qty ?? 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
if ($request->input('asset_acceptance') == 'accepted') {
|
if ($request->input('asset_acceptance') == 'accepted') {
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for the eula-pdfs directory
|
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
// Generate the PDF content
|
||||||
if ($request->filled('signature_output')) {
|
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$assigned_user = User::find($acceptance->assigned_to_id);
|
|
||||||
// this is horrible
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
if (!$asset_model) {
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$pdf_view_route ='account.accept.accept-license-eula';
|
|
||||||
$license = License::find($item->license_id);
|
|
||||||
$display_model = $license->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$pdf_view_route ='account.accept.accept-component-eula';
|
|
||||||
$component = Component::find($item->id);
|
|
||||||
$display_model = $component->name;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$pdf_view_route ='account.accept.accept-consumable-eula';
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-asset-eula';
|
|
||||||
// $asset_model = AssetModel::find($item->model_id);
|
|
||||||
// $display_model = $asset_model->name;
|
|
||||||
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
|
|
||||||
//
|
|
||||||
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
|
||||||
// $pdf_view_route ='account.accept.accept-accessory-eula';
|
|
||||||
// $accessory = Accessory::find($item->id);
|
|
||||||
// $display_model = $accessory->name;
|
|
||||||
// $assigned_to = User::find($item->assignedTo);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gather the data for the PDF. We fire this whether there is a signature required or not,
|
|
||||||
* since we want the moment-in-time proof of what the EULA was when they accepted it.
|
|
||||||
*/
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
$path_logo = "";
|
|
||||||
|
|
||||||
// Check for the PDF logo path and use that, otherwise use the regular logo path
|
|
||||||
if (!is_null($branding_settings->acceptance_pdf_logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
|
|
||||||
} elseif (!is_null($branding_settings->logo)) {
|
|
||||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'eula' => $item->getEula(),
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
|
||||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
|
||||||
'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,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Log the acceptance
|
||||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
||||||
|
|
||||||
// Send the PDF to the signing user
|
// Send the PDF to the signing user
|
||||||
@@ -247,15 +186,14 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
// Add the attachment for the signing user into the $data array
|
// Add the attachment for the signing user into the $data array
|
||||||
$data['file'] = $pdf_filename;
|
$data['file'] = $pdf_filename;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$assigned_user->notify(new AcceptanceAssetAcceptedToUserNotification($data));
|
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
@@ -263,112 +201,43 @@ class AcceptanceController extends Controller
|
|||||||
|
|
||||||
$return_msg = trans('admin/users/message.accepted');
|
$return_msg = trans('admin/users/message.accepted');
|
||||||
|
|
||||||
|
// Item was declined
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/**
|
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
|
||||||
* Check for the eula-pdfs directory
|
$acceptance->decline($sig_filename, $request->input('note'));
|
||||||
*/
|
|
||||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
||||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
|
||||||
|
|
||||||
// Check if the signature directory exists, if not create it
|
|
||||||
if (!Storage::exists('private_uploads/signatures')) {
|
|
||||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item was accepted, check for a signature
|
|
||||||
if ($request->filled('signature_output')) {
|
|
||||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
|
||||||
$data_uri = $request->input('signature_output');
|
|
||||||
$encoded_image = explode(',', $data_uri);
|
|
||||||
$decoded_image = base64_decode($encoded_image[1]);
|
|
||||||
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
|
|
||||||
|
|
||||||
// No image data is present, kick them back.
|
|
||||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
|
||||||
} else {
|
|
||||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the data to send the declined notification
|
|
||||||
$branding_settings = SettingsController::getPDFBranding();
|
|
||||||
|
|
||||||
// This is the most horriblest
|
|
||||||
switch($acceptance->checkoutable_type){
|
|
||||||
case 'App\Models\Asset':
|
|
||||||
$asset_model = AssetModel::find($item->model_id);
|
|
||||||
$display_model = $asset_model->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Accessory':
|
|
||||||
$accessory = Accessory::find($item->id);
|
|
||||||
$display_model = $accessory->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\LicenseSeat':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Component':
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'App\Models\Consumable':
|
|
||||||
$consumable = Consumable::find($item->id);
|
|
||||||
$display_model = $consumable->name;
|
|
||||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'item_tag' => $item->asset_tag,
|
|
||||||
'item_model' => $display_model,
|
|
||||||
'item_serial' => $item->serial,
|
|
||||||
'item_status' => $item->assetstatus?->name,
|
|
||||||
'note' => $request->input('note'),
|
|
||||||
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
|
|
||||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
|
||||||
'assigned_to' => $assigned_to,
|
|
||||||
'company_name' => $branding_settings->site_name,
|
|
||||||
'date_settings' => $branding_settings->date_display_format,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($pdf_view_route!='') {
|
|
||||||
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
|
||||||
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
||||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
||||||
}
|
|
||||||
|
|
||||||
$acceptance->decline($sig_filename, $request->input('note'));
|
|
||||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||||
|
Log::debug('New event acceptance.');
|
||||||
event(new CheckoutDeclined($acceptance));
|
event(new CheckoutDeclined($acceptance));
|
||||||
$return_msg = trans('admin/users/message.declined');
|
$return_msg = trans('admin/users/message.declined');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send an email notification if one is requested
|
||||||
if ($acceptance->alert_on_response_id) {
|
if ($acceptance->alert_on_response_id) {
|
||||||
try {
|
try {
|
||||||
$recipient = User::find($acceptance->alert_on_response_id);
|
$recipient = User::find($acceptance->alert_on_response_id);
|
||||||
|
|
||||||
if ($recipient) {
|
if ($recipient) {
|
||||||
|
Log::debug('Attempting to send email acceptance.');
|
||||||
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
|
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
|
||||||
$acceptance,
|
$acceptance,
|
||||||
$recipient,
|
$recipient,
|
||||||
$request->input('asset_acceptance') === 'accepted',
|
$request->input('asset_acceptance') === 'accepted',
|
||||||
));
|
));
|
||||||
|
Log::debug('Send email notification sucess on checkout acceptance response.');
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
Log::warning($e);
|
Log::warning($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Actionlog;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use \Illuminate\Http\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use \Illuminate\Http\Response;
|
||||||
|
|
||||||
class ActionlogController extends Controller
|
class ActionlogController extends Controller
|
||||||
{
|
{
|
||||||
public function displaySig($filename) : RedirectResponse | Response | bool
|
public function displaySig($filename) : RedirectResponse | Response | bool
|
||||||
@@ -39,17 +41,29 @@ class ActionlogController extends Controller
|
|||||||
|
|
||||||
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('view', \App\Models\Asset::class);
|
|
||||||
|
|
||||||
if (config('filesystems.default') == 's3_private') {
|
if ($actionlog = Actionlog::where('filename', $filename)->with('user')->with('target')->firstOrFail()) {
|
||||||
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
|
|
||||||
|
$this->authorize('view', $actionlog->target);
|
||||||
|
$this->authorize('view', $actionlog->user);
|
||||||
|
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
|
||||||
|
if (request()->input('inline') == 'true') {
|
||||||
|
return response()->file(config('app.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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
|
return redirect()->back()->with('error', trans('general.record_not_found'));
|
||||||
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,15 @@ class AccessoriesController extends Controller
|
|||||||
'notes',
|
'notes',
|
||||||
'checkouts_count',
|
'checkouts_count',
|
||||||
'qty',
|
'qty',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'supplier',
|
||||||
|
'manufacturer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@@ -61,10 +70,23 @@ class AccessoriesController extends Controller
|
|||||||
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
|
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
|
||||||
->withCount('checkouts as checkouts_count');
|
->withCount('checkouts as checkouts_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
$filter = [];
|
||||||
$accessories = $accessories->TextSearch($request->input('search'));
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$accessories->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$accessories->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('company_id')) {
|
if ($request->filled('company_id')) {
|
||||||
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
|
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
@@ -288,32 +310,42 @@ class AccessoriesController extends Controller
|
|||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
$accessory_checkout->created_by = auth()->id();
|
$accessory_checkout->created_by = auth()->id();
|
||||||
$accessory_checkout->save();
|
$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
|
// Set this value to be able to pass the qty through to the event
|
||||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
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
|
* 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] [<snipe@snipe.net>]
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param int $accessoryUserId
|
* @param int $accessoryUserId
|
||||||
* @param string $backto
|
* @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] [<snipe@snipe.net>]
|
||||||
* @internal param int $accessoryId
|
* @internal param int $accessoryId
|
||||||
*/
|
*/
|
||||||
public function checkin(Request $request, $accessoryUserId = null)
|
public function checkin(Request $request, $accessoryUserId = null)
|
||||||
{
|
{
|
||||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
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);
|
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||||
@@ -327,7 +359,14 @@ class AccessoriesController extends Controller
|
|||||||
$user = User::find($accessory_checkout->assigned_to);
|
$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')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
||||||
|
|||||||
@@ -46,10 +46,20 @@ class AssetModelsController extends Controller
|
|||||||
'manufacturer',
|
'manufacturer',
|
||||||
'requestable',
|
'requestable',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
|
'assets_assigned_count',
|
||||||
|
'assets_archived_count',
|
||||||
|
'remaining',
|
||||||
'category',
|
'category',
|
||||||
'fieldset',
|
'fieldset',
|
||||||
'deleted_at',
|
'deleted_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
|
'require_serial',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'manufacturer',
|
||||||
|
'category',
|
||||||
];
|
];
|
||||||
|
|
||||||
$assetmodels = AssetModel::select([
|
$assetmodels = AssetModel::select([
|
||||||
@@ -69,9 +79,31 @@ class AssetModelsController extends Controller
|
|||||||
'models.fieldset_id',
|
'models.fieldset_id',
|
||||||
'models.deleted_at',
|
'models.deleted_at',
|
||||||
'models.updated_at',
|
'models.updated_at',
|
||||||
|
'models.require_serial'
|
||||||
])
|
])
|
||||||
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
||||||
->withCount('assets as assets_count');
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('availableAssets as remaining')
|
||||||
|
->withCount('assignedAssets as assets_assigned_count')
|
||||||
|
->withCount('archivedAssets as assets_archived_count');
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$assetmodels->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$assetmodels->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->input('status')=='deleted') {
|
if ($request->input('status')=='deleted') {
|
||||||
$assetmodels->onlyTrashed();
|
$assetmodels->onlyTrashed();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Events\CheckoutableCheckedIn;
|
|||||||
use App\Http\Requests\StoreAssetRequest;
|
use App\Http\Requests\StoreAssetRequest;
|
||||||
use App\Http\Requests\UpdateAssetRequest;
|
use App\Http\Requests\UpdateAssetRequest;
|
||||||
use App\Http\Traits\MigratesLegacyAssetLocations;
|
use App\Http\Traits\MigratesLegacyAssetLocations;
|
||||||
|
use App\Http\Transformers\ComponentsTransformer;
|
||||||
use App\Models\AccessoryCheckout;
|
use App\Models\AccessoryCheckout;
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
@@ -115,17 +116,39 @@ class AssetsController extends Controller
|
|||||||
'asset_eol_date',
|
'asset_eol_date',
|
||||||
'requestable',
|
'requestable',
|
||||||
'jobtitle',
|
'jobtitle',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'model',
|
||||||
|
'location',
|
||||||
|
'rtd_location',
|
||||||
|
'category',
|
||||||
|
'status_label',
|
||||||
|
'manufacturer',
|
||||||
|
'supplier',
|
||||||
|
'jobtitle',
|
||||||
|
'assigned_to',
|
||||||
|
'created_by',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$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 = [];
|
$filter = [];
|
||||||
|
|
||||||
if ($request->filled('filter')) {
|
if ($request->filled('filter')) {
|
||||||
$filter = json_decode($request->input('filter'), true);
|
$filter = json_decode($request->input('filter'), true);
|
||||||
}
|
|
||||||
|
|
||||||
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
foreach ($all_custom_fields as $field) {
|
return in_array($key, $allowed_columns);
|
||||||
$allowed_columns[] = $field->db_column_name();
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$assets = Asset::select('assets.*')
|
$assets = Asset::select('assets.*')
|
||||||
@@ -141,6 +164,7 @@ class AssetsController extends Controller
|
|||||||
'model.category',
|
'model.category',
|
||||||
'model.manufacturer',
|
'model.manufacturer',
|
||||||
'model.fieldset',
|
'model.fieldset',
|
||||||
|
'model.depreciation',
|
||||||
'supplier'
|
'supplier'
|
||||||
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
||||||
|
|
||||||
@@ -159,7 +183,7 @@ class AssetsController extends Controller
|
|||||||
// Search custom fields by column name
|
// Search custom fields by column name
|
||||||
foreach ($all_custom_fields as $field) {
|
foreach ($all_custom_fields as $field) {
|
||||||
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
|
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
|
||||||
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
$assets->where('assets.'.$field->db_column_name(), '=', $request->input($field->db_column_name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +628,7 @@ class AssetsController extends Controller
|
|||||||
$asset->use_text = $asset->present()->fullName;
|
$asset->use_text = $asset->present()->fullName;
|
||||||
|
|
||||||
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
||||||
$asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
|
$asset->use_text .= ' → ' . $asset->assigned->display_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1284,9 +1308,19 @@ class AssetsController extends Controller
|
|||||||
|
|
||||||
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
|
||||||
{
|
{
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
return [];
|
$query = Asset::where([
|
||||||
// to do
|
'assigned_to' => $asset->id,
|
||||||
|
'assigned_type' => Asset::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$total = $query->count();
|
||||||
|
|
||||||
|
$assets = $query->applyOffsetAndLimit($total)->get();
|
||||||
|
|
||||||
|
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
|
||||||
@@ -1306,6 +1340,18 @@ class AssetsController extends Controller
|
|||||||
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
|
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function assignedComponents(Request $request, Asset $asset): JsonResponse|array
|
||||||
|
{
|
||||||
|
$this->authorize('view', Asset::class);
|
||||||
|
$this->authorize('view', $asset);
|
||||||
|
|
||||||
|
$asset->loadCount('components');
|
||||||
|
$total = $asset->components_count;
|
||||||
|
|
||||||
|
$components = $asset->load(['components' => fn($query) => $query->applyOffsetAndLimit($total)])->components;
|
||||||
|
|
||||||
|
return (new ComponentsTransformer)->transformComponents($components, $total);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate asset labels by tag
|
* Generate asset labels by tag
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\CategoriesTransformer;
|
use App\Http\Transformers\CategoriesTransformer;
|
||||||
@@ -38,6 +40,8 @@ class CategoriesController extends Controller
|
|||||||
'consumables_count',
|
'consumables_count',
|
||||||
'components_count',
|
'components_count',
|
||||||
'licenses_count',
|
'licenses_count',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
'image',
|
'image',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
@@ -59,6 +63,23 @@ class CategoriesController extends Controller
|
|||||||
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
|
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
|
||||||
|
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$categories->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$categories->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This checks to see if we should override the Admin Setting to show archived assets in list.
|
* This checks to see if we should override the Admin Setting to show archived assets in list.
|
||||||
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
|
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
|
||||||
@@ -72,10 +93,6 @@ class CategoriesController extends Controller
|
|||||||
$categories = $categories->withCount('showableAssets as assets_count');
|
$categories = $categories->withCount('showableAssets as assets_count');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
|
||||||
$categories = $categories->TextSearch($request->input('search'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$categories->where('name', '=', $request->input('name'));
|
$categories->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
@@ -209,17 +226,21 @@ class CategoriesController extends Controller
|
|||||||
* @param int $id
|
* @param int $id
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Category $category): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Category::class);
|
$this->authorize('delete', Category::class);
|
||||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
|
try {
|
||||||
|
DestroyCategoryAction::run(category: $category);
|
||||||
if (! $category->isDeletable()) {
|
} catch (ItemStillHasChildren $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>$category->category_type]))
|
Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['asset_type' => $category->category_type]))
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(
|
||||||
|
Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$category->delete();
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,16 +45,40 @@ class ComponentsController extends Controller
|
|||||||
'qty',
|
'qty',
|
||||||
'image',
|
'image',
|
||||||
'notes',
|
'notes',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'manufacturer',
|
||||||
|
'supplier',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$components = Component::select('components.*')
|
$components = Component::select('components.*')
|
||||||
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
|
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
|
||||||
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
|
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
$filter = [];
|
||||||
$components = $components->TextSearch($request->input('search'));
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$components->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$components->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$components->where('name', '=', $request->input('name'));
|
$components->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,53 @@ class ConsumablesController extends Controller
|
|||||||
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
|
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
|
||||||
->withCount('users as consumables_users_count');
|
->withCount('users as consumables_users_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
|
||||||
$consumables = $consumables->TextSearch(e($request->input('search')));
|
// These must match a column on the consumables table directly.
|
||||||
|
$allowed_columns = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'order_number',
|
||||||
|
'min_amt',
|
||||||
|
'purchase_date',
|
||||||
|
'purchase_cost',
|
||||||
|
'company',
|
||||||
|
'category',
|
||||||
|
'model_number',
|
||||||
|
'item_no',
|
||||||
|
'manufacturer',
|
||||||
|
'location',
|
||||||
|
'qty',
|
||||||
|
'image',
|
||||||
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'supplier',
|
||||||
|
'manufacturer',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$consumables->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$consumables->TextSearch($request->input('search'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($request->filled('name')) {
|
if ($request->filled('name')) {
|
||||||
$consumables->where('name', '=', $request->input('name'));
|
$consumables->where('name', '=', $request->input('name'));
|
||||||
}
|
}
|
||||||
@@ -96,25 +139,6 @@ class ConsumablesController extends Controller
|
|||||||
$consumables = $consumables->OrderByCreatedBy($order);
|
$consumables = $consumables->OrderByCreatedBy($order);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
|
|
||||||
// These must match a column on the consumables table directly.
|
|
||||||
$allowed_columns = [
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'order_number',
|
|
||||||
'min_amt',
|
|
||||||
'purchase_date',
|
|
||||||
'purchase_cost',
|
|
||||||
'company',
|
|
||||||
'category',
|
|
||||||
'model_number',
|
|
||||||
'item_no',
|
|
||||||
'manufacturer',
|
|
||||||
'location',
|
|
||||||
'qty',
|
|
||||||
'image'
|
|
||||||
];
|
|
||||||
|
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||||
$consumables = $consumables->orderBy($sort, $order);
|
$consumables = $consumables->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
@@ -228,11 +252,16 @@ class ConsumablesController extends Controller
|
|||||||
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
|
'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'),
|
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||||
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
'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) ? [
|
||||||
'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null,
|
'id' => (int) $consumable_assignment->adminuser->id,
|
||||||
|
'name'=> e($consumable_assignment->adminuser->display_name),
|
||||||
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ImportController extends Controller
|
|||||||
if (function_exists('iconv')) {
|
if (function_exists('iconv')) {
|
||||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||||
$encoding = $detector->getEncoding($file_contents);
|
$encoding = $detector->getEncoding($file_contents);
|
||||||
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
|
\Log::debug("Discovered encoding: $encoding in uploaded CSV");
|
||||||
$reader = null;
|
$reader = null;
|
||||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||||
$transliterated = false;
|
$transliterated = false;
|
||||||
@@ -103,7 +103,7 @@ class ImportController extends Controller
|
|||||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$import->header_row = $reader->fetchOne(0);
|
$import->header_row = $reader->nth(0);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
@@ -136,7 +136,7 @@ class ImportController extends Controller
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Grab the first row to display via ajax as the user picks fields
|
// Grab the first row to display via ajax as the user picks fields
|
||||||
$import->first_row = $reader->fetchOne(1);
|
$import->first_row = $reader->nth(1);
|
||||||
} catch (JsonEncodingException $e) {
|
} catch (JsonEncodingException $e) {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Helper::formatStandardApiResponse(
|
Helper::formatStandardApiResponse(
|
||||||
@@ -195,7 +195,7 @@ class ImportController extends Controller
|
|||||||
// Run a backup immediately before processing
|
// Run a backup immediately before processing
|
||||||
if ($request->get('run-backup')) {
|
if ($request->get('run-backup')) {
|
||||||
Log::debug('Backup manually requested via importer');
|
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 {
|
} else {
|
||||||
Log::debug('NO BACKUP requested via importer');
|
Log::debug('NO BACKUP requested via importer');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ class LicenseSeatsController extends Controller
|
|||||||
// nothing to update
|
// nothing to update
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
if( $touched && $licenseSeat->unreassignable_seat) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
|
||||||
|
}
|
||||||
// the logging functions expect only one "target". if both asset and user are present in the request,
|
// the logging functions expect only one "target". if both asset and user are present in the request,
|
||||||
// we simply let assets take precedence over users...
|
// we simply let assets take precedence over users...
|
||||||
if ($licenseSeat->isDirty('assigned_to')) {
|
if ($licenseSeat->isDirty('assigned_to')) {
|
||||||
@@ -145,7 +147,11 @@ class LicenseSeatsController extends Controller
|
|||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
|
|
||||||
if ($is_checkin) {
|
if ($is_checkin) {
|
||||||
$licenseSeat->logCheckin($target, $request->input('notes'));
|
if(!$licenseSeat->license->reassignable){
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
$licenseSeat->save();
|
||||||
|
}
|
||||||
|
$licenseSeat->logCheckin($target, $licenseSeat->notes);
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Http\Transformers\LicensesTransformer;
|
use App\Http\Transformers\LicensesTransformer;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
|
use App\Models\Setting;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -25,6 +26,15 @@ class LicensesController extends Controller
|
|||||||
$this->authorize('view', License::class);
|
$this->authorize('view', License::class);
|
||||||
|
|
||||||
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
|
||||||
|
$settings = Setting::getSettings();
|
||||||
|
|
||||||
|
if ($request->input('status')=='inactive') {
|
||||||
|
$licenses->ExpiredLicenses();
|
||||||
|
} elseif ($request->input('status')=='expiring') {
|
||||||
|
$licenses->ExpiringLicenses($settings->alert_interval);
|
||||||
|
} else {
|
||||||
|
$licenses->ActiveLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('company_id')) {
|
if ($request->filled('company_id')) {
|
||||||
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
$licenses->where('licenses.company_id', '=', $request->input('company_id'));
|
||||||
@@ -94,6 +104,8 @@ class LicensesController extends Controller
|
|||||||
$licenses->onlyTrashed();
|
$licenses->onlyTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
|
|||||||
@@ -37,10 +37,14 @@ class LocationsController extends Controller
|
|||||||
'address',
|
'address',
|
||||||
'address2',
|
'address2',
|
||||||
'assets_count',
|
'assets_count',
|
||||||
'assets_count',
|
'assigned_assets_count',
|
||||||
|
'rtd_assets_count',
|
||||||
|
'accessories_count',
|
||||||
'assigned_accessories_count',
|
'assigned_accessories_count',
|
||||||
'assigned_assets_count',
|
'components_count',
|
||||||
'assigned_assets_count',
|
'consumables_count',
|
||||||
|
'users_count',
|
||||||
|
'children_count',
|
||||||
'city',
|
'city',
|
||||||
'country',
|
'country',
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -54,7 +58,6 @@ class LocationsController extends Controller
|
|||||||
'rtd_assets_count',
|
'rtd_assets_count',
|
||||||
'state',
|
'state',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'users_count',
|
|
||||||
'zip',
|
'zip',
|
||||||
'notes',
|
'notes',
|
||||||
];
|
];
|
||||||
@@ -79,8 +82,9 @@ class LocationsController extends Controller
|
|||||||
'locations.currency',
|
'locations.currency',
|
||||||
'locations.company_id',
|
'locations.company_id',
|
||||||
'locations.notes',
|
'locations.notes',
|
||||||
|
'locations.created_by',
|
||||||
|
'locations.deleted_at',
|
||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
->withCount('assignedAccessories as assigned_accessories_count')
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
@@ -88,6 +92,8 @@ class LocationsController extends Controller
|
|||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->with('adminuser');
|
->with('adminuser');
|
||||||
|
|
||||||
// Only scope locations if the setting is enabled
|
// Only scope locations if the setting is enabled
|
||||||
@@ -131,6 +137,14 @@ class LocationsController extends Controller
|
|||||||
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('parent_id')) {
|
||||||
|
$locations->where('locations.parent_id', '=', $request->input('parent_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('status') == 'deleted') {
|
||||||
|
$locations->onlyTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||||
$limit = app('api_limit_value');
|
$limit = app('api_limit_value');
|
||||||
@@ -224,8 +238,13 @@ class LocationsController extends Controller
|
|||||||
])
|
])
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
return (new LocationsTransformer)->transformLocation($location);
|
return (new LocationsTransformer)->transformLocation($location);
|
||||||
@@ -320,11 +339,15 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')
|
->withCount('users as users_count')
|
||||||
->withCount('accessories as accessories_count')
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
if (! $location->isDeletable()) {
|
if (! $location->isDeletable()) {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ namespace App\Http\Controllers\Api;
|
|||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
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\Asset;
|
||||||
use App\Models\AssetMaintenance;
|
use App\Models\Maintenance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
@@ -18,13 +18,13 @@ use Illuminate\Http\JsonResponse;
|
|||||||
*
|
*
|
||||||
* @version v2.0
|
* @version v2.0
|
||||||
*/
|
*/
|
||||||
class AssetMaintenancesController extends Controller
|
class MaintenancesController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the JSON response for asset maintenances listing view.
|
* 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 <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
@@ -33,7 +33,7 @@ class AssetMaintenancesController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('view', Asset::class);
|
$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');
|
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
@@ -45,11 +45,15 @@ class AssetMaintenancesController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('supplier_id')) {
|
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')) {
|
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('url')) {
|
||||||
|
$maintenances->where('maintenances.url', '=', $request->input('url'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('asset_maintenance_type')) {
|
if ($request->filled('asset_maintenance_type')) {
|
||||||
@@ -63,7 +67,7 @@ class AssetMaintenancesController extends Controller
|
|||||||
|
|
||||||
$allowed_columns = [
|
$allowed_columns = [
|
||||||
'id',
|
'id',
|
||||||
'title',
|
'name',
|
||||||
'asset_maintenance_time',
|
'asset_maintenance_time',
|
||||||
'asset_maintenance_type',
|
'asset_maintenance_type',
|
||||||
'cost',
|
'cost',
|
||||||
@@ -112,7 +116,7 @@ class AssetMaintenancesController extends Controller
|
|||||||
|
|
||||||
$total = $maintenances->count();
|
$total = $maintenances->count();
|
||||||
$maintenances = $maintenances->skip($offset)->take($limit)->get();
|
$maintenances = $maintenances->skip($offset)->take($limit)->get();
|
||||||
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total);
|
return (new MaintenancesTransformer())->transformMaintenances($maintenances, $total);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -121,22 +125,23 @@ class AssetMaintenancesController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Validates and stores the new asset maintenance
|
* Validates and stores the new asset maintenance
|
||||||
*
|
*
|
||||||
* @see AssetMaintenancesController::getCreate() method for the form
|
* @see MaintenancesController::getCreate() method for the form
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function store(Request $request) : JsonResponse | array
|
public function store(ImageUploadRequest $request) : JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
|
||||||
// create a new model instance
|
// create a new model instance
|
||||||
$maintenance = new AssetMaintenance();
|
$maintenance = new Maintenance();
|
||||||
$maintenance->fill($request->all());
|
$maintenance->fill($request->all());
|
||||||
$maintenance->created_by = auth()->id();
|
$maintenance->created_by = auth()->id();
|
||||||
|
$maintenance = $request->handleImages($maintenance);
|
||||||
// Was the asset maintenance created?
|
// Was the asset maintenance created?
|
||||||
if ($maintenance->save()) {
|
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')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +162,11 @@ class AssetMaintenancesController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$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?
|
// Can this user manage this asset?
|
||||||
if (! Company::isCurrentUserHasAccess($maintenance->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
|
// The asset this miantenance is attached to is not valid or has been deleted
|
||||||
@@ -172,13 +177,13 @@ class AssetMaintenancesController extends Controller
|
|||||||
$maintenance->fill($request->all());
|
$maintenance->fill($request->all());
|
||||||
|
|
||||||
if ($maintenance->save()) {
|
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, $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])));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,20 +191,20 @@ class AssetMaintenancesController extends Controller
|
|||||||
* Delete an asset maintenance
|
* Delete an asset maintenance
|
||||||
*
|
*
|
||||||
* @author A. Gianotto <snipe@snipe.net>
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
* @param int $assetMaintenanceId
|
* @param int $maintenanceId
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function destroy($assetMaintenanceId) : JsonResponse | array
|
public function destroy($maintenanceId) : JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
// Check if the asset maintenance exists
|
// 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')));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -208,19 +213,19 @@ class AssetMaintenancesController extends Controller
|
|||||||
* View an asset maintenance
|
* View an asset maintenance
|
||||||
*
|
*
|
||||||
* @author A. Gianotto <snipe@snipe.net>
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
* @param int $assetMaintenanceId
|
* @param int $maintenanceId
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
*/
|
*/
|
||||||
public function show($assetMaintenanceId) : JsonResponse | array
|
public function show($maintenanceId) : JsonResponse | array
|
||||||
{
|
{
|
||||||
$this->authorize('view', Asset::class);
|
$this->authorize('view', Asset::class);
|
||||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
$maintenance = Maintenance::findOrFail($maintenanceId);
|
||||||
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\ManufacturersTransformer;
|
use App\Http\Transformers\ManufacturersTransformer;
|
||||||
@@ -184,19 +191,19 @@ class ManufacturersController extends Controller
|
|||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Manufacturer $manufacturer): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Manufacturer::class);
|
|
||||||
$manufacturer = Manufacturer::findOrFail($id);
|
|
||||||
$this->authorize('delete', $manufacturer);
|
$this->authorize('delete', $manufacturer);
|
||||||
|
try {
|
||||||
if ($manufacturer->isDeletable()) {
|
DeleteManufacturerAction::run($manufacturer);
|
||||||
$manufacturer->delete();
|
} catch (ItemStillHasChildren $e) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')])));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
95
app/Http/Controllers/Api/NotesController.php
Normal file
95
app/Http/Controllers/Api/NotesController.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class controls all API actions related to notes for
|
||||||
|
* the Snipe-IT Asset Management application.
|
||||||
|
*/
|
||||||
|
class NotesController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieve a list of manual notes (action logs) for a given asset.
|
||||||
|
*
|
||||||
|
* Checks authorization to view assets, attempts to find the asset by ID,
|
||||||
|
* and fetches related action log entries of type 'note added', including
|
||||||
|
* user information for each note. Returns a JSON response with the notes or errors.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The incoming HTTP request.
|
||||||
|
* @param Asset $asset The ID of the asset whose notes to retrieve.
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function index(Asset $asset): JsonResponse
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ class ProfileController extends Controller
|
|||||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||||
$assets = [
|
$assets = [
|
||||||
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
||||||
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
'name' => e($checkoutRequest->itemRequested()->display_name),
|
||||||
'type' => e($checkoutRequest->itemType()),
|
'type' => e($checkoutRequest->itemType()),
|
||||||
'qty' => (int) $checkoutRequest->quantity,
|
'qty' => (int) $checkoutRequest->quantity,
|
||||||
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Transformers\DatatablesTransformer;
|
use App\Http\Transformers\DatatablesTransformer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@@ -51,10 +50,22 @@ class SettingsController extends Controller
|
|||||||
})->slice(0, 10)->map(function ($item) use ($settings) {
|
})->slice(0, 10)->map(function ($item) use ($settings) {
|
||||||
return (object) [
|
return (object) [
|
||||||
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
'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,
|
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
||||||
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
||||||
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
||||||
'email' => $item[$settings['ldap_email']][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) {
|
if ($users->count() > 0) {
|
||||||
@@ -78,7 +89,7 @@ class SettingsController extends Controller
|
|||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::debug('Connection failed but we cannot debug it any further on our end.');
|
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')) {
|
if (!config('app.lock_passwords')) {
|
||||||
try {
|
try {
|
||||||
Notification::send(Setting::first(), new MailTest());
|
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);
|
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||||
} catch (\Exception $e) {
|
} 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);
|
return response()->json(['message' => $e->getMessage()], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,4 +329,4 @@ class SettingsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Transformers\SelectlistTransformer;
|
use App\Http\Transformers\SelectlistTransformer;
|
||||||
@@ -191,27 +198,40 @@ class SuppliersController extends Controller
|
|||||||
* @since [v4.0]
|
* @since [v4.0]
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function destroy($id) : JsonResponse
|
public function destroy(Supplier $supplier): 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);
|
|
||||||
$this->authorize('delete', $supplier);
|
$this->authorize('delete', $supplier);
|
||||||
|
try {
|
||||||
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
if ($supplier->assets_count > 0) {
|
} catch (ItemStillHasAssets $e) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_assets', [
|
||||||
|
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_maintenances', [
|
||||||
|
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_licenses', [
|
||||||
|
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_accessories', [
|
||||||
|
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_consumables', [
|
||||||
|
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_components', [
|
||||||
|
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
|
||||||
|
])));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
|
||||||
}
|
}
|
||||||
|
|
||||||
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->licenses_count > 0) {
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count])));
|
|
||||||
}
|
|
||||||
|
|
||||||
$supplier->delete();
|
|
||||||
|
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,9 @@ use App\Helpers\StorageHelper;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\UploadFileRequest;
|
use App\Http\Requests\UploadFileRequest;
|
||||||
use App\Http\Transformers\UploadedFilesTransformer;
|
use App\Http\Transformers\UploadedFilesTransformer;
|
||||||
use App\Models\Accessory;
|
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
|
||||||
use App\Models\AssetModel;
|
|
||||||
use App\Models\Component;
|
|
||||||
use App\Models\Consumable;
|
|
||||||
use App\Models\License;
|
|
||||||
use App\Models\Location;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
@@ -28,45 +19,6 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
static $map_object_type = [
|
|
||||||
'accessories' => Accessory::class,
|
|
||||||
'assets' => Asset::class,
|
|
||||||
'components' => Component::class,
|
|
||||||
'consumables' => Consumable::class,
|
|
||||||
'hardware' => Asset::class,
|
|
||||||
'licenses' => License::class,
|
|
||||||
'locations' => Location::class,
|
|
||||||
'models' => AssetModel::class,
|
|
||||||
'users' => User::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
static $map_storage_path = [
|
|
||||||
'accessories' => 'private_uploads/accessories/',
|
|
||||||
'assets' => 'private_uploads/assets/',
|
|
||||||
'components' => 'private_uploads/components/',
|
|
||||||
'consumables' => 'private_uploads/consumables/',
|
|
||||||
'hardware' => 'private_uploads/assets/',
|
|
||||||
'licenses' => 'private_uploads/licenses/',
|
|
||||||
'locations' => 'private_uploads/locations/',
|
|
||||||
'models' => 'private_uploads/assetmodels/',
|
|
||||||
'users' => 'private_uploads/users/',
|
|
||||||
];
|
|
||||||
|
|
||||||
static $map_file_prefix= [
|
|
||||||
'accessories' => 'accessory',
|
|
||||||
'assets' => 'asset',
|
|
||||||
'components' => 'component',
|
|
||||||
'consumables' => 'consumable',
|
|
||||||
'hardware' => 'asset',
|
|
||||||
'licenses' => 'license',
|
|
||||||
'locations' => 'location',
|
|
||||||
'models' => 'model',
|
|
||||||
'users' => 'user',
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List files for an object
|
* List files for an object
|
||||||
*
|
*
|
||||||
@@ -80,7 +32,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -93,19 +45,23 @@ class UploadedFilesController extends Controller
|
|||||||
'id',
|
'id',
|
||||||
'filename',
|
'filename',
|
||||||
'action_type',
|
'action_type',
|
||||||
|
'action_date',
|
||||||
'note',
|
'note',
|
||||||
'created_at',
|
'created_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
$uploads = $object->uploads();
|
|
||||||
$offset = ($request->input('offset') > $object->count()) ? $object->count() : abs($request->input('offset'));
|
$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');
|
$limit = app('api_limit_value');
|
||||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'action_logs.created_at';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||||
|
|
||||||
// Text search on action_logs fields
|
// Text search on action_logs fields
|
||||||
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searcghing across all relations
|
// 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
|
// and we generally won't need that here
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
|
|
||||||
$uploads->where(
|
$uploads->where(
|
||||||
@@ -116,8 +72,10 @@ class UploadedFilesController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$total = $uploads->count();
|
||||||
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
|
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
|
||||||
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
|
|
||||||
|
return (new UploadedFilesTransformer())->transformFiles($uploads, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -134,7 +92,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -182,7 +140,7 @@ class UploadedFilesController extends Controller
|
|||||||
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
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
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('view', $object);
|
$this->authorize('view', $object);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -226,7 +184,7 @@ class UploadedFilesController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check the permissions to make sure the user can view the object
|
// Check the permissions to make sure the user can view the object
|
||||||
$object = self::$map_object_type[$object_type]::find($id);
|
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||||
|
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
@@ -235,8 +193,12 @@ class UploadedFilesController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
// Check for the file
|
// Check for the file
|
||||||
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
|
$log = Actionlog::query()
|
||||||
->where('item_id', $object->id)->first();
|
->where('id', $file_id)
|
||||||
|
->where('action_type', 'uploaded')
|
||||||
|
->where('item_type', self::$map_object_type[$object_type])
|
||||||
|
->where('item_id', $object->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($log) {
|
if ($log) {
|
||||||
// Check the file actually exists, and delete it
|
// Check the file actually exists, and delete it
|
||||||
@@ -244,7 +206,7 @@ class UploadedFilesController extends Controller
|
|||||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||||
}
|
}
|
||||||
// Delete the record of the file
|
// Delete the record of the file
|
||||||
if ($log->delete()) {
|
if ($log->logUploadDelete($object, $log->filename)) {
|
||||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,4 +217,4 @@ class UploadedFilesController extends Controller
|
|||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
|
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ use App\Models\Consumable;
|
|||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\CurrentInventory;
|
use App\Notifications\CurrentInventory;
|
||||||
|
use App\Notifications\WelcomeNotification;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -62,12 +65,14 @@ class UsersController extends Controller
|
|||||||
'users.jobtitle',
|
'users.jobtitle',
|
||||||
'users.last_login',
|
'users.last_login',
|
||||||
'users.last_name',
|
'users.last_name',
|
||||||
|
'users.display_name',
|
||||||
'users.locale',
|
'users.locale',
|
||||||
'users.location_id',
|
'users.location_id',
|
||||||
'users.manager_id',
|
'users.manager_id',
|
||||||
'users.notes',
|
'users.notes',
|
||||||
'users.permissions',
|
'users.permissions',
|
||||||
'users.phone',
|
'users.phone',
|
||||||
|
'users.mobile',
|
||||||
'users.state',
|
'users.state',
|
||||||
'users.two_factor_enrolled',
|
'users.two_factor_enrolled',
|
||||||
'users.two_factor_optin',
|
'users.two_factor_optin',
|
||||||
@@ -81,7 +86,12 @@ class UsersController extends Controller
|
|||||||
'users.autoassign_licenses',
|
'users.autoassign_licenses',
|
||||||
'users.website',
|
'users.website',
|
||||||
|
|
||||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
|
])->with('manager')
|
||||||
|
->with('groups')
|
||||||
|
->with('userloc')
|
||||||
|
->with('company')
|
||||||
|
->with('department')
|
||||||
|
->with('createdBy')
|
||||||
->withCount([
|
->withCount([
|
||||||
'assets as assets_count' => function(Builder $query) {
|
'assets as assets_count' => function(Builder $query) {
|
||||||
$query->withoutTrashed();
|
$query->withoutTrashed();
|
||||||
@@ -93,19 +103,101 @@ class UsersController extends Controller
|
|||||||
'managedLocations as manages_locations_count'
|
'managedLocations as manages_locations_count'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$allowed_columns =
|
||||||
|
[
|
||||||
|
'last_name',
|
||||||
|
'first_name',
|
||||||
|
'display_name',
|
||||||
|
'email',
|
||||||
|
'jobtitle',
|
||||||
|
'username',
|
||||||
|
'employee_num',
|
||||||
|
'groups',
|
||||||
|
'activated',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'two_factor_enrolled',
|
||||||
|
'two_factor_optin',
|
||||||
|
'last_login',
|
||||||
|
'assets_count',
|
||||||
|
'licenses_count',
|
||||||
|
'consumables_count',
|
||||||
|
'accessories_count',
|
||||||
|
'manages_users_count',
|
||||||
|
'manages_locations_count',
|
||||||
|
'phone',
|
||||||
|
'mobile',
|
||||||
|
'address',
|
||||||
|
'city',
|
||||||
|
'state',
|
||||||
|
'country',
|
||||||
|
'zip',
|
||||||
|
'id',
|
||||||
|
'ldap_import',
|
||||||
|
'two_factor_optin',
|
||||||
|
'two_factor_enrolled',
|
||||||
|
'remote',
|
||||||
|
'vip',
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'autoassign_licenses',
|
||||||
|
'website',
|
||||||
|
'locale',
|
||||||
|
'notes',
|
||||||
|
'employee_num',
|
||||||
|
|
||||||
if ($request->filled('search') != '') {
|
// These are *relationships* so we wouldn't normally include them in this array,
|
||||||
$users = $users->TextSearch($request->input('search'));
|
// since they would normally create a `column not found` error,
|
||||||
|
// BUT we account for them in the ordering switch down at the end of this method
|
||||||
|
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
|
||||||
|
'company',
|
||||||
|
'location',
|
||||||
|
'department',
|
||||||
|
'manager',
|
||||||
|
'created_by',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($request->filled('filter')) {
|
||||||
|
$filter = json_decode($request->input('filter'), true);
|
||||||
|
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||||
|
return in_array($key, $allowed_columns);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((! is_null($filter)) && (count($filter)) > 0) {
|
||||||
|
$users->ByFilter($filter);
|
||||||
|
} elseif ($request->filled('search')) {
|
||||||
|
$users->TextSearch($request->input('search'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('activated')) {
|
if ($request->filled('activated')) {
|
||||||
$users = $users->where('users.activated', '=', $request->input('activated'));
|
$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')) {
|
if ($request->filled('company_id')) {
|
||||||
$users = $users->where('users.company_id', '=', $request->input('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')) {
|
if ($request->filled('location_id')) {
|
||||||
$users = $users->where('users.location_id', '=', $request->input('location_id'));
|
$users = $users->where('users.location_id', '=', $request->input('location_id'));
|
||||||
}
|
}
|
||||||
@@ -130,6 +222,10 @@ class UsersController extends Controller
|
|||||||
$users = $users->where('users.last_name', '=', $request->input('last_name'));
|
$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')) {
|
if ($request->filled('employee_num')) {
|
||||||
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
|
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
|
||||||
}
|
}
|
||||||
@@ -256,47 +352,6 @@ class UsersController extends Controller
|
|||||||
$users->orderBy('first_name', $order);
|
$users->orderBy('first_name', $order);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$allowed_columns =
|
|
||||||
[
|
|
||||||
'last_name',
|
|
||||||
'first_name',
|
|
||||||
'email',
|
|
||||||
'jobtitle',
|
|
||||||
'username',
|
|
||||||
'employee_num',
|
|
||||||
'groups',
|
|
||||||
'activated',
|
|
||||||
'created_at',
|
|
||||||
'updated_at',
|
|
||||||
'two_factor_enrolled',
|
|
||||||
'two_factor_optin',
|
|
||||||
'last_login',
|
|
||||||
'assets_count',
|
|
||||||
'licenses_count',
|
|
||||||
'consumables_count',
|
|
||||||
'accessories_count',
|
|
||||||
'manages_users_count',
|
|
||||||
'manages_locations_count',
|
|
||||||
'phone',
|
|
||||||
'address',
|
|
||||||
'city',
|
|
||||||
'state',
|
|
||||||
'country',
|
|
||||||
'zip',
|
|
||||||
'id',
|
|
||||||
'ldap_import',
|
|
||||||
'two_factor_optin',
|
|
||||||
'two_factor_enrolled',
|
|
||||||
'remote',
|
|
||||||
'vip',
|
|
||||||
'start_date',
|
|
||||||
'end_date',
|
|
||||||
'autoassign_licenses',
|
|
||||||
'website',
|
|
||||||
'locale',
|
|
||||||
'notes',
|
|
||||||
];
|
|
||||||
|
|
||||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
|
||||||
$users = $users->orderBy($sort, $order);
|
$users = $users->orderBy($sort, $order);
|
||||||
break;
|
break;
|
||||||
@@ -330,6 +385,7 @@ class UsersController extends Controller
|
|||||||
'users.employee_num',
|
'users.employee_num',
|
||||||
'users.first_name',
|
'users.first_name',
|
||||||
'users.last_name',
|
'users.last_name',
|
||||||
|
'users.display_name',
|
||||||
'users.gravatar',
|
'users.gravatar',
|
||||||
'users.avatar',
|
'users.avatar',
|
||||||
'users.email',
|
'users.email',
|
||||||
@@ -340,20 +396,17 @@ class UsersController extends Controller
|
|||||||
$users = $users->where(function ($query) use ($request) {
|
$users = $users->where(function ($query) use ($request) {
|
||||||
$query->SimpleNameSearch($request->get('search'))
|
$query->SimpleNameSearch($request->get('search'))
|
||||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||||
|
->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%')
|
||||||
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
||||||
->orWhere('employee_num', '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);
|
$users = $users->paginate(50);
|
||||||
|
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$name_str = '';
|
$name_str = $user->display_name;
|
||||||
if ($user->last_name != '') {
|
|
||||||
$name_str .= $user->last_name.', ';
|
|
||||||
}
|
|
||||||
$name_str .= $user->first_name;
|
|
||||||
|
|
||||||
if ($user->username != '') {
|
if ($user->username != '') {
|
||||||
$name_str .= ' ('.$user->username.')';
|
$name_str .= ' ('.$user->username.')';
|
||||||
@@ -405,9 +458,20 @@ class UsersController extends Controller
|
|||||||
$user->password = $user->noPassword();
|
$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->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')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
@@ -475,8 +539,29 @@ class UsersController extends Controller
|
|||||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('password')) {
|
// check for permissions related fields and pull them out if the current user cannot edit them
|
||||||
$user->password = bcrypt($request->input('password'));
|
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()
|
// We need to use has() instead of filled()
|
||||||
@@ -498,7 +583,7 @@ class UsersController extends Controller
|
|||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
->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()) {
|
if ($user->save()) {
|
||||||
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
||||||
@@ -791,4 +876,37 @@ class UsersController extends Controller
|
|||||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
|
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 <snipe@snipe.net>
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,12 +82,26 @@ class AssetModelsController extends Controller
|
|||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->created_by = auth()->id();
|
$model->created_by = auth()->id();
|
||||||
$model->requestable = $request->has('requestable');
|
$model->requestable = $request->has('requestable');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
|
|
||||||
if ($request->input('fieldset_id') != '') {
|
if ($request->input('fieldset_id') != '') {
|
||||||
$model->fieldset_id = $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 ($model->save()) {
|
||||||
if ($this->shouldAddDefaultValues($request->input())) {
|
if ($this->shouldAddDefaultValues($request->input())) {
|
||||||
@@ -142,7 +156,7 @@ class AssetModelsController extends Controller
|
|||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
$model->requestable = $request->input('requestable', '0');
|
$model->requestable = $request->input('requestable', '0');
|
||||||
|
$model->require_serial = $request->input('require_serial', 0);
|
||||||
$model->fieldset_id = $request->input('fieldset_id');
|
$model->fieldset_id = $request->input('fieldset_id');
|
||||||
|
|
||||||
if ($model->save()) {
|
if ($model->save()) {
|
||||||
@@ -271,7 +285,7 @@ class AssetModelsController extends Controller
|
|||||||
->with('depreciation_list', Helper::depreciationList())
|
->with('depreciation_list', Helper::depreciationList())
|
||||||
->with('item', $model)
|
->with('item', $model)
|
||||||
->with('model_id', $model->id)
|
->with('model_id', $model->id)
|
||||||
->with('clone_model', $cloned_model);
|
->with('cloned_model', $cloned_model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\AssetModel;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use \Symfony\Component\HttpFoundation\StreamedResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
|
|
||||||
class AssetModelsFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Upload a file to the server.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $modelId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@since [v1.0]
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
*/
|
|
||||||
public function store(UploadFileRequest $request, $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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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'));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -65,6 +65,8 @@ class AssetCheckoutController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if the asset exists
|
// Check if the asset exists
|
||||||
if (! $asset = Asset::find($assetId)) {
|
if (! $asset = Asset::find($assetId)) {
|
||||||
@@ -81,6 +83,7 @@ class AssetCheckoutController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
$asset = $this->updateAssetLocation($asset, $target);
|
$asset = $this->updateAssetLocation($asset, $target);
|
||||||
|
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Assets;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Asset;
|
|
||||||
use \Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
|
||||||
use \Illuminate\Http\RedirectResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
|
|
||||||
class AssetFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Upload a file to the server.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $assetId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@since [v1.0]
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
*/
|
|
||||||
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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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'));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -110,17 +110,35 @@ class AssetsController extends Controller
|
|||||||
// This is only necessary on create, not update, since bulk editing is handled
|
// This is only necessary on create, not update, since bulk editing is handled
|
||||||
// differently
|
// differently
|
||||||
$asset_tags = $request->input('asset_tags');
|
$asset_tags = $request->input('asset_tags');
|
||||||
|
$model = AssetModel::find($request->input('model_id'));
|
||||||
|
$serial_errors = [];
|
||||||
|
$serials = $request->input('serials');
|
||||||
|
|
||||||
$settings = Setting::getSettings();
|
$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 = [];
|
$successes = [];
|
||||||
$failures = [];
|
$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 = new Asset();
|
||||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
|
||||||
|
$asset->model()->associate($model);
|
||||||
$asset->name = $request->input('name');
|
$asset->name = $request->input('name');
|
||||||
|
|
||||||
// Check for a corresponding serial
|
// Check for a corresponding serial
|
||||||
@@ -132,7 +150,7 @@ class AssetsController extends Controller
|
|||||||
$asset->asset_tag = $asset_tags[$a];
|
$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->model_id = $request->input('model_id');
|
||||||
$asset->order_number = $request->input('order_number');
|
$asset->order_number = $request->input('order_number');
|
||||||
$asset->notes = $request->input('notes');
|
$asset->notes = $request->input('notes');
|
||||||
@@ -157,14 +175,21 @@ class AssetsController extends Controller
|
|||||||
$asset->location_id = $request->input('rtd_location_id', null);
|
$asset->location_id = $request->input('rtd_location_id', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the image (if one was chosen.)
|
if ($request->has('use_cloned_image')) {
|
||||||
if ($request->has('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);
|
$asset = $request->handleImages($asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update custom fields in the database.
|
// Update custom fields in the database.
|
||||||
// Validation for these fields is handled through the AssetRequest form request
|
// Validation for these fields is handled through the AssetRequest form request
|
||||||
$model = AssetModel::find($request->get('model_id'));
|
|
||||||
|
|
||||||
if (($model) && ($model->fieldset)) {
|
if (($model) && ($model->fieldset)) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
foreach ($model->fieldset->fields as $field) {
|
||||||
@@ -226,8 +251,15 @@ class AssetsController extends Controller
|
|||||||
$failures[] = join(",", $asset->getErrors()->all());
|
$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 ($successes) {
|
||||||
@@ -331,7 +363,7 @@ class AssetsController extends Controller
|
|||||||
$asset->purchase_cost = $request->input('purchase_cost', null);
|
$asset->purchase_cost = $request->input('purchase_cost', null);
|
||||||
$asset->purchase_date = $request->input('purchase_date', null);
|
$asset->purchase_date = $request->input('purchase_date', null);
|
||||||
$asset->next_audit_date = $request->input('next_audit_date', null);
|
$asset->next_audit_date = $request->input('next_audit_date', null);
|
||||||
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
|
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model?->eol > 0)) {
|
||||||
$asset->purchase_date = $request->input('purchase_date', null);
|
$asset->purchase_date = $request->input('purchase_date', null);
|
||||||
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
|
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
|
||||||
$asset->eol_explicit = false;
|
$asset->eol_explicit = false;
|
||||||
@@ -347,7 +379,7 @@ class AssetsController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
$asset->eol_explicit = true;
|
$asset->eol_explicit = true;
|
||||||
}
|
}
|
||||||
} elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
|
} elseif (!$request->filled('asset_eol_date') && (($asset->model?->eol) == 0)) {
|
||||||
$asset->asset_eol_date = null;
|
$asset->asset_eol_date = null;
|
||||||
$asset->eol_explicit = false;
|
$asset->eol_explicit = false;
|
||||||
}
|
}
|
||||||
@@ -366,6 +398,7 @@ class AssetsController extends Controller
|
|||||||
$asset->assigned_to = null;
|
$asset->assigned_to = null;
|
||||||
$asset->assigned_type = null;
|
$asset->assigned_type = null;
|
||||||
$asset->accepted = null;
|
$asset->accepted = null;
|
||||||
|
$asset->last_checkin = now();
|
||||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
|
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +442,9 @@ class AssetsController extends Controller
|
|||||||
$model = AssetModel::find($request->get('model_id'));
|
$model = AssetModel::find($request->get('model_id'));
|
||||||
if (($model) && ($model->fieldset)) {
|
if (($model) && ($model->fieldset)) {
|
||||||
foreach ($model->fieldset->fields as $field) {
|
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 ($request->has($field->db_column)) {
|
||||||
if ($field->field_encrypted == '1') {
|
if ($field->field_encrypted == '1') {
|
||||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||||
@@ -435,6 +471,13 @@ class AssetsController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
//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()) {
|
if ($asset->save()) {
|
||||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||||
->with('success', trans('admin/hardware/message.update.success'));
|
->with('success', trans('admin/hardware/message.update.success'));
|
||||||
@@ -641,8 +684,9 @@ class AssetsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getClone(Asset $asset)
|
public function getClone(Asset $asset)
|
||||||
{
|
{
|
||||||
$this->authorize('create', $asset);
|
$this->authorize('create', Asset::class);
|
||||||
$cloned = clone $asset;
|
$cloned = clone $asset;
|
||||||
|
$cloned_model = $asset;
|
||||||
$cloned->id = null;
|
$cloned->id = null;
|
||||||
$cloned->asset_tag = '';
|
$cloned->asset_tag = '';
|
||||||
$cloned->serial = '';
|
$cloned->serial = '';
|
||||||
@@ -652,6 +696,7 @@ class AssetsController extends Controller
|
|||||||
return view('hardware/edit')
|
return view('hardware/edit')
|
||||||
->with('statuslabel_list', Helper::statusLabelList())
|
->with('statuslabel_list', Helper::statusLabelList())
|
||||||
->with('statuslabel_types', Helper::statusTypeList())
|
->with('statuslabel_types', Helper::statusTypeList())
|
||||||
|
->with('cloned_model', $cloned_model)
|
||||||
->with('item', $cloned);
|
->with('item', $cloned);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,7 +822,7 @@ class AssetsController extends Controller
|
|||||||
'item_id' => $asset->id,
|
'item_id' => $asset->id,
|
||||||
'item_type' => Asset::class,
|
'item_type' => Asset::class,
|
||||||
'created_by' => auth()->id(),
|
'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_id' => $item[$asset_tag][$batch_counter]['user_id'],
|
||||||
'target_type' => User::class,
|
'target_type' => User::class,
|
||||||
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
||||||
@@ -805,7 +850,7 @@ class AssetsController extends Controller
|
|||||||
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
|
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
|
||||||
'item_type' => Asset::class,
|
'item_type' => Asset::class,
|
||||||
'created_by' => auth()->id(),
|
'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,
|
'target_id' => null,
|
||||||
'created_at' => $checkin_date,
|
'created_at' => $checkin_date,
|
||||||
'action_type' => 'checkin',
|
'action_type' => 'checkin',
|
||||||
|
|||||||
@@ -161,8 +161,9 @@ class BulkAssetsController extends Controller
|
|||||||
|
|
||||||
$models = $assets->unique('model_id');
|
$models = $assets->unique('model_id');
|
||||||
$modelNames = [];
|
$modelNames = [];
|
||||||
|
|
||||||
foreach($models as $model) {
|
foreach($models as $model) {
|
||||||
$modelNames[] = $model->model->name;
|
$modelNames[] = $model->model?->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('bulk_actions')) {
|
if ($request->filled('bulk_actions')) {
|
||||||
@@ -196,7 +197,6 @@ class BulkAssetsController extends Controller
|
|||||||
|
|
||||||
case 'edit':
|
case 'edit':
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
|
||||||
return view('hardware/bulk')
|
return view('hardware/bulk')
|
||||||
->with('assets', $asset_ids)
|
->with('assets', $asset_ids)
|
||||||
->with('statuslabel_list', Helper::statusLabelList())
|
->with('statuslabel_list', Helper::statusLabelList())
|
||||||
@@ -224,11 +224,8 @@ class BulkAssetsController extends Controller
|
|||||||
$error_array = array();
|
$error_array = array();
|
||||||
|
|
||||||
// Get the back url from the session and then destroy the session
|
// 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', url()->previous());
|
||||||
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
|
||||||
}
|
|
||||||
|
|
||||||
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||||
|
|
||||||
@@ -243,10 +240,6 @@ class BulkAssetsController extends Controller
|
|||||||
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
|
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
||||||
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||||
@@ -277,6 +270,7 @@ class BulkAssetsController extends Controller
|
|||||||
|| ($request->filled('company_id'))
|
|| ($request->filled('company_id'))
|
||||||
|| ($request->filled('status_id'))
|
|| ($request->filled('status_id'))
|
||||||
|| ($request->filled('model_id'))
|
|| ($request->filled('model_id'))
|
||||||
|
|| ($request->filled('notes'))
|
||||||
|| ($request->filled('next_audit_date'))
|
|| ($request->filled('next_audit_date'))
|
||||||
|| ($request->filled('asset_eol_date'))
|
|| ($request->filled('asset_eol_date'))
|
||||||
|| ($request->filled('null_name'))
|
|| ($request->filled('null_name'))
|
||||||
@@ -473,7 +467,7 @@ class BulkAssetsController extends Controller
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Does the model have a fieldset?
|
// Does the model have a fieldset?
|
||||||
if ($asset->model->fieldset) {
|
if ($asset->model?->fieldset) {
|
||||||
foreach ($asset->model->fieldset->fields as $field) {
|
foreach ($asset->model->fieldset->fields as $field) {
|
||||||
|
|
||||||
// null custom fields
|
// null custom fields
|
||||||
@@ -543,7 +537,13 @@ class BulkAssetsController extends Controller
|
|||||||
} // end asset foreach
|
} // end asset foreach
|
||||||
|
|
||||||
if ($has_errors > 0) {
|
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'));
|
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
||||||
@@ -618,9 +618,25 @@ class BulkAssetsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('checkout', Asset::class);
|
$this->authorize('checkout', Asset::class);
|
||||||
|
|
||||||
|
$alreadyAssigned = collect();
|
||||||
|
|
||||||
|
if (old('selected_assets') && is_array(old('selected_assets'))) {
|
||||||
|
$assets = Asset::findMany(old('selected_assets'));
|
||||||
|
|
||||||
|
[$assignable, $alreadyAssigned] = $assets->partition(function (Asset $asset) {
|
||||||
|
return !$asset->assigned_to;
|
||||||
|
});
|
||||||
|
|
||||||
|
session()->flashInput(['selected_assets' => $assignable->pluck('id')->values()->toArray()]);
|
||||||
|
}
|
||||||
|
|
||||||
$do_not_change = ['' => trans('general.do_not_change')];
|
$do_not_change = ['' => trans('general.do_not_change')];
|
||||||
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
|
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
|
||||||
return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list);
|
|
||||||
|
return view('hardware/bulk-checkout', [
|
||||||
|
'statusLabel_list' => $status_label_list,
|
||||||
|
'removed_assets' => $alreadyAssigned,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -634,6 +650,7 @@ class BulkAssetsController extends Controller
|
|||||||
$admin = auth()->user();
|
$admin = auth()->user();
|
||||||
|
|
||||||
$target = $this->determineCheckoutTarget();
|
$target = $this->determineCheckoutTarget();
|
||||||
|
session()->put(['checkout_to_type' => $target]);
|
||||||
|
|
||||||
if (! is_array($request->get('selected_assets'))) {
|
if (! is_array($request->get('selected_assets'))) {
|
||||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||||
@@ -643,6 +660,30 @@ class BulkAssetsController extends Controller
|
|||||||
|
|
||||||
$assets = Asset::findOrFail($asset_ids);
|
$assets = Asset::findOrFail($asset_ids);
|
||||||
|
|
||||||
|
// Prevent checking out assets that are already checked out
|
||||||
|
if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) {
|
||||||
|
// re-add the asset ids so the assets select is re-populated
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
|
||||||
|
return redirect(route('hardware.bulkcheckout.show'))
|
||||||
|
->with('error', trans('general.error_assets_already_checked_out'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent checking out assets across companies if FMCS enabled
|
||||||
|
if (Setting::getSettings()->full_multiple_companies_support && $target->company_id) {
|
||||||
|
$company_ids = $assets->pluck('company_id')->unique();
|
||||||
|
|
||||||
|
// if there is more than one unique company id or the singular company id does not match
|
||||||
|
// then the checkout is invalid
|
||||||
|
if ($company_ids->count() > 1 || $company_ids->first() != $target->company_id) {
|
||||||
|
// re-add the asset ids so the assets select is re-populated
|
||||||
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||||
|
|
||||||
|
return redirect(route('hardware.bulkcheckout.show'))
|
||||||
|
->with('error', trans('general.error_user_company_multiple'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request('checkout_to_type') == 'asset') {
|
if (request('checkout_to_type') == 'asset') {
|
||||||
foreach ($asset_ids as $asset_id) {
|
foreach ($asset_ids as $asset_id) {
|
||||||
if ($target->id == $asset_id) {
|
if ($target->id == $asset_id) {
|
||||||
@@ -735,4 +776,33 @@ class BulkAssetsController extends Controller
|
|||||||
}
|
}
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller
|
|||||||
$update_array['min_amt'] = $request->input('min_amt');
|
$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) {
|
if (count($update_array) > 0) {
|
||||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||||
|
|||||||
59
app/Http/Controllers/BulkCategoriesController.php
Normal file
59
app/Http/Controllers/BulkCategoriesController.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkCategoriesController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Category::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$category = Category::find($id);
|
||||||
|
if (is_null($category)) {
|
||||||
|
$errors[] = trans('admin/categories/message.does_not_exist');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DestroyCategoryAction::run(category: $category);
|
||||||
|
$success_count++;
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasAssetModels) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_asset_models_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasAssets) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasComponents) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasConsumables) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
|
||||||
|
} catch (ItemStillHasLicenses) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('categories.index')->with('success', trans_choice('admin/categories/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('categories.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/Http/Controllers/BulkManufacturersController.php
Normal file
57
app/Http/Controllers/BulkManufacturersController.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkManufacturersController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Manufacturer::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$manufacturer = Manufacturer::find($id);
|
||||||
|
if (is_null($manufacturer)) {
|
||||||
|
$errors[] = trans('admin/manufacturers/message.does_not_exist');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DeleteManufacturerAction::run(manufacturer: $manufacturer);
|
||||||
|
$success_count++;
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_accessories_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('manufacturers.index')->with('success', trans_choice('admin/manufacturers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('manufacturers.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
app/Http/Controllers/BulkSuppliersController.php
Normal file
58
app/Http/Controllers/BulkSuppliersController.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BulkSuppliersController extends Controller
|
||||||
|
{
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', Supplier::class);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_count = 0;
|
||||||
|
|
||||||
|
foreach ($request->ids as $id) {
|
||||||
|
$supplier = Supplier::find($id);
|
||||||
|
if (is_null($supplier)) {
|
||||||
|
$errors[] = trans('admin/suppliers/message.delete.not_found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_assets', ['asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_accessories', ['accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_consumables', ['consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
$errors[] = trans('general.bulk_delete_associations.assoc_components', ['components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
$errors[] = trans('general.something_went_wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
if ($success_count > 0) {
|
||||||
|
return redirect()->route('suppliers.index')->with('success', trans_choice('admin/suppliers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
|
||||||
|
}
|
||||||
|
return redirect()->route('suppliers.index')->with('multi_error_messages', $errors);
|
||||||
|
} else {
|
||||||
|
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.bulk_success'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Categories\DestroyCategoryAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssetModels;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
@@ -143,20 +151,18 @@ class CategoriesController extends Controller
|
|||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param int $categoryId
|
* @param int $categoryId
|
||||||
*/
|
*/
|
||||||
public function destroy($categoryId) : RedirectResponse
|
public function destroy(Category $category): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Category::class);
|
$this->authorize('delete', Category::class);
|
||||||
// Check if the category exists
|
try {
|
||||||
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))) {
|
DestroyCategoryAction::run($category);
|
||||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
|
} catch (ItemStillHasChildren $e) {
|
||||||
|
return redirect()->route('categories.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.category')]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.delete.error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $category->isDeletable()) {
|
|
||||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=> $category->category_type]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
|
||||||
$category->delete();
|
|
||||||
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,13 +102,15 @@ class ComponentCheckoutController extends Controller
|
|||||||
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$component->checkout_qty = $request->input('assigned_qty');
|
||||||
|
|
||||||
// Update the component data
|
// Update the component data
|
||||||
$component->asset_id = $request->input('asset_id');
|
$component->asset_id = $request->input('asset_id');
|
||||||
$component->assets()->attach($component->id, [
|
$component->assets()->attach($component->id, [
|
||||||
'component_id' => $component->id,
|
'component_id' => $component->id,
|
||||||
'created_by' => auth()->user()->id,
|
'created_by' => auth()->user()->id,
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
'assigned_qty' => $request->input('assigned_qty'),
|
'assigned_qty' => $component->checkout_qty,
|
||||||
'asset_id' => $request->input('asset_id'),
|
'asset_id' => $request->input('asset_id'),
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -88,7 +88,12 @@ class ComponentsController extends Controller
|
|||||||
|
|
||||||
$component = $request->handleImages($component);
|
$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()) {
|
if ($component->save()) {
|
||||||
return Helper::getRedirectOption($request, $component->id, 'Components')
|
return Helper::getRedirectOption($request, $component->id, 'Components')
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Components;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Component;
|
|
||||||
use Illuminate\Support\Facades\Response;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ComponentsFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Validates and stores files associated with a component.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $componentId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
@@ -81,13 +81,29 @@ class ConsumablesController extends Controller
|
|||||||
$consumable->purchase_date = $request->input('purchase_date');
|
$consumable->purchase_date = $request->input('purchase_date');
|
||||||
$consumable->purchase_cost = $request->input('purchase_cost');
|
$consumable->purchase_cost = $request->input('purchase_cost');
|
||||||
$consumable->qty = $request->input('qty');
|
$consumable->qty = $request->input('qty');
|
||||||
$consumable->created_by = auth()->id();
|
$consumable->created_by = auth()->id();
|
||||||
$consumable->notes = $request->input('notes');
|
$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()) {
|
if ($consumable->save()) {
|
||||||
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
|
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
|
||||||
@@ -213,9 +229,10 @@ class ConsumablesController extends Controller
|
|||||||
$consumable_to_close = $consumable;
|
$consumable_to_close = $consumable;
|
||||||
$consumable = clone $consumable_to_close;
|
$consumable = clone $consumable_to_close;
|
||||||
$consumable->id = null;
|
$consumable->id = null;
|
||||||
$consumable->image = null;
|
|
||||||
$consumable->created_by = 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Consumables;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Consumable;
|
|
||||||
use Illuminate\Support\Facades\Response;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Symfony\Consumable\HttpFoundation\JsonResponse;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
class ConsumablesFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Validates and stores files associated with a consumable.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $consumableId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,15 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
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\Support\Facades\Auth;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
@@ -32,6 +41,45 @@ abstract class Controller extends BaseController
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
view()->share('signedIn', Auth::check());
|
view()->share('signedIn', Auth::check());
|
||||||
|
|||||||
@@ -144,10 +144,9 @@ class CustomFieldsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
|
public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('update', CustomField::class);
|
||||||
$field = CustomField::find($field_id);
|
$field = CustomField::find($field_id);
|
||||||
|
|
||||||
$this->authorize('update', $field);
|
|
||||||
|
|
||||||
// Check that the field exists - this is mostly related to the demo, where we
|
// 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
|
// 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
|
// 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])
|
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
|
||||||
->with('success', trans('admin/custom_fields/message.field.delete.success'));
|
->with('success', trans('admin/custom_fields/message.field.delete.success'));
|
||||||
} else {
|
} 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] [<uberbrady@gmail.com>]
|
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function destroy($field_id) : RedirectResponse
|
public function destroy(CustomField $field) : RedirectResponse
|
||||||
{
|
{
|
||||||
if ($field = CustomField::find($field_id)) {
|
$this->authorize('delete', CustomField::class);
|
||||||
$this->authorize('delete', $field);
|
|
||||||
|
|
||||||
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
|
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
|
||||||
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
|
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.in_use'));
|
||||||
}
|
|
||||||
$field->delete();
|
|
||||||
return redirect()->route("fields.index")
|
|
||||||
->with("success", trans('admin/custom_fields/message.field.delete.success'));
|
|
||||||
}
|
}
|
||||||
|
$field->delete();
|
||||||
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
|
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
|
public function edit(Request $request, CustomField $field) : View | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('update', $field);
|
$this->authorize('update', CustomField::class);
|
||||||
$fieldsets = CustomFieldset::get();
|
$fieldsets = CustomFieldset::get();
|
||||||
$customFormat = '';
|
$customFormat = '';
|
||||||
if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) {
|
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
|
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);
|
$show_in_email = $request->get("show_in_email", 0);
|
||||||
$display_in_user_view = $request->get("display_in_user_view", 0);
|
$display_in_user_view = $request->get("display_in_user_view", 0);
|
||||||
|
|
||||||
@@ -265,7 +261,6 @@ class CustomFieldsController extends Controller
|
|||||||
|
|
||||||
if ($field->save()) {
|
if ($field->save()) {
|
||||||
|
|
||||||
|
|
||||||
// Sync fields with fieldsets
|
// Sync fields with fieldsets
|
||||||
$fieldset_array = $request->input('associate_fieldsets');
|
$fieldset_array = $request->input('associate_fieldsets');
|
||||||
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
|
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
|
||||||
|
|||||||
@@ -64,12 +64,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare the rules for the form validation
|
// Declare the rules for the form validation
|
||||||
$rules = [
|
$rules = [
|
||||||
@@ -87,7 +82,9 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
if($licenseSeat->assigned_to != null){
|
if($licenseSeat->assigned_to != null){
|
||||||
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
|
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
|
||||||
session()->put('checkedInFrom', $return_to->id);
|
if ($return_to) {
|
||||||
|
session()->put('checkedInFrom', $return_to->id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_to = Asset::find($licenseSeat->asset_id);
|
$return_to = Asset::find($licenseSeat->asset_id);
|
||||||
}
|
}
|
||||||
@@ -96,6 +93,9 @@ class LicenseCheckinController extends Controller
|
|||||||
$licenseSeat->assigned_to = null;
|
$licenseSeat->assigned_to = null;
|
||||||
$licenseSeat->asset_id = null;
|
$licenseSeat->asset_id = null;
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
if (! $licenseSeat->license->reassignable) {
|
||||||
|
$licenseSeat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
|
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||||
if ($request->get('redirect_option') === 'target'){
|
if ($request->get('redirect_option') === 'target'){
|
||||||
@@ -104,7 +104,7 @@ class LicenseCheckinController extends Controller
|
|||||||
|
|
||||||
// Was the asset updated?
|
// Was the asset updated?
|
||||||
if ($licenseSeat->save()) {
|
if ($licenseSeat->save()) {
|
||||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
|
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes));
|
||||||
|
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
@@ -130,21 +130,17 @@ class LicenseCheckinController extends Controller
|
|||||||
$license = License::findOrFail($licenseId);
|
$license = License::findOrFail($licenseId);
|
||||||
$this->authorize('checkin', $license);
|
$this->authorize('checkin', $license);
|
||||||
|
|
||||||
if (! $license->reassignable) {
|
|
||||||
// Not allowed to checkin
|
|
||||||
Session::flash('error', 'License not reassignable.');
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
||||||
->whereNotNull('assigned_to')
|
->whereNotNull('assigned_to')
|
||||||
->with('user')
|
->with('user', 'license')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$license = $licenseSeatsByUser->first()?->license;
|
||||||
foreach ($licenseSeatsByUser as $user_seat) {
|
foreach ($licenseSeatsByUser as $user_seat) {
|
||||||
$user_seat->assigned_to = null;
|
$user_seat->assigned_to = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$user_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($user_seat->save()) {
|
if ($user_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
|
||||||
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
@@ -157,9 +153,12 @@ class LicenseCheckinController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
|
$license = $licenseSeatsByAsset->first()?->license;
|
||||||
foreach ($licenseSeatsByAsset as $asset_seat) {
|
foreach ($licenseSeatsByAsset as $asset_seat) {
|
||||||
$asset_seat->asset_id = null;
|
$asset_seat->asset_id = null;
|
||||||
|
if ($license && ! $license->reassignable) {
|
||||||
|
$asset_seat->unreassignable_seat = true;
|
||||||
|
}
|
||||||
if ($asset_seat->save()) {
|
if ($asset_seat->save()) {
|
||||||
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
|
||||||
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
* @param $id
|
* @param $id
|
||||||
* @return \Illuminate\Contracts\View\View
|
* @return \Illuminate\Contracts\View\View |\Illuminate\Http\RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function create(License $license)
|
public function create(License $license)
|
||||||
@@ -39,6 +39,16 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 the checkout view
|
||||||
return view('licenses/checkout', compact('license'));
|
return view('licenses/checkout', compact('license'));
|
||||||
}
|
}
|
||||||
@@ -65,22 +75,31 @@ class LicenseCheckoutController extends Controller
|
|||||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->authorize('checkout', $license);
|
$this->authorize('checkout', $license);
|
||||||
|
|
||||||
|
// Make sure there is at least one available to checkout
|
||||||
|
if ($license->availCount()->count() < 1) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the license is expired or terminated
|
||||||
|
if ($license->isInactive()) {
|
||||||
|
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive'));
|
||||||
|
}
|
||||||
|
|
||||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||||
$licenseSeat->created_by = auth()->id();
|
$licenseSeat->created_by = auth()->id();
|
||||||
$licenseSeat->notes = $request->input('notes');
|
$licenseSeat->notes = $request->input('notes');
|
||||||
|
|
||||||
|
|
||||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
|
||||||
|
|
||||||
if ($request->filled('asset_id')) {
|
if ($request->filled('asset_id')) {
|
||||||
|
session()->put(['checkout_to_type' => 'asset']);
|
||||||
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
||||||
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
||||||
|
|
||||||
} elseif ($request->filled('assigned_to')) {
|
} elseif ($request->filled('assigned_to')) {
|
||||||
|
session()->put(['checkout_to_type' => 'user']);
|
||||||
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
||||||
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
||||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
||||||
@@ -89,6 +108,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if ($checkoutTarget) {
|
if ($checkoutTarget) {
|
||||||
|
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
->with('success', trans('admin/licenses/message.checkout.success'));
|
->with('success', trans('admin/licenses/message.checkout.success'));
|
||||||
}
|
}
|
||||||
@@ -110,6 +130,7 @@ class LicenseCheckoutController extends Controller
|
|||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (! $licenseSeat->license->is($license)) {
|
if (! $licenseSeat->license->is($license)) {
|
||||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch')));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Licenses;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\License;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class LicenseFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Validates and stores files associated with a license.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $licenseId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -102,7 +102,11 @@ class LicensesController extends Controller
|
|||||||
$license->created_by = auth()->id();
|
$license->created_by = auth()->id();
|
||||||
$license->min_amt = $request->input('min_amt');
|
$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()) {
|
if ($license->save()) {
|
||||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||||
@@ -241,16 +245,28 @@ class LicensesController extends Controller
|
|||||||
$license = License::with('assignedusers')->find($license->id);
|
$license = License::with('assignedusers')->find($license->id);
|
||||||
|
|
||||||
$users_count = User::where('autoassign_licenses', '1')->count();
|
$users_count = User::where('autoassign_licenses', '1')->count();
|
||||||
$total_seats_count = $license->totalSeatsByLicenseID();
|
|
||||||
|
$total_seats_count = (int) $license->totalSeatsByLicenseID();
|
||||||
$available_seats_count = $license->availCount()->count();
|
$available_seats_count = $license->availCount()->count();
|
||||||
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
$unreassignable_seats_count = License::unReassignableCount($license);
|
||||||
|
|
||||||
|
if(!$license->reassignable){
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count - $unreassignable_seats_count );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
||||||
|
}
|
||||||
|
if($license->isInactive()){
|
||||||
|
session()->flash('warning', (trans('admin/licenses/message.checkout.license_is_inactive')));
|
||||||
|
}
|
||||||
|
|
||||||
$this->authorize('view', $license);
|
$this->authorize('view', $license);
|
||||||
return view('licenses.view', compact('license'))
|
return view('licenses.view', compact('license'))
|
||||||
->with('users_count', $users_count)
|
->with('users_count', $users_count)
|
||||||
->with('total_seats_count', $total_seats_count)
|
->with('total_seats_count', $total_seats_count)
|
||||||
->with('available_seats_count', $available_seats_count)
|
->with('available_seats_count', $available_seats_count)
|
||||||
->with('checkedout_seats_count', $checkedout_seats_count);
|
->with('checkedout_seats_count', $checkedout_seats_count)
|
||||||
|
->with('unreassignable_seats_count', $unreassignable_seats_count);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,13 +320,16 @@ class LicensesController extends Controller
|
|||||||
$response = new StreamedResponse(function () {
|
$response = new StreamedResponse(function () {
|
||||||
// Open output stream
|
// Open output stream
|
||||||
$handle = fopen('php://output', 'w');
|
$handle = fopen('php://output', 'w');
|
||||||
$licenses= License::with('company',
|
$licenses = License::with('company',
|
||||||
'manufacturer',
|
'manufacturer',
|
||||||
'category',
|
'category',
|
||||||
'supplier',
|
'supplier',
|
||||||
'adminuser',
|
'adminuser',
|
||||||
'assignedusers')
|
'assignedusers');
|
||||||
->orderBy('created_at', 'DESC');
|
if (request()->filled('category_id')) {
|
||||||
|
$licenses = $licenses->where('category_id', request()->input('category_id'));
|
||||||
|
}
|
||||||
|
$licenses = $licenses->orderBy('created_at', 'DESC');
|
||||||
Company::scopeCompanyables($licenses)
|
Company::scopeCompanyables($licenses)
|
||||||
->chunk(500, function ($licenses) use ($handle) {
|
->chunk(500, function ($licenses) use ($handle) {
|
||||||
$headers = [
|
$headers = [
|
||||||
@@ -357,7 +376,7 @@ class LicensesController extends Controller
|
|||||||
$license->order_number,
|
$license->order_number,
|
||||||
$license->free_seat_count,
|
$license->free_seat_count,
|
||||||
$license->seats,
|
$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->depreciation ? $license->depreciation->name: '',
|
||||||
$license->updated_at,
|
$license->updated_at,
|
||||||
$license->deleted_at,
|
$license->deleted_at,
|
||||||
|
|||||||
@@ -96,7 +96,18 @@ class LocationsController extends Controller
|
|||||||
$location->company_id = $request->input('company_id');
|
$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()) {
|
if ($location->save()) {
|
||||||
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success'));
|
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success'));
|
||||||
@@ -178,30 +189,36 @@ class LocationsController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('delete', Location::class);
|
$this->authorize('delete', Location::class);
|
||||||
|
|
||||||
if (is_null($location = Location::find($locationId))) {
|
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||||
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
|
->withCount('children as children_count')
|
||||||
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
|
->find($locationId);
|
||||||
|
|
||||||
|
if (!$location) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($location->users()->count() > 0) {
|
if ($location->isDeletable()) {
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
|
||||||
} elseif ($location->children()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
|
|
||||||
} elseif ($location->assets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
} elseif ($location->assignedassets()->count() > 0) {
|
|
||||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($location->image) {
|
if ($location->image) {
|
||||||
try {
|
try {
|
||||||
Storage::disk('public')->delete('locations/'.$location->image);
|
Storage::disk('public')->delete('locations/'.$location->image);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$location->delete();
|
||||||
|
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
||||||
|
} else {
|
||||||
|
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
|
||||||
}
|
}
|
||||||
$location->delete();
|
|
||||||
|
|
||||||
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,23 +253,41 @@ class LocationsController extends Controller
|
|||||||
$this->authorize('view', Location::class);
|
$this->authorize('view', Location::class);
|
||||||
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
return view('locations/print')
|
||||||
->with('assets', $assets)
|
->with('assigned', false)
|
||||||
->with('users',$users)
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
->with('location', $location)
|
->with('location', $location)
|
||||||
->with('parent', $parent)
|
->with('consumables', $location->consumables)
|
||||||
->with('manager', $manager)
|
->with('components', $location->components)
|
||||||
->with('company', $company);
|
->with('children', $location->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function print_all_assigned($id) : View | RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('view', Location::class);
|
||||||
|
if ($location = Location::where('id', $id)->first()) {
|
||||||
|
return view('locations/print')
|
||||||
|
->with('assigned', true)
|
||||||
|
->with('assets', $location->assets)
|
||||||
|
->with('assignedAssets', $location->assignedAssets)
|
||||||
|
->with('accessories', $location->accessories)
|
||||||
|
->with('assignedAccessories', $location->assignedAccessories)
|
||||||
|
->with('users',$location->users)
|
||||||
|
->with('location', $location)
|
||||||
|
->with('consumables', $location->consumables)
|
||||||
|
->with('components', $location->components)
|
||||||
|
->with('children', $location->children);
|
||||||
|
}
|
||||||
|
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that presents a form to clone a location.
|
* Returns a view that presents a form to clone a location.
|
||||||
@@ -275,9 +310,9 @@ class LocationsController extends Controller
|
|||||||
|
|
||||||
// unset these values
|
// unset these values
|
||||||
$location->id = null;
|
$location->id = null;
|
||||||
$location->image = null;
|
|
||||||
|
|
||||||
return view('locations/edit')
|
return view('locations/edit')
|
||||||
|
->with('cloned_model', $location_to_clone)
|
||||||
->with('item', $location);
|
->with('item', $location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,33 +345,12 @@ class LocationsController extends Controller
|
|||||||
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check validation
|
|
||||||
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||||
|
|
||||||
}
|
}
|
||||||
public function print_all_assigned($id) : View | RedirectResponse
|
|
||||||
{
|
|
||||||
$this->authorize('view', Location::class);
|
|
||||||
if ($location = Location::where('id', $id)->first()) {
|
|
||||||
$parent = Location::where('id', $location->parent_id)->first();
|
|
||||||
$manager = User::where('id', $location->manager_id)->first();
|
|
||||||
$company = Company::where('id', $location->company_id)->first();
|
|
||||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
|
||||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
|
||||||
return view('locations/print')
|
|
||||||
->with('assets', $assets)
|
|
||||||
->with('users',$users)
|
|
||||||
->with('location', $location)
|
|
||||||
->with('parent', $parent)
|
|
||||||
->with('manager', $manager)
|
|
||||||
->with('company', $company);
|
|
||||||
}
|
|
||||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that allows the user to bulk delete locations
|
* Returns a view that allows the user to bulk delete locations
|
||||||
@@ -355,8 +369,12 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')->get();
|
||||||
|
|
||||||
$valid_count = 0;
|
$valid_count = 0;
|
||||||
@@ -389,9 +407,13 @@ class LocationsController extends Controller
|
|||||||
$locations = Location::whereIn('id', $locations_raw_array)
|
$locations = Location::whereIn('id', $locations_raw_array)
|
||||||
->withCount('assignedAssets as assigned_assets_count')
|
->withCount('assignedAssets as assigned_assets_count')
|
||||||
->withCount('assets as assets_count')
|
->withCount('assets as assets_count')
|
||||||
|
->withCount('assignedAccessories as assigned_accessories_count')
|
||||||
|
->withCount('accessories as accessories_count')
|
||||||
->withCount('rtd_assets as rtd_assets_count')
|
->withCount('rtd_assets as rtd_assets_count')
|
||||||
->withCount('children as children_count')
|
->withCount('children as children_count')
|
||||||
->withCount('users as users_count')->get();
|
->withCount('users as users_count')
|
||||||
|
->withCount('consumables as consumables_count')
|
||||||
|
->withCount('components as components_count')->get();
|
||||||
|
|
||||||
$success_count = 0;
|
$success_count = 0;
|
||||||
$error_count = 0;
|
$error_count = 0;
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\Location;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use \Symfony\Component\HttpFoundation\StreamedResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
|
|
||||||
class LocationsFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Upload a file to the server.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $modelId
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@since [v1.0]
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
*/
|
|
||||||
public function store(UploadFileRequest $request, Location $location) : RedirectResponse
|
|
||||||
{
|
|
||||||
$this->authorize('update', $location);
|
|
||||||
|
|
||||||
if ($request->hasFile('file')) {
|
|
||||||
|
|
||||||
if (! Storage::exists('private_uploads/locations')) {
|
|
||||||
Storage::makeDirectory('private_uploads/locations', 775);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($request->file('file') as $file) {
|
|
||||||
$file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file);
|
|
||||||
$location->logUpload($file_name, $request->get('notes'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for permissions and display the file.
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @param int $modelId
|
|
||||||
* @param int $fileId
|
|
||||||
* @since [v1.0]
|
|
||||||
*/
|
|
||||||
public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
|
|
||||||
{
|
|
||||||
|
|
||||||
$this->authorize('view', $location);
|
|
||||||
|
|
||||||
if (! $log = Actionlog::find($fileId)) {
|
|
||||||
return redirect()->back()->withFragment('files')->with('error', 'No matching file record');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = 'private_uploads/locations/'.$log->filename;
|
|
||||||
|
|
||||||
if (! Storage::exists($file)) {
|
|
||||||
return redirect()->back()->withFragment('files')->with('error', 'No matching file on server');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request('inline') == 'true') {
|
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Content-Disposition' => 'inline',
|
|
||||||
];
|
|
||||||
|
|
||||||
return Storage::download($file, $log->filename, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageHelper::downloader($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the associated file
|
|
||||||
*
|
|
||||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @param int $modelId
|
|
||||||
* @param int $fileId
|
|
||||||
* @since [v1.0]
|
|
||||||
*/
|
|
||||||
public function destroy(Location $location, $fileId = null) : RedirectResponse
|
|
||||||
{
|
|
||||||
$rel_path = 'private_uploads/locations';
|
|
||||||
$this->authorize('update', $location);
|
|
||||||
$log = Actionlog::find($fileId);
|
|
||||||
|
|
||||||
if ($log) {
|
|
||||||
|
|
||||||
// This should be moved to purge
|
|
||||||
// if (Storage::exists($rel_path.'/'.$log->filename)) {
|
|
||||||
// Storage::delete($rel_path.'/'.$log->filename);
|
|
||||||
// }
|
|
||||||
$log->delete();
|
|
||||||
|
|
||||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetMaintenance;
|
use App\Models\Maintenance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@@ -17,29 +18,23 @@ use \Illuminate\Http\RedirectResponse;
|
|||||||
*
|
*
|
||||||
* @version v2.0
|
* @version v2.0
|
||||||
*/
|
*/
|
||||||
class AssetMaintenancesController extends Controller
|
class MaintenancesController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a view that invokes the ajax tables which actually contains
|
* Returns a view that invokes the ajax tables which actually contains
|
||||||
* the content for the asset maintenances listing, which is generated in getDatatable.
|
* the content for the asset maintenances listing.
|
||||||
*
|
|
||||||
* @todo This should be replaced with middleware and/or policies
|
|
||||||
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response
|
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
|
||||||
* @version v1.0
|
|
||||||
* @since [v1.8]
|
|
||||||
*/
|
*/
|
||||||
public function index() : View
|
public function index() : View
|
||||||
{
|
{
|
||||||
$this->authorize('view', Asset::class);
|
$this->authorize('view', Asset::class);
|
||||||
return view('asset_maintenances/index');
|
return view('maintenances.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a form view to create a new asset maintenance.
|
* Returns a form view to create a new asset maintenance.
|
||||||
*
|
*
|
||||||
* @see AssetMaintenancesController::postCreate() method that stores the data
|
* @see MaintenancesController::postCreate() method that stores the data
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
@@ -55,21 +50,21 @@ class AssetMaintenancesController extends Controller
|
|||||||
$asset->asset_id = $asset->id;
|
$asset->asset_id = $asset->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('asset_maintenances/edit')
|
return view('maintenances/edit')
|
||||||
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
|
->with('maintenanceType', Maintenance::getImprovementOptions())
|
||||||
->with('asset', $asset)
|
->with('asset', $asset)
|
||||||
->with('item', new AssetMaintenance);
|
->with('item', new Maintenance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and stores the new asset maintenance
|
* Validates and stores the new asset maintenance
|
||||||
*
|
*
|
||||||
* @see AssetMaintenancesController::getCreate() method for the form
|
* @see MaintenancesController::getCreate() method for the form
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function store(Request $request) : RedirectResponse
|
public function store(ImageUploadRequest $request) : RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
|
|
||||||
@@ -78,72 +73,74 @@ class AssetMaintenancesController extends Controller
|
|||||||
// Loop through the selected assets
|
// Loop through the selected assets
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
|
|
||||||
$assetMaintenance = new AssetMaintenance();
|
$maintenance = new Maintenance();
|
||||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
$maintenance->supplier_id = $request->input('supplier_id');
|
||||||
$assetMaintenance->is_warranty = $request->input('is_warranty');
|
$maintenance->is_warranty = $request->input('is_warranty');
|
||||||
$assetMaintenance->cost = $request->input('cost');
|
$maintenance->cost = $request->input('cost');
|
||||||
$assetMaintenance->notes = $request->input('notes');
|
$maintenance->notes = $request->input('notes');
|
||||||
|
$maintenance->url = $request->input('url');
|
||||||
|
|
||||||
// Save the asset maintenance data
|
// Save the asset maintenance data
|
||||||
$assetMaintenance->asset_id = $asset->id;
|
$maintenance->asset_id = $asset->id;
|
||||||
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||||
$assetMaintenance->title = $request->input('title');
|
$maintenance->name = $request->input('name');
|
||||||
$assetMaintenance->start_date = $request->input('start_date');
|
$maintenance->start_date = $request->input('start_date');
|
||||||
$assetMaintenance->completion_date = $request->input('completion_date');
|
$maintenance->completion_date = $request->input('completion_date');
|
||||||
$assetMaintenance->created_by = auth()->id();
|
$maintenance->created_by = auth()->id();
|
||||||
|
|
||||||
if (($assetMaintenance->completion_date !== null)
|
if (($maintenance->completion_date !== null)
|
||||||
&& ($assetMaintenance->start_date !== '')
|
&& ($maintenance->start_date !== '')
|
||||||
&& ($assetMaintenance->start_date !== '0000-00-00')
|
&& ($maintenance->start_date !== '0000-00-00')
|
||||||
) {
|
) {
|
||||||
$startDate = Carbon::parse($assetMaintenance->start_date);
|
$startDate = Carbon::parse($maintenance->start_date);
|
||||||
$completionDate = Carbon::parse($assetMaintenance->completion_date);
|
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||||
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maintenance = $request->handleImages($maintenance);
|
||||||
|
|
||||||
// Was the asset maintenance created?
|
// Was the asset maintenance created?
|
||||||
if (!$assetMaintenance->save()) {
|
if (!$maintenance->save()) {
|
||||||
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
|
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('maintenances.index')
|
return redirect()->route('maintenances.index')
|
||||||
->with('success', trans('admin/asset_maintenances/message.create.success'));
|
->with('success', trans('admin/maintenances/message.create.success'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a form view to edit a selected asset maintenance.
|
* Returns a form view to edit a selected asset maintenance.
|
||||||
*
|
*
|
||||||
* @see AssetMaintenancesController::postEdit() method that stores the data
|
* @see MaintenancesController::postEdit() method that stores the data
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
|
public function edit(Maintenance $maintenance) : View | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
$this->authorize('update', $maintenance->asset);
|
$this->authorize('update', $maintenance->asset);
|
||||||
|
|
||||||
return view('asset_maintenances/edit')
|
return view('maintenances/edit')
|
||||||
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
|
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
|
||||||
->with('asset_ids', request()->input('asset_ids', []))
|
->with('asset_ids', request()->input('asset_ids', []))
|
||||||
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
|
->with('maintenanceType', Maintenance::getImprovementOptions())
|
||||||
->with('item', $maintenance);
|
->with('item', $maintenance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and stores an update to an asset maintenance
|
* Validates and stores an update to an asset maintenance
|
||||||
*
|
*
|
||||||
* @see AssetMaintenancesController::postEdit() method that stores the data
|
* @see MaintenancesController::postEdit() method that stores the data
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param int $assetMaintenanceId
|
* @param int $maintenanceId
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
|
public function update(ImageUploadRequest $request, Maintenance $maintenance) : View | RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
$this->authorize('update', $maintenance->asset);
|
$this->authorize('update', $maintenance->asset);
|
||||||
@@ -153,9 +150,10 @@ class AssetMaintenancesController extends Controller
|
|||||||
$maintenance->cost = $request->input('cost');
|
$maintenance->cost = $request->input('cost');
|
||||||
$maintenance->notes = $request->input('notes');
|
$maintenance->notes = $request->input('notes');
|
||||||
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||||
$maintenance->title = $request->input('title');
|
$maintenance->name = $request->input('name');
|
||||||
$maintenance->start_date = $request->input('start_date');
|
$maintenance->start_date = $request->input('start_date');
|
||||||
$maintenance->completion_date = $request->input('completion_date');
|
$maintenance->completion_date = $request->input('completion_date');
|
||||||
|
$maintenance->url = $request->input('url');
|
||||||
|
|
||||||
|
|
||||||
// Todo - put this in a getter/setter?
|
// Todo - put this in a getter/setter?
|
||||||
@@ -176,10 +174,11 @@ class AssetMaintenancesController extends Controller
|
|||||||
$completionDate = Carbon::parse($maintenance->completion_date);
|
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||||
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||||
}
|
}
|
||||||
|
$maintenance = $request->handleImages($maintenance);
|
||||||
|
|
||||||
if ($maintenance->save()) {
|
if ($maintenance->save()) {
|
||||||
return redirect()->route('maintenances.index')
|
return redirect()->route('maintenances.index')
|
||||||
->with('success', trans('admin/asset_maintenances/message.edit.success'));
|
->with('success', trans('admin/maintenances/message.edit.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
||||||
@@ -189,11 +188,11 @@ class AssetMaintenancesController extends Controller
|
|||||||
* Delete an asset maintenance
|
* Delete an asset maintenance
|
||||||
*
|
*
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @param int $assetMaintenanceId
|
* @param int $maintenanceId
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function destroy(AssetMaintenance $maintenance) : RedirectResponse
|
public function destroy(Maintenance $maintenance) : RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('update', Asset::class);
|
$this->authorize('update', Asset::class);
|
||||||
$this->authorize('update', $maintenance->asset);
|
$this->authorize('update', $maintenance->asset);
|
||||||
@@ -201,19 +200,19 @@ class AssetMaintenancesController extends Controller
|
|||||||
$maintenance->delete();
|
$maintenance->delete();
|
||||||
// Redirect to the asset_maintenance management page
|
// Redirect to the asset_maintenance management page
|
||||||
return redirect()->route('maintenances.index')
|
return redirect()->route('maintenances.index')
|
||||||
->with('success', trans('admin/asset_maintenances/message.delete.success'));
|
->with('success', trans('admin/maintenances/message.delete.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View an asset maintenance
|
* View an asset maintenance
|
||||||
*
|
*
|
||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @param int $assetMaintenanceId
|
* @param int $maintenanceId
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
* @since [v1.8]
|
* @since [v1.8]
|
||||||
*/
|
*/
|
||||||
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
|
public function show(Maintenance $maintenance) : View | RedirectResponse
|
||||||
{
|
{
|
||||||
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
|
return view('maintenances.view')->with('maintenance', $maintenance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Manufacturers\DeleteManufacturerAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasChildren;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
|
use App\Helpers\Helper;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Manufacturer;
|
use App\Models\Manufacturer;
|
||||||
@@ -51,7 +59,7 @@ class ManufacturersController extends Controller
|
|||||||
$manufacturers_count = Manufacturer::withTrashed()->count();
|
$manufacturers_count = Manufacturer::withTrashed()->count();
|
||||||
|
|
||||||
if ($manufacturers_count == 0) {
|
if ($manufacturers_count == 0) {
|
||||||
Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']);
|
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('success', trans('general.seeding.manufacturers.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,32 +165,18 @@ class ManufacturersController extends Controller
|
|||||||
* @param int $manufacturerId
|
* @param int $manufacturerId
|
||||||
* @since [v1.0]
|
* @since [v1.0]
|
||||||
*/
|
*/
|
||||||
public function destroy($manufacturerId) : RedirectResponse
|
public function destroy(Manufacturer $manufacturer): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Manufacturer::class);
|
$this->authorize('delete', $manufacturer);
|
||||||
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
|
try {
|
||||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
|
DeleteManufacturerAction::run($manufacturer);
|
||||||
|
} catch (ItemStillHasChildren $e) {
|
||||||
|
return redirect()->route('manufacturers.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('manufacturers.index')->with('error', trans('general.something_went_wrong'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $manufacturer->isDeletable()) {
|
|
||||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($manufacturer->image) {
|
|
||||||
try {
|
|
||||||
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::info($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Soft delete the manufacturer if active, permanent delete if is already deleted
|
|
||||||
if ($manufacturer->deleted_at === null) {
|
|
||||||
$manufacturer->delete();
|
|
||||||
} else {
|
|
||||||
$manufacturer->forceDelete();
|
|
||||||
}
|
|
||||||
// Redirect to the manufacturers management page
|
|
||||||
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
|
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use App\Models\Actionlog;
|
|||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetModel;
|
use App\Models\AssetModel;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\AssetMaintenance;
|
use App\Models\Maintenance;
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\CustomField;
|
use App\Models\CustomField;
|
||||||
@@ -17,13 +17,11 @@ use App\Models\Depreciation;
|
|||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use App\Models\ReportTemplate;
|
use App\Models\ReportTemplate;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Notifications\CheckoutAssetNotification;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Notification;
|
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
@@ -161,7 +159,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = e($asset->serial);
|
$row[] = e($asset->serial);
|
||||||
|
|
||||||
if ($target = $asset->assignedTo) {
|
if ($target = $asset->assignedTo) {
|
||||||
$row[] = e($target->present()->name());
|
$row[] = e($target->display_name);
|
||||||
} else {
|
} else {
|
||||||
$row[] = ''; // Empty string if unassigned
|
$row[] = ''; // Empty string if unassigned
|
||||||
}
|
}
|
||||||
@@ -276,22 +274,18 @@ class ReportsController extends Controller
|
|||||||
$target_name = '';
|
$target_name = '';
|
||||||
|
|
||||||
if ($actionlog->target) {
|
if ($actionlog->target) {
|
||||||
if ($actionlog->targetType() == 'user') {
|
$target_name = $actionlog->target->display_name;
|
||||||
$target_name = $actionlog->target->getFullNameAttribute();
|
|
||||||
} else {
|
|
||||||
$target_name = $actionlog->target->getDisplayNameAttribute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($actionlog->item){
|
if ($actionlog->item){
|
||||||
$item_name = e($actionlog->item->getDisplayNameAttribute());
|
$item_name = e($actionlog->item->display_name);
|
||||||
} else {
|
} else {
|
||||||
$item_name = '';
|
$item_name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
$actionlog->created_at,
|
$actionlog->created_at,
|
||||||
($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
|
($actionlog->adminuser) ? $actionlog->adminuser->display_name : '',
|
||||||
$actionlog->present()->actionType(),
|
$actionlog->present()->actionType(),
|
||||||
e($actionlog->itemType()),
|
e($actionlog->itemType()),
|
||||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||||
@@ -300,10 +294,10 @@ class ReportsController extends Controller
|
|||||||
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
||||||
$target_name,
|
$target_name,
|
||||||
($actionlog->note) ? e($actionlog->note) : '',
|
($actionlog->note) ? e($actionlog->note) : '',
|
||||||
$actionlog->log_meta,
|
|
||||||
$actionlog->remote_ip,
|
$actionlog->remote_ip,
|
||||||
$actionlog->user_agent,
|
$actionlog->user_agent,
|
||||||
$actionlog->action_source,
|
$actionlog->action_source,
|
||||||
|
$actionlog->log_meta,
|
||||||
];
|
];
|
||||||
fputcsv($handle, $row);
|
fputcsv($handle, $row);
|
||||||
}
|
}
|
||||||
@@ -442,10 +436,8 @@ class ReportsController extends Controller
|
|||||||
// Open output stream
|
// Open output stream
|
||||||
$handle = fopen('php://output', 'w');
|
$handle = fopen('php://output', 'w');
|
||||||
stream_set_timeout($handle, 2000);
|
stream_set_timeout($handle, 2000);
|
||||||
|
|
||||||
if ($request->filled('use_bom')) {
|
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||||
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
|
|
||||||
}
|
|
||||||
|
|
||||||
$header = [];
|
$header = [];
|
||||||
|
|
||||||
@@ -691,6 +683,14 @@ class ReportsController extends Controller
|
|||||||
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
|
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->filled('purchase_cost_start')) {
|
||||||
|
if ($request->filled('purchase_cost_end')) {
|
||||||
|
$assets->whereBetween('assets.purchase_cost', [$request->input('purchase_cost_start'), $request->input('purchase_cost_end')]);
|
||||||
|
} else {
|
||||||
|
$assets->where('assets.purchase_cost', ">", $request->input('purchase_cost_start'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
|
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
|
||||||
$created_start = Carbon::parse($request->input('created_start'))->startOfDay();
|
$created_start = Carbon::parse($request->input('created_start'))->startOfDay();
|
||||||
$created_end = Carbon::parse($request->input('created_end'))->endOfDay();
|
$created_end = Carbon::parse($request->input('created_end'))->endOfDay();
|
||||||
@@ -832,7 +832,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location')) {
|
if ($request->filled('location')) {
|
||||||
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
|
$row[] = ($asset->location) ? $asset->location->display_name : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('location_address')) {
|
if ($request->filled('location_address')) {
|
||||||
@@ -845,7 +845,7 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('rtd_location')) {
|
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')) {
|
if ($request->filled('rtd_location_address')) {
|
||||||
@@ -858,8 +858,8 @@ class ReportsController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('assigned_to')) {
|
if ($request->filled('assigned_to')) {
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : '');
|
$row[] = ($asset->assigned) ? $asset->assigned->display_name : '';
|
||||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
|
$row[] = ($asset->assigned) ? $asset->assignedType() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->filled('username')) {
|
if ($request->filled('username')) {
|
||||||
@@ -1038,11 +1038,11 @@ class ReportsController extends Controller
|
|||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
*/
|
*/
|
||||||
public function getAssetMaintenancesReport() : View
|
public function getMaintenancesReport() : View
|
||||||
{
|
{
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
|
|
||||||
return view('reports.asset_maintenances');
|
return view('reports.maintenances');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1051,11 +1051,11 @@ class ReportsController extends Controller
|
|||||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||||
* @version v1.0
|
* @version v1.0
|
||||||
*/
|
*/
|
||||||
public function exportAssetMaintenancesReport() : Response
|
public function exportMaintenancesReport() : Response
|
||||||
{
|
{
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
// Grab all the improvements
|
// Grab all the improvements
|
||||||
$assetMaintenances = AssetMaintenance::with('asset', 'supplier')
|
$Maintenances = Maintenance::with('asset', 'supplier')
|
||||||
->orderBy('created_at', 'DESC')
|
->orderBy('created_at', 'DESC')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
@@ -1063,36 +1063,36 @@ class ReportsController extends Controller
|
|||||||
|
|
||||||
$header = [
|
$header = [
|
||||||
trans('admin/hardware/table.asset_tag'),
|
trans('admin/hardware/table.asset_tag'),
|
||||||
trans('admin/asset_maintenances/table.asset_name'),
|
trans('admin/maintenances/table.asset_name'),
|
||||||
trans('general.supplier'),
|
trans('general.supplier'),
|
||||||
trans('admin/asset_maintenances/form.asset_maintenance_type'),
|
trans('admin/maintenances/form.asset_maintenance_type'),
|
||||||
trans('admin/asset_maintenances/form.title'),
|
trans('admin/maintenances/form.title'),
|
||||||
trans('admin/asset_maintenances/form.start_date'),
|
trans('admin/maintenances/form.start_date'),
|
||||||
trans('admin/asset_maintenances/form.completion_date'),
|
trans('admin/maintenances/form.completion_date'),
|
||||||
trans('admin/asset_maintenances/form.asset_maintenance_time'),
|
trans('admin/maintenances/form.asset_maintenance_time'),
|
||||||
trans('admin/asset_maintenances/form.cost'),
|
trans('admin/maintenances/form.cost'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$header = array_map('trim', $header);
|
$header = array_map('trim', $header);
|
||||||
$rows[] = implode(',', $header);
|
$rows[] = implode(',', $header);
|
||||||
|
|
||||||
foreach ($assetMaintenances as $assetMaintenance) {
|
foreach ($Maintenances as $maintenance) {
|
||||||
$row = [];
|
$row = [];
|
||||||
$row[] = str_replace(',', '', e($assetMaintenance->asset->asset_tag));
|
$row[] = str_replace(',', '', e($maintenance->asset->asset_tag));
|
||||||
$row[] = str_replace(',', '', e($assetMaintenance->asset->name));
|
$row[] = str_replace(',', '', e($maintenance->asset->name));
|
||||||
$row[] = str_replace(',', '', e($assetMaintenance->supplier->name));
|
$row[] = str_replace(',', '', e($maintenance->supplier->name));
|
||||||
$row[] = e($assetMaintenance->improvement_type);
|
$row[] = e($maintenance->improvement_type);
|
||||||
$row[] = e($assetMaintenance->title);
|
$row[] = e($maintenance->name);
|
||||||
$row[] = e($assetMaintenance->start_date);
|
$row[] = e($maintenance->start_date);
|
||||||
$row[] = e($assetMaintenance->completion_date);
|
$row[] = e($maintenance->completion_date);
|
||||||
if (is_null($assetMaintenance->asset_maintenance_time)) {
|
if (is_null($maintenance->asset_maintenance_time)) {
|
||||||
$improvementTime = (int) Carbon::now()
|
$improvementTime = (int) Carbon::now()
|
||||||
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
|
->diffInDays(Carbon::parse($maintenance->start_date), true);
|
||||||
} else {
|
} else {
|
||||||
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
|
$improvementTime = (int) $maintenance->asset_maintenance_time;
|
||||||
}
|
}
|
||||||
$row[] = $improvementTime;
|
$row[] = $improvementTime;
|
||||||
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
|
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($maintenance->cost);
|
||||||
$rows[] = implode(',', $row);
|
$rows[] = implode(',', $row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,7 +1262,7 @@ class ReportsController extends Controller
|
|||||||
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
||||||
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
||||||
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
|
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
|
||||||
$rows[] = implode(',', $row);
|
$rows[] = implode(',', $row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ class SettingsController extends Controller
|
|||||||
$setting->ldap_default_group = $request->input('ldap_default_group');
|
$setting->ldap_default_group = $request->input('ldap_default_group');
|
||||||
$setting->ldap_filter = $request->input('ldap_filter');
|
$setting->ldap_filter = $request->input('ldap_filter');
|
||||||
$setting->ldap_username_field = $request->input('ldap_username_field');
|
$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_lname_field = $request->input('ldap_lname_field');
|
||||||
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
||||||
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
||||||
@@ -889,7 +890,12 @@ class SettingsController extends Controller
|
|||||||
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
|
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
|
||||||
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
|
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
|
||||||
$setting->ldap_phone_field = $request->input('ldap_phone');
|
$setting->ldap_phone_field = $request->input('ldap_phone');
|
||||||
|
$setting->ldap_mobile = $request->input('ldap_mobile');
|
||||||
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
|
$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_country = $request->input('ldap_country');
|
||||||
$setting->ldap_location = $request->input('ldap_location');
|
$setting->ldap_location = $request->input('ldap_location');
|
||||||
$setting->ldap_dept = $request->input('ldap_dept');
|
$setting->ldap_dept = $request->input('ldap_dept');
|
||||||
@@ -1084,6 +1090,7 @@ class SettingsController extends Controller
|
|||||||
|
|
||||||
if (! config('app.lock_passwords')) {
|
if (! config('app.lock_passwords')) {
|
||||||
if (Storage::exists($path.'/'.$filename)) {
|
if (Storage::exists($path.'/'.$filename)) {
|
||||||
|
Log::warning('User '.auth()->user()->username.' is attempting to download backup file: '.$filename);
|
||||||
return StorageHelper::downloader($path.'/'.$filename);
|
return StorageHelper::downloader($path.'/'.$filename);
|
||||||
} else {
|
} else {
|
||||||
// Redirect to the backup page
|
// Redirect to the backup page
|
||||||
@@ -1111,6 +1118,7 @@ class SettingsController extends Controller
|
|||||||
if (Storage::exists($path . '/' . $filename)) {
|
if (Storage::exists($path . '/' . $filename)) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Log::warning('User '.auth()->user()->username.' is attempting to delete backup file: '.$filename);
|
||||||
Storage::delete($path . '/' . $filename);
|
Storage::delete($path . '/' . $filename);
|
||||||
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
|
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -1190,7 +1198,7 @@ class SettingsController extends Controller
|
|||||||
'--force' => true,
|
'--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 = [
|
$restore_params = [
|
||||||
'--force' => true,
|
'--force' => true,
|
||||||
@@ -1339,9 +1347,11 @@ class SettingsController extends Controller
|
|||||||
'name' => config('mail.from.name'),
|
'name' => config('mail.from.name'),
|
||||||
'email' => config('mail.from.address'),
|
'email' => config('mail.from.address'),
|
||||||
])->notify(new MailTest());
|
])->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')));
|
return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent')));
|
||||||
} catch (\Exception $e) {
|
} 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()));
|
return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Suppliers\DestroySupplierAction;
|
||||||
|
use App\Exceptions\ItemStillHasAccessories;
|
||||||
|
use App\Exceptions\ItemStillHasComponents;
|
||||||
|
use App\Exceptions\ItemStillHasConsumables;
|
||||||
|
use App\Exceptions\ItemStillHasMaintenances;
|
||||||
|
use App\Exceptions\ItemStillHasAssets;
|
||||||
|
use App\Exceptions\ItemStillHasLicenses;
|
||||||
use App\Http\Requests\ImageUploadRequest;
|
use App\Http\Requests\ImageUploadRequest;
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use \Illuminate\Contracts\View\View;
|
use \Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller handles all actions related to Suppliers for
|
* This controller handles all actions related to Suppliers for
|
||||||
@@ -119,30 +126,41 @@ class SuppliersController extends Controller
|
|||||||
*
|
*
|
||||||
* @param int $supplierId
|
* @param int $supplierId
|
||||||
*/
|
*/
|
||||||
public function destroy($supplierId) : RedirectResponse
|
public function destroy(Supplier $supplier): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('delete', Supplier::class);
|
$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))) {
|
try {
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
|
DestroySupplierAction::run(supplier: $supplier);
|
||||||
|
} catch (ItemStillHasAssets $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_assets', [
|
||||||
|
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasMaintenances $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_maintenances', [
|
||||||
|
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasLicenses $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_licenses', [
|
||||||
|
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasAccessories $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_accessories', [
|
||||||
|
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasConsumables $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_consumables', [
|
||||||
|
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (ItemStillHasComponents $e) {
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_components', [
|
||||||
|
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
|
||||||
|
]));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
report($e);
|
||||||
|
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($supplier->assets_count > 0) {
|
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.success'));
|
||||||
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->licenses_count > 0) {
|
|
||||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$supplier->delete();
|
|
||||||
|
|
||||||
return redirect()->route('suppliers.index')->with('success',
|
|
||||||
trans('admin/suppliers/message.delete.success')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,6 +173,5 @@ class SuppliersController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('view', Supplier::class);
|
$this->authorize('view', Supplier::class);
|
||||||
return view('suppliers/view', compact('supplier'));
|
return view('suppliers/view', compact('supplier'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
162
app/Http/Controllers/UploadedFilesController.php
Normal file
162
app/Http/Controllers/UploadedFilesController.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\StorageHelper;
|
||||||
|
use App\Http\Requests\UploadFileRequest;
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller provide the health route for
|
||||||
|
* the Snipe-IT Asset Management application.
|
||||||
|
*
|
||||||
|
* @version v1.0
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
|
||||||
|
*/
|
||||||
|
class UploadedFilesController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.2.2]
|
||||||
|
* @author [A. Gianotto <snipe@snipe.net>]
|
||||||
|
*/
|
||||||
|
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 <snipe@snipe.net>]
|
||||||
|
*/
|
||||||
|
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 <snipe@snipe.net>]
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Users;
|
|
||||||
|
|
||||||
use App\Helpers\StorageHelper;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\UploadFileRequest;
|
|
||||||
use App\Models\Actionlog;
|
|
||||||
use App\Models\User;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class UserFilesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Return JSON response with a list of user details for the getIndex() view.
|
|
||||||
*
|
|
||||||
* @param UploadFileRequest $request
|
|
||||||
* @param int $userId
|
|
||||||
* @return string JSON
|
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
|
||||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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] [<snipe@snipe.net>]
|
|
||||||
* @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'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,14 +14,9 @@ use App\Models\Group;
|
|||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\WelcomeNotification;
|
use App\Notifications\WelcomeNotification;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Redirect;
|
|
||||||
use Str;
|
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
use App\Notifications\CurrentInventory;
|
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.
|
//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->email = trim($request->input('email'));
|
||||||
$user->username = trim($request->input('username'));
|
$user->username = trim($request->input('username'));
|
||||||
|
$user->display_name = $request->input('display_name');
|
||||||
if ($request->filled('password')) {
|
if ($request->filled('password')) {
|
||||||
$user->password = bcrypt($request->input('password'));
|
$user->password = bcrypt($request->input('password'));
|
||||||
}
|
}
|
||||||
@@ -105,6 +101,7 @@ class UsersController extends Controller
|
|||||||
$user->activated = $request->input('activated', 0);
|
$user->activated = $request->input('activated', 0);
|
||||||
$user->jobtitle = $request->input('jobtitle');
|
$user->jobtitle = $request->input('jobtitle');
|
||||||
$user->phone = $request->input('phone');
|
$user->phone = $request->input('phone');
|
||||||
|
$user->mobile = $request->input('mobile');
|
||||||
$user->location_id = $request->input('location_id', null);
|
$user->location_id = $request->input('location_id', null);
|
||||||
$user->department_id = $request->input('department_id', null);
|
$user->department_id = $request->input('department_id', null);
|
||||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||||
@@ -130,30 +127,35 @@ class UsersController extends Controller
|
|||||||
}
|
}
|
||||||
$user->permissions = json_encode($permissions_array);
|
$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');
|
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->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')) {
|
if ($request->filled('groups')) {
|
||||||
$user->groups()->sync($request->input('groups'));
|
$user->groups()->sync($request->input('groups'));
|
||||||
} else {
|
} else {
|
||||||
$user->groups()->sync([]);
|
$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 Helper::getRedirectOption($request, $user->id, 'Users')
|
return Helper::getRedirectOption($request, $user->id, 'Users')
|
||||||
->with('success', trans('admin/users/message.success.create'));
|
->with('success', trans('admin/users/message.success.create'));
|
||||||
}
|
}
|
||||||
@@ -248,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
|
// Update the user fields
|
||||||
$user->username = trim($request->input('username'));
|
|
||||||
$user->email = trim($request->input('email'));
|
|
||||||
$user->first_name = $request->input('first_name');
|
$user->first_name = $request->input('first_name');
|
||||||
$user->last_name = $request->input('last_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->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||||
$user->locale = $request->input('locale');
|
$user->locale = $request->input('locale');
|
||||||
$user->employee_num = $request->input('employee_num');
|
$user->employee_num = $request->input('employee_num');
|
||||||
$user->activated = $request->input('activated', 0);
|
|
||||||
$user->jobtitle = $request->input('jobtitle', null);
|
$user->jobtitle = $request->input('jobtitle', null);
|
||||||
$user->phone = $request->input('phone');
|
$user->phone = $request->input('phone');
|
||||||
|
$user->mobile = $request->input('mobile');
|
||||||
$user->location_id = $request->input('location_id', null);
|
$user->location_id = $request->input('location_id', null);
|
||||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||||
$user->manager_id = $request->input('manager_id', null);
|
$user->manager_id = $request->input('manager_id', null);
|
||||||
@@ -273,8 +271,6 @@ class UsersController extends Controller
|
|||||||
$user->city = $request->input('city', null);
|
$user->city = $request->input('city', null);
|
||||||
$user->state = $request->input('state', null);
|
$user->state = $request->input('state', null);
|
||||||
$user->country = $request->input('country', 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->zip = $request->input('zip', null);
|
||||||
$user->remote = $request->input('remote', 0);
|
$user->remote = $request->input('remote', 0);
|
||||||
$user->vip = $request->input('vip', 0);
|
$user->vip = $request->input('vip', 0);
|
||||||
@@ -283,30 +279,49 @@ class UsersController extends Controller
|
|||||||
$user->end_date = $request->input('end_date', null);
|
$user->end_date = $request->input('end_date', null);
|
||||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
$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
|
// Update the location of any assets checked out to this user
|
||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)
|
->where('assigned_to', $user->id)
|
||||||
->update(['location_id' => $request->input('location_id', null)]);
|
->update(['location_id' => $request->input('location_id', null)]);
|
||||||
|
|
||||||
// Do we want to update the user password?
|
// check for permissions related fields and only set them if the user has permission to edit them
|
||||||
if ($request->filled('password')) {
|
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
|
||||||
$user->password = bcrypt($request->input('password'));
|
|
||||||
|
$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
|
// Update the location of any assets checked out to this user
|
||||||
Asset::where('assigned_type', User::class)
|
Asset::where('assigned_type', User::class)
|
||||||
->where('assigned_to', $user->id)
|
->where('assigned_to', $user->id)
|
||||||
->update(['location_id' => $user->location_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
|
// Handle uploaded avatar
|
||||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||||
@@ -440,7 +455,7 @@ class UsersController extends Controller
|
|||||||
app('request')->request->set('permissions', $permissions);
|
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
|
// Make sure they can view this particular user
|
||||||
$this->authorize('view', $user_to_clone);
|
$this->authorize('view', $user_to_clone);
|
||||||
|
|
||||||
@@ -455,6 +470,8 @@ class UsersController extends Controller
|
|||||||
$user->last_name = '';
|
$user->last_name = '';
|
||||||
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
||||||
$user->id = null;
|
$user->id = null;
|
||||||
|
$user->username = null;
|
||||||
|
$user->avatar = null;
|
||||||
|
|
||||||
// Get this user's groups
|
// Get this user's groups
|
||||||
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
|
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
|
||||||
@@ -470,7 +487,7 @@ class UsersController extends Controller
|
|||||||
->with('user', $user)
|
->with('user', $user)
|
||||||
->with('groups', Group::pluck('name', 'id'))
|
->with('groups', Group::pluck('name', 'id'))
|
||||||
->with('userGroups', $userGroups)
|
->with('userGroups', $userGroups)
|
||||||
->with('clone_user', $user_to_clone)
|
->with('cloned_model', $user_to_clone)
|
||||||
->with('item', $user);
|
->with('item', $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,10 +578,10 @@ class UsersController extends Controller
|
|||||||
$user->employee_num,
|
$user->employee_num,
|
||||||
$user->first_name,
|
$user->first_name,
|
||||||
$user->last_name,
|
$user->last_name,
|
||||||
$user->present()->fullName(),
|
$user->display_name,
|
||||||
$user->username,
|
$user->username,
|
||||||
$user->email,
|
$user->email,
|
||||||
($user->manager) ? $user->manager->present()->fullName() : '',
|
($user->manager) ? $user->manager->display_name : '',
|
||||||
($user->userloc) ? $user->userloc->name : '',
|
($user->userloc) ? $user->userloc->name : '',
|
||||||
($user->department) ? $user->department->name : '',
|
($user->department) ? $user->department->name : '',
|
||||||
$user->assets->count(),
|
$user->assets->count(),
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class ViewAssetsController extends Controller
|
|||||||
$logaction->target_type = User::class;
|
$logaction->target_type = User::class;
|
||||||
|
|
||||||
$data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1;
|
$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'] = $item;
|
||||||
$data['item_type'] = $itemType;
|
$data['item_type'] = $itemType;
|
||||||
$data['target'] = auth()->user();
|
$data['target'] = auth()->user();
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ class AssetCountForSidebar
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$total_assets = Asset::count();
|
$total_assets = Asset::AssetsForShow()->count();
|
||||||
if ($settings->show_archived_in_list != '1') {
|
|
||||||
$total_assets -= Asset::Archived()->count();
|
|
||||||
}
|
|
||||||
view()->share('total_assets', $total_assets);
|
view()->share('total_assets', $total_assets);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::debug($e);
|
Log::debug($e);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user