Compare commits
1003 Commits
fix_action
...
use-tcpdf-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
685bbf4375 | ||
|
|
5d03038734 | ||
|
|
75b11de0f4 | ||
|
|
c5bede8594 | ||
|
|
cd9ea6ae3b | ||
|
|
113b762ec7 | ||
|
|
78704d8b85 | ||
|
|
1109db76fe | ||
|
|
b1b390febf | ||
|
|
be451fa0c0 | ||
|
|
1fa553c785 | ||
|
|
905f61371d | ||
|
|
7da5210a01 | ||
|
|
18172d3896 | ||
|
|
c28e78b9e2 | ||
|
|
e7827a3847 | ||
|
|
db9f85e9da | ||
|
|
27022954b1 | ||
|
|
30362c924f | ||
|
|
bf63b15b46 | ||
|
|
19aea4bd6c | ||
|
|
090890e9c6 | ||
|
|
605022a9e3 | ||
|
|
b06c58fe7b | ||
|
|
f5c8b3eb04 | ||
|
|
739980aa09 | ||
|
|
afde5943e3 | ||
|
|
32300cb42c | ||
|
|
de3b1697c8 | ||
|
|
a18fb10b5a | ||
|
|
52140dbe06 | ||
|
|
db5bb1928e | ||
|
|
65b66beb07 | ||
|
|
c83504b4e7 | ||
|
|
cd2e7ee31d | ||
|
|
c3a0a0415a | ||
|
|
709f4672b7 | ||
|
|
e6c030b050 | ||
|
|
7bd3a791a1 | ||
|
|
b9cfc03b4f | ||
|
|
131327a64d | ||
|
|
77d002a158 | ||
|
|
94699893ac | ||
|
|
9f81989bdd | ||
|
|
15abe36c53 | ||
|
|
3094e007ee | ||
|
|
eb259aee22 | ||
|
|
c05c8defb9 | ||
|
|
bf5668a42e | ||
|
|
ec310bc8fb | ||
|
|
db477421b2 | ||
|
|
30a9496cf5 | ||
|
|
6cefa0d0b3 | ||
|
|
9284984265 | ||
|
|
53b96168a9 | ||
|
|
eadce51f10 | ||
|
|
b3c583b6dc | ||
|
|
28abeab31d | ||
|
|
12a649ec4b | ||
|
|
35b79e4d14 | ||
|
|
751dad7f2e | ||
|
|
b08d86220a | ||
|
|
3a27ecc475 | ||
|
|
da6fab5d43 | ||
|
|
ca95b29cd6 | ||
|
|
c5c68e9dd5 | ||
|
|
44fbde26fa | ||
|
|
6e2bcd6aa9 | ||
|
|
9c0202e5ce | ||
|
|
39ef353073 | ||
|
|
7b5d90dd81 | ||
|
|
d1129081df | ||
|
|
315a812df5 | ||
|
|
cfc979acf0 | ||
|
|
d7407d70a3 | ||
|
|
8ccd2e97a8 | ||
|
|
988204619f | ||
|
|
cad6cc3007 | ||
|
|
b303875f1d | ||
|
|
d5cc61f378 | ||
|
|
0d7ec43262 | ||
|
|
d3747f4daa | ||
|
|
af695e7dc8 | ||
|
|
1edbfd87df | ||
|
|
454be01a6c | ||
|
|
745fc515f1 | ||
|
|
715b9c1182 | ||
|
|
95be847d87 | ||
|
|
c1a6546eba | ||
|
|
648c25a0a7 | ||
|
|
f2ec7f2975 | ||
|
|
f518af6d61 | ||
|
|
b11c6a5c06 | ||
|
|
5822e4e692 | ||
|
|
e4f06b0ca8 | ||
|
|
2f093c0e82 | ||
|
|
5d9dc0e74d | ||
|
|
adc3a34929 | ||
|
|
cb2ffe6b3f | ||
|
|
b3e3d01672 | ||
|
|
4a6520fc78 | ||
|
|
75ab6c9b13 | ||
|
|
2f77fcb526 | ||
|
|
60604c3481 | ||
|
|
671c113cd2 | ||
|
|
8a74d21ede | ||
|
|
75995b2109 | ||
|
|
d1eefc3fea | ||
|
|
16795382fc | ||
|
|
eb17974adc | ||
|
|
22852c27f8 | ||
|
|
f4a94d975d | ||
|
|
7a36bbbd1e | ||
|
|
2b401b965b | ||
|
|
314bc5b44f | ||
|
|
76374f0d5a | ||
|
|
264efb015e | ||
|
|
e74460aefc | ||
|
|
55a5a12b30 | ||
|
|
58944a38eb | ||
|
|
469e3bd475 | ||
|
|
17650c5735 | ||
|
|
15e64155b5 | ||
|
|
39955ac760 | ||
|
|
855a176ca9 | ||
|
|
47b2b30455 | ||
|
|
b702e3e2de | ||
|
|
a6b74d56c6 | ||
|
|
a4222bcaef | ||
|
|
ecf24511cd | ||
|
|
abb097a391 | ||
|
|
dd742a2e4a | ||
|
|
128bdf500a | ||
|
|
73ac00bc51 | ||
|
|
3524e23e38 | ||
|
|
be0f3910bb | ||
|
|
07dbc6842c | ||
|
|
5a16b59462 | ||
|
|
13cd7071b8 | ||
|
|
40108b196c | ||
|
|
c8e79aa5ca | ||
|
|
e60f2b2332 | ||
|
|
b6d397bcca | ||
|
|
6503f9c667 | ||
|
|
4770e469b4 | ||
|
|
29a18c7c8b | ||
|
|
6db0003e3f | ||
|
|
c538c460fa | ||
|
|
822339fe42 | ||
|
|
b84d9282ca | ||
|
|
952b6f33bb | ||
|
|
c57c4b8ff2 | ||
|
|
39e6223ff2 | ||
|
|
d8dd274c08 | ||
|
|
15f97b6cb9 | ||
|
|
fc091c1174 | ||
|
|
c07ef4d87f | ||
|
|
11eee833bb | ||
|
|
35b358d336 | ||
|
|
ae109be631 | ||
|
|
3f7ed73395 | ||
|
|
fec9d716ee | ||
|
|
da5b1afd19 | ||
|
|
618106c103 | ||
|
|
312be98132 | ||
|
|
e0bb77a6d6 | ||
|
|
855922c21a | ||
|
|
bc645d2621 | ||
|
|
9c06ff3899 | ||
|
|
2a37aa3b49 | ||
|
|
bf591320af | ||
|
|
56e687bed2 | ||
|
|
07b25fe376 | ||
|
|
c2ecd20b7d | ||
|
|
1b42abcc98 | ||
|
|
9efb49d510 | ||
|
|
2d6270c697 | ||
|
|
0823c23a6e | ||
|
|
b3f0ce4b2a | ||
|
|
8b83584b67 | ||
|
|
9eb686fe08 | ||
|
|
765051ce88 | ||
|
|
ed402e0122 | ||
|
|
1488271a83 | ||
|
|
48bbf8d005 | ||
|
|
e97b969d66 | ||
|
|
cdd12df81a | ||
|
|
050a3afc74 | ||
|
|
270401c693 | ||
|
|
551822ce7d | ||
|
|
4b8c371097 | ||
|
|
90fbf6da46 | ||
|
|
0c3103e3d2 | ||
|
|
6a8e1566fe | ||
|
|
ced30082a6 | ||
|
|
f6c64abc1a | ||
|
|
7f9939a896 | ||
|
|
1c99f2dfdd | ||
|
|
1974fccac3 | ||
|
|
911552035e | ||
|
|
ff25d275ee | ||
|
|
1fcf5e03e7 | ||
|
|
9b4101855f | ||
|
|
9253d894d3 | ||
|
|
ebd79f22c7 | ||
|
|
c1b139fb9a | ||
|
|
a88bcea8ca | ||
|
|
21566560a7 | ||
|
|
e3ca43bf40 | ||
|
|
61abb8d5cb | ||
|
|
ecad656551 | ||
|
|
615e6d6e4f | ||
|
|
6dceefb96e | ||
|
|
69eff394fd | ||
|
|
a9da3aca81 | ||
|
|
91f3556375 | ||
|
|
aab7c3a840 | ||
|
|
9c823119e3 | ||
|
|
f5128833f6 | ||
|
|
2bc144354a | ||
|
|
e6fec6ec34 | ||
|
|
53389875bf | ||
|
|
3b243b38c8 | ||
|
|
3d9580808b | ||
|
|
2141ee71d4 | ||
|
|
01dd07083e | ||
|
|
42a28ea06b | ||
|
|
180cb6ba8e | ||
|
|
a78762e40b | ||
|
|
9797bb19e2 | ||
|
|
08a9554b3c | ||
|
|
d79bd825ee | ||
|
|
fe3d225cfa | ||
|
|
376e0db66e | ||
|
|
5fdabc1a62 | ||
|
|
dfe2a75d72 | ||
|
|
ba85af11aa | ||
|
|
db58b80d27 | ||
|
|
5cb8aae383 | ||
|
|
817530429b | ||
|
|
4a7b7183d2 | ||
|
|
94bd39cf23 | ||
|
|
4038a22093 | ||
|
|
682baec0c9 | ||
|
|
ff91be491d | ||
|
|
ef35a0f2f1 | ||
|
|
f12a3bb08b | ||
|
|
c8a5065ffa | ||
|
|
23da5573f3 | ||
|
|
b08f985776 | ||
|
|
9b968baaa7 | ||
|
|
07edbe6f1c | ||
|
|
1f55a8b6e3 | ||
|
|
f6b9e11810 | ||
|
|
c18a3e4266 | ||
|
|
5840ef1c6f | ||
|
|
7974baddf5 | ||
|
|
4bf569758f | ||
|
|
f56fd9bb0b | ||
|
|
357ee5fc45 | ||
|
|
c6dea085b2 | ||
|
|
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 | ||
|
|
0fdbdfd5c2 | ||
|
|
31056ff858 | ||
|
|
8d2643696b | ||
|
|
e7488d19e9 | ||
|
|
2bb3b6d64c | ||
|
|
5744e48ae8 | ||
|
|
82d0a21440 | ||
|
|
58133cffac | ||
|
|
bfd8c2f310 | ||
|
|
30d447c023 | ||
|
|
9a0846b8a6 | ||
|
|
3667fcddd7 | ||
|
|
906741d662 | ||
|
|
12be088c4f | ||
|
|
6737ba80cd | ||
|
|
862a3d938e | ||
|
|
09e82377a5 | ||
|
|
59470864e7 | ||
|
|
c95aeb3730 | ||
|
|
5c55c90d68 | ||
|
|
e47972731b | ||
|
|
5851cc9e41 | ||
|
|
6f615230e9 | ||
|
|
d91598a25e | ||
|
|
9e416778d9 | ||
|
|
860a117567 | ||
|
|
b8fe3b18d4 | ||
|
|
40269a724b | ||
|
|
ec828318d8 | ||
|
|
d31e7ed534 | ||
|
|
5c2dbe438b | ||
|
|
10857635ac | ||
|
|
df2545ef80 | ||
|
|
f6ff729316 | ||
|
|
38678803eb | ||
|
|
67c931f196 | ||
|
|
1c23092d0e | ||
|
|
a90ff21cbf | ||
|
|
0ce0cee81f | ||
|
|
f4be5ffb5d | ||
|
|
19958748bf | ||
|
|
d6ca8468e3 | ||
|
|
7bccb7718b | ||
|
|
f6b63b5e44 | ||
|
|
9a2c5ff195 | ||
|
|
3597f759da | ||
|
|
3ed3b21286 | ||
|
|
b89b636474 | ||
|
|
2afc595452 | ||
|
|
c7262f2885 | ||
|
|
8662aa2277 | ||
|
|
8095e0ab72 | ||
|
|
be3c8ddd5c | ||
|
|
ec5b9ce903 | ||
|
|
bd2acefecc | ||
|
|
18e49e9067 | ||
|
|
a0d65520a3 | ||
|
|
a35731d9d5 | ||
|
|
9d3623cca6 | ||
|
|
2fe08a721f | ||
|
|
7abc3a7d7d | ||
|
|
d4a34f1a3c | ||
|
|
ddda4848d3 | ||
|
|
8516856d37 | ||
|
|
132327594b | ||
|
|
d2a2c63070 | ||
|
|
170a5158fa | ||
|
|
1d8493d388 | ||
|
|
ff39e8bd2c | ||
|
|
c3442033da | ||
|
|
f1dd84edba | ||
|
|
06b040a337 | ||
|
|
fa546ddc5b | ||
|
|
f811352c79 | ||
|
|
7ed8963b9f | ||
|
|
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 | ||
|
|
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 | ||
|
|
dd86de017e | ||
|
|
3eabde9630 | ||
|
|
640c51af31 | ||
|
|
7167b17d25 | ||
|
|
8a35948678 | ||
|
|
0fe63d3fb9 | ||
|
|
e4302c3e88 | ||
|
|
a7df6fb465 | ||
|
|
133e7598e0 | ||
|
|
c1a52ffa75 | ||
|
|
4f46313388 | ||
|
|
03b2cc9cd2 | ||
|
|
1a2bf8dc95 | ||
|
|
dd63fbeb84 | ||
|
|
59e435c418 | ||
|
|
f89f0a19b5 | ||
|
|
cbc6ef95cb | ||
|
|
0ceecc9e1d | ||
|
|
c816902025 | ||
|
|
cfb03cdca0 | ||
|
|
266f77b08c | ||
|
|
257d58c236 | ||
|
|
015f3d936c | ||
|
|
18d2a0ffd7 | ||
|
|
24afde0e46 | ||
|
|
8499faa55a | ||
|
|
c60dd809b8 | ||
|
|
297b8e33f2 | ||
|
|
d0593c6b8d | ||
|
|
8a40d7e35c | ||
|
|
b670b2014c | ||
|
|
440e969f52 | ||
|
|
14b79f2f1c | ||
|
|
00cf49a61f | ||
|
|
4f534e0e84 | ||
|
|
83a19fbbbf | ||
|
|
610cb884fc | ||
|
|
ba92cec62b | ||
|
|
d92e961a52 | ||
|
|
b13e74756a | ||
|
|
4ef3072766 | ||
|
|
e96e2461d3 | ||
|
|
7a2e2be169 | ||
|
|
8d2a5a7e4a | ||
|
|
b7b0e4fab5 | ||
|
|
a624a79b30 | ||
|
|
313135da6f | ||
|
|
58d27d1247 | ||
|
|
edfb28168f | ||
|
|
8d0e03bb06 | ||
|
|
855f6f77cf | ||
|
|
6236cffe14 | ||
|
|
322a71fbb8 | ||
|
|
4d9f8476f3 | ||
|
|
d7d93b14b2 | ||
|
|
d1af3ece6e | ||
|
|
8153b20984 | ||
|
|
a50f605c29 | ||
|
|
daf23edd10 | ||
|
|
2eaaeb8259 | ||
|
|
a02c62d62c | ||
|
|
e0232a8e84 | ||
|
|
6ea5693b2f | ||
|
|
030c2114d1 | ||
|
|
2cb18e3668 | ||
|
|
cd9f8be563 | ||
|
|
a02792e9bf | ||
|
|
41bb422244 | ||
|
|
54663d3342 | ||
|
|
2529f7369f | ||
|
|
909c33dccf | ||
|
|
1adc9f1aa9 | ||
|
|
e9948f0718 | ||
|
|
49da9e58fd | ||
|
|
2f74a8afe1 | ||
|
|
f3e288d078 | ||
|
|
988000952e | ||
|
|
6537f3794b | ||
|
|
d31718ba8a | ||
|
|
9dd4bc5fa8 | ||
|
|
df5f1bd522 | ||
|
|
ddffab9169 | ||
|
|
553ab8851a | ||
|
|
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 | ||
|
|
77fdc370c7 | ||
|
|
301290fb6d | ||
|
|
07fffe2f79 | ||
|
|
0227a63fa5 | ||
|
|
27764b863c | ||
|
|
032fd75f9e | ||
|
|
0bf4f861f3 | ||
|
|
fd8f90cb52 | ||
|
|
b6c6b025c8 | ||
|
|
3d89e98d1f | ||
|
|
7c5110ed5d | ||
|
|
0a474f48ad | ||
|
|
c409bfd5be | ||
|
|
39d5d5b2e0 | ||
|
|
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 | ||
|
|
6f57d6b876 | ||
|
|
e0bad99ea1 | ||
|
|
e39eb09cfb | ||
|
|
64d397c3f3 | ||
|
|
465ac1d1e1 | ||
|
|
18d6becebc | ||
|
|
943a4093ad | ||
|
|
b0917a5131 | ||
|
|
0972c4e340 | ||
|
|
3bbd0fdbcd | ||
|
|
43a237bf95 | ||
|
|
95f867b267 | ||
|
|
e96daf469a | ||
|
|
f2cdfe9e47 | ||
|
|
929b67e768 | ||
|
|
0573dc136a | ||
|
|
48588f6a9e | ||
|
|
8214b11da5 | ||
|
|
36090bf83e | ||
|
|
bffb2fe82f | ||
|
|
500cbf5d92 | ||
|
|
88579b9bf3 | ||
|
|
e8bb9bde99 | ||
|
|
0ee3cca4da | ||
|
|
f89ee6b7f2 | ||
|
|
aebfb52c85 | ||
|
|
667bd7af0e | ||
|
|
bbf69bc582 | ||
|
|
f2b7a3d002 | ||
|
|
3fd9e3ab56 | ||
|
|
8e11466a54 | ||
|
|
dade9797d5 | ||
|
|
97c1e65ffc | ||
|
|
b4e22f4a21 | ||
|
|
2b8ea9a233 | ||
|
|
58b6feb3ca | ||
|
|
41c4920d45 | ||
|
|
d1ddd8de98 | ||
|
|
f41307eb4a | ||
|
|
59de77feb0 | ||
|
|
8ebbcf6e80 | ||
|
|
24c6e836dd | ||
|
|
8e38b3898e | ||
|
|
ce9a5e35c9 | ||
|
|
b092779697 | ||
|
|
ab30a96d16 | ||
|
|
dab0fb16ad | ||
|
|
5be398bc99 | ||
|
|
fe4172957f | ||
|
|
ff3a59d347 | ||
|
|
f9aedea26f | ||
|
|
5abd2c7151 | ||
|
|
bfcaf4f37b | ||
|
|
5f4e1835bc | ||
|
|
c1f1ae6b64 | ||
|
|
c4fcc6c24e | ||
|
|
dd73ad9941 | ||
|
|
ac21f7569f | ||
|
|
4ef0158da4 | ||
|
|
4db3b3ba0e | ||
|
|
dc43d85323 | ||
|
|
62651f381c | ||
|
|
3e9098907a | ||
|
|
e18df250f8 | ||
|
|
be5c5a51da | ||
|
|
a728fad675 | ||
|
|
185629b310 | ||
|
|
30ebea4f2d | ||
|
|
b135c1eac2 | ||
|
|
88fef73d6f | ||
|
|
556a9039e9 | ||
|
|
cdfe6c21c1 | ||
|
|
b094ebdd66 | ||
|
|
526a7ddea6 | ||
|
|
bb5ad31cba | ||
|
|
549da2efed | ||
|
|
e5e586dc43 | ||
|
|
8a682beb0e | ||
|
|
699e9f75c9 | ||
|
|
759e30977b | ||
|
|
6cfdb49cc3 | ||
|
|
1195121bf0 | ||
|
|
8bc067b18b | ||
|
|
76f59f7b85 | ||
|
|
55ebb4671f | ||
|
|
8a9cf07063 | ||
|
|
ca9ff8cf19 | ||
|
|
7217d9c427 | ||
|
|
9d712ad8f1 | ||
|
|
f3e49e7010 | ||
|
|
ba94f1b920 | ||
|
|
edcd46dd67 | ||
|
|
5cf6c89dde | ||
|
|
58676b1f83 | ||
|
|
8ff7c30e5a | ||
|
|
cd989768d4 | ||
|
|
6cbdefe3d9 | ||
|
|
c6ecc0d8e8 | ||
|
|
e0f5663bf4 | ||
|
|
aafc8996c1 | ||
|
|
128da40cbf | ||
|
|
ea0460e97e | ||
|
|
d8e7123576 | ||
|
|
6f45ec655f | ||
|
|
ec17c168ea | ||
|
|
119b097521 | ||
|
|
c731633a84 | ||
|
|
6a4d6ade39 | ||
|
|
b3b4697fc9 | ||
|
|
72c706d697 | ||
|
|
8a7af24bd4 | ||
|
|
dd01bd3e5f | ||
|
|
59cade9f82 | ||
|
|
6bb9b79832 | ||
|
|
f8fe7b5803 | ||
|
|
4d6279d61c | ||
|
|
5ef581f328 | ||
|
|
20a59c343e | ||
|
|
d6feb522b7 | ||
|
|
951aee8292 | ||
|
|
6e2d7912b5 | ||
|
|
b20925b550 | ||
|
|
c8b213c190 | ||
|
|
942de9dce5 | ||
|
|
483f684b04 | ||
|
|
26774b4193 | ||
|
|
a7a597d609 | ||
|
|
1c12b9278a | ||
|
|
de4764bd05 | ||
|
|
c1e7a78d23 | ||
|
|
8894bb91cc | ||
|
|
c955126f01 | ||
|
|
ce53b48d04 | ||
|
|
6015aeddee | ||
|
|
7b04e30964 | ||
|
|
6794f5e783 | ||
|
|
6e41ceff39 | ||
|
|
7ab47ff0de | ||
|
|
92d24d8702 | ||
|
|
bcbfd46682 | ||
|
|
bfd96a695f | ||
|
|
f27e8534dc | ||
|
|
040cd7ddbf | ||
|
|
8d6b21a076 | ||
|
|
2d36b25017 | ||
|
|
1f9e4306ae | ||
|
|
5242e0b36e | ||
|
|
e50505532e | ||
|
|
f05ef18d55 | ||
|
|
f6eccd7277 | ||
|
|
4d1258c64b | ||
|
|
103cbfd038 | ||
|
|
47069ad3f4 | ||
|
|
317f620992 | ||
|
|
8f43694582 | ||
|
|
df30076ffd | ||
|
|
f81750617e | ||
|
|
e4534c4319 | ||
|
|
d6c09aae6b | ||
|
|
d3d5230d0c | ||
|
|
6f3c5c44a5 | ||
|
|
67b32ca14d | ||
|
|
bffaf477ea | ||
|
|
cba45ece12 | ||
|
|
3290d7f401 | ||
|
|
ef3827376d | ||
|
|
4ae8a91051 | ||
|
|
ff4819ac68 | ||
|
|
58af133853 | ||
|
|
8199cd2118 | ||
|
|
9f02b80cf1 | ||
|
|
d3e4e81168 | ||
|
|
38195c0a8f | ||
|
|
5fa11e4278 | ||
|
|
c39b52fcb5 | ||
|
|
ec65fc1e65 | ||
|
|
32d8646d96 | ||
|
|
e8835fc2b1 | ||
|
|
054a06c5dc | ||
|
|
9c61d2eb22 | ||
|
|
d66b6cfee6 | ||
|
|
89c0427b2f | ||
|
|
3fec10d447 | ||
|
|
f8b4981bfe | ||
|
|
130669a2f9 | ||
|
|
c2c79ee231 | ||
|
|
86f10bd702 | ||
|
|
b496a06fc0 | ||
|
|
f865a6cb37 | ||
|
|
89186ea4f8 | ||
|
|
fb19985186 | ||
|
|
ebc6e1221a | ||
|
|
2b91dcb700 | ||
|
|
9d2e333fd6 | ||
|
|
013ad1069c | ||
|
|
ec059717f6 | ||
|
|
418566db3f | ||
|
|
7be9463be6 | ||
|
|
51712bc7d6 | ||
|
|
7b889d22d2 | ||
|
|
e8aad989ec | ||
|
|
4006d64d60 | ||
|
|
d792d99375 | ||
|
|
b11036a2e5 | ||
|
|
01de69a250 | ||
|
|
5e1c2e7feb | ||
|
|
b842aa11e5 | ||
|
|
ff01078b60 | ||
|
|
b1e92293fc | ||
|
|
443f69bd82 | ||
|
|
f4decbf52e | ||
|
|
bd2c311e4f | ||
|
|
2dcab6d0b3 | ||
|
|
c68a97198f | ||
|
|
2702c3da2b | ||
|
|
da06e9afd5 | ||
|
|
1623f13539 | ||
|
|
5910982a4f | ||
|
|
74630b36b0 | ||
|
|
ace4a5d614 | ||
|
|
0d41947f64 | ||
|
|
78de3b3591 | ||
|
|
0a4a6e7ba3 | ||
|
|
090399b336 | ||
|
|
47afb15970 | ||
|
|
b1ba3376aa | ||
|
|
8c1e19e77c | ||
|
|
0801d1473c | ||
|
|
6d98878c72 | ||
|
|
2d404fdadc | ||
|
|
b264fde165 | ||
|
|
970ff25e5e | ||
|
|
9dd3eee65c | ||
|
|
957faa6651 | ||
|
|
cc7dcc6e81 | ||
|
|
f666cba104 | ||
|
|
7f35498919 | ||
|
|
297205ff91 | ||
|
|
6f99381d13 | ||
|
|
45a42b00ad | ||
|
|
6586858284 | ||
|
|
19cce15e54 | ||
|
|
62b8e4c46f | ||
|
|
74b7d27408 | ||
|
|
a60ffc0702 | ||
|
|
9b8524ba27 | ||
|
|
32526d77b8 | ||
|
|
ba7db8f7b3 | ||
|
|
04712ad252 | ||
|
|
5711a9e148 | ||
|
|
a0f40c2dfb | ||
|
|
34636016eb | ||
|
|
95027e329c | ||
|
|
c9778a73c7 | ||
|
|
628d2a0a0a | ||
|
|
cc0ff1ec1f | ||
|
|
6543540509 | ||
|
|
cd53fc6318 | ||
|
|
4934b6c4da | ||
|
|
59db38524b | ||
|
|
3f5cfc3a4b | ||
|
|
3443f02c0a | ||
|
|
9a012ca01e | ||
|
|
509ef34cca | ||
|
|
49c289a094 | ||
|
|
10e5d88fb6 | ||
|
|
ae64fb3fdb | ||
|
|
cac2fde504 | ||
|
|
bb38a96fd1 | ||
|
|
79dbcb10c9 | ||
|
|
7c80fdea58 | ||
|
|
5500a42744 | ||
|
|
ae46264707 | ||
|
|
f2d8665e54 | ||
|
|
db7110d6b2 | ||
|
|
4e06b597fe | ||
|
|
fe006d05d3 | ||
|
|
358b70e280 | ||
|
|
a060dde625 | ||
|
|
3cfed72af4 | ||
|
|
4c59989236 | ||
|
|
6c1adff5c8 | ||
|
|
9293bdca06 | ||
|
|
beeccbfb44 | ||
|
|
0d3d2e2e78 | ||
|
|
2af7605451 | ||
|
|
976cc1c86f | ||
|
|
cbbf3aa6c8 | ||
|
|
4f8ff98d5b | ||
|
|
d4fe81c290 | ||
|
|
cbdf03aa66 | ||
|
|
d756670c56 | ||
|
|
9ef7b0e64a | ||
|
|
2d7c0f7e5f | ||
|
|
ae98f6276e | ||
|
|
7424a5987b | ||
|
|
2d08749207 | ||
|
|
db50e98ae3 | ||
|
|
7ec0925c69 | ||
|
|
df1361aa43 | ||
|
|
6db04c86df | ||
|
|
bec80b443c | ||
|
|
333501fe55 | ||
|
|
063553d4f7 | ||
|
|
19b9e50281 | ||
|
|
2a68b4aeff | ||
|
|
5e25150521 | ||
|
|
cb183d3645 | ||
|
|
96bce301a0 | ||
|
|
360f5b7538 | ||
|
|
16fb1018a2 | ||
|
|
ad6fe855a9 | ||
|
|
c50c97d149 | ||
|
|
8b98ae15f0 | ||
|
|
261f84d5f5 | ||
|
|
29989ac24e | ||
|
|
ed817dc414 | ||
|
|
7494fa6bc9 | ||
|
|
62e50dbe52 | ||
|
|
30c090ba2d | ||
|
|
7ff82e6043 | ||
|
|
61d3e2fb49 | ||
|
|
fb18c1a0be | ||
|
|
5ec52f7471 | ||
|
|
ea447365fa | ||
|
|
60989d6766 | ||
|
|
84ec5aea26 | ||
|
|
444c13c6ea | ||
|
|
12d5e4f7d2 | ||
|
|
0fb1639915 | ||
|
|
03725c8e0c | ||
|
|
7129008428 | ||
|
|
3b832f507f | ||
|
|
b5849500f9 | ||
|
|
11b47b308b | ||
|
|
69b9b0bbc0 | ||
|
|
3c1088f030 | ||
|
|
b0067fee51 | ||
|
|
732c3dae89 | ||
|
|
d45bd67cae | ||
|
|
9200de5032 | ||
|
|
3fbbff5a47 | ||
|
|
c22efc2c3d | ||
|
|
8c0281bf70 | ||
|
|
720a4bc4a2 | ||
|
|
7fd93645b3 | ||
|
|
fcbfbca6d0 | ||
|
|
f2bca9491c | ||
|
|
b48f309ab6 | ||
|
|
0b1be3e63b | ||
|
|
248a05a916 | ||
|
|
2c141579dd | ||
|
|
8c129c10af | ||
|
|
63ce2a14fe | ||
|
|
f435ebb110 | ||
|
|
843f001bf6 | ||
|
|
0d28165c04 | ||
|
|
ee31bfbcd4 | ||
|
|
1c67d6802d | ||
|
|
5da8c86ec7 | ||
|
|
10a2d59ec1 | ||
|
|
34e8360b10 | ||
|
|
ca259ee4c3 | ||
|
|
2143952a1e | ||
|
|
6bab6e7151 | ||
|
|
d217c2e295 | ||
|
|
52bf0faaa5 | ||
|
|
3f3f2bfc61 | ||
|
|
f050864fb4 | ||
|
|
db11fc35f4 | ||
|
|
f47a2b10c0 | ||
|
|
344b4e7d60 | ||
|
|
7a23372489 | ||
|
|
9da15a8e58 | ||
|
|
50e0e4a07b | ||
|
|
5e1562ae4c | ||
|
|
8a0ed49623 | ||
|
|
a2ff8f9609 |
@@ -4180,6 +4180,60 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lukaskraic",
|
||||
"name": "Lukas Kraic",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/58696401?v=4",
|
||||
"profile": "https://github.com/lukaskraic",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mckaygerhard",
|
||||
"name": "Герхард PICCORO Lenz McKAY ",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1571724?v=4",
|
||||
"profile": "https://github-readme-stats.vercel.app/api?username=mckaygerhard",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "FlorestanII",
|
||||
"name": "Johannes Pollitt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15015119?v=4",
|
||||
"profile": "https://github.com/FlorestanII",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "strobelm",
|
||||
"name": "Michael Strobel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14185442?v=4",
|
||||
"profile": "https://strobelm.de",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nickwest",
|
||||
"name": "Nicky West",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/634790?v=4",
|
||||
"profile": "http://nickwest.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "akaspeh1",
|
||||
"name": "akaspeh1",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1347327?v=4",
|
||||
"profile": "https://github.com/akaspeh1",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=db
|
||||
DB_SOCKET=null
|
||||
DB_PORT='3306'
|
||||
DB_DATABASE=snipeit
|
||||
DB_USERNAME=snipeit
|
||||
@@ -168,6 +169,7 @@ AWS_DEFAULT_REGION=null
|
||||
LOGIN_MAX_ATTEMPTS=5
|
||||
LOGIN_LOCKOUT_DURATION=60
|
||||
RESET_PASSWORD_LINK_EXPIRES=900
|
||||
INVITE_PASSWORD_LINK_EXPIRES=1500
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
|
||||
10
.env.example
10
.env.example
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_SOCKET=null
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
@@ -174,6 +175,7 @@ LOGIN_AUTOCOMPLETE=false
|
||||
RESET_PASSWORD_LINK_EXPIRES=15
|
||||
PASSWORD_CONFIRM_TIMEOUT=10800
|
||||
PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
||||
INVITE_PASSWORD_LINK_EXPIRES=1500
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
@@ -191,11 +193,17 @@ LDAP_TIME_LIM=600
|
||||
IMPORT_TIME_LIMIT=600
|
||||
IMPORT_MEMORY_LIMIT=500M
|
||||
REPORT_TIME_LIMIT=12000
|
||||
REQUIRE_SAML=false
|
||||
API_THROTTLE_PER_MINUTE=120
|
||||
CSV_ESCAPE_FORMULAS=true
|
||||
LIVEWIRE_URL_PREFIX=null
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SAML SETTINGS
|
||||
# --------------------------------------------
|
||||
REQUIRE_SAML=false
|
||||
SAML_KEY_SIZE=2048
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: HASHING
|
||||
# --------------------------------------------
|
||||
|
||||
2
.github/workflows/SA-codeql.yml
vendored
2
.github/workflows/SA-codeql.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
language: [ 'javascript' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/codacy-analysis.yml
vendored
4
.github/workflows/codacy-analysis.yml
vendored
@@ -32,11 +32,11 @@ jobs:
|
||||
steps:
|
||||
# Checkout the repository to the GitHub Actions runner
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||
- name: Run Codacy Analysis CLI
|
||||
uses: codacy/codacy-analysis-cli-action@v4.4.5
|
||||
uses: codacy/codacy-analysis-cli-action@v4.4.7
|
||||
with:
|
||||
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
||||
# You can also omit the token and run the tools that support default configurations
|
||||
|
||||
2
.github/workflows/crowdin-upload.yml
vendored
2
.github/workflows/crowdin-upload.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Crowdin push
|
||||
uses: crowdin/github-action@v2
|
||||
|
||||
2
.github/workflows/docker-alpine.yml
vendored
2
.github/workflows/docker-alpine.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
|
||||
2
.github/workflows/docker-ubuntu.yml
vendored
2
.github/workflows/docker-ubuntu.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
|
||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Docker Hub Description
|
||||
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
|
||||
|
||||
14
.github/workflows/tests-mysql.yml
vendored
14
.github/workflows/tests-mysql.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: none
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
@@ -76,4 +76,16 @@ jobs:
|
||||
DB_DATABASE: snipeit
|
||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
LOG_CHANNEL: single
|
||||
LOG_LEVEL: debug
|
||||
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 }}"
|
||||
coverage: none
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
@@ -75,4 +75,16 @@ jobs:
|
||||
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
|
||||
DB_USERNAME: snipeit
|
||||
DB_PASSWORD: password
|
||||
LOG_CHANNEL: single
|
||||
LOG_LEVEL: debug
|
||||
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 }}"
|
||||
coverage: none
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
@@ -61,4 +61,16 @@ jobs:
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
LOG_CHANNEL: single
|
||||
LOG_LEVEL: debug
|
||||
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
|
||||
|
||||
@@ -67,7 +67,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/80526133?v=4" width="110px;"/><br /><sub>AlexanderWPapyrus</sub>](https://github.com/AlexanderWPapyrus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [<img src="https://avatars.githubusercontent.com/u/306231?v=4" width="110px;"/><br /><sub>Alexandr Hacicheant</sub>](https://github.com/disc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [<img src="https://avatars.githubusercontent.com/u/3032891?v=4" width="110px;"/><br /><sub>Hex</sub>](https://hex128.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [<img src="https://avatars.githubusercontent.com/u/8697942?v=4" width="110px;"/><br /><sub>Arunas Skirius</sub>](https://github.com/arukompas)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [<img src="https://avatars.githubusercontent.com/u/104396?v=4" width="110px;"/><br /><sub>Ben Periton</sub>](https://github.com/benperiton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [<img src="https://avatars.githubusercontent.com/u/11906832?v=4" width="110px;"/><br /><sub>Byron Wolfman</sub>](https://wolfman.dev/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [<img src="https://avatars.githubusercontent.com/u/56485508?v=4" width="110px;"/><br /><sub>Calvin</sub>](https://github.com/CalvinSchwartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/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 -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
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('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||
|
||||
|
||||
// Map the LDAP attributes to the Snipe-IT user fields.
|
||||
$ldap_map = [
|
||||
"username" => Setting::getSettings()->ldap_username_field,
|
||||
"last_name" => Setting::getSettings()->ldap_lname_field,
|
||||
@@ -63,11 +65,17 @@ class LdapSync extends Command
|
||||
"emp_num" => Setting::getSettings()->ldap_emp_num,
|
||||
"email" => Setting::getSettings()->ldap_email,
|
||||
"phone" => Setting::getSettings()->ldap_phone_field,
|
||||
"mobile" => Setting::getSettings()->ldap_mobile,
|
||||
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
|
||||
"address" => Setting::getSettings()->ldap_address,
|
||||
"city" => Setting::getSettings()->ldap_city,
|
||||
"state" => Setting::getSettings()->ldap_state,
|
||||
"zip" => Setting::getSettings()->ldap_zip,
|
||||
"country" => Setting::getSettings()->ldap_country,
|
||||
"location" => Setting::getSettings()->ldap_location,
|
||||
"dept" => Setting::getSettings()->ldap_dept,
|
||||
"manager" => Setting::getSettings()->ldap_manager,
|
||||
"display_name" => Setting::getSettings()->ldap_display_name,
|
||||
];
|
||||
|
||||
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
||||
@@ -182,7 +190,7 @@ class LdapSync extends Command
|
||||
// Inject location information fields
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
$results[$i]['ldap_location_override'] = false;
|
||||
$results[$i]['location_id'] = 0;
|
||||
$results[$i]['location_id'] = null;
|
||||
}
|
||||
|
||||
// Grab subsets based on location-specific DNs, and overwrite location for these users.
|
||||
@@ -234,9 +242,11 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
|
||||
// Assign the mapped LDAP attributes for each user to the Snipe-IT user fields
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
$item = [];
|
||||
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
|
||||
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? '';
|
||||
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
|
||||
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
|
||||
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
|
||||
@@ -244,8 +254,13 @@ class LdapSync extends Command
|
||||
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
|
||||
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
||||
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
|
||||
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? '';
|
||||
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
|
||||
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? '';
|
||||
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? '';
|
||||
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? '';
|
||||
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
|
||||
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? '';
|
||||
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
|
||||
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
|
||||
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
|
||||
@@ -278,6 +293,9 @@ class LdapSync extends Command
|
||||
if($ldap_map["username"] != null){
|
||||
$user->username = $item['username'];
|
||||
}
|
||||
if($ldap_map["display_name"] != null){
|
||||
$user->display_name = $item['display_name'];
|
||||
}
|
||||
if($ldap_map["last_name"] != null){
|
||||
$user->last_name = $item['lastname'];
|
||||
}
|
||||
@@ -293,6 +311,9 @@ class LdapSync extends Command
|
||||
if($ldap_map["phone"] != null){
|
||||
$user->phone = $item['telephone'];
|
||||
}
|
||||
if($ldap_map["mobile"] != null){
|
||||
$user->mobile = $item['mobile'];
|
||||
}
|
||||
if($ldap_map["jobtitle"] != null){
|
||||
$user->jobtitle = $item['jobtitle'];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use App\Models\Ldap;
|
||||
|
||||
/**
|
||||
* Check if a given ip is in a network
|
||||
@@ -160,7 +161,15 @@ class LdapTroubleshooter extends Command
|
||||
$output[] = "-x";
|
||||
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
||||
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
|
||||
$output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword));
|
||||
|
||||
try {
|
||||
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||
} catch (\Exception $e) {
|
||||
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$output[] = "-w ". escapeshellarg($w);
|
||||
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
|
||||
if($settings->ldap_tls) {
|
||||
$this->line("# adding STARTTLS option");
|
||||
@@ -171,6 +180,23 @@ class LdapTroubleshooter extends Command
|
||||
$this->line(implode(" \\\n",$output));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
//PHP Version check for warning
|
||||
$php_version = phpversion();
|
||||
list($major, $minor, $patch) = explode('.', $php_version);
|
||||
if (
|
||||
$major < 8 ||
|
||||
($major == 8 && $minor < 3) ||
|
||||
($major == 8 && $minor == 3 && $patch < 21) ||
|
||||
($major == 8 && $minor == 4 && $patch < 7)
|
||||
) {
|
||||
$this->warn("PHP Version: $php_version WARNING - Versions before 8.3.21 or 8.4.7 will return INCONSISTENT results!");
|
||||
if (!$this->confirm("Are you sure you wish to continue?")) {
|
||||
$this->warn("ABORTING");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->option('force')) {
|
||||
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
|
||||
if(!$confirmation) {
|
||||
@@ -179,7 +205,7 @@ class LdapTroubleshooter extends Command
|
||||
}
|
||||
}
|
||||
//$this->line(print_r($settings,true));
|
||||
$this->info("STAGE 1: Checking settings");
|
||||
$this->line("STAGE 1: Checking settings");
|
||||
if(!$settings->ldap_enabled) {
|
||||
$this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)");
|
||||
}
|
||||
@@ -210,32 +236,40 @@ class LdapTroubleshooter extends Command
|
||||
$this->info("Determined LDAP hostname to be: ".$parsed['host']);
|
||||
}
|
||||
|
||||
$this->info("Performing DNS lookup of: ".$parsed['host']);
|
||||
$ips = dns_get_record($parsed['host']);
|
||||
$raw_ips = [];
|
||||
|
||||
//$this->info("Host IP is: ".print_r($ips,true));
|
||||
if (inet_pton($parsed['host']) !== false) {
|
||||
$this->line($parsed['host'] . " already looks like an address; skipping DNS lookup");
|
||||
$raw_ips[] = $parsed['host'];
|
||||
} else {
|
||||
$this->line("Performing DNS lookup of: " . $parsed['host']);
|
||||
$ips = dns_get_record($parsed['host']);
|
||||
|
||||
if(!$ips || count($ips) == 0) {
|
||||
$this->error("ERROR: DNS lookup of host: ".$parsed['host']." has failed. ABORTING.");
|
||||
exit(-1);
|
||||
}
|
||||
$this->debugout("IP's? ".print_r($ips,true));
|
||||
foreach($ips as $ip) {
|
||||
if(!isset($ip['ip'])) {
|
||||
continue;
|
||||
//$this->info("Host IP is: ".print_r($ips,true));
|
||||
|
||||
if (!$ips || count($ips) == 0) {
|
||||
$this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING.");
|
||||
exit(-1);
|
||||
}
|
||||
$raw_ips[]=$ip['ip'];
|
||||
if($ip['ip'] == "127.0.0.1") {
|
||||
$this->debugout("IP's? " . print_r($ips, true));
|
||||
foreach ($ips as $ip) {
|
||||
if (!isset($ip['ip'])) {
|
||||
continue;
|
||||
}
|
||||
$raw_ips[] = $ip['ip'];
|
||||
}
|
||||
}
|
||||
foreach ($raw_ips as $ip) {
|
||||
if ($ip == "127.0.0.1") {
|
||||
$this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong");
|
||||
}
|
||||
if(ip_in_range($ip['ip'],'10.0.0.0/8') || ip_in_range($ip['ip'],'192.168.0.0/16') || ip_in_range($ip['ip'], '172.16.0.0/12')) {
|
||||
if (ip_in_range($ip, '10.0.0.0/8') || ip_in_range($ip, '192.168.0.0/16') || ip_in_range($ip, '172.16.0.0/12')) {
|
||||
$this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("STAGE 2: Checking basic network connectivity");
|
||||
$ports = [389,636];
|
||||
$this->line("STAGE 2: Checking basic network connectivity");
|
||||
$ports = [636, 389];
|
||||
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
|
||||
$ports[] = $parsed['port'];
|
||||
}
|
||||
@@ -246,7 +280,7 @@ class LdapTroubleshooter extends Command
|
||||
$errstr = '';
|
||||
$timeout = 30.0;
|
||||
$result = '';
|
||||
$this->info("Attempting to connect to port: ".$port." - may take up to $timeout seconds");
|
||||
$this->line("Attempting to connect to port: " . $port . " - may take up to $timeout seconds");
|
||||
try {
|
||||
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
|
||||
} catch(Exception $e) {
|
||||
@@ -265,9 +299,9 @@ class LdapTroubleshooter extends Command
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
$this->info("STAGE 3: Determine encryption algorithm, if any");
|
||||
$this->line("STAGE 3: Determine encryption algorithm, if any");
|
||||
|
||||
$ldap_urls = [];
|
||||
$ldap_urls = []; // [url, cert-check?, start_tls?]
|
||||
$pretty_ldap_urls = [];
|
||||
foreach($open_ports as $port) {
|
||||
$this->line("Trying TLS first for port $port");
|
||||
@@ -275,35 +309,46 @@ class LdapTroubleshooter extends Command
|
||||
if($this->test_anonymous_bind($ldap_url)) {
|
||||
$this->info("Anonymous bind succesful to $ldap_url!");
|
||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
|
||||
$pretty_ldap_urls[] = [$ldap_url, "enabled", "n/a (no)"];
|
||||
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
|
||||
} else {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
|
||||
}
|
||||
|
||||
if($this->test_anonymous_bind($ldap_url, false)) {
|
||||
$this->info("Anonymous bind succesful to $ldap_url with certifcate-checks disabled");
|
||||
$ldap_urls[] = [ $ldap_url, false, false ];
|
||||
$pretty_ldap_urls[] = [ $ldap_url, "no", "no" ];
|
||||
$this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled");
|
||||
$ldap_urls[] = [$ldap_url, false, false];
|
||||
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"];
|
||||
continue;
|
||||
} else {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
|
||||
}
|
||||
|
||||
// now switching to ldap:// URL's from ldaps://
|
||||
$ldap_url = "ldap://".$parsed['host'].":$port";
|
||||
|
||||
if($this->test_anonymous_bind($ldap_url, true, true)) {
|
||||
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
|
||||
$ldap_urls[] = [ $ldap_url, true, true ];
|
||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "YES" ];
|
||||
$pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"];
|
||||
continue;
|
||||
} else {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without STARTTLS");
|
||||
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without certificate checks.");
|
||||
}
|
||||
|
||||
if ($this->test_anonymous_bind($ldap_url, false, true)) {
|
||||
$this->info("Plain connection to $ldap_url with STARTTLS and cert checks *disabled* successful!");
|
||||
$ldap_urls[] = [$ldap_url, false, true];
|
||||
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "STARTTLS ENABLED"];
|
||||
continue;
|
||||
} else {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled, and cert checks disabled. Trying without STARTTLS");
|
||||
}
|
||||
|
||||
if($this->test_anonymous_bind($ldap_url)) {
|
||||
$this->info("Plain connection to $ldap_url succesful!");
|
||||
$ldap_urls[] = [ $ldap_url, true, false ];
|
||||
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
|
||||
$pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"];
|
||||
continue;
|
||||
} else {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
|
||||
@@ -313,23 +358,29 @@ class LdapTroubleshooter extends Command
|
||||
$this->debugout(print_r($ldap_urls,true));
|
||||
|
||||
if(count($ldap_urls) > 0 ) {
|
||||
$this->info("Found working LDAP URL's: ");
|
||||
$this->debugout("Found working LDAP URL's: ");
|
||||
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
|
||||
$this->info("LDAP URL: ".$ldap_url[0]);
|
||||
$this->info($ldap_url[0]. ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled"). ($ldap_url[2] ? " STARTTLS Enabled ": " STARTTLS Disabled"));
|
||||
$this->debugout("LDAP URL: " . $ldap_url[0]);
|
||||
$this->debugout($ldap_url[0] . ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled") . ($ldap_url[2] ? " STARTTLS Enabled " : " STARTTLS Disabled"));
|
||||
}
|
||||
$this->table(["URL", "Cert Checks Enabled?", "STARTTLS Enabled?"],$pretty_ldap_urls);
|
||||
$this->table(["URL", "Cert Checks?", "STARTTLS?"], $pretty_ldap_urls);
|
||||
} else {
|
||||
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->info("STAGE 4: Test Administrative Bind for LDAP Sync");
|
||||
$this->line("STAGE 4: Test Administrative Bind for LDAP Sync");
|
||||
foreach($ldap_urls AS $ldap_url) {
|
||||
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, Crypt::decrypt($settings->ldap_pword));
|
||||
try {
|
||||
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||
} catch (\Exception $e) {
|
||||
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||
exit(0);
|
||||
}
|
||||
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w);
|
||||
}
|
||||
|
||||
$this->info("STAGE 5: Test BaseDN");
|
||||
$this->line("STAGE 5: Test BaseDN");
|
||||
//grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
|
||||
$all_defined_constants = get_defined_constants();
|
||||
$ldap_constants = [];
|
||||
@@ -341,16 +392,23 @@ class LdapTroubleshooter extends Command
|
||||
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
||||
|
||||
foreach($ldap_urls AS $ldap_url) {
|
||||
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword),$settings)) {
|
||||
try {
|
||||
$w = Crypt::Decrypt($settings->ldap_pword);
|
||||
} catch (\Exception $e) {
|
||||
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,$w,$settings)) {
|
||||
$this->info("Success getting informational bind!");
|
||||
} else {
|
||||
$this->error("Unable to get information from bind.");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("STAGE 6: Test LDAP Login to Snipe-IT");
|
||||
$this->line("STAGE 6: Test LDAP Login to Snipe-IT");
|
||||
foreach($ldap_urls AS $ldap_url) {
|
||||
$this->info("Starting auth to ".$ldap_url[0]);
|
||||
$this->line("Starting auth to " . $ldap_url[0]);
|
||||
while(true) {
|
||||
$with_tls = $ldap_url[1] ? "with": "without";
|
||||
$with_startssl = $ldap_url[2] ? "using": "not using";
|
||||
@@ -359,7 +417,12 @@ class LdapTroubleshooter extends Command
|
||||
}
|
||||
$username = $this->ask("Username");
|
||||
$password = $this->secret("Password");
|
||||
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
|
||||
$results = $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
|
||||
if ($results) {
|
||||
$this->info("Success authenticating with " . $username);
|
||||
} else {
|
||||
$this->error("Unable to authenticate with " . $username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,14 +431,17 @@ class LdapTroubleshooter extends Command
|
||||
|
||||
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
|
||||
{
|
||||
if ($check_cert) {
|
||||
$this->line("we *ARE* checking certs");
|
||||
Ldap::ignoreCertificates(false);
|
||||
|
||||
} else {
|
||||
$this->line("we are IGNORING certs");
|
||||
Ldap::ignoreCertificates(true);
|
||||
}
|
||||
$lconn = ldap_connect($ldap_url);
|
||||
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3?
|
||||
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
|
||||
if(!$check_cert) {
|
||||
putenv('LDAPTLS_REQCERT=never'); // This is horrible; is this *really* the only way to do it?
|
||||
} else {
|
||||
putenv('LDAPTLS_REQCERT'); // have to very explicitly and manually *UN* set the env var here to ensure it works
|
||||
}
|
||||
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
|
||||
// client-side TLS certificate support for LDAP (Google Secure LDAP)
|
||||
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
|
||||
@@ -404,9 +470,10 @@ class LdapTroubleshooter extends Command
|
||||
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
|
||||
try {
|
||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||
$this->info("gonna try to bind now, this can take a while if we mess it up");
|
||||
$this->line("Attempting to bind now, this can take a while if we mess it up");
|
||||
$bind_results = ldap_bind($lconn);
|
||||
$this->info("Bind results are: ".$bind_results." which translate into boolean: ".(bool)$bind_results);
|
||||
$this->line("Bind results are: " . $bind_results . " which translate into boolean: " . (bool)$bind_results);
|
||||
ldap_close($lconn);
|
||||
return (bool)$bind_results;
|
||||
} catch (Exception $e) {
|
||||
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
|
||||
@@ -421,6 +488,7 @@ class LdapTroubleshooter extends Command
|
||||
try {
|
||||
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||
$bind_results = ldap_bind($lconn, $username, $password);
|
||||
ldap_close($lconn);
|
||||
if(!$bind_results) {
|
||||
$this->error("WARNING: Failed to bind to $ldap_url as $username");
|
||||
return false;
|
||||
@@ -446,22 +514,62 @@ class LdapTroubleshooter extends Command
|
||||
return false;
|
||||
}
|
||||
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
||||
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
||||
$results = ldap_get_entries($conn, $result);
|
||||
$cleaned_results = $this->ldap_results_cleaner($results);
|
||||
$this->line(print_r($cleaned_results,true));
|
||||
//okay, great - now how do we display those results? I have no idea.
|
||||
$cleaned_results = [];
|
||||
try {
|
||||
// This _may_ only work for Active Directory?
|
||||
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
||||
$results = ldap_get_entries($conn, $result);
|
||||
$cleaned_results = $this->ldap_results_cleaner($results);
|
||||
//$this->line(print_r($cleaned_results,true));
|
||||
$default_naming_contexts = $cleaned_results[0]['namingcontexts'];
|
||||
$this->info("Default Naming Contexts:");
|
||||
$this->info(implode(", ", $default_naming_contexts));
|
||||
//okay, great - now how do we display those results? I have no idea.
|
||||
} catch (\Exception $e) {
|
||||
$this->error("Unable to get base naming contexts - here's what we *did* get:");
|
||||
$this->line(print_r($cleaned_results, true));
|
||||
}
|
||||
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
|
||||
$this->comment("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
|
||||
$this->debugout("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
|
||||
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
|
||||
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
|
||||
$entries = ldap_get_entries($conn, $search_results);
|
||||
$this->info("Printing first 10 results: ");
|
||||
for($i=0;$i<10;$i++) {
|
||||
$this->info($search_results[$i]);
|
||||
$pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10);
|
||||
//print_r($data);
|
||||
$headers = [];
|
||||
foreach ($pretty_data as $row) {
|
||||
//populate headers
|
||||
foreach ($row as $key => $value) {
|
||||
//skip objectsid and objectguid because it junks up output
|
||||
if ($key == "objectsid" || $key == "objectguid") {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($key, $headers)) {
|
||||
$headers[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
$table = [];
|
||||
//repeat again to populate table
|
||||
foreach ($pretty_data as $row) {
|
||||
$newrow = [];
|
||||
foreach ($headers as $header) {
|
||||
if (is_array(@$row[$header])) {
|
||||
$newrow[] = "[" . implode(", ", $row[$header]) . "]";
|
||||
} else {
|
||||
$newrow[] = @$row[$header];
|
||||
}
|
||||
}
|
||||
$table[] = $newrow;
|
||||
}
|
||||
|
||||
$this->table($headers, $table);
|
||||
} catch (\Exception $e) {
|
||||
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
ldap_close($conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -477,7 +585,7 @@ class LdapTroubleshooter extends Command
|
||||
{
|
||||
if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
|
||||
// POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout)
|
||||
$this->info('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
|
||||
$this->line('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
|
||||
return $function();
|
||||
} else {
|
||||
$parent_pid = posix_getpid();
|
||||
@@ -514,4 +622,6 @@ class LdapTroubleshooter extends Command
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class MoveUploadsToNewDisk extends Command
|
||||
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
|
||||
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
|
||||
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
|
||||
$private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*");
|
||||
$private_uploads['assetmodels'] = glob('storage/private_uploads/models'."/*.*");
|
||||
$private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*");
|
||||
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
|
||||
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use Schema;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -59,6 +59,9 @@ class PaveIt extends Command
|
||||
'migrations',
|
||||
'settings',
|
||||
'users',
|
||||
'telescope_entries',
|
||||
'telescope_entries_tags',
|
||||
'telescope_monitoring',
|
||||
];
|
||||
|
||||
// We only need to find out what these are so we can nuke these columns on the assets table.
|
||||
@@ -66,8 +69,8 @@ class PaveIt extends Command
|
||||
foreach ($custom_fields as $custom_field) {
|
||||
$this->info('DROP the '.$custom_field->db_column.' column from assets as well.');
|
||||
|
||||
if (\Schema::hasColumn('assets', $custom_field->db_column)) {
|
||||
\Schema::table('assets', function ($table) use ($custom_field) {
|
||||
if (Schema::hasColumn('assets', $custom_field->db_column)) {
|
||||
Schema::table('assets', function ($table) use ($custom_field) {
|
||||
$table->dropColumn($custom_field->db_column);
|
||||
});
|
||||
}
|
||||
@@ -84,8 +87,8 @@ class PaveIt extends Command
|
||||
}
|
||||
|
||||
// Leave in the demo oauth keys so we don't have to reset them every day in the demos
|
||||
\DB::statement('delete from oauth_clients WHERE id > 2');
|
||||
\DB::statement('delete from oauth_access_tokens WHERE id > 2');
|
||||
DB::statement('delete from oauth_clients WHERE id > 2');
|
||||
DB::statement('delete from oauth_access_tokens WHERE user_id > 2');
|
||||
|
||||
}
|
||||
}
|
||||
@@ -62,19 +62,19 @@ class Purge extends Command
|
||||
$assetcount = $assets->count();
|
||||
$this->info($assets->count().' assets purged.');
|
||||
$asset_assoc = 0;
|
||||
$asset_maintenances = 0;
|
||||
$maintenances = 0;
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$this->info('- Asset "'.$asset->present()->name().'" deleted.');
|
||||
$this->info('- Asset "'.$asset->display_name.'" deleted.');
|
||||
$asset_assoc += $asset->assetlog()->count();
|
||||
$asset->assetlog()->forceDelete();
|
||||
$asset_maintenances += $asset->assetmaintenances()->count();
|
||||
$asset->assetmaintenances()->forceDelete();
|
||||
$maintenances += $asset->maintenances()->count();
|
||||
$asset->maintenances()->forceDelete();
|
||||
$asset->forceDelete();
|
||||
}
|
||||
|
||||
$this->info($asset_assoc.' corresponding log records purged.');
|
||||
$this->info($asset_maintenances.' corresponding maintenance records purged.');
|
||||
$this->info($maintenances.' corresponding maintenance records purged.');
|
||||
|
||||
$locations = Location::whereNotNull('deleted_at')->withTrashed()->get();
|
||||
$this->info($locations->count().' locations purged.');
|
||||
|
||||
@@ -243,6 +243,8 @@ class RestoreFromBackup extends Command
|
||||
$private_dirs = [
|
||||
'storage/private_uploads/accessories',
|
||||
'storage/private_uploads/assetmodels',
|
||||
'storage/private_uploads/maintenances',
|
||||
'storage/private_uploads/models',
|
||||
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
|
||||
'storage/private_uploads/audits',
|
||||
'storage/private_uploads/components',
|
||||
@@ -260,9 +262,10 @@ class RestoreFromBackup extends Command
|
||||
];
|
||||
$public_dirs = [
|
||||
'public/uploads/accessories',
|
||||
'public/uploads/assetmodels',
|
||||
'public/uploads/maintenances',
|
||||
'public/uploads/assets', // these are asset _pictures_, not asset files
|
||||
'public/uploads/avatars',
|
||||
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
|
||||
'public/uploads/categories',
|
||||
'public/uploads/companies',
|
||||
'public/uploads/components',
|
||||
@@ -329,9 +332,9 @@ class RestoreFromBackup extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
|
||||
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', 'avif'
|
||||
];
|
||||
|
||||
$good_extensions = config('filesystems.allowed_upload_extensions_array');
|
||||
|
||||
foreach (array_merge($private_files, $public_files) as $file) {
|
||||
$has_wildcard = (strpos($file, '*') !== false);
|
||||
if ($has_wildcard) {
|
||||
|
||||
@@ -77,7 +77,7 @@ class SendAcceptanceReminder extends Command
|
||||
if(!$email){
|
||||
$no_email_list[] = [
|
||||
'id' => $acceptance->assignedTo?->id,
|
||||
'name' => $acceptance->assignedTo?->present()->fullName(),
|
||||
'name' => $acceptance->assignedTo?->display_name,
|
||||
];
|
||||
} else {
|
||||
$count++;
|
||||
|
||||
@@ -116,18 +116,35 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
|
||||
default:
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This handles API validation exceptions that happen at the Form Request level, so they
|
||||
// never even get to the controller where we normally nicely format JSON responses
|
||||
if ($e instanceof ValidationException) {
|
||||
$response = $this->invalidJson($request, $e);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// This is traaaaash but it handles models that are not found while using route model binding :(
|
||||
// The only alternative is to set that at *each* route, which is crazypants
|
||||
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
||||
$ids = method_exists($e, 'getIds') ? $e->getIds() : [];
|
||||
|
||||
if (in_array('bulkedit', $ids, true)) {
|
||||
$error_array = session()->get('bulk_asset_errors');
|
||||
return redirect()
|
||||
->route('hardware.index')
|
||||
->withErrors($error_array, 'bulk_asset_errors')
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// This gets the MVC model name from the exception and formats in a way that's less fugly
|
||||
$model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))));
|
||||
$model_name = trim(strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))));
|
||||
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
|
||||
|
||||
// Sigh.
|
||||
@@ -143,7 +160,7 @@ class Handler extends ExceptionHandler
|
||||
$route = 'maintenances.index';
|
||||
} elseif ($route === 'licenseseats.index') {
|
||||
$route = 'licenses.index';
|
||||
} elseif ($route === 'customfields.index') {
|
||||
} elseif (($route === 'customfieldsets.index') || ($route === 'customfields.index')) {
|
||||
$route = 'fields.index';
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Carbon\Carbon;
|
||||
@@ -81,6 +82,13 @@ class Helper
|
||||
'zu' => 'zu-ZA', // Zulu
|
||||
];
|
||||
|
||||
public static function hasRtl($value) {
|
||||
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
|
||||
return preg_match($rtlChar, $value) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simple helper to invoke the markdown parser
|
||||
*
|
||||
@@ -876,6 +884,48 @@ class Helper
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is a video, so we can show a preview
|
||||
*
|
||||
* @param File $file
|
||||
* @return string | Boolean
|
||||
* @author [B. Wetherington] [<bwetherington@grokability.com>]
|
||||
* @since [v8.1.18]
|
||||
*/
|
||||
public static function checkUploadIsVideo($file)
|
||||
{
|
||||
$finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
||||
$filetype = @finfo_file($finfo, $file);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (($filetype == 'video/mp4') || ($filetype == 'video/quicktime') || ($filetype == 'video/mpeg') || ($filetype == 'video/ogg') || ($filetype == 'video/webm') || ($filetype == 'video/x-msvide')) {
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is audio, so we can show a preview
|
||||
*
|
||||
* @param File $file
|
||||
* @return string | Boolean
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
*/
|
||||
public static function checkUploadIsAudio($file)
|
||||
{
|
||||
$finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
||||
$filetype = @finfo_file($finfo, $file);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (($filetype == 'audio/mpeg') || ($filetype == 'audio/ogg')) {
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the permissions in the permissions config file and determines if
|
||||
* permissions are granted based on a $selected_arr array.
|
||||
@@ -1154,22 +1204,42 @@ class Helper
|
||||
'webp' => 'far fa-image',
|
||||
'avif' => 'far fa-image',
|
||||
'svg' => 'fas fa-vector-square',
|
||||
|
||||
// word
|
||||
'doc' => 'far fa-file-word',
|
||||
'docx' => 'far fa-file-word',
|
||||
|
||||
// Excel
|
||||
'xls' => 'far fa-file-excel',
|
||||
'xlsx' => 'far fa-file-excel',
|
||||
'ods' => 'far fa-file-excel',
|
||||
|
||||
// Presentation
|
||||
'ppt' => 'far fa-file-powerpoint',
|
||||
'odp' => 'far fa-file-powerpoint',
|
||||
|
||||
// archive
|
||||
'zip' => 'fas fa-file-archive',
|
||||
'rar' => 'fas fa-file-archive',
|
||||
|
||||
//Text
|
||||
'odt' => 'far fa-file-alt',
|
||||
'txt' => 'far fa-file-alt',
|
||||
'rtf' => 'far fa-file-alt',
|
||||
'xml' => 'fas fa-code',
|
||||
|
||||
// Misc
|
||||
'pdf' => 'far fa-file-pdf',
|
||||
'lic' => 'far fa-save',
|
||||
|
||||
// video
|
||||
'mov' => 'fa-solid fa-video',
|
||||
'mp4' => 'fa-solid fa-video',
|
||||
|
||||
// audio
|
||||
'ogg' => 'fa-solid fa-file-audio',
|
||||
'mp3' => 'fa-solid fa-file-audio',
|
||||
'wav' => 'fa-solid fa-file-audio',
|
||||
];
|
||||
|
||||
if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
|
||||
@@ -1480,68 +1550,59 @@ class Helper
|
||||
}
|
||||
|
||||
|
||||
static public function getRedirectOption($request, $id, $table, $item_id = null)
|
||||
static public function getRedirectOption($request, $id, $table, $item_id = null) : RedirectResponse
|
||||
{
|
||||
|
||||
$redirect_option = Session::get('redirect_option');
|
||||
$checkout_to_type = Session::get('checkout_to_type');
|
||||
$redirect_option = Session::get('redirect_option') ?? $request->redirect_option;
|
||||
$checkout_to_type = Session::get('checkout_to_type') ?? null;
|
||||
$checkedInFrom = Session::get('checkedInFrom');
|
||||
$other_redirect = Session::get('other_redirect');
|
||||
$backUrl = Session::pull('back_url', route('home'));
|
||||
|
||||
// return to previous page
|
||||
if ($redirect_option === 'back') {
|
||||
return redirect()->to($backUrl);
|
||||
}
|
||||
|
||||
// return to index
|
||||
if ($redirect_option == 'index') {
|
||||
switch ($table) {
|
||||
case "Assets":
|
||||
return route('hardware.index');
|
||||
case "Users":
|
||||
return route('users.index');
|
||||
case "Licenses":
|
||||
return route('licenses.index');
|
||||
case "Accessories":
|
||||
return route('accessories.index');
|
||||
case "Components":
|
||||
return route('components.index');
|
||||
case "Consumables":
|
||||
return route('consumables.index');
|
||||
}
|
||||
return match ($table) {
|
||||
'Assets' => redirect()->route('hardware.index'),
|
||||
'Users' => redirect()->route('users.index'),
|
||||
'Licenses' => redirect()->route('licenses.index'),
|
||||
'Accessories' => redirect()->route('accessories.index'),
|
||||
'Components' => redirect()->route('components.index'),
|
||||
'Consumables' => redirect()->route('consumables.index'),
|
||||
};
|
||||
}
|
||||
|
||||
// return to thing being assigned
|
||||
if ($redirect_option == 'item') {
|
||||
switch ($table) {
|
||||
case "Assets":
|
||||
return route('hardware.show', $id ?? $item_id);
|
||||
case "Users":
|
||||
return route('users.show', $id ?? $item_id);
|
||||
case "Licenses":
|
||||
return route('licenses.show', $id ?? $item_id);
|
||||
case "Accessories":
|
||||
return route('accessories.show', $id ?? $item_id);
|
||||
case "Components":
|
||||
return route('components.show', $id ?? $item_id);
|
||||
case "Consumables":
|
||||
return route('consumables.show', $id ?? $item_id);
|
||||
}
|
||||
return match ($table) {
|
||||
'Assets' => redirect()->route('hardware.show', $id ?? $item_id),
|
||||
'Users' => redirect()->route('users.show', $id ?? $item_id),
|
||||
'Licenses' => redirect()->route('licenses.show', $id ?? $item_id),
|
||||
'Accessories' => redirect()->route('accessories.show', $id ?? $item_id),
|
||||
'Components' => redirect()->route('components.show', $id ?? $item_id),
|
||||
'Consumables' => redirect()->route('consumables.show', $id ?? $item_id),
|
||||
};
|
||||
}
|
||||
|
||||
// return to assignment target
|
||||
if ($redirect_option == 'target') {
|
||||
switch ($checkout_to_type) {
|
||||
case 'user':
|
||||
return route('users.show', $request->assigned_user ?? $checkedInFrom);
|
||||
case 'location':
|
||||
return route('locations.show', $request->assigned_location ?? $checkedInFrom);
|
||||
case 'asset':
|
||||
return route('hardware.show', $request->assigned_asset ?? $checkedInFrom);
|
||||
}
|
||||
return match ($checkout_to_type) {
|
||||
'user' => redirect()->route('users.show', $request->assigned_user ?? $checkedInFrom),
|
||||
'location' => redirect()->route('locations.show', $request->assigned_location ?? $checkedInFrom),
|
||||
'asset' => redirect()->route('hardware.show', $request->assigned_asset ?? $checkedInFrom),
|
||||
};
|
||||
}
|
||||
|
||||
// return to somewhere else
|
||||
if ($redirect_option == 'other_redirect') {
|
||||
switch ($other_redirect) {
|
||||
case 'audit':
|
||||
return route('assets.audit.due');
|
||||
}
|
||||
return match ($other_redirect) {
|
||||
'audit' => redirect()->route('assets.audit.due'),
|
||||
'model' => redirect()->route('models.show', $request->model_id),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1652,5 +1713,5 @@ class Helper
|
||||
}
|
||||
}
|
||||
return $mismatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class IconHelper
|
||||
case 'clone':
|
||||
return 'far fa-clone';
|
||||
case 'delete':
|
||||
case 'upload deleted':
|
||||
return 'fas fa-trash';
|
||||
case 'create':
|
||||
return 'fa-solid fa-plus';
|
||||
@@ -43,6 +44,8 @@ class IconHelper
|
||||
return 'fa-regular fa-envelope';
|
||||
case 'phone':
|
||||
return 'fa-solid fa-phone';
|
||||
case 'mobile':
|
||||
return 'fas fa-mobile-screen-button';
|
||||
case 'long-arrow-right':
|
||||
return 'fas fa-long-arrow-alt-right';
|
||||
case 'download':
|
||||
@@ -151,6 +154,7 @@ class IconHelper
|
||||
case 'location':
|
||||
return 'fas fa-map-marker-alt';
|
||||
case 'superadmin':
|
||||
case 'admin':
|
||||
return 'fas fa-crown';
|
||||
case 'print':
|
||||
return 'fa-solid fa-print';
|
||||
|
||||
@@ -16,38 +16,84 @@ class StorageHelper
|
||||
$disk = config('filesystems.default');
|
||||
}
|
||||
switch (config("filesystems.disks.$disk.driver")) {
|
||||
case 'local':
|
||||
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
|
||||
case 'local':
|
||||
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
|
||||
|
||||
case 's3':
|
||||
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
|
||||
case 's3':
|
||||
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
|
||||
|
||||
default:
|
||||
return Storage::disk($disk)->download($filename);
|
||||
default:
|
||||
return Storage::disk($disk)->download($filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMediaType($file_with_path) {
|
||||
|
||||
// Get the file extension and determine the media type
|
||||
if (Storage::exists($file_with_path)) {
|
||||
$fileinfo = pathinfo($file_with_path);
|
||||
$extension = strtolower($fileinfo['extension']);
|
||||
switch ($extension) {
|
||||
case 'avif':
|
||||
case 'jpg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'svg':
|
||||
case 'webp':
|
||||
return 'image';
|
||||
case 'pdf':
|
||||
return 'pdf';
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'ogg':
|
||||
return 'audio';
|
||||
case 'mp4':
|
||||
case 'webm':
|
||||
case 'mov':
|
||||
return 'video';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return 'document';
|
||||
case 'txt':
|
||||
return 'text';
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
case 'ods':
|
||||
return 'spreadsheet';
|
||||
default:
|
||||
return $extension; // Default for unknown types
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This determines the file types that should be allowed inline and checks their fileinfo extension
|
||||
* to determine that they are safe to display inline.
|
||||
*
|
||||
* @author <A. Gianotto> [<snipe@snipe.net]>
|
||||
* @since v7.0.14
|
||||
* @param $file_with_path
|
||||
* @since v7.0.14
|
||||
* @param $file_with_path
|
||||
* @return bool
|
||||
*/
|
||||
public static function allowSafeInline($file_with_path) {
|
||||
public static function allowSafeInline($file_with_path)
|
||||
{
|
||||
|
||||
$allowed_inline = [
|
||||
'pdf',
|
||||
'svg',
|
||||
'jpg',
|
||||
'gif',
|
||||
'svg',
|
||||
'avif',
|
||||
'webp',
|
||||
'gif',
|
||||
'gif',
|
||||
'jpg',
|
||||
'mov',
|
||||
'mp3',
|
||||
'mp4',
|
||||
'ogg',
|
||||
'pdf',
|
||||
'png',
|
||||
'svg',
|
||||
'wav',
|
||||
'webm',
|
||||
'webp',
|
||||
];
|
||||
|
||||
|
||||
@@ -59,10 +105,24 @@ class StorageHelper
|
||||
|
||||
}
|
||||
|
||||
public static function getFiletype($file_with_path)
|
||||
{
|
||||
|
||||
// The file exists and is allowed to be displayed inline
|
||||
if (Storage::exists($file_with_path)) {
|
||||
return pathinfo($file_with_path, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decide whether to show the file inline or download it.
|
||||
*/
|
||||
public static function showOrDownloadFile($file, $filename) {
|
||||
public static function showOrDownloadFile($file, $filename)
|
||||
{
|
||||
|
||||
$headers = [];
|
||||
|
||||
|
||||
@@ -77,13 +77,30 @@ class AccessoriesController extends Controller
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
if ($request->has('use_cloned_image')) {
|
||||
$cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id'));
|
||||
if ($cloned_model_img) {
|
||||
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
||||
$new_image = 'accessories/'.$new_image_name;
|
||||
Storage::disk('public')->copy('accessories/'.$cloned_model_img->image, $new_image);
|
||||
$accessory->image = $new_image_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$accessory = $request->handleImages($accessory);
|
||||
}
|
||||
|
||||
if($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
// Was the accessory created?
|
||||
if ($accessory->save()) {
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success'));
|
||||
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
||||
->with('success', trans('admin/accessories/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
@@ -113,11 +130,12 @@ class AccessoriesController extends Controller
|
||||
|
||||
$this->authorize('create', Accessory::class);
|
||||
$cloned = clone $accessory;
|
||||
$accessory_to_clone = $accessory;
|
||||
$cloned->id = null;
|
||||
$cloned->deleted_at = '';
|
||||
$cloned->location_id = null;
|
||||
|
||||
return view('accessories/edit')
|
||||
->with('cloned_model', $accessory_to_clone)
|
||||
->with('item', $cloned);
|
||||
|
||||
}
|
||||
@@ -167,7 +185,8 @@ class AccessoriesController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($accessory->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success'));
|
||||
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
||||
->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
|
||||
@@ -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'));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,8 @@ class AccessoryCheckinController extends Controller
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
||||
->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
|
||||
|
||||
@@ -71,6 +71,7 @@ class AccessoryCheckoutController extends Controller
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
$target = $this->determineCheckoutTarget();
|
||||
session()->put(['checkout_to_type' => $target]);
|
||||
|
||||
$accessory->checkout_qty = $request->input('checkout_qty', 1);
|
||||
|
||||
@@ -97,7 +98,7 @@ class AccessoryCheckoutController extends Controller
|
||||
|
||||
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))
|
||||
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
||||
->with('success', trans('admin/accessories/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Events\CheckoutDeclined;
|
||||
use App\Events\ItemAccepted;
|
||||
use App\Events\ItemDeclined;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
@@ -20,17 +21,21 @@ use App\Models\License;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
|
||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Http\Controllers\SettingsController;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Carbon\Carbon;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use TCPDF;
|
||||
use App\Helpers\Helper;
|
||||
|
||||
class AcceptanceController extends Controller
|
||||
{
|
||||
@@ -148,6 +153,8 @@ class AcceptanceController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$assigned_user = User::find($acceptance->assigned_to_id);
|
||||
// this is horrible
|
||||
switch($acceptance->checkoutable_type){
|
||||
case 'App\Models\Asset':
|
||||
@@ -157,35 +164,30 @@ class AcceptanceController extends Controller
|
||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
$display_model = $asset_model->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Accessory':
|
||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
||||
$accessory = Accessory::find($item->id);
|
||||
$display_model = $accessory->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\LicenseSeat':
|
||||
$pdf_view_route ='account.accept.accept-license-eula';
|
||||
$license = License::find($item->license_id);
|
||||
$display_model = $license->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Component':
|
||||
$pdf_view_route ='account.accept.accept-component-eula';
|
||||
$pdf_view_route = 'account.accept.accept-component-eula';
|
||||
$component = Component::find($item->id);
|
||||
$display_model = $component->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Consumable':
|
||||
$pdf_view_route ='account.accept.accept-consumable-eula';
|
||||
$consumable = Consumable::find($item->id);
|
||||
$display_model = $consumable->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
}
|
||||
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
||||
@@ -216,7 +218,7 @@ class AcceptanceController extends Controller
|
||||
} elseif (!is_null($branding_settings->logo)) {
|
||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
'item_tag' => $item->asset_tag,
|
||||
'item_model' => $display_model,
|
||||
@@ -224,24 +226,113 @@ class AcceptanceController extends Controller
|
||||
'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_to,
|
||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d H:i:s'),
|
||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d H:i:s'),
|
||||
'assigned_to' => $assigned_user->display_name,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
||||
'logo' => $path_logo,
|
||||
'date_settings' => $branding_settings->date_display_format,
|
||||
'admin' => auth()->user()->present()?->fullName,
|
||||
];
|
||||
|
||||
|
||||
if ($pdf_view_route!='') {
|
||||
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());
|
||||
//$pdf = new PDF;
|
||||
|
||||
// set some language dependent data:
|
||||
$lg = Array();
|
||||
$lg['a_meta_charset'] = 'UTF-8';
|
||||
$lg['w_page'] = 'page';
|
||||
|
||||
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||
// $pdf->SetHeaderData(PDF_HEADER_LOGO, 5, PDF_HEADER_TITLE.' 006', PDF_HEADER_STRING);
|
||||
// $pdf->SetHeaderData('https://snipe-it.test/uploads/snipe-logo.png', '5', $data['company_name'], $item->company?->name);
|
||||
//$pdf->headerText = ('Anything you want ' . date('c'));
|
||||
$pdf->setRTL(false);
|
||||
//$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, $data['company_name'], '');
|
||||
$pdf->setLanguageArray($lg);
|
||||
$pdf->SetCreator('Snipe-IT');
|
||||
$pdf->SetAuthor($data['assigned_to']);
|
||||
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
|
||||
// $pdf->SetSubject('Document Subject');
|
||||
//$pdf->SetKeywords('keywords, here');
|
||||
$pdf->SetFont('dejavusans', '', 8, '', true);
|
||||
|
||||
|
||||
$pdf->SetPrintHeader(false);
|
||||
$pdf->SetPrintFooter(false);
|
||||
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
|
||||
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
|
||||
|
||||
$pdf->AddPage();
|
||||
$pdf->writeHTML('<img src="'.$path_logo.'" height="30">', true, 0, true, 0, '');
|
||||
|
||||
// $pdf->writeHTML(trans('general.date').': '.date($data['date_settings']), true, 0, true, 0, '');
|
||||
$pdf->writeHTML("<strong>".trans('general.asset_tag').'</strong>: '.$data['item_tag'], true, 0, true, 0, '');
|
||||
$pdf->writeHTML("<strong>".trans('general.asset_model').'</strong>: '.$data['item_model'], true, 0, true, 0, '');
|
||||
$pdf->writeHTML("<strong>".trans('admin/hardware/form.serial').'</strong>: '.$data['item_serial'], true, 0, true, 0, '');
|
||||
$pdf->writeHTML("<strong>".trans('general.assigned_date').'</strong>: '.$data['check_out_date'], true, 0, true, 0, '');
|
||||
$pdf->writeHTML("<strong>".trans('general.assignee').'</strong>: '.$data['assigned_to'], true, 0, true, 0, '');
|
||||
$pdf->Ln();
|
||||
// $html = view($pdf_view_route, $data)->render();
|
||||
// $pdf->writeHTML($html, true, 0, true, 0, '');
|
||||
|
||||
// $eula_lines = explode("\n\n", $item->getEula());
|
||||
$eula_lines = preg_split("/\r\n|\n|\r/", $item->getEula());
|
||||
|
||||
foreach ($eula_lines as $eula_line) {
|
||||
if (Helper::hasRtl($eula_line)) {
|
||||
$pdf->setRTL(true);
|
||||
} else {
|
||||
$pdf->setRTL(false);
|
||||
}
|
||||
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
|
||||
}
|
||||
$pdf->Ln();
|
||||
$pdf->Ln();
|
||||
$pdf->setRTL(false);
|
||||
$pdf->writeHTML('<br><br>', true, 0, true, 0, '');
|
||||
|
||||
if ($data['note'] != null) {
|
||||
$pdf->writeHTML("<strong>".trans('general.notes') . '</strong>: ' . $data['note'], true, 0, true, 0, '');
|
||||
$pdf->Ln();
|
||||
}
|
||||
|
||||
if ($data['signature'] != null) {
|
||||
|
||||
$pdf->writeHTML('<img src="'.$data['signature'].'" style="max-width: 600px;">', true, 0, true, 0, '');
|
||||
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
|
||||
}
|
||||
|
||||
$pdf->writeHTML("<strong>".trans('general.accepted_date').'</strong>: '.$data['accepted_date'], true, 0, true, 0, '');
|
||||
|
||||
|
||||
$pdf_content = $pdf->Output($pdf_filename, 'S');
|
||||
|
||||
|
||||
//$html = view($pdf_view_route, $data)->render();
|
||||
//$pdf = PDF::writeHTML($html, true, false, true, false, '');
|
||||
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
|
||||
}
|
||||
|
||||
$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
|
||||
if (($request->input('send_copy') == '1') && ($assigned_user->email !='')) {
|
||||
|
||||
// Add the attachment for the signing user into the $data array
|
||||
$data['file'] = $pdf_filename;
|
||||
$locale = $assigned_user->locale;
|
||||
try {
|
||||
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($locale));
|
||||
} catch (\Exception $e) {
|
||||
Log::warning($e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
||||
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
|
||||
} catch (\Exception $e) {
|
||||
Log::warning($e);
|
||||
}
|
||||
@@ -333,10 +424,29 @@ class AcceptanceController extends Controller
|
||||
|
||||
$acceptance->decline($sig_filename, $request->input('note'));
|
||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||
Log::debug('New event acceptance.');
|
||||
event(new CheckoutDeclined($acceptance));
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
}
|
||||
|
||||
if ($acceptance->alert_on_response_id) {
|
||||
try {
|
||||
$recipient = User::find($acceptance->alert_on_response_id);
|
||||
|
||||
if ($recipient) {
|
||||
Log::debug('Attempting to send email acceptance.');
|
||||
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
|
||||
$acceptance,
|
||||
$recipient,
|
||||
$request->input('asset_acceptance') === 'accepted',
|
||||
));
|
||||
Log::debug('Send email notification sucess on checkout acceptance response.');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::warning($e);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||
|
||||
|
||||
@@ -288,32 +288,42 @@ class AccessoriesController extends Controller
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
|
||||
$accessory_checkout->created_by = auth()->id();
|
||||
$accessory_checkout->save();
|
||||
|
||||
$payload = [
|
||||
'accessory_id' => $accessory->id,
|
||||
'assigned_to' => $target->id,
|
||||
'assigned_type' => $target::class,
|
||||
'note' => $request->input('note'),
|
||||
'created_by' => auth()->id(),
|
||||
'pivot' => $accessory_checkout->id,
|
||||
];
|
||||
}
|
||||
|
||||
// Set this value to be able to pass the qty through to the event
|
||||
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkout.success')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the item so that it can be checked out again to someone else
|
||||
*
|
||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param int $accessoryUserId
|
||||
* @param string $backto
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @return JsonResponse
|
||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @internal param int $accessoryId
|
||||
*/
|
||||
public function checkin(Request $request, $accessoryUserId = null)
|
||||
{
|
||||
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist', ['id' => $accessoryUserId])));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_checkout->accessory_id);
|
||||
@@ -327,7 +337,14 @@ class AccessoriesController extends Controller
|
||||
$user = User::find($accessory_checkout->assigned_to);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
|
||||
$payload = [
|
||||
'accessory_id' => $accessory->id,
|
||||
'note' => $request->input('note'),
|
||||
'created_by' => auth()->id(),
|
||||
'pivot' => $accessory_checkout->id,
|
||||
];
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Transformers\UploadedFilesTransformer;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
/**
|
||||
* This class controls file related actions related
|
||||
* to assets for the Snipe-IT Asset Management application.
|
||||
*
|
||||
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
|
||||
*
|
||||
* @version v1.0
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
class AssetFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Accepts a POST to upload a file to the server.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param int $assetId
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to update this asset
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
// If the file storage directory doesn't exist; create it
|
||||
if (! Storage::exists('private_uploads/assets')) {
|
||||
Storage::makeDirectory('private_uploads/assets', 775);
|
||||
}
|
||||
|
||||
// Loop over the attached files and add them to the asset
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
|
||||
|
||||
$asset->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
// All done - report success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success')));
|
||||
}
|
||||
|
||||
// We only reach here if no files were included in the POST, so tell the user this
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the files for an asset.
|
||||
*
|
||||
* @param int $assetId
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function list(Asset $asset, Request $request) : JsonResponse | array
|
||||
{
|
||||
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'filename',
|
||||
'eol',
|
||||
'notes',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$files = $files->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||
$offset = ($request->input('offset') > $files->count()) ? $files->count() : abs($request->input('offset'));
|
||||
$limit = app('api_limit_value');
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$files = $files->orderBy($sort, $order);
|
||||
|
||||
$files = $files->skip($offset)->take($limit)->get();
|
||||
return (new UploadedFilesTransformer())->transformFiles($files, $files->count());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
// Check that the file being requested exists for the asset
|
||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
// Form the full filename with path
|
||||
$file = 'private_uploads/assets/'.$log->filename;
|
||||
Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type == 'audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
// Check the file actually exists on the filesystem
|
||||
if (! Storage::exists($file)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v6.0]
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
public function destroy(Asset $asset, $fileId = null) : JsonResponse
|
||||
{
|
||||
|
||||
$rel_path = 'private_uploads/assets';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
// Check for the file
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
if ($log) {
|
||||
// Check the file actually exists, and delete it
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
|
||||
// Delete the record of the file
|
||||
$log->delete();
|
||||
|
||||
// All deleting done - notify the user of success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200);
|
||||
}
|
||||
|
||||
// The file doesn't seem to really exist, so report an error
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Http\Transformers\AssetModelsTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
|
||||
/**
|
||||
* This class controls file related actions related
|
||||
* to assets for the Snipe-IT Asset Management application.
|
||||
*
|
||||
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
|
||||
*
|
||||
* @version v1.0
|
||||
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
|
||||
*/
|
||||
class AssetModelFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Accepts a POST to upload a file to the server.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param int $assetModelId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to update this asset
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
// If the file storage directory doesn't exist; create it
|
||||
if (! Storage::exists('private_uploads/assetmodels')) {
|
||||
Storage::makeDirectory('private_uploads/assetmodels', 775);
|
||||
}
|
||||
|
||||
// Loop over the attached files and add them to the asset
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
|
||||
|
||||
$assetModel->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
// All done - report success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
|
||||
}
|
||||
|
||||
// We only reach here if no files were included in the POST, so tell the user this
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the files for an asset.
|
||||
*
|
||||
* @param int $assetmodel
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function list($assetmodel_id) : JsonResponse | array
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetmodel_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
$assetmodel = AssetModel::with('uploads')->find($assetmodel_id);
|
||||
$this->authorize('view', $assetmodel);
|
||||
return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('view', $assetModel);
|
||||
|
||||
// Check that the file being requested exists for the asset
|
||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
// Form the full filename with path
|
||||
$file = 'private_uploads/assetmodels/'.$log->filename;
|
||||
Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type == 'audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
// Check the file actually exists on the filesystem
|
||||
if (! Storage::exists($file)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
}
|
||||
|
||||
// Send back an error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @param int $assetModelId
|
||||
* @param int $fileId
|
||||
* @since [v7.0.12]
|
||||
* @author [r-xyz]
|
||||
*/
|
||||
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
|
||||
{
|
||||
// Start by checking if the asset being acted upon exists
|
||||
if (! $assetModel = AssetModel::find($assetModelId)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
|
||||
}
|
||||
|
||||
$rel_path = 'private_uploads/assetmodels';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($assetModel->id)) {
|
||||
$this->authorize('update', $assetModel);
|
||||
|
||||
// Check for the file
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
// Check the file actually exists, and delete it
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
// Delete the record of the file
|
||||
$log->delete();
|
||||
|
||||
// All deleting done - notify the user of success
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
|
||||
}
|
||||
|
||||
// The file doesn't seem to really exist, so report an error
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ class AssetModelsController extends Controller
|
||||
'fieldset',
|
||||
'deleted_at',
|
||||
'updated_at',
|
||||
'require_serial',
|
||||
];
|
||||
|
||||
$assetmodels = AssetModel::select([
|
||||
@@ -69,6 +70,7 @@ class AssetModelsController extends Controller
|
||||
'models.fieldset_id',
|
||||
'models.deleted_at',
|
||||
'models.updated_at',
|
||||
'models.require_serial'
|
||||
])
|
||||
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
|
||||
->withCount('assets as assets_count');
|
||||
@@ -154,7 +156,7 @@ class AssetModelsController extends Controller
|
||||
$assetmodel = $request->handleImages($assetmodel);
|
||||
|
||||
if ($assetmodel->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.create.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
|
||||
|
||||
@@ -207,7 +209,7 @@ class AssetModelsController extends Controller
|
||||
$assetmodel = AssetModel::findOrFail($id);
|
||||
$assetmodel->fill($request->all());
|
||||
$assetmodel = $request->handleImages($assetmodel);
|
||||
|
||||
|
||||
/**
|
||||
* Allow custom_fieldset_id to override and populate fieldset_id.
|
||||
* This is stupid, but required for legacy API support.
|
||||
@@ -222,7 +224,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
if ($assetmodel->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
|
||||
|
||||
@@ -114,17 +114,23 @@ class AssetsController extends Controller
|
||||
'byod',
|
||||
'asset_eol_date',
|
||||
'requestable',
|
||||
'jobtitle',
|
||||
];
|
||||
|
||||
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||
|
||||
foreach ($all_custom_fields as $field) {
|
||||
$allowed_columns[] = $field->db_column_name();
|
||||
}
|
||||
|
||||
$filter = [];
|
||||
|
||||
if ($request->filled('filter')) {
|
||||
$filter = json_decode($request->input('filter'), true);
|
||||
}
|
||||
|
||||
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||
foreach ($all_custom_fields as $field) {
|
||||
$allowed_columns[] = $field->db_column_name();
|
||||
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
|
||||
return in_array($key, $allowed_columns);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
|
||||
$assets = Asset::select('assets.*')
|
||||
@@ -140,6 +146,7 @@ class AssetsController extends Controller
|
||||
'model.category',
|
||||
'model.manufacturer',
|
||||
'model.fieldset',
|
||||
'model.depreciation',
|
||||
'supplier'
|
||||
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
||||
|
||||
@@ -395,6 +402,9 @@ class AssetsController extends Controller
|
||||
case 'assigned_to':
|
||||
$assets->OrderAssigned($order);
|
||||
break;
|
||||
case 'jobtitle':
|
||||
$assets->OrderByJobTitle($order);
|
||||
break;
|
||||
case 'created_by':
|
||||
$assets->OrderByCreatedByName($order);
|
||||
break;
|
||||
@@ -600,7 +610,7 @@ class AssetsController extends Controller
|
||||
$asset->use_text = $asset->present()->fullName;
|
||||
|
||||
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
||||
$asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
|
||||
$asset->use_text .= ' → ' . $asset->assigned->display_name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ class CompaniesController extends Controller
|
||||
|
||||
$companies = Company::withCount(['assets as assets_count' => function ($query) {
|
||||
$query->AssetsForShow();
|
||||
}])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
|
||||
}])
|
||||
->with('adminuser')
|
||||
->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
|
||||
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$companies->TextSearch($request->input('search'));
|
||||
@@ -119,6 +122,7 @@ class CompaniesController extends Controller
|
||||
{
|
||||
$this->authorize('view', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
$this->authorize('view', $company);
|
||||
return (new CompaniesTransformer)->transformCompany($company);
|
||||
|
||||
}
|
||||
@@ -136,6 +140,7 @@ class CompaniesController extends Controller
|
||||
{
|
||||
$this->authorize('update', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
$this->authorize('update', $company);
|
||||
$company->fill($request->all());
|
||||
$company = $request->handleImages($company);
|
||||
|
||||
@@ -188,6 +193,7 @@ class CompaniesController extends Controller
|
||||
'companies.image',
|
||||
]);
|
||||
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -228,11 +228,16 @@ class ConsumablesController extends Controller
|
||||
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
||||
$rows[] = [
|
||||
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
|
||||
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
|
||||
'user' => ($consumable_assignment->user) ? [
|
||||
'id' => (int) $consumable_assignment->user->id,
|
||||
'name'=> e($consumable_assignment->user->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
||||
'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response
|
||||
'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null,
|
||||
'created_by' => ($consumable_assignment->adminuser) ? [
|
||||
'id' => (int) $consumable_assignment->adminuser->id,
|
||||
'name'=> e($consumable_assignment->adminuser->display_name),
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class ImportController extends Controller
|
||||
if (function_exists('iconv')) {
|
||||
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
|
||||
$encoding = $detector->getEncoding($file_contents);
|
||||
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
|
||||
\Log::debug("Discovered encoding: $encoding in uploaded CSV");
|
||||
$reader = null;
|
||||
if (strcasecmp($encoding, 'UTF-8') != 0) {
|
||||
$transliterated = false;
|
||||
@@ -103,7 +103,7 @@ class ImportController extends Controller
|
||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||
|
||||
try {
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
$import->header_row = $reader->nth(0);
|
||||
} catch (JsonEncodingException $e) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
@@ -136,7 +136,7 @@ class ImportController extends Controller
|
||||
|
||||
try {
|
||||
// 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) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
@@ -195,7 +195,7 @@ class ImportController extends Controller
|
||||
// Run a backup immediately before processing
|
||||
if ($request->get('run-backup')) {
|
||||
Log::debug('Backup manually requested via importer');
|
||||
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
|
||||
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H-i-s')]);
|
||||
} else {
|
||||
Log::debug('NO BACKUP requested via importer');
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ class LicenseSeatsController extends Controller
|
||||
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
|
||||
->where('license_seats.license_id', $licenseId);
|
||||
|
||||
if ($request->input('status') == 'available') {
|
||||
$seats->whereNull('license_seats.assigned_to');
|
||||
}
|
||||
|
||||
if ($request->input('status') == 'assigned') {
|
||||
$seats->ByAssigned();
|
||||
}
|
||||
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
if ($request->input('sort') == 'department') {
|
||||
@@ -119,7 +128,9 @@ class LicenseSeatsController extends Controller
|
||||
// nothing to update
|
||||
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,
|
||||
// we simply let assets take precedence over users...
|
||||
if ($licenseSeat->isDirty('assigned_to')) {
|
||||
@@ -136,7 +147,11 @@ class LicenseSeatsController extends Controller
|
||||
if ($licenseSeat->save()) {
|
||||
|
||||
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')));
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetMaintenancesTransformer;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Transformers\MaintenancesTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Models\Maintenance;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -18,13 +18,13 @@ use Illuminate\Http\JsonResponse;
|
||||
*
|
||||
* @version v2.0
|
||||
*/
|
||||
class AssetMaintenancesController extends Controller
|
||||
class MaintenancesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Generates the JSON response for asset maintenances listing view.
|
||||
*
|
||||
* @see AssetMaintenancesController::getIndex() method that generates view
|
||||
* @see MaintenancesController::getIndex() method that generates view
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
@@ -33,7 +33,7 @@ class AssetMaintenancesController extends Controller
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
$maintenances = AssetMaintenance::select('asset_maintenances.*')
|
||||
$maintenances = Maintenance::select('maintenances.*')
|
||||
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
@@ -45,11 +45,11 @@ class AssetMaintenancesController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('supplier_id')) {
|
||||
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
|
||||
$maintenances->where('maintenances.supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('created_by')) {
|
||||
$maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by'));
|
||||
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_maintenance_type')) {
|
||||
@@ -63,7 +63,7 @@ class AssetMaintenancesController extends Controller
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'title',
|
||||
'name',
|
||||
'asset_maintenance_time',
|
||||
'asset_maintenance_type',
|
||||
'cost',
|
||||
@@ -75,6 +75,7 @@ class AssetMaintenancesController extends Controller
|
||||
'serial',
|
||||
'created_by',
|
||||
'supplier',
|
||||
'location',
|
||||
'is_warranty',
|
||||
'status_label',
|
||||
];
|
||||
@@ -98,6 +99,9 @@ class AssetMaintenancesController extends Controller
|
||||
case 'serial':
|
||||
$maintenances = $maintenances->OrderByAssetSerial($order);
|
||||
break;
|
||||
case 'location':
|
||||
$maintenances = $maintenances->OrderLocationName($order);
|
||||
break;
|
||||
case 'status_label':
|
||||
$maintenances = $maintenances->OrderStatusName($order);
|
||||
break;
|
||||
@@ -108,7 +112,7 @@ class AssetMaintenancesController extends Controller
|
||||
|
||||
$total = $maintenances->count();
|
||||
$maintenances = $maintenances->skip($offset)->take($limit)->get();
|
||||
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total);
|
||||
return (new MaintenancesTransformer())->transformMaintenances($maintenances, $total);
|
||||
|
||||
|
||||
}
|
||||
@@ -117,22 +121,23 @@ class AssetMaintenancesController extends Controller
|
||||
/**
|
||||
* Validates and stores the new asset maintenance
|
||||
*
|
||||
* @see AssetMaintenancesController::getCreate() method for the form
|
||||
* @see MaintenancesController::getCreate() method for the form
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function store(Request $request) : JsonResponse | array
|
||||
public function store(ImageUploadRequest $request) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
// create a new model instance
|
||||
$maintenance = new AssetMaintenance();
|
||||
$maintenance = new Maintenance();
|
||||
$maintenance->fill($request->all());
|
||||
$maintenance->created_by = auth()->id();
|
||||
|
||||
$maintenance = $request->handleImages($maintenance);
|
||||
// Was the asset maintenance created?
|
||||
if ($maintenance->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.create.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.create.success')));
|
||||
|
||||
}
|
||||
|
||||
@@ -153,11 +158,11 @@ class AssetMaintenancesController extends Controller
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if ($maintenance = AssetMaintenance::with('asset')->find($id)) {
|
||||
if ($maintenance = Maintenance::with('asset')->find($id)) {
|
||||
|
||||
// Can this user manage this asset?
|
||||
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
|
||||
}
|
||||
|
||||
// The asset this miantenance is attached to is not valid or has been deleted
|
||||
@@ -168,13 +173,13 @@ class AssetMaintenancesController extends Controller
|
||||
$maintenance->fill($request->all());
|
||||
|
||||
if ($maintenance->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.edit.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.edit.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors()));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id])));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id])));
|
||||
|
||||
}
|
||||
|
||||
@@ -182,20 +187,20 @@ class AssetMaintenancesController extends Controller
|
||||
* Delete an asset maintenance
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @param int $assetMaintenanceId
|
||||
* @param int $maintenanceId
|
||||
* @version v1.0
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function destroy($assetMaintenanceId) : JsonResponse | array
|
||||
public function destroy($maintenanceId) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
|
||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
||||
$maintenance = Maintenance::findOrFail($maintenanceId);
|
||||
|
||||
$assetMaintenance->delete();
|
||||
$maintenance->delete();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.delete.success')));
|
||||
|
||||
|
||||
}
|
||||
@@ -204,19 +209,19 @@ class AssetMaintenancesController extends Controller
|
||||
* View an asset maintenance
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @param int $assetMaintenanceId
|
||||
* @param int $maintenanceId
|
||||
* @version v1.0
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function show($assetMaintenanceId) : JsonResponse | array
|
||||
public function show($maintenanceId) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
||||
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
$maintenance = Maintenance::findOrFail($maintenanceId);
|
||||
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
|
||||
}
|
||||
|
||||
return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance);
|
||||
return (new MaintenancesTransformer())->transformMaintenance($maintenance);
|
||||
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,19 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ProfileTransformer;
|
||||
use App\Models\CheckoutRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Passport\TokenRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
@@ -65,7 +69,7 @@ class ProfileController extends Controller
|
||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||
$assets = [
|
||||
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
|
||||
'name' => e($checkoutRequest->itemRequested()->present()->name()),
|
||||
'name' => e($checkoutRequest->itemRequested()->display_name),
|
||||
'type' => e($checkoutRequest->itemType()),
|
||||
'qty' => (int) $checkoutRequest->quantity,
|
||||
'location' => ($checkoutRequest->location()) ? e($checkoutRequest->location()->name) : null,
|
||||
@@ -167,6 +171,22 @@ class ProfileController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the EULAs accepted by the user.
|
||||
*
|
||||
* @param \App\Http\Transformers\ActionlogsTransformer $transformer
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*@since [v8.1.16]
|
||||
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
|
||||
*/
|
||||
public function eulas(ProfileTransformer $transformer)
|
||||
{
|
||||
// Only return this user's EULAs
|
||||
$eulas = auth()->user()->eulas;
|
||||
return response()->json(
|
||||
$transformer->transformFiles($eulas, $eulas->count())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Transformers\DatatablesTransformer;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -51,10 +50,22 @@ class SettingsController extends Controller
|
||||
})->slice(0, 10)->map(function ($item) use ($settings) {
|
||||
return (object) [
|
||||
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
||||
'display_name' => $item[$settings['ldap_display_name']][0] ?? null,
|
||||
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
||||
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
||||
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
||||
'email' => $item[$settings['ldap_email']][0] ?? null,
|
||||
'phone' => $item[$settings['ldap_phone_field']][0] ?? null,
|
||||
'mobile' => $item[$settings['ldap_mobile']][0] ?? null,
|
||||
'jobtitle' => $item[$settings['ldap_jobtitle']][0] ?? null,
|
||||
'department' => $item[$settings['ldap_department']][0] ?? null,
|
||||
'manager' => $item[$settings['ldap_manager']][0] ?? null,
|
||||
'address' => $item[$settings['ldap_address']][0] ?? null,
|
||||
'city' => $item[$settings['ldap_city']][0] ?? null,
|
||||
'state' => $item[$settings['ldap_state']][0] ?? null,
|
||||
'zip' => $item[$settings['ldap_zip']][0] ?? null,
|
||||
'country' => $item[$settings['ldap_country']][0] ?? null,
|
||||
'location' => $item[$settings['ldap_location']][0] ?? null,
|
||||
];
|
||||
});
|
||||
if ($users->count() > 0) {
|
||||
@@ -78,7 +89,7 @@ class SettingsController extends Controller
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Connection failed but we cannot debug it any further on our end.');
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,8 +161,11 @@ class SettingsController extends Controller
|
||||
if (!config('app.lock_passwords')) {
|
||||
try {
|
||||
Notification::send(Setting::first(), new MailTest());
|
||||
Log::debug('Attempting to sending to '.config('mail.reply_to.address'));
|
||||
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Mail sent error using '.config('mail.reply_to.address') .': '. $e->getMessage());
|
||||
Log::debug($e);
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
@@ -315,4 +329,4 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ class SuppliersController extends Controller
|
||||
public function destroy($id) : JsonResponse
|
||||
{
|
||||
$this->authorize('delete', Supplier::class);
|
||||
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
$supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
$this->authorize('delete', $supplier);
|
||||
|
||||
|
||||
@@ -202,8 +202,8 @@ class SuppliersController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
|
||||
}
|
||||
|
||||
if ($supplier->asset_maintenances_count > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count])));
|
||||
if ($supplier->maintenances_count > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])));
|
||||
}
|
||||
|
||||
if ($supplier->licenses_count > 0) {
|
||||
|
||||
216
app/Http/Controllers/Api/UploadedFilesController.php
Normal file
216
app/Http/Controllers/Api/UploadedFilesController.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Http\Transformers\UploadedFilesTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
|
||||
class UploadedFilesController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* List files for an object
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param string $object_type the type of object to upload the file to
|
||||
* @param int $id the ID of the object to list files for
|
||||
* @since [v8.1.17]
|
||||
* @author [A. Gianotto <snipe@snipe.net>]
|
||||
*/
|
||||
public function index(Request $request, $object_type, $id) : JsonResponse | array
|
||||
{
|
||||
|
||||
// Check the permissions to make sure the user can view the object
|
||||
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||
$this->authorize('view', $object);
|
||||
|
||||
if (!$object) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
|
||||
}
|
||||
|
||||
// Columns allowed for sorting
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'filename',
|
||||
'action_type',
|
||||
'action_date',
|
||||
'note',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
|
||||
$uploads = self::$map_object_type[$object_type]::withTrashed()->find($id)->uploads()
|
||||
->with('adminuser');
|
||||
|
||||
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
||||
$limit = app('api_limit_value');
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
|
||||
// Text search on action_logs fields
|
||||
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searching across all relations
|
||||
// and we generally won't need that here
|
||||
if ($request->filled('search')) {
|
||||
|
||||
$uploads->where(
|
||||
function ($query) use ($request) {
|
||||
$query->where('filename', 'LIKE', '%' . $request->input('search') . '%')
|
||||
->orWhere('note', 'LIKE', '%' . $request->input('search') . '%');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$total = $uploads->count();
|
||||
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
|
||||
|
||||
return (new UploadedFilesTransformer())->transformFiles($uploads, $total);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a POST to upload a file to the server.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param string $object_type the type of object to upload the file to
|
||||
* @param int $id the ID of the object to store so we can check permisisons
|
||||
* @since [v8.1.17]
|
||||
* @author [A. Gianotto <snipe@snipe.net>]
|
||||
*/
|
||||
public function store(UploadFileRequest $request, $object_type, $id) : JsonResponse
|
||||
{
|
||||
|
||||
// Check the permissions to make sure the user can view the object
|
||||
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||
$this->authorize('view', $object);
|
||||
|
||||
if (!$object) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
|
||||
}
|
||||
|
||||
// If the file storage directory doesn't exist, create it
|
||||
if (! Storage::exists(self::$map_storage_path[$object_type])) {
|
||||
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
|
||||
}
|
||||
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
// Loop over the attached files and add them to the object
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
|
||||
$files[] = $file_name;
|
||||
$object->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
|
||||
->where('item_type', '=', self::$map_object_type[$object_type])
|
||||
->where('item_id', '=', $id)->whereIn('filename', $files)
|
||||
->get();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files))));
|
||||
}
|
||||
|
||||
// No files were submitted
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.nofiles')));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param string $object_type the type of object to upload the file to
|
||||
* @param int $id the ID of the object to delete from so we can check permisisons
|
||||
* @param $file_id the ID of the file to delete from the action_logs table
|
||||
* @since [v8.1.17]
|
||||
* @author [A. Gianotto <snipe@snipe.net>]
|
||||
*/
|
||||
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
|
||||
{
|
||||
// Check the permissions to make sure the user can view the object
|
||||
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||
$this->authorize('view', $object);
|
||||
|
||||
if (!$object) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
|
||||
}
|
||||
|
||||
|
||||
// Check that the file being requested exists for the object
|
||||
if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id)
|
||||
) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_id')), 200);
|
||||
}
|
||||
|
||||
|
||||
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.file_not_found'), 200));
|
||||
}
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @param \App\Http\Requests\UploadFileRequest $request
|
||||
* @param string $object_type the type of object to upload the file to
|
||||
* @param int $id the ID of the object to delete from so we can check permisisons
|
||||
* @param $file_id the ID of the file to delete from the action_logs table
|
||||
* @since [v8.1.17]
|
||||
* @author [A. Gianotto <snipe@snipe.net>]
|
||||
*/
|
||||
public function destroy($object_type, $id, $file_id) : JsonResponse
|
||||
{
|
||||
|
||||
// Check the permissions to make sure the user can view the object
|
||||
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
|
||||
$this->authorize('update', self::$map_object_type[$object_type]);
|
||||
|
||||
if (!$object) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
|
||||
}
|
||||
|
||||
|
||||
// Check for the file
|
||||
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
|
||||
->where('item_id', $object->id)->first();
|
||||
|
||||
if ($log) {
|
||||
// Check the file actually exists, and delete it
|
||||
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
|
||||
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
|
||||
}
|
||||
// Delete the record of the file
|
||||
if ($log->logUploadDelete($object, $log->filename)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// The file doesn't seem to really exist, so report an error
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,12 @@ use App\Models\Consumable;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use App\Notifications\WelcomeNotification;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -62,12 +65,14 @@ class UsersController extends Controller
|
||||
'users.jobtitle',
|
||||
'users.last_login',
|
||||
'users.last_name',
|
||||
'users.display_name',
|
||||
'users.locale',
|
||||
'users.location_id',
|
||||
'users.manager_id',
|
||||
'users.notes',
|
||||
'users.permissions',
|
||||
'users.phone',
|
||||
'users.mobile',
|
||||
'users.state',
|
||||
'users.two_factor_enrolled',
|
||||
'users.two_factor_optin',
|
||||
@@ -81,7 +86,12 @@ class UsersController extends Controller
|
||||
'users.autoassign_licenses',
|
||||
'users.website',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
|
||||
])->with('manager')
|
||||
->with('groups')
|
||||
->with('userloc')
|
||||
->with('company')
|
||||
->with('department')
|
||||
->with('createdBy')
|
||||
->withCount([
|
||||
'assets as assets_count' => function(Builder $query) {
|
||||
$query->withoutTrashed();
|
||||
@@ -102,10 +112,26 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.activated', '=', $request->input('activated'));
|
||||
}
|
||||
|
||||
if ($request->input('admins') == 'true') {
|
||||
$users = $users->OnlyAdminsAndSuperAdmins();
|
||||
}
|
||||
|
||||
if ($request->input('superadmins') == 'true') {
|
||||
$users = $users->OnlySuperAdmins();
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$users = $users->where('users.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('phone')) {
|
||||
$users = $users->where('users.phone', '=', $request->input('phone'));
|
||||
}
|
||||
|
||||
if ($request->filled('mobile')) {
|
||||
$users = $users->where('users.mobile', '=', $request->input('mobile'));
|
||||
}
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
$users = $users->where('users.location_id', '=', $request->input('location_id'));
|
||||
}
|
||||
@@ -130,6 +156,10 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.last_name', '=', $request->input('last_name'));
|
||||
}
|
||||
|
||||
if ($request->filled('display_name')) {
|
||||
$users = $users->where('users.display_name', '=', $request->input('display_name'));
|
||||
}
|
||||
|
||||
if ($request->filled('employee_num')) {
|
||||
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
|
||||
}
|
||||
@@ -207,11 +237,11 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('manages_users_count')) {
|
||||
$users->has('manages_users_count', '=', $request->input('manages_users_count'));
|
||||
$users->has('managesUsers', '=', $request->input('manages_users_count'));
|
||||
}
|
||||
|
||||
if ($request->filled('manages_locations_count')) {
|
||||
$users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
|
||||
$users->has('managedLocations', '=', $request->input('manages_locations_count'));
|
||||
}
|
||||
|
||||
if ($request->filled('autoassign_licenses')) {
|
||||
@@ -260,6 +290,7 @@ class UsersController extends Controller
|
||||
[
|
||||
'last_name',
|
||||
'first_name',
|
||||
'display_name',
|
||||
'email',
|
||||
'jobtitle',
|
||||
'username',
|
||||
@@ -278,6 +309,7 @@ class UsersController extends Controller
|
||||
'manages_users_count',
|
||||
'manages_locations_count',
|
||||
'phone',
|
||||
'mobile',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
@@ -330,6 +362,7 @@ class UsersController extends Controller
|
||||
'users.employee_num',
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
'users.display_name',
|
||||
'users.gravatar',
|
||||
'users.avatar',
|
||||
'users.email',
|
||||
@@ -340,20 +373,17 @@ class UsersController extends Controller
|
||||
$users = $users->where(function ($query) use ($request) {
|
||||
$query->SimpleNameSearch($request->get('search'))
|
||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||
});
|
||||
}
|
||||
|
||||
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
|
||||
$users = $users->orderBy('display_name', 'asc')->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
|
||||
$users = $users->paginate(50);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$name_str = '';
|
||||
if ($user->last_name != '') {
|
||||
$name_str .= $user->last_name.', ';
|
||||
}
|
||||
$name_str .= $user->first_name;
|
||||
$name_str = $user->display_name;
|
||||
|
||||
if ($user->username != '') {
|
||||
$name_str .= ' ('.$user->username.')';
|
||||
@@ -405,9 +435,20 @@ class UsersController extends Controller
|
||||
$user->password = $user->noPassword();
|
||||
}
|
||||
|
||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
|
||||
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||
|
||||
try {
|
||||
$user->notify(new WelcomeNotification($user));
|
||||
} catch (\Exception $e) {
|
||||
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($request->filled('groups')) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
} else {
|
||||
@@ -475,8 +516,29 @@ class UsersController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
||||
}
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
// check for permissions related fields and pull them out if the current user cannot edit them
|
||||
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
if ($request->filled('username')) {
|
||||
$user->username = $request->input('username');
|
||||
}
|
||||
|
||||
if ($request->filled('display_name')) {
|
||||
$user->display_name = $request->input('display_name');
|
||||
}
|
||||
|
||||
if ($request->filled('email')) {
|
||||
$user->email = $request->input('email');
|
||||
}
|
||||
|
||||
if ($request->filled('activated')) {
|
||||
$user->activated = $request->input('activated');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We need to use has() instead of filled()
|
||||
@@ -498,7 +560,7 @@ class UsersController extends Controller
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||
}
|
||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
// Check if the request has groups passed and has a value, AND that the user us a superuser
|
||||
@@ -677,7 +739,6 @@ class UsersController extends Controller
|
||||
$this->authorize('view', License::class);
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$this->authorize('update', $user);
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
@@ -748,7 +809,7 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function eulas(User $user, ActionlogsTransformer $transformer)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$eulas = $user->eulas;
|
||||
return response()->json(
|
||||
@@ -792,4 +853,37 @@ class UsersController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the LDAP sync command to import users from LDAP via API.
|
||||
*
|
||||
* @author A. Gianotto <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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Asset Maintenance for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v2.0
|
||||
*/
|
||||
class AssetMaintenancesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Checks for permissions for this action.
|
||||
*
|
||||
* @todo This should be replaced with middleware and/or policies
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
private static function getInsufficientPermissionsRedirect(): RedirectResponse
|
||||
{
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('general.insufficient_permissions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the asset maintenances listing, which is generated in getDatatable.
|
||||
*
|
||||
* @todo This should be replaced with middleware and/or policies
|
||||
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function index() : View
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
return view('asset_maintenances/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form view to create a new asset maintenance.
|
||||
*
|
||||
* @see AssetMaintenancesController::postCreate() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
* @return mixed
|
||||
*/
|
||||
public function create() : View
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$asset = null;
|
||||
|
||||
if ($asset = Asset::find(request('asset_id'))) {
|
||||
// We have to set this so that the correct property is set in the select2 ajax dropdown
|
||||
$asset->asset_id = $asset->id;
|
||||
}
|
||||
|
||||
return view('asset_maintenances/edit')
|
||||
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
|
||||
->with('asset', $asset)
|
||||
->with('item', new AssetMaintenance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores the new asset maintenance
|
||||
*
|
||||
* @see AssetMaintenancesController::getCreate() method for the form
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function store(Request $request) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
$assetMaintenance = new AssetMaintenance();
|
||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
||||
$assetMaintenance->is_warranty = $request->input('is_warranty');
|
||||
$assetMaintenance->cost = $request->input('cost');
|
||||
$assetMaintenance->notes = $request->input('notes');
|
||||
|
||||
// Save the asset maintenance data
|
||||
$assetMaintenance->asset_id = $asset->id;
|
||||
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||
$assetMaintenance->title = $request->input('title');
|
||||
$assetMaintenance->start_date = $request->input('start_date');
|
||||
$assetMaintenance->completion_date = $request->input('completion_date');
|
||||
$assetMaintenance->created_by = auth()->id();
|
||||
|
||||
if (($assetMaintenance->completion_date !== null)
|
||||
&& ($assetMaintenance->start_date !== '')
|
||||
&& ($assetMaintenance->start_date !== '0000-00-00')
|
||||
) {
|
||||
$startDate = Carbon::parse($assetMaintenance->start_date);
|
||||
$completionDate = Carbon::parse($assetMaintenance->completion_date);
|
||||
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
}
|
||||
|
||||
|
||||
// Was the asset maintenance created?
|
||||
if (!$assetMaintenance->save()) {
|
||||
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/asset_maintenances/message.create.success'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form view to edit a selected asset maintenance.
|
||||
*
|
||||
* @see AssetMaintenancesController::postEdit() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param int $assetMaintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
|
||||
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
|
||||
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
// Prepare Improvement Type List
|
||||
$assetMaintenanceType = ['' => trans('general.select')] + AssetMaintenance::getImprovementOptions();
|
||||
|
||||
return view('asset_maintenances/edit')
|
||||
->with('selectedAsset', null)
|
||||
->with('assetMaintenanceType', $assetMaintenanceType)
|
||||
->with('item', $maintenance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores an update to an asset maintenance
|
||||
*
|
||||
* @see AssetMaintenancesController::postEdit() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param Request $request
|
||||
* @param int $assetMaintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
|
||||
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
|
||||
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
$maintenance->supplier_id = $request->input('supplier_id');
|
||||
$maintenance->is_warranty = $request->input('is_warranty');
|
||||
$maintenance->cost = $request->input('cost');
|
||||
$maintenance->notes = $request->input('notes');
|
||||
|
||||
$asset = Asset::find(request('asset_id'));
|
||||
|
||||
if (! Company::isCurrentUserHasAccess($asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
// Save the asset maintenance data
|
||||
$maintenance->asset_id = $request->input('asset_id');
|
||||
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||
$maintenance->title = $request->input('title');
|
||||
$maintenance->start_date = $request->input('start_date');
|
||||
$maintenance->completion_date = $request->input('completion_date');
|
||||
|
||||
if (($maintenance->completion_date == null)
|
||||
) {
|
||||
if (($maintenance->asset_maintenance_time !== 0)
|
||||
|| (! is_null($maintenance->asset_maintenance_time))
|
||||
) {
|
||||
$maintenance->asset_maintenance_time = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (($maintenance->completion_date !== null)
|
||||
&& ($maintenance->start_date !== '')
|
||||
&& ($maintenance->start_date !== '0000-00-00')
|
||||
) {
|
||||
$startDate = Carbon::parse($maintenance->start_date);
|
||||
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
}
|
||||
|
||||
// Was the asset maintenance created?
|
||||
if ($maintenance->save()) {
|
||||
|
||||
// Redirect to the new asset maintenance page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/asset_maintenances/message.edit.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an asset maintenance
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param int $assetMaintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function destroy($assetMaintenanceId) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
// Delete the asset maintenance
|
||||
$assetMaintenance->delete();
|
||||
|
||||
// Redirect to the asset_maintenance management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/asset_maintenances/message.delete.success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* View an asset maintenance
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param int $assetMaintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
|
||||
}
|
||||
}
|
||||
@@ -82,12 +82,26 @@ class AssetModelsController extends Controller
|
||||
$model->notes = $request->input('notes');
|
||||
$model->created_by = auth()->id();
|
||||
$model->requestable = $request->has('requestable');
|
||||
$model->require_serial = $request->input('require_serial', 0);
|
||||
|
||||
if ($request->input('fieldset_id') != '') {
|
||||
$model->fieldset_id = $request->input('fieldset_id');
|
||||
}
|
||||
|
||||
$model = $request->handleImages($model);
|
||||
if ($request->has('use_cloned_image')) {
|
||||
$cloned_model_img = AssetModel::select('image')->find($request->input('clone_image_from_id'));
|
||||
if ($cloned_model_img) {
|
||||
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
||||
$new_image = 'models/'.$new_image_name;
|
||||
Storage::disk('public')->copy('models/'.$cloned_model_img->image, $new_image);
|
||||
$model->image = $new_image_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$model = $request->handleImages($model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($model->save()) {
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
@@ -142,7 +156,7 @@ class AssetModelsController extends Controller
|
||||
$model->category_id = $request->input('category_id');
|
||||
$model->notes = $request->input('notes');
|
||||
$model->requestable = $request->input('requestable', '0');
|
||||
|
||||
$model->require_serial = $request->input('require_serial', 0);
|
||||
$model->fieldset_id = $request->input('fieldset_id');
|
||||
|
||||
if ($model->save()) {
|
||||
@@ -271,7 +285,7 @@ class AssetModelsController extends Controller
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', $model)
|
||||
->with('model_id', $model->id)
|
||||
->with('clone_model', $cloned_model);
|
||||
->with('cloned_model', $cloned_model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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'));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,6 @@ class AssetCheckinController extends Controller
|
||||
});
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
$asset->name = $request->get('name');
|
||||
@@ -123,11 +122,14 @@ class AssetCheckinController extends Controller
|
||||
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
// Handle last checkin date
|
||||
$checkin_at = date('Y-m-d H:i:s');
|
||||
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||
$originalValues['action_date'] = $checkin_at;
|
||||
$checkin_at = $request->get('checkin_at');
|
||||
|
||||
}
|
||||
$asset->last_checkin = $checkin_at;
|
||||
|
||||
$asset->licenseseats->each(function (LicenseSeat $seat) {
|
||||
$seat->update(['assigned_to' => null]);
|
||||
@@ -151,7 +153,8 @@ class AssetCheckinController extends Controller
|
||||
if ($asset->save()) {
|
||||
|
||||
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
|
||||
|
||||
@@ -65,6 +65,8 @@ class AssetCheckoutController extends Controller
|
||||
*/
|
||||
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse
|
||||
{
|
||||
|
||||
|
||||
try {
|
||||
// Check if the asset exists
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
@@ -81,6 +83,7 @@ class AssetCheckoutController extends Controller
|
||||
$admin = auth()->user();
|
||||
|
||||
$target = $this->determineCheckoutTarget();
|
||||
session()->put(['checkout_to_type' => $target]);
|
||||
|
||||
$asset = $this->updateAssetLocation($asset, $target);
|
||||
|
||||
@@ -123,7 +126,7 @@ class AssetCheckoutController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
|
||||
@@ -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
|
||||
// differently
|
||||
$asset_tags = $request->input('asset_tags');
|
||||
$model = AssetModel::find($request->input('model_id'));
|
||||
$serial_errors = [];
|
||||
$serials = $request->input('serials');
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
//Validate required serial based on model setting
|
||||
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||
if ($model && $model->require_serial === 1 && empty($serials[$a])) {
|
||||
$serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($serial_errors)) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->withErrors($serial_errors);
|
||||
}
|
||||
|
||||
$asset = null;
|
||||
$companyId = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$successes = [];
|
||||
$failures = [];
|
||||
$serials = $request->input('serials');
|
||||
$asset = null;
|
||||
|
||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
||||
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
|
||||
$asset = new Asset();
|
||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
||||
|
||||
$asset->model()->associate($model);
|
||||
$asset->name = $request->input('name');
|
||||
|
||||
// Check for a corresponding serial
|
||||
@@ -132,7 +150,7 @@ class AssetsController extends Controller
|
||||
$asset->asset_tag = $asset_tags[$a];
|
||||
}
|
||||
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$asset->company_id = $companyId;
|
||||
$asset->model_id = $request->input('model_id');
|
||||
$asset->order_number = $request->input('order_number');
|
||||
$asset->notes = $request->input('notes');
|
||||
@@ -157,14 +175,21 @@ class AssetsController extends Controller
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
}
|
||||
|
||||
// Create the image (if one was chosen.)
|
||||
if ($request->has('image')) {
|
||||
if ($request->has('use_cloned_image')) {
|
||||
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
|
||||
if ($cloned_model_img) {
|
||||
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
||||
$new_image = 'assets/'.$new_image_name;
|
||||
Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image);
|
||||
$asset->image = $new_image_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$asset = $request->handleImages($asset);
|
||||
}
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handled through the AssetRequest form request
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
@@ -226,25 +251,32 @@ class AssetsController extends Controller
|
||||
$failures[] = join(",", $asset->getErrors()->all());
|
||||
}
|
||||
}
|
||||
if($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
session()->put(['checkout_to_type' => $request->get('checkout_to_type'),
|
||||
'other_redirect' => 'model' ]);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
|
||||
if ($successes) {
|
||||
if ($failures) {
|
||||
//some succeeded, some failed
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets') //FIXME - not tested
|
||||
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]))
|
||||
->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)]));
|
||||
} else {
|
||||
if (count($successes) == 1) {
|
||||
//the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed
|
||||
//and re-translated
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)]));
|
||||
} else {
|
||||
//multi-success
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]));
|
||||
}
|
||||
}
|
||||
@@ -265,6 +297,7 @@ class AssetsController extends Controller
|
||||
public function edit(Asset $asset) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize($asset);
|
||||
session()->put('back_url', url()->previous());
|
||||
return view('hardware/edit')
|
||||
->with('item', $asset)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
@@ -408,6 +441,9 @@ class AssetsController extends Controller
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->element == 'checkbox' && !$request->has($field->db_column)) {
|
||||
$asset->{$field->db_column} = null;
|
||||
}
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
@@ -427,11 +463,22 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
session()->put([
|
||||
'redirect_option' => $request->get('redirect_option'),
|
||||
'checkout_to_type' => $request->get('checkout_to_type'),
|
||||
'other_redirect' => $request->get('redirect_option') === 'other_redirect' ? 'model' : null,
|
||||
]);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
if ($asset->save()) {
|
||||
//Validate required serial based on model setting
|
||||
if ($model && $model->require_serial === 1 && empty($serial[1])) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
|
||||
->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [
|
||||
'asset_model' => $model->name
|
||||
]));
|
||||
}
|
||||
if ($asset->save()) {
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
|
||||
@@ -636,8 +683,9 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function getClone(Asset $asset)
|
||||
{
|
||||
$this->authorize('create', $asset);
|
||||
$this->authorize('create', Asset::class);
|
||||
$cloned = clone $asset;
|
||||
$cloned_model = $asset;
|
||||
$cloned->id = null;
|
||||
$cloned->asset_tag = '';
|
||||
$cloned->serial = '';
|
||||
@@ -647,6 +695,7 @@ class AssetsController extends Controller
|
||||
return view('hardware/edit')
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('statuslabel_types', Helper::statusTypeList())
|
||||
->with('cloned_model', $cloned_model)
|
||||
->with('item', $cloned);
|
||||
}
|
||||
|
||||
@@ -772,7 +821,7 @@ class AssetsController extends Controller
|
||||
'item_id' => $asset->id,
|
||||
'item_type' => Asset::class,
|
||||
'created_by' => auth()->id(),
|
||||
'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer',
|
||||
'note' => 'Checkout imported by '.auth()->user()->display_name.' from history importer',
|
||||
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
|
||||
'target_type' => User::class,
|
||||
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
||||
@@ -800,7 +849,7 @@ class AssetsController extends Controller
|
||||
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
|
||||
'item_type' => Asset::class,
|
||||
'created_by' => auth()->id(),
|
||||
'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer',
|
||||
'note' => 'Checkin imported by '.auth()->user()->display_name.' from history importer',
|
||||
'target_id' => null,
|
||||
'created_at' => $checkin_date,
|
||||
'action_type' => 'checkin',
|
||||
@@ -996,7 +1045,7 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
|
||||
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.audit.success'));
|
||||
return Helper::getRedirectOption($request, $asset->id, 'Assets')->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
|
||||
@@ -54,6 +54,15 @@ class BulkAssetsController extends Controller
|
||||
$asset_ids = $request->input('ids');
|
||||
|
||||
if ($request->input('bulk_actions') === 'checkout') {
|
||||
$status_check =$this->hasUndeployableStatus($asset_ids);
|
||||
if($status_check && $status_check['status'] === true){
|
||||
|
||||
$asset_tags = implode(', ', array_column($status_check['tags'], 'asset_tag'));
|
||||
$asset_ids = $status_check['asset_ids'];
|
||||
|
||||
session()->flash('warning', trans('admin/hardware/message.undeployable', ['asset_tags' => $asset_tags]));
|
||||
}
|
||||
|
||||
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
||||
return redirect()->route('hardware.bulkcheckout.show');
|
||||
}
|
||||
@@ -103,11 +112,47 @@ class BulkAssetsController extends Controller
|
||||
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
|
||||
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
|
||||
|
||||
$assets = Asset::with('assignedTo', 'location', 'model')
|
||||
$query = Asset::with('assignedTo', 'location', 'model')
|
||||
->whereIn('assets.id', $asset_ids)
|
||||
->withTrashed();
|
||||
|
||||
$assets = $assets->get();
|
||||
|
||||
switch ($sort_override) {
|
||||
case 'model':
|
||||
$query->OrderModels($order);
|
||||
break;
|
||||
case 'model_number':
|
||||
$query->OrderModelNumber($order);
|
||||
break;
|
||||
case 'category':
|
||||
$query->OrderCategory($order);
|
||||
break;
|
||||
case 'manufacturer':
|
||||
$query->OrderManufacturer($order);
|
||||
break;
|
||||
case 'company':
|
||||
$query->OrderCompany($order);
|
||||
break;
|
||||
case 'location':
|
||||
$query->OrderLocation($order);
|
||||
break;
|
||||
case 'rtd_location':
|
||||
$query->OrderRtdLocation($order);
|
||||
break;
|
||||
case 'status_label':
|
||||
$query->OrderStatus($order);
|
||||
break;
|
||||
case 'supplier':
|
||||
$query->OrderSupplier($order);
|
||||
break;
|
||||
case 'assigned_to':
|
||||
$query->OrderAssigned($order);
|
||||
break;
|
||||
default:
|
||||
$query->orderBy($column_sort, $order);
|
||||
break;
|
||||
}
|
||||
$assets = $query->get();
|
||||
|
||||
if ($assets->isEmpty()) {
|
||||
Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
|
||||
@@ -116,6 +161,7 @@ class BulkAssetsController extends Controller
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
|
||||
foreach($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
@@ -151,7 +197,6 @@ class BulkAssetsController extends Controller
|
||||
|
||||
case 'edit':
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
@@ -160,40 +205,7 @@ class BulkAssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sort_override) {
|
||||
case 'model':
|
||||
$assets->OrderModels($order);
|
||||
break;
|
||||
case 'model_number':
|
||||
$assets->OrderModelNumber($order);
|
||||
break;
|
||||
case 'category':
|
||||
$assets->OrderCategory($order);
|
||||
break;
|
||||
case 'manufacturer':
|
||||
$assets->OrderManufacturer($order);
|
||||
break;
|
||||
case 'company':
|
||||
$assets->OrderCompany($order);
|
||||
break;
|
||||
case 'location':
|
||||
$assets->OrderLocation($order);
|
||||
case 'rtd_location':
|
||||
$assets->OrderRtdLocation($order);
|
||||
break;
|
||||
case 'status_label':
|
||||
$assets->OrderStatus($order);
|
||||
break;
|
||||
case 'supplier':
|
||||
$assets->OrderSupplier($order);
|
||||
break;
|
||||
case 'assigned_to':
|
||||
$assets->OrderAssigned($order);
|
||||
break;
|
||||
default:
|
||||
$assets->orderBy($column_sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return redirect()->back()->with('error', 'No action selected');
|
||||
}
|
||||
@@ -212,14 +224,26 @@ class BulkAssetsController extends Controller
|
||||
$error_array = array();
|
||||
|
||||
// Get the back url from the session and then destroy the session
|
||||
$bulk_back_url = route('hardware.index');
|
||||
|
||||
if ($request->session()->has('bulk_back_url')) {
|
||||
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
||||
}
|
||||
$bulk_back_url = $request->session()->pull('bulk_back_url', url()->previous());
|
||||
|
||||
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||
|
||||
// find custom field input attributes that start with 'null_'
|
||||
$null_custom_fields_inputs = array_filter($request->all(), function ($key) {
|
||||
// filter out all keys that start with 'null_'
|
||||
return (strpos($key, 'null_') === 0);
|
||||
}, ARRAY_FILTER_USE_KEY);;
|
||||
// remove 'null' from the keys
|
||||
$custom_fields_to_null = [];
|
||||
foreach ($null_custom_fields_inputs as $key => $value) {
|
||||
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
|
||||
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||
@@ -257,7 +281,9 @@ class BulkAssetsController extends Controller
|
||||
|| ($request->filled('null_expected_checkin_date'))
|
||||
|| ($request->filled('null_next_audit_date'))
|
||||
|| ($request->filled('null_asset_eol_date'))
|
||||
|| ($request->filled('null_notes'))
|
||||
|| ($request->anyFilled($custom_field_columns))
|
||||
|| ($request->anyFilled(array_keys($null_custom_fields_inputs)))
|
||||
|
||||
) {
|
||||
// Let's loop through those assets and build an update array
|
||||
@@ -280,10 +306,14 @@ class BulkAssetsController extends Controller
|
||||
->conditionallyAddItem('supplier_id')
|
||||
->conditionallyAddItem('warranty_months')
|
||||
->conditionallyAddItem('next_audit_date')
|
||||
->conditionallyAddItem('asset_eol_date');
|
||||
->conditionallyAddItem('asset_eol_date')
|
||||
->conditionallyAddItem('notes');
|
||||
foreach ($custom_field_columns as $key => $custom_field_column) {
|
||||
$this->conditionallyAddItem($custom_field_column);
|
||||
}
|
||||
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
|
||||
$this->conditionallyAddItem($key);
|
||||
}
|
||||
|
||||
if (!($asset->eol_explicit)) {
|
||||
if ($request->filled('model_id')) {
|
||||
@@ -334,6 +364,10 @@ class BulkAssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->input('null_notes')=='1') {
|
||||
$this->update_array['notes'] = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
@@ -374,10 +408,12 @@ class BulkAssetsController extends Controller
|
||||
// This could probably be added to a form request.
|
||||
// If the asset isn't assigned, we don't care what the status is.
|
||||
// Otherwise we need to make sure the status type is still a deployable one.
|
||||
if (
|
||||
($asset->assigned_to == '')
|
||||
|| ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1')
|
||||
) {
|
||||
|
||||
$unassigned = $asset->assigned_to == '';
|
||||
$deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1';
|
||||
$pending = $updated_status->pending === 1;
|
||||
|
||||
if ($unassigned || $deployable || $pending) {
|
||||
$this->update_array['status_id'] = $updated_status->id;
|
||||
}
|
||||
|
||||
@@ -429,6 +465,7 @@ class BulkAssetsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Start all the custom fields shenanigans
|
||||
*/
|
||||
|
||||
@@ -436,6 +473,15 @@ class BulkAssetsController extends Controller
|
||||
if ($asset->model->fieldset) {
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
|
||||
// null custom fields
|
||||
if ($custom_fields_to_null) {
|
||||
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
|
||||
if ($field->db_column == $key) {
|
||||
$this->update_array[$field->db_column] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
|
||||
if (Gate::allows('admin')) {
|
||||
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
|
||||
@@ -494,7 +540,13 @@ class BulkAssetsController extends Controller
|
||||
} // end asset foreach
|
||||
|
||||
if ($has_errors > 0) {
|
||||
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
|
||||
session()->put('bulkedit_ids', $request->input('ids'));
|
||||
session()->put('bulk_asset_errors',$error_array);
|
||||
|
||||
return redirect()
|
||||
->route('hardware.index')
|
||||
->with('bulk_asset_errors', $error_array)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
||||
@@ -585,6 +637,7 @@ class BulkAssetsController extends Controller
|
||||
$admin = auth()->user();
|
||||
|
||||
$target = $this->determineCheckoutTarget();
|
||||
session()->put(['checkout_to_type' => $target]);
|
||||
|
||||
if (! is_array($request->get('selected_assets'))) {
|
||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
@@ -665,4 +718,54 @@ class BulkAssetsController extends Controller
|
||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
||||
}
|
||||
}
|
||||
public function hasUndeployableStatus (array $asset_ids)
|
||||
{
|
||||
$undeployable = Asset::whereIn('id', $asset_ids)
|
||||
->undeployable()
|
||||
->get();
|
||||
|
||||
$undeployableTags = $undeployable->map(function ($asset) {
|
||||
return [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
$undeployableIds = array_column($undeployableTags, 'id');
|
||||
$filtered_ids = array_diff($asset_ids, $undeployableIds);
|
||||
|
||||
if($undeployable->isNotEmpty()) {
|
||||
return ['status' => true, 'tags' => $undeployableTags, 'asset_ids' => $filtered_ids];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function bulkEditForm(): View|RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
$asset_ids = session()->pull('bulkedit_ids', []);
|
||||
|
||||
if (empty($asset_ids)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||
}
|
||||
|
||||
$assets = Asset::with('model')->withTrashed()->whereIn('id', $asset_ids)->get();
|
||||
|
||||
if ($assets->isEmpty()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
|
||||
}
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
foreach ($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('models', $models->pluck(['model']))
|
||||
->with('modelNames', $modelNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller
|
||||
$update_array['min_amt'] = $request->input('min_amt');
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('require_serial')) {
|
||||
$update_array['require_serial'] = $request->input('require_serial');
|
||||
}
|
||||
|
||||
if (count($update_array) > 0) {
|
||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||
|
||||
@@ -68,6 +68,7 @@ class CategoriesController extends Controller
|
||||
$category->eula_text = $request->input('eula_text');
|
||||
$category->use_default_eula = $request->input('use_default_eula', '0');
|
||||
$category->require_acceptance = $request->input('require_acceptance', '0');
|
||||
$category->alert_on_response = $request->input('alert_on_response', '0');
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
$category->notes = $request->input('notes');
|
||||
$category->created_by = auth()->id();
|
||||
@@ -121,6 +122,7 @@ class CategoriesController extends Controller
|
||||
$category->eula_text = $request->input('eula_text');
|
||||
$category->use_default_eula = $request->input('use_default_eula', '0');
|
||||
$category->require_acceptance = $request->input('require_acceptance', '0');
|
||||
$category->alert_on_response = $request->input('alert_on_response', '0');
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
$category->notes = $request->input('notes');
|
||||
|
||||
|
||||
@@ -123,11 +123,13 @@ final class CompaniesController extends Controller
|
||||
*/
|
||||
public function destroy($companyId) : RedirectResponse
|
||||
{
|
||||
|
||||
if (is_null($company = Company::find($companyId))) {
|
||||
return redirect()->route('companies.index')
|
||||
->with('error', trans('admin/companies/message.not_found'));
|
||||
}
|
||||
|
||||
|
||||
$this->authorize('delete', $company);
|
||||
if (! $company->isDeletable()) {
|
||||
return redirect()->route('companies.index')
|
||||
|
||||
@@ -100,8 +100,8 @@ class ComponentCheckinController extends Controller
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
|
||||
trans('admin/components/message.checkin.success'));
|
||||
return Helper::getRedirectOption($request, $component->id, 'Components')
|
||||
->with('success', trans('admin/components/message.checkin.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
|
||||
@@ -120,6 +120,7 @@ class ComponentCheckoutController extends Controller
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
|
||||
return Helper::getRedirectOption($request, $component->id, 'Components')
|
||||
->with('success', trans('admin/components/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +88,16 @@ class ComponentsController extends Controller
|
||||
|
||||
$component = $request->handleImages($component);
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
if($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success'));
|
||||
return Helper::getRedirectOption($request, $component->id, 'Components')
|
||||
->with('success', trans('admin/components/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
@@ -111,6 +117,7 @@ class ComponentsController extends Controller
|
||||
{
|
||||
|
||||
$this->authorize('update', $component);
|
||||
session()->put('back_url', url()->previous());
|
||||
return view('components/edit')
|
||||
->with('item', $component)
|
||||
->with('category_type', 'component');
|
||||
@@ -164,7 +171,8 @@ class ComponentsController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success'));
|
||||
return Helper::getRedirectOption($request, $component->id, 'Components')
|
||||
->with('success', trans('admin/components/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ class ConsumableCheckoutController extends Controller
|
||||
|
||||
|
||||
// Redirect to the new consumable page
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success'));
|
||||
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
|
||||
->with('success', trans('admin/consumables/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\Consumable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
@@ -81,16 +81,33 @@ class ConsumablesController extends Controller
|
||||
$consumable->purchase_date = $request->input('purchase_date');
|
||||
$consumable->purchase_cost = $request->input('purchase_cost');
|
||||
$consumable->qty = $request->input('qty');
|
||||
$consumable->created_by = auth()->id();
|
||||
$consumable->created_by = auth()->id();
|
||||
$consumable->notes = $request->input('notes');
|
||||
|
||||
|
||||
$consumable = $request->handleImages($consumable);
|
||||
if ($request->has('use_cloned_image')) {
|
||||
$cloned_model_img = Consumable::select('image')->find($request->input('clone_image_from_id'));
|
||||
if ($cloned_model_img) {
|
||||
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
||||
$new_image = 'consumables/'.$new_image_name;
|
||||
Storage::disk('public')->copy('consumables/'.$cloned_model_img->image, $new_image);
|
||||
$consumable->image = $new_image_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$consumable = $request->handleImages($consumable);
|
||||
}
|
||||
|
||||
if($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($consumable->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success'));
|
||||
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
|
||||
->with('success', trans('admin/consumables/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
|
||||
@@ -107,6 +124,7 @@ class ConsumablesController extends Controller
|
||||
public function edit(Consumable $consumable) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize($consumable);
|
||||
session()->put('back_url', url()->previous());
|
||||
return view('consumables/edit')
|
||||
->with('item', $consumable)
|
||||
->with('category_type', 'consumable');
|
||||
@@ -160,7 +178,8 @@ class ConsumablesController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($consumable->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success'));
|
||||
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
|
||||
->with('success', trans('admin/consumables/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
|
||||
@@ -210,9 +229,10 @@ class ConsumablesController extends Controller
|
||||
$consumable_to_close = $consumable;
|
||||
$consumable = clone $consumable_to_close;
|
||||
$consumable->id = null;
|
||||
$consumable->image = null;
|
||||
$consumable->created_by = null;
|
||||
|
||||
return view('consumables/edit')->with('item', $consumable);
|
||||
return view('consumables/edit')
|
||||
->with('cloned_model', $consumable_to_close)
|
||||
->with('item', $consumable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use App\Models\Maintenance;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
@@ -32,6 +41,45 @@ abstract class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
static $map_object_type = [
|
||||
'accessories' => Accessory::class,
|
||||
'maintenances' => Maintenance::class,
|
||||
'assets' => Asset::class,
|
||||
'components' => Component::class,
|
||||
'consumables' => Consumable::class,
|
||||
'hardware' => Asset::class,
|
||||
'licenses' => License::class,
|
||||
'locations' => Location::class,
|
||||
'models' => AssetModel::class,
|
||||
'users' => User::class,
|
||||
];
|
||||
|
||||
static $map_storage_path = [
|
||||
'accessories' => 'private_uploads/accessories/',
|
||||
'maintenances' => 'private_uploads/maintenances/',
|
||||
'assets' => 'private_uploads/assets/',
|
||||
'components' => 'private_uploads/components/',
|
||||
'consumables' => 'private_uploads/consumables/',
|
||||
'hardware' => 'private_uploads/assets/',
|
||||
'licenses' => 'private_uploads/licenses/',
|
||||
'locations' => 'private_uploads/locations/',
|
||||
'models' => 'private_uploads/models/',
|
||||
'users' => 'private_uploads/users/',
|
||||
];
|
||||
|
||||
static $map_file_prefix= [
|
||||
'accessories' => 'accessory',
|
||||
'maintenances' => 'maintenance',
|
||||
'assets' => 'asset',
|
||||
'components' => 'component',
|
||||
'consumables' => 'consumable',
|
||||
'hardware' => 'asset',
|
||||
'licenses' => 'license',
|
||||
'locations' => 'location',
|
||||
'models' => 'model',
|
||||
'users' => 'user',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
view()->share('signedIn', Auth::check());
|
||||
|
||||
@@ -144,10 +144,9 @@ class CustomFieldsController extends Controller
|
||||
*/
|
||||
public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::find($field_id);
|
||||
|
||||
$this->authorize('update', $field);
|
||||
|
||||
// Check that the field exists - this is mostly related to the demo, where we
|
||||
// rewrite the data every x minutes, so it's possible someone might be disassociating
|
||||
// a field from a fieldset just as we're wiping the database
|
||||
@@ -157,11 +156,12 @@ class CustomFieldsController extends Controller
|
||||
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
|
||||
->with('success', trans('admin/custom_fields/message.field.delete.success'));
|
||||
} else {
|
||||
return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]);
|
||||
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error'))
|
||||
->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]);
|
||||
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error'));
|
||||
|
||||
|
||||
}
|
||||
@@ -172,20 +172,16 @@ class CustomFieldsController extends Controller
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function destroy($field_id) : RedirectResponse
|
||||
public function destroy(CustomField $field) : RedirectResponse
|
||||
{
|
||||
if ($field = CustomField::find($field_id)) {
|
||||
$this->authorize('delete', $field);
|
||||
$this->authorize('delete', CustomField::class);
|
||||
|
||||
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
|
||||
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
|
||||
}
|
||||
$field->delete();
|
||||
return redirect()->route("fields.index")
|
||||
->with("success", trans('admin/custom_fields/message.field.delete.success'));
|
||||
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
|
||||
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.in_use'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
|
||||
$field->delete();
|
||||
return redirect()->route("fields.index")
|
||||
->with("success", trans('admin/custom_fields/message.field.delete.success'));
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +194,7 @@ class CustomFieldsController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, CustomField $field) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $field);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$fieldsets = CustomFieldset::get();
|
||||
$customFormat = '';
|
||||
if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) {
|
||||
@@ -228,7 +224,7 @@ class CustomFieldsController extends Controller
|
||||
*/
|
||||
public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $field);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$show_in_email = $request->get("show_in_email", 0);
|
||||
$display_in_user_view = $request->get("display_in_user_view", 0);
|
||||
|
||||
@@ -265,7 +261,6 @@ class CustomFieldsController extends Controller
|
||||
|
||||
if ($field->save()) {
|
||||
|
||||
|
||||
// Sync fields with fieldsets
|
||||
$fieldset_array = $request->input('associate_fieldsets');
|
||||
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
|
||||
|
||||
@@ -64,12 +64,7 @@ class LicenseCheckinController extends Controller
|
||||
|
||||
$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
|
||||
$rules = [
|
||||
@@ -86,8 +81,10 @@ class LicenseCheckinController extends Controller
|
||||
}
|
||||
|
||||
if($licenseSeat->assigned_to != null){
|
||||
$return_to = User::find($licenseSeat->assigned_to);
|
||||
session()->put('checkedInFrom', $return_to->id);
|
||||
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
|
||||
if ($return_to) {
|
||||
session()->put('checkedInFrom', $return_to->id);
|
||||
}
|
||||
} else {
|
||||
$return_to = Asset::find($licenseSeat->asset_id);
|
||||
}
|
||||
@@ -96,16 +93,22 @@ class LicenseCheckinController extends Controller
|
||||
$licenseSeat->assigned_to = null;
|
||||
$licenseSeat->asset_id = null;
|
||||
$licenseSeat->notes = $request->input('notes');
|
||||
if (! $licenseSeat->license->reassignable) {
|
||||
$licenseSeat->unreassignable_seat = true;
|
||||
}
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($request->get('redirect_option') === 'target'){
|
||||
session()->put(['checkout_to_type' => 'user']);
|
||||
}
|
||||
|
||||
// Was the asset updated?
|
||||
if ($licenseSeat->save()) {
|
||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
|
||||
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes));
|
||||
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
|
||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||
->with('success', trans('admin/licenses/message.checkin.success'));
|
||||
}
|
||||
|
||||
// Redirect to the license page with error
|
||||
@@ -127,21 +130,17 @@ class LicenseCheckinController extends Controller
|
||||
$license = License::findOrFail($licenseId);
|
||||
$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)
|
||||
->whereNotNull('assigned_to')
|
||||
->with('user')
|
||||
->with('user', 'license')
|
||||
->get();
|
||||
|
||||
$license = $licenseSeatsByUser->first()?->license;
|
||||
foreach ($licenseSeatsByUser as $user_seat) {
|
||||
$user_seat->assigned_to = null;
|
||||
|
||||
if ($license && ! $license->reassignable) {
|
||||
$user_seat->unreassignable_seat = true;
|
||||
}
|
||||
if ($user_seat->save()) {
|
||||
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'));
|
||||
@@ -154,9 +153,12 @@ class LicenseCheckinController extends Controller
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
$license = $licenseSeatsByAsset->first()?->license;
|
||||
foreach ($licenseSeatsByAsset as $asset_seat) {
|
||||
$asset_seat->asset_id = null;
|
||||
|
||||
if ($license && ! $license->reassignable) {
|
||||
$asset_seat->unreassignable_seat = true;
|
||||
}
|
||||
if ($asset_seat->save()) {
|
||||
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'));
|
||||
|
||||
@@ -39,6 +39,11 @@ class LicenseCheckoutController extends Controller
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||
}
|
||||
|
||||
// We don't currently allow checking out licenses to locations, so we'll reset that to user if needed
|
||||
if (session()->get('checkout_to_type') == 'location') {
|
||||
session()->put(['checkout_to_type' => 'user']);
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('licenses/checkout', compact('license'));
|
||||
}
|
||||
@@ -70,17 +75,15 @@ class LicenseCheckoutController extends Controller
|
||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||
$licenseSeat->created_by = auth()->id();
|
||||
$licenseSeat->notes = $request->input('notes');
|
||||
|
||||
|
||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
||||
|
||||
if ($request->filled('asset_id')) {
|
||||
|
||||
session()->put(['checkout_to_type' => 'asset']);
|
||||
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
|
||||
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
|
||||
|
||||
} elseif ($request->filled('assigned_to')) {
|
||||
session()->put(['checkout_to_type' => 'user']);
|
||||
$checkoutTarget = $this->checkoutToUser($licenseSeat);
|
||||
$request->request->add(['assigned_user' => $checkoutTarget->id]);
|
||||
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
|
||||
@@ -89,7 +92,9 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
|
||||
if ($checkoutTarget) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
|
||||
|
||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||
->with('success', trans('admin/licenses/message.checkout.success'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,10 +102,15 @@ class LicensesController extends Controller
|
||||
$license->created_by = auth()->id();
|
||||
$license->min_amt = $request->input('min_amt');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
if($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success'));
|
||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||
->with('success', trans('admin/licenses/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($license->getErrors());
|
||||
@@ -125,7 +130,7 @@ class LicensesController extends Controller
|
||||
{
|
||||
|
||||
$this->authorize('update', $license);
|
||||
|
||||
session()->put('back_url', url()->previous());
|
||||
$maintained_list = [
|
||||
'' => 'Maintained',
|
||||
'1' => 'Yes',
|
||||
@@ -181,7 +186,8 @@ class LicensesController extends Controller
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success'));
|
||||
return Helper::getRedirectOption($request, $license->id, 'Licenses')
|
||||
->with('success', trans('admin/licenses/message.update.success'));
|
||||
}
|
||||
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
|
||||
return redirect()->back()->withInput()->withErrors($license->getErrors());
|
||||
@@ -239,16 +245,25 @@ class LicensesController extends Controller
|
||||
$license = License::with('assignedusers')->find($license->id);
|
||||
|
||||
$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();
|
||||
$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);
|
||||
}
|
||||
|
||||
$this->authorize('view', $license);
|
||||
return view('licenses.view', compact('license'))
|
||||
->with('users_count', $users_count)
|
||||
->with('total_seats_count', $total_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);
|
||||
|
||||
}
|
||||
|
||||
@@ -302,13 +317,16 @@ class LicensesController extends Controller
|
||||
$response = new StreamedResponse(function () {
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
$licenses= License::with('company',
|
||||
$licenses = License::with('company',
|
||||
'manufacturer',
|
||||
'category',
|
||||
'supplier',
|
||||
'adminuser',
|
||||
'assignedusers')
|
||||
->orderBy('created_at', 'DESC');
|
||||
'assignedusers');
|
||||
if (request()->filled('category_id')) {
|
||||
$licenses = $licenses->where('category_id', request()->input('category_id'));
|
||||
}
|
||||
$licenses = $licenses->orderBy('created_at', 'DESC');
|
||||
Company::scopeCompanyables($licenses)
|
||||
->chunk(500, function ($licenses) use ($handle) {
|
||||
$headers = [
|
||||
@@ -355,7 +373,7 @@ class LicensesController extends Controller
|
||||
$license->order_number,
|
||||
$license->free_seat_count,
|
||||
$license->seats,
|
||||
($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')),
|
||||
($license->adminuser ? $license->adminuser->display_name : trans('admin/reports/general.deleted_user')),
|
||||
$license->depreciation ? $license->depreciation->name: '',
|
||||
$license->updated_at,
|
||||
$license->deleted_at,
|
||||
|
||||
@@ -96,7 +96,18 @@ class LocationsController extends Controller
|
||||
$location->company_id = $request->input('company_id');
|
||||
}
|
||||
|
||||
$location = $request->handleImages($location);
|
||||
if ($request->has('use_cloned_image')) {
|
||||
$cloned_model_img = Location::select('image')->find($request->input('clone_image_from_id'));
|
||||
if ($cloned_model_img) {
|
||||
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
||||
$new_image = 'locations/'.$new_image_name;
|
||||
Storage::disk('public')->copy('locations/'.$cloned_model_img->image, $new_image);
|
||||
$location->image = $new_image_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$location = $request->handleImages($location);
|
||||
}
|
||||
|
||||
if ($location->save()) {
|
||||
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success'));
|
||||
@@ -275,9 +286,9 @@ class LocationsController extends Controller
|
||||
|
||||
// unset these values
|
||||
$location->id = null;
|
||||
$location->image = null;
|
||||
|
||||
return view('locations/edit')
|
||||
->with('cloned_model', $location_to_clone)
|
||||
->with('item', $location);
|
||||
}
|
||||
|
||||
|
||||
@@ -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'));
|
||||
|
||||
}
|
||||
}
|
||||
216
app/Http/Controllers/MaintenancesController.php
Normal file
216
app/Http/Controllers/MaintenancesController.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Maintenance;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use \Illuminate\Http\RedirectResponse;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Asset Maintenance for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v2.0
|
||||
*/
|
||||
class MaintenancesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the asset maintenances listing.
|
||||
*/
|
||||
public function index() : View
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
return view('maintenances.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form view to create a new asset maintenance.
|
||||
*
|
||||
* @see MaintenancesController::postCreate() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
* @return mixed
|
||||
*/
|
||||
public function create() : View
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$asset = null;
|
||||
|
||||
if ($asset = Asset::find(request('asset_id'))) {
|
||||
// We have to set this so that the correct property is set in the select2 ajax dropdown
|
||||
$asset->asset_id = $asset->id;
|
||||
}
|
||||
|
||||
return view('maintenances/edit')
|
||||
->with('maintenanceType', Maintenance::getImprovementOptions())
|
||||
->with('asset', $asset)
|
||||
->with('item', new Maintenance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores the new asset maintenance
|
||||
*
|
||||
* @see MaintenancesController::getCreate() method for the form
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function store(ImageUploadRequest $request) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
|
||||
|
||||
// Loop through the selected assets
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
$maintenance = new Maintenance();
|
||||
$maintenance->supplier_id = $request->input('supplier_id');
|
||||
$maintenance->is_warranty = $request->input('is_warranty');
|
||||
$maintenance->cost = $request->input('cost');
|
||||
$maintenance->notes = $request->input('notes');
|
||||
|
||||
// Save the asset maintenance data
|
||||
$maintenance->asset_id = $asset->id;
|
||||
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||
$maintenance->name = $request->input('name');
|
||||
$maintenance->start_date = $request->input('start_date');
|
||||
$maintenance->completion_date = $request->input('completion_date');
|
||||
$maintenance->created_by = auth()->id();
|
||||
|
||||
if (($maintenance->completion_date !== null)
|
||||
&& ($maintenance->start_date !== '')
|
||||
&& ($maintenance->start_date !== '0000-00-00')
|
||||
) {
|
||||
$startDate = Carbon::parse($maintenance->start_date);
|
||||
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
}
|
||||
|
||||
$maintenance = $request->handleImages($maintenance);
|
||||
|
||||
// Was the asset maintenance created?
|
||||
if (!$maintenance->save()) {
|
||||
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/maintenances/message.create.success'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form view to edit a selected asset maintenance.
|
||||
*
|
||||
* @see MaintenancesController::postEdit() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function edit(Maintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$this->authorize('update', $maintenance->asset);
|
||||
|
||||
return view('maintenances/edit')
|
||||
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
|
||||
->with('asset_ids', request()->input('asset_ids', []))
|
||||
->with('maintenanceType', Maintenance::getImprovementOptions())
|
||||
->with('item', $maintenance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores an update to an asset maintenance
|
||||
*
|
||||
* @see MaintenancesController::postEdit() method that stores the data
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param Request $request
|
||||
* @param int $maintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, Maintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$this->authorize('update', $maintenance->asset);
|
||||
|
||||
$maintenance->supplier_id = $request->input('supplier_id');
|
||||
$maintenance->is_warranty = $request->input('is_warranty', 0);
|
||||
$maintenance->cost = $request->input('cost');
|
||||
$maintenance->notes = $request->input('notes');
|
||||
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
|
||||
$maintenance->name = $request->input('name');
|
||||
$maintenance->start_date = $request->input('start_date');
|
||||
$maintenance->completion_date = $request->input('completion_date');
|
||||
|
||||
|
||||
// Todo - put this in a getter/setter?
|
||||
if (($maintenance->completion_date == null))
|
||||
{
|
||||
if (($maintenance->asset_maintenance_time !== 0)
|
||||
|| (! is_null($maintenance->asset_maintenance_time))
|
||||
) {
|
||||
$maintenance->asset_maintenance_time = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (($maintenance->completion_date !== null)
|
||||
&& ($maintenance->start_date !== '')
|
||||
&& ($maintenance->start_date !== '0000-00-00')
|
||||
) {
|
||||
$startDate = Carbon::parse($maintenance->start_date);
|
||||
$completionDate = Carbon::parse($maintenance->completion_date);
|
||||
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
|
||||
}
|
||||
$maintenance = $request->handleImages($maintenance);
|
||||
|
||||
if ($maintenance->save()) {
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/maintenances/message.edit.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an asset maintenance
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param int $maintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function destroy(Maintenance $maintenance) : RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
$this->authorize('update', $maintenance->asset);
|
||||
// Delete the asset maintenance
|
||||
$maintenance->delete();
|
||||
// Redirect to the asset_maintenance management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('success', trans('admin/maintenances/message.delete.success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* View an asset maintenance
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @param int $maintenanceId
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show(Maintenance $maintenance) : View | RedirectResponse
|
||||
{
|
||||
return view('maintenances.view')->with('maintenance', $maintenance);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class ManufacturersController extends Controller
|
||||
$manufacturers_count = Manufacturer::withTrashed()->count();
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,21 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Transformers\ProfileTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to User Profiles for
|
||||
* the Snipe-IT Asset Management application.
|
||||
@@ -220,7 +226,7 @@ class ProfileController extends Controller
|
||||
|
||||
if (!$user = User::find(auth()->id())) {
|
||||
return redirect()->back()
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => auth()->id()]));
|
||||
}
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
@@ -234,4 +240,28 @@ class ProfileController extends Controller
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
|
||||
{
|
||||
|
||||
$logentry = Actionlog::where('filename', $filename)->first();
|
||||
|
||||
// Make sure the user has permission to view this file
|
||||
if (auth()->id() != $logentry->target_id) {
|
||||
return redirect()->route('account')->with('error', trans('general.generic_model_not_found', ['model' => 'file']));
|
||||
}
|
||||
|
||||
if (config('filesystems.default') == 's3_private') {
|
||||
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
|
||||
}
|
||||
|
||||
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
|
||||
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\AssetMaintenance;
|
||||
use App\Models\Maintenance;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
@@ -17,13 +17,11 @@ use App\Models\Depreciation;
|
||||
use App\Models\License;
|
||||
use App\Models\ReportTemplate;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\CheckoutAssetNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
@@ -161,7 +159,7 @@ class ReportsController extends Controller
|
||||
$row[] = e($asset->serial);
|
||||
|
||||
if ($target = $asset->assignedTo) {
|
||||
$row[] = e($target->present()->name());
|
||||
$row[] = e($target->display_name);
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -184,7 +182,7 @@ class ReportsController extends Controller
|
||||
$currency = e(Setting::getSettings()->default_currency);
|
||||
}
|
||||
|
||||
$row[] = $asset->purchase_date;
|
||||
$row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false);
|
||||
$row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost);
|
||||
$row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue());
|
||||
$row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
|
||||
@@ -277,7 +275,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($actionlog->target) {
|
||||
if ($actionlog->targetType() == 'user') {
|
||||
$target_name = $actionlog->target->getFullNameAttribute();
|
||||
$target_name = $actionlog->target->display_name;
|
||||
} else {
|
||||
$target_name = $actionlog->target->getDisplayNameAttribute();
|
||||
}
|
||||
@@ -291,7 +289,7 @@ class ReportsController extends Controller
|
||||
|
||||
$row = [
|
||||
$actionlog->created_at,
|
||||
($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
|
||||
($actionlog->adminuser) ? e($actionlog->adminuser->display_name) : '',
|
||||
$actionlog->present()->actionType(),
|
||||
e($actionlog->itemType()),
|
||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||
@@ -832,7 +830,7 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('location')) {
|
||||
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
|
||||
$row[] = ($asset->location) ? $asset->location->display_name : '';
|
||||
}
|
||||
|
||||
if ($request->filled('location_address')) {
|
||||
@@ -845,7 +843,7 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('rtd_location')) {
|
||||
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : '';
|
||||
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->display_name : '';
|
||||
}
|
||||
|
||||
if ($request->filled('rtd_location_address')) {
|
||||
@@ -858,8 +856,8 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('assigned_to')) {
|
||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : '');
|
||||
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
|
||||
$row[] = ($asset->assigned) ? $asset->assigned->display_name : '';
|
||||
$row[] = ($asset->assigned) ? $asset->assignedType() : '';
|
||||
}
|
||||
|
||||
if ($request->filled('username')) {
|
||||
@@ -1038,11 +1036,11 @@ class ReportsController extends Controller
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
public function getAssetMaintenancesReport() : View
|
||||
public function getMaintenancesReport() : View
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
|
||||
return view('reports.asset_maintenances');
|
||||
return view('reports.maintenances');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1051,11 +1049,11 @@ class ReportsController extends Controller
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
public function exportAssetMaintenancesReport() : Response
|
||||
public function exportMaintenancesReport() : Response
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
// Grab all the improvements
|
||||
$assetMaintenances = AssetMaintenance::with('asset', 'supplier')
|
||||
$Maintenances = Maintenance::with('asset', 'supplier')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->get();
|
||||
|
||||
@@ -1063,36 +1061,36 @@ class ReportsController extends Controller
|
||||
|
||||
$header = [
|
||||
trans('admin/hardware/table.asset_tag'),
|
||||
trans('admin/asset_maintenances/table.asset_name'),
|
||||
trans('admin/maintenances/table.asset_name'),
|
||||
trans('general.supplier'),
|
||||
trans('admin/asset_maintenances/form.asset_maintenance_type'),
|
||||
trans('admin/asset_maintenances/form.title'),
|
||||
trans('admin/asset_maintenances/form.start_date'),
|
||||
trans('admin/asset_maintenances/form.completion_date'),
|
||||
trans('admin/asset_maintenances/form.asset_maintenance_time'),
|
||||
trans('admin/asset_maintenances/form.cost'),
|
||||
trans('admin/maintenances/form.asset_maintenance_type'),
|
||||
trans('admin/maintenances/form.title'),
|
||||
trans('admin/maintenances/form.start_date'),
|
||||
trans('admin/maintenances/form.completion_date'),
|
||||
trans('admin/maintenances/form.asset_maintenance_time'),
|
||||
trans('admin/maintenances/form.cost'),
|
||||
];
|
||||
|
||||
$header = array_map('trim', $header);
|
||||
$rows[] = implode(',', $header);
|
||||
|
||||
foreach ($assetMaintenances as $assetMaintenance) {
|
||||
foreach ($Maintenances as $maintenance) {
|
||||
$row = [];
|
||||
$row[] = str_replace(',', '', e($assetMaintenance->asset->asset_tag));
|
||||
$row[] = str_replace(',', '', e($assetMaintenance->asset->name));
|
||||
$row[] = str_replace(',', '', e($assetMaintenance->supplier->name));
|
||||
$row[] = e($assetMaintenance->improvement_type);
|
||||
$row[] = e($assetMaintenance->title);
|
||||
$row[] = e($assetMaintenance->start_date);
|
||||
$row[] = e($assetMaintenance->completion_date);
|
||||
if (is_null($assetMaintenance->asset_maintenance_time)) {
|
||||
$row[] = str_replace(',', '', e($maintenance->asset->asset_tag));
|
||||
$row[] = str_replace(',', '', e($maintenance->asset->name));
|
||||
$row[] = str_replace(',', '', e($maintenance->supplier->name));
|
||||
$row[] = e($maintenance->improvement_type);
|
||||
$row[] = e($maintenance->name);
|
||||
$row[] = e($maintenance->start_date);
|
||||
$row[] = e($maintenance->completion_date);
|
||||
if (is_null($maintenance->asset_maintenance_time)) {
|
||||
$improvementTime = (int) Carbon::now()
|
||||
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
|
||||
->diffInDays(Carbon::parse($maintenance->start_date), true);
|
||||
} else {
|
||||
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
|
||||
$improvementTime = (int) $maintenance->asset_maintenance_time;
|
||||
}
|
||||
$row[] = $improvementTime;
|
||||
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
|
||||
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($maintenance->cost);
|
||||
$rows[] = implode(',', $row);
|
||||
}
|
||||
|
||||
@@ -1262,7 +1260,7 @@ class ReportsController extends Controller
|
||||
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
|
||||
$row[] = str_replace(',', '', e($item['assetItem']->name));
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +352,7 @@ class SettingsController extends Controller
|
||||
$setting->dash_chart_type = $request->input('dash_chart_type');
|
||||
$setting->profile_edit = $request->input('profile_edit', 0);
|
||||
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
|
||||
$setting->manager_view_enabled = $request->input('manager_view_enabled', 0);
|
||||
|
||||
|
||||
if ($request->input('per_page') != '') {
|
||||
@@ -872,6 +873,7 @@ class SettingsController extends Controller
|
||||
$setting->ldap_default_group = $request->input('ldap_default_group');
|
||||
$setting->ldap_filter = $request->input('ldap_filter');
|
||||
$setting->ldap_username_field = $request->input('ldap_username_field');
|
||||
$setting->ldap_display_name = $request->input('ldap_display_name');
|
||||
$setting->ldap_lname_field = $request->input('ldap_lname_field');
|
||||
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
||||
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
||||
@@ -888,7 +890,12 @@ class SettingsController extends Controller
|
||||
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
|
||||
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
|
||||
$setting->ldap_phone_field = $request->input('ldap_phone');
|
||||
$setting->ldap_mobile = $request->input('ldap_mobile');
|
||||
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
|
||||
$setting->ldap_address = $request->input('ldap_address');
|
||||
$setting->ldap_city = $request->input('ldap_city');
|
||||
$setting->ldap_state = $request->input('ldap_state');
|
||||
$setting->ldap_zip = $request->input('ldap_zip');
|
||||
$setting->ldap_country = $request->input('ldap_country');
|
||||
$setting->ldap_location = $request->input('ldap_location');
|
||||
$setting->ldap_dept = $request->input('ldap_dept');
|
||||
@@ -923,7 +930,7 @@ class SettingsController extends Controller
|
||||
* @since v5.0.0
|
||||
*/
|
||||
public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse
|
||||
{
|
||||
{
|
||||
if (is_null($setting = Setting::getSettings())) {
|
||||
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
|
||||
}
|
||||
@@ -1083,6 +1090,7 @@ class SettingsController extends Controller
|
||||
|
||||
if (! config('app.lock_passwords')) {
|
||||
if (Storage::exists($path.'/'.$filename)) {
|
||||
Log::warning('User '.auth()->user()->username.' is attempting to download backup file: '.$filename);
|
||||
return StorageHelper::downloader($path.'/'.$filename);
|
||||
} else {
|
||||
// Redirect to the backup page
|
||||
@@ -1110,6 +1118,7 @@ class SettingsController extends Controller
|
||||
if (Storage::exists($path . '/' . $filename)) {
|
||||
|
||||
try {
|
||||
Log::warning('User '.auth()->user()->username.' is attempting to delete backup file: '.$filename);
|
||||
Storage::delete($path . '/' . $filename);
|
||||
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
|
||||
} catch (\Exception $e) {
|
||||
@@ -1189,7 +1198,7 @@ class SettingsController extends Controller
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
|
||||
Log::warning('User '.auth()->user()->username.' is attempting to restore from: '. storage_path($path).'/'.$filename);
|
||||
|
||||
$restore_params = [
|
||||
'--force' => true,
|
||||
@@ -1338,9 +1347,11 @@ class SettingsController extends Controller
|
||||
'name' => config('mail.from.name'),
|
||||
'email' => config('mail.from.address'),
|
||||
])->notify(new MailTest());
|
||||
|
||||
Log::debug('Attempting to send mail to '.config('mail.from.address'));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent')));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Mail sent from '.config('mail.from.address') .' with errors '. $e->getMessage());
|
||||
Log::debug($e);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Supplier;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use \Illuminate\Contracts\View\View;
|
||||
|
||||
@@ -122,7 +121,7 @@ class SuppliersController extends Controller
|
||||
public function destroy($supplierId) : RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', Supplier::class);
|
||||
if (is_null($supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
|
||||
if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
|
||||
}
|
||||
|
||||
@@ -130,8 +129,8 @@ class SuppliersController extends Controller
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]));
|
||||
}
|
||||
|
||||
if ($supplier->asset_maintenances_count > 0) {
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count]));
|
||||
if ($supplier->maintenances_count > 0) {
|
||||
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]));
|
||||
}
|
||||
|
||||
if ($supplier->licenses_count > 0) {
|
||||
|
||||
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\User;
|
||||
use App\Notifications\WelcomeNotification;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Redirect;
|
||||
use Str;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use App\Notifications\CurrentInventory;
|
||||
|
||||
@@ -95,6 +90,7 @@ class UsersController extends Controller
|
||||
//Username, email, and password need to be handled specially because the need to respect config values on an edit.
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->display_name = $request->input('display_name');
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
@@ -105,6 +101,7 @@ class UsersController extends Controller
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle');
|
||||
$user->phone = $request->input('phone');
|
||||
$user->mobile = $request->input('mobile');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
@@ -130,31 +127,37 @@ class UsersController extends Controller
|
||||
}
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// we have to invoke the
|
||||
// we have to invoke the form request here to handle image uploads
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
if ($request->get('redirect_option') === 'back'){
|
||||
session()->put(['redirect_option' => 'index']);
|
||||
} else {
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
}
|
||||
|
||||
|
||||
if ($user->save()) {
|
||||
|
||||
if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) {
|
||||
|
||||
try {
|
||||
$user->notify(new WelcomeNotification($user));
|
||||
} catch (\Exception $e) {
|
||||
Log::warning('Could not send welcome notification for user: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($request->filled('groups')) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
} else {
|
||||
$user->groups()->sync([]);
|
||||
}
|
||||
|
||||
if (($request->input('email_user') == 1) && ($request->filled('email'))) {
|
||||
// Send the credentials through email
|
||||
$data = [];
|
||||
$data['email'] = e($request->input('email'));
|
||||
$data['username'] = e($request->input('username'));
|
||||
$data['first_name'] = e($request->input('first_name'));
|
||||
$data['last_name'] = e($request->input('last_name'));
|
||||
$data['password'] = e($request->input('password'));
|
||||
|
||||
$user->notify(new WelcomeNotification($data));
|
||||
}
|
||||
|
||||
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create'));
|
||||
return Helper::getRedirectOption($request, $user->id, 'Users')
|
||||
->with('success', trans('admin/users/message.success.create'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
@@ -186,6 +189,7 @@ class UsersController extends Controller
|
||||
{
|
||||
|
||||
$this->authorize('update', User::class);
|
||||
session()->put('back_url', url()->previous());
|
||||
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id);
|
||||
|
||||
if ($user) {
|
||||
@@ -246,22 +250,18 @@ class UsersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->display_name = $request->input('display_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->mobile = $request->input('mobile');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
@@ -271,8 +271,6 @@ class UsersController extends Controller
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
@@ -281,30 +279,49 @@ class UsersController extends Controller
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
|
||||
// Set this here so that we can overwrite it later if the user is an admin or superadmin
|
||||
$user->activated = $request->input('activated', auth()->user()->is($user) ? 1 : $user->activated);
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
// check for permissions related fields and only set them if the user has permission to edit them
|
||||
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
|
||||
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! auth()->user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $user->location_id]);
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! auth()->user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
@@ -312,7 +329,7 @@ class UsersController extends Controller
|
||||
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))
|
||||
return Helper::getRedirectOption($request, $user->id, 'Users')
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
@@ -438,7 +455,7 @@ class UsersController extends Controller
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
|
||||
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
|
||||
$user_to_clone = User::with('userloc')->withTrashed()->find($user->id);
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user_to_clone);
|
||||
|
||||
@@ -453,6 +470,8 @@ class UsersController extends Controller
|
||||
$user->last_name = '';
|
||||
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
||||
$user->id = null;
|
||||
$user->username = null;
|
||||
$user->avatar = null;
|
||||
|
||||
// Get this user's groups
|
||||
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
|
||||
@@ -468,7 +487,7 @@ class UsersController extends Controller
|
||||
->with('user', $user)
|
||||
->with('groups', Group::pluck('name', 'id'))
|
||||
->with('userGroups', $userGroups)
|
||||
->with('clone_user', $user_to_clone)
|
||||
->with('cloned_model', $user_to_clone)
|
||||
->with('item', $user);
|
||||
}
|
||||
|
||||
@@ -510,6 +529,8 @@ class UsersController extends Controller
|
||||
trans('admin/companies/table.title'),
|
||||
trans('admin/users/table.title'),
|
||||
trans('general.employee_number'),
|
||||
trans('admin/users/table.first_name'),
|
||||
trans('admin/users/table.last_name'),
|
||||
trans('admin/users/table.name'),
|
||||
trans('admin/users/table.username'),
|
||||
trans('admin/users/table.email'),
|
||||
@@ -555,10 +576,12 @@ class UsersController extends Controller
|
||||
($user->company) ? $user->company->name : '',
|
||||
$user->jobtitle,
|
||||
$user->employee_num,
|
||||
$user->present()->fullName(),
|
||||
$user->first_name,
|
||||
$user->last_name,
|
||||
$user->display_name,
|
||||
$user->username,
|
||||
$user->email,
|
||||
($user->manager) ? $user->manager->present()->fullName() : '',
|
||||
($user->manager) ? $user->manager->display_name : '',
|
||||
($user->userloc) ? $user->userloc->name : '',
|
||||
($user->department) ? $user->department->name : '',
|
||||
$user->assets->count(),
|
||||
|
||||
@@ -27,50 +27,126 @@ use Exception;
|
||||
class ViewAssetsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Redirect to the profile page.
|
||||
* Extract custom fields that should be displayed in user view.
|
||||
*
|
||||
* @param User $user
|
||||
* @return array
|
||||
*/
|
||||
private function extractCustomFields(User $user): array
|
||||
{
|
||||
$fieldArray = [];
|
||||
foreach ($user->assets as $asset) {
|
||||
if ($asset->model && $asset->model->fieldset) {
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if ($field->display_in_user_view == '1') {
|
||||
$fieldArray[$field->db_column] = $field->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($fieldArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of users viewable by the current user.
|
||||
*
|
||||
* @param User $authUser
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private function getViewableUsers(User $authUser): \Illuminate\Support\Collection
|
||||
{
|
||||
// SuperAdmin sees all users
|
||||
if ($authUser->isSuperUser()) {
|
||||
return User::select('id', 'first_name', 'last_name', 'username')
|
||||
->where('activated', 1)
|
||||
->orderBy('last_name')
|
||||
->orderBy('first_name')
|
||||
->get();
|
||||
}
|
||||
|
||||
// Regular manager sees only their subordinates + self
|
||||
$managedUsers = $authUser->getAllSubordinates();
|
||||
|
||||
// If user has subordinates, show them with self at beginning
|
||||
if ($managedUsers->count() > 0) {
|
||||
return collect([$authUser])->merge($managedUsers)
|
||||
->sortBy('last_name')
|
||||
->sortBy('first_name');
|
||||
}
|
||||
|
||||
// User has no subordinates, only sees themselves
|
||||
return collect([$authUser]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected user ID from request or default to current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param \Illuminate\Support\Collection $subordinates
|
||||
* @param int $defaultUserId
|
||||
* @return int
|
||||
*/
|
||||
private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int
|
||||
{
|
||||
// If no subordinates or no user_id in request, return default
|
||||
if ($subordinates->count() <= 1 || !$request->filled('user_id')) {
|
||||
return $defaultUserId;
|
||||
}
|
||||
|
||||
$requestedUserId = (int) $request->input('user_id');
|
||||
|
||||
// Validate if the requested user is allowed
|
||||
if ($subordinates->contains('id', $requestedUserId)) {
|
||||
return $requestedUserId;
|
||||
}
|
||||
|
||||
// If invalid ID or not authorized, return default
|
||||
return $defaultUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show user's assigned assets with optional manager view functionality.
|
||||
*
|
||||
*/
|
||||
public function getIndex() : View | RedirectResponse
|
||||
public function getIndex(Request $request) : View | RedirectResponse
|
||||
{
|
||||
$user = User::with(
|
||||
$authUser = auth()->user();
|
||||
$settings = Setting::getSettings();
|
||||
$subordinates = collect();
|
||||
$selectedUserId = $authUser->id;
|
||||
|
||||
// Process manager view if enabled
|
||||
if ($settings->manager_view_enabled) {
|
||||
$subordinates = $this->getViewableUsers($authUser);
|
||||
$selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id);
|
||||
}
|
||||
|
||||
// Load the data for the user to be viewed (either auth user or selected subordinate)
|
||||
$userToView = User::with([
|
||||
'assets',
|
||||
'assets.model',
|
||||
'assets.model.fieldset.fields',
|
||||
'consumables',
|
||||
'accessories',
|
||||
'licenses',
|
||||
)->find(auth()->id());
|
||||
|
||||
$field_array = array();
|
||||
|
||||
// Loop through all the custom fields that are applied to any model the user has assigned
|
||||
foreach ($user->assets as $asset) {
|
||||
|
||||
// Make sure the model has a custom fieldset before trying to loop through the associated fields
|
||||
if ($asset->model->fieldset) {
|
||||
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
// check and make sure they're allowed to see the value of the custom field
|
||||
if ($field->display_in_user_view == '1') {
|
||||
$field_array[$field->db_column] = $field->name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
'licenses'
|
||||
])->find($selectedUserId);
|
||||
|
||||
// If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error
|
||||
if (!$userToView) {
|
||||
return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found'));
|
||||
}
|
||||
|
||||
// Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns
|
||||
array_unique($field_array);
|
||||
// Process custom fields for the user being viewed
|
||||
$fieldArray = $this->extractCustomFields($userToView);
|
||||
|
||||
if (isset($user->id)) {
|
||||
return view('account/view-assets', compact('user', 'field_array' ))
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', $user->id));
|
||||
// Pass the necessary data to the view
|
||||
return view('account/view-assets', [
|
||||
'user' => $userToView, // Use 'user' for compatibility with the existing view
|
||||
'field_array' => $fieldArray,
|
||||
'settings' => $settings,
|
||||
'subordinates' => $subordinates,
|
||||
'selectedUserId' => $selectedUserId
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +185,7 @@ class ViewAssetsController extends Controller
|
||||
$logaction->target_type = User::class;
|
||||
|
||||
$data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1;
|
||||
$data['requested_by'] = $user->present()->fullName();
|
||||
$data['requested_by'] = $user->display_name;
|
||||
$data['item'] = $item;
|
||||
$data['item_type'] = $itemType;
|
||||
$data['target'] = auth()->user();
|
||||
|
||||
@@ -26,7 +26,6 @@ class SecurityHeaders
|
||||
$response = $next($request);
|
||||
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||
|
||||
// Ugh. Feature-Policy is dumb and clumsy and mostly irrelevant for Snipe-IT,
|
||||
// since we don't provide any way to IFRAME anything in in the first place.
|
||||
|
||||
@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ImageUploadRequest extends Request
|
||||
{
|
||||
@@ -70,19 +71,25 @@ class ImageUploadRequest extends Request
|
||||
public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
|
||||
{
|
||||
|
||||
$type = strtolower(class_basename(get_class($item)));
|
||||
$type = class_basename(get_class($item));
|
||||
|
||||
if (is_null($path)) {
|
||||
|
||||
$path = str_plural($type);
|
||||
$path = strtolower(str_plural($type));
|
||||
|
||||
if ($type == 'assetmodel') {
|
||||
if ($type == 'AssetModel') {
|
||||
$path = 'models';
|
||||
}
|
||||
|
||||
if ($type == 'user') {
|
||||
$path = 'avatars';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->makeDirectory($path);
|
||||
}
|
||||
|
||||
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
|
||||
@@ -93,10 +100,9 @@ class ImageUploadRequest extends Request
|
||||
|
||||
if (isset($image)) {
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
$ext = $image->guessExtension();
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
|
||||
$file_name = $type.'-'.$form_fieldname.($item->id ?? '-'.$item->id).'-'.str_random(10).'.'.$ext;
|
||||
|
||||
if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
|
||||
// If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing
|
||||
@@ -138,7 +144,7 @@ class ImageUploadRequest extends Request
|
||||
// Remove Current image if exists
|
||||
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
|
||||
$item->{$db_fieldname} = $file_name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If the user isn't uploading anything new but wants to delete their old image, do so
|
||||
|
||||
@@ -41,6 +41,7 @@ class SettingsSamlRequest extends FormRequest
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$setting = Setting::getSettings();
|
||||
if ($this->input('saml_enabled') == '1') {
|
||||
$idpMetadata = $this->input('saml_idp_metadata');
|
||||
if (! empty($idpMetadata)) {
|
||||
@@ -56,7 +57,7 @@ class SettingsSamlRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
$was_custom_x509cert = strpos(Setting::getSettings()->saml_custom_settings, 'sp_x509cert') !== false;
|
||||
$was_custom_x509cert = strpos($setting->saml_custom_settings, 'sp_x509cert') !== false;
|
||||
|
||||
$custom_x509cert = '';
|
||||
$custom_privateKey = '';
|
||||
@@ -108,7 +109,7 @@ class SettingsSamlRequest extends FormRequest
|
||||
];
|
||||
|
||||
$pkey = openssl_pkey_new([
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_bits' => config('app.saml_key_size'),
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
]);
|
||||
|
||||
@@ -126,10 +127,14 @@ class SettingsSamlRequest extends FormRequest
|
||||
}
|
||||
|
||||
if (! (empty($x509cert) && empty($privateKey))) {
|
||||
$this->merge([
|
||||
'saml_sp_x509cert' => $x509cert,
|
||||
'saml_sp_privatekey' => $privateKey,
|
||||
]);
|
||||
// $this->merge([
|
||||
// 'saml_sp_x509cert' => $x509cert,
|
||||
// 'saml_sp_privatekey' => $privateKey,
|
||||
// ]);
|
||||
$setting->saml_sp_x509cert = $x509cert;
|
||||
$setting->saml_sp_privatekey = $privateKey;
|
||||
$setting->save();
|
||||
|
||||
}
|
||||
} else {
|
||||
$validator->errors()->add('saml_integration', 'openssl.cnf is missing/invalid');
|
||||
@@ -145,15 +150,21 @@ class SettingsSamlRequest extends FormRequest
|
||||
}
|
||||
|
||||
if (! empty($x509certNew)) {
|
||||
$this->merge([
|
||||
'saml_sp_x509certNew' => $x509certNew,
|
||||
]);
|
||||
// $this->merge([
|
||||
// 'saml_sp_x509certNew' => $x509certNew,
|
||||
// ]);
|
||||
$setting->saml_sp_x509certNew = $x509certNew;
|
||||
$setting->save();
|
||||
}
|
||||
} else {
|
||||
$this->merge([
|
||||
'saml_sp_x509certNew' => '',
|
||||
]);
|
||||
// $this->merge([
|
||||
// 'saml_sp_x509certNew' => '',
|
||||
// ]);
|
||||
$setting->saml_sp_x509certNew = '';
|
||||
$setting->save();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Http\Traits\ConvertsBase64ToFiles;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use \App\Helpers\Helper;
|
||||
|
||||
class UploadFileRequest extends Request
|
||||
{
|
||||
@@ -27,44 +28,76 @@ class UploadFileRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
|
||||
$max_file_size = Helper::file_upload_max_size();
|
||||
|
||||
return [
|
||||
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
|
||||
'file.*' => 'required|mimes:'.config('filesystems.allowed_upload_extensions_for_validator').'|max:'.$max_file_size,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes (if needed) and Saves a file to the appropriate location
|
||||
* Returns the 'short' (storage-relative) filename
|
||||
*
|
||||
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
|
||||
* a way to merge them or extend one into the other?
|
||||
*/
|
||||
public function handleFile(string $dirname, string $name_prefix, $file): string
|
||||
{
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($file->getMimeType() === 'image/svg+xml') {
|
||||
Log::debug('This is an SVG');
|
||||
Log::debug($file_name);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
Storage::put($dirname.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Upload no workie :( ');
|
||||
Log::debug($e);
|
||||
}
|
||||
|
||||
$uploaded_file = $this->handleSVG($file);
|
||||
} else {
|
||||
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
|
||||
$uploaded_file = file_get_contents($file);
|
||||
}
|
||||
|
||||
try {
|
||||
Storage::put($dirname.$file_name, $uploaded_file);
|
||||
} catch (\Exception $e) {
|
||||
Log::debug($e);
|
||||
}
|
||||
|
||||
return $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
public function handleSVG($file)
|
||||
{
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
return $sanitizer->sanitize($dirtySVG);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation error messages that apply to the request, but
|
||||
* replace the attribute name with the name of the file that was attempted and failed
|
||||
* to make it clearer to the user which file is the bad one.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
if (($this->file) && (is_array($this->file))) {
|
||||
|
||||
for ($i = 0; $i < count($this->file); $i++) {
|
||||
|
||||
try {
|
||||
|
||||
if ($this->file[$i]) {
|
||||
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$attributes['file.'.$i] = 'Invalid file';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class AccessoriesTransformer
|
||||
'checkouts_count' => $accessory->checkouts_count,
|
||||
'created_by' => ($accessory->adminuser) ? [
|
||||
'id' => (int) $accessory->adminuser->id,
|
||||
'name'=> e($accessory->adminuser->present()->fullName()),
|
||||
'name'=> e($accessory->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
@@ -16,6 +17,7 @@ use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ActionlogsTransformer
|
||||
{
|
||||
@@ -48,17 +50,20 @@ class ActionlogsTransformer
|
||||
|
||||
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
||||
{
|
||||
|
||||
$icon = $actionlog->present()->icon();
|
||||
|
||||
if (($actionlog->filename!='') && ($actionlog->action_type!='upload deleted')) {
|
||||
$icon = Helper::filetype_icon($actionlog->filename);
|
||||
}
|
||||
|
||||
static $custom_fields = false;
|
||||
|
||||
if ($custom_fields === false) {
|
||||
$custom_fields = CustomField::all();
|
||||
}
|
||||
|
||||
if ($actionlog->filename!='') {
|
||||
$icon = Helper::filetype_icon($actionlog->filename);
|
||||
}
|
||||
|
||||
|
||||
// This is necessary since we can't escape special characters within a JSON object
|
||||
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
|
||||
@@ -113,8 +118,8 @@ class ActionlogsTransformer
|
||||
|
||||
// Display the changes if the user is an admin or superadmin
|
||||
if (Gate::allows('admin')) {
|
||||
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
|
||||
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
|
||||
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old, ['allowed_classes' => false]) : '';
|
||||
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new, ['allowed_classes' => false]) : '';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -133,24 +138,6 @@ class ActionlogsTransformer
|
||||
$clean_meta= $this->changedInfo($clean_meta);
|
||||
}
|
||||
|
||||
$file_url = '';
|
||||
if($actionlog->filename!='') {
|
||||
if ($actionlog->action_type == 'accepted') {
|
||||
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
|
||||
} else {
|
||||
if ($actionlog->item) {
|
||||
if ($actionlog->itemType() == 'asset') {
|
||||
$file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'accessory') {
|
||||
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'license') {
|
||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'user') {
|
||||
$file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$array = [
|
||||
'id' => (int) $actionlog->id,
|
||||
@@ -158,13 +145,15 @@ class ActionlogsTransformer
|
||||
'file' => ($actionlog->filename!='')
|
||||
?
|
||||
[
|
||||
'url' => $file_url,
|
||||
'url' => $actionlog->uploads_file_url(),
|
||||
'filename' => $actionlog->filename,
|
||||
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
|
||||
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
||||
] : null,
|
||||
|
||||
'item' => ($actionlog->item) ? [
|
||||
'id' => (int) $actionlog->item->id,
|
||||
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
|
||||
'name' => e($actionlog->item->display_name) ?? null,
|
||||
'type' => e($actionlog->itemType()),
|
||||
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
||||
] : null,
|
||||
@@ -179,27 +168,27 @@ class ActionlogsTransformer
|
||||
'action_type' => $actionlog->present()->actionType(),
|
||||
'admin' => ($actionlog->adminuser) ? [
|
||||
'id' => (int) $actionlog->adminuser->id,
|
||||
'name' => e($actionlog->adminuser->getFullNameAttribute()),
|
||||
'name' => e($actionlog->adminuser->display_name),
|
||||
'first_name'=> e($actionlog->adminuser->first_name),
|
||||
'last_name'=> e($actionlog->adminuser->last_name)
|
||||
] : null,
|
||||
'created_by' => ($actionlog->adminuser) ? [
|
||||
'id' => (int) $actionlog->adminuser->id,
|
||||
'name' => e($actionlog->adminuser->getFullNameAttribute()),
|
||||
'name' => e($actionlog->adminuser->display_name),
|
||||
'first_name'=> e($actionlog->adminuser->first_name),
|
||||
'last_name'=> e($actionlog->adminuser->last_name)
|
||||
] : null,
|
||||
'target' => ($actionlog->target) ? [
|
||||
'id' => (int) $actionlog->target->id,
|
||||
'name' => ($actionlog->targetType()=='user') ? e($actionlog->target->getFullNameAttribute()) : e($actionlog->target->getDisplayNameAttribute()),
|
||||
'name' => ($actionlog->target->display_name) ?? null,
|
||||
'type' => e($actionlog->targetType()),
|
||||
] : null,
|
||||
|
||||
'note' => ($actionlog->note) ? Helper::parseEscapedMarkedownInline($actionlog->note): null,
|
||||
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
|
||||
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
|
||||
'remote_ip' => ($actionlog->remote_ip) ?? null,
|
||||
'user_agent' => ($actionlog->user_agent) ?? null,
|
||||
'remote_ip' => e($actionlog->remote_ip) ?? null,
|
||||
'user_agent' => e($actionlog->user_agent) ?? null,
|
||||
'action_source' => ($actionlog->action_source) ?? null,
|
||||
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
|
||||
];
|
||||
|
||||
@@ -65,10 +65,11 @@ class AssetModelsTransformer
|
||||
'default_fieldset_values' => $default_field_values,
|
||||
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
||||
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
||||
'require_serial' => $assetmodel->require_serial,
|
||||
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
|
||||
'created_by' => ($assetmodel->adminuser) ? [
|
||||
'id' => (int) $assetmodel->adminuser->id,
|
||||
'name'=> e($assetmodel->adminuser->present()->fullName()),
|
||||
'name'=> e($assetmodel->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),
|
||||
|
||||
@@ -58,6 +58,13 @@ class AssetsTransformer
|
||||
'id' => (int) $asset->model->manufacturer->id,
|
||||
'name'=> e($asset->model->manufacturer->name),
|
||||
] : null,
|
||||
'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? [
|
||||
'id' => (int) $asset->model->depreciation->id,
|
||||
'name'=> e($asset->model->depreciation->name),
|
||||
'months'=> (int) $asset->model->depreciation->months,
|
||||
'type'=> e($asset->model->depreciation->depreciation_type),
|
||||
'minimum'=> ($asset->model->depreciation->depreciation_min) ? (int) $asset->model->depreciation->depreciation_min : null,
|
||||
] : null,
|
||||
'supplier' => ($asset->supplier) ? [
|
||||
'id' => (int) $asset->supplier->id,
|
||||
'name'=> e($asset->supplier->name),
|
||||
@@ -84,7 +91,7 @@ class AssetsTransformer
|
||||
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
|
||||
'created_by' => ($asset->adminuser) ? [
|
||||
'id' => (int) $asset->adminuser->id,
|
||||
'name'=> e($asset->adminuser->present()->fullName()),
|
||||
'name'=> e($asset->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'),
|
||||
@@ -101,7 +108,7 @@ class AssetsTransformer
|
||||
'checkout_counter' => (int) $asset->checkout_counter,
|
||||
'requests_counter' => (int) $asset->requests_counter,
|
||||
'user_can_checkout' => (bool) $asset->availableForCheckout(),
|
||||
'book_value' => Helper::formatCurrencyOutput($asset->getLinearDepreciatedValue()),
|
||||
'book_value' => Helper::formatCurrencyOutput($asset->getDepreciatedValue()),
|
||||
];
|
||||
|
||||
|
||||
@@ -203,6 +210,7 @@ class AssetsTransformer
|
||||
'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null,
|
||||
'email'=> ($asset->assigned->email) ? e($asset->assigned->email) : null,
|
||||
'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null,
|
||||
'jobtitle' => $asset->assigned->jobtitle ? e($asset->assigned->jobtitle) : null,
|
||||
'type' => 'user',
|
||||
] : null;
|
||||
}
|
||||
@@ -279,7 +287,7 @@ class AssetsTransformer
|
||||
'id' => (int) $asset->id,
|
||||
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
|
||||
'type' => 'asset',
|
||||
'name' => e($asset->present()->fullName()),
|
||||
'name' => e($asset->display_name),
|
||||
'model' => ($asset->model) ? e($asset->model->name) : null,
|
||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||
'asset_tag' => e($asset->asset_tag),
|
||||
|
||||
@@ -64,7 +64,7 @@ class CategoriesTransformer
|
||||
'licenses_count' => (int) $category->licenses_count,
|
||||
'created_by' => ($category->adminuser) ? [
|
||||
'id' => (int) $category->adminuser->id,
|
||||
'name'=> e($category->adminuser->present()->fullName()),
|
||||
'name'=> e($category->adminuser->display_name),
|
||||
] : null,
|
||||
'notes' => Helper::parseEscapedMarkedownInline($category->notes),
|
||||
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
|
||||
|
||||
@@ -38,7 +38,7 @@ class CompaniesTransformer
|
||||
'users_count' => (int) $company->users_count,
|
||||
'created_by' => ($company->adminuser) ? [
|
||||
'id' => (int) $company->adminuser->id,
|
||||
'name'=> e($company->adminuser->present()->fullName()),
|
||||
'name'=> e($company->adminuser->display_name),
|
||||
] : null,
|
||||
'notes' => Helper::parseEscapedMarkedownInline($company->notes),
|
||||
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),
|
||||
|
||||
@@ -51,7 +51,7 @@ class ComponentsTransformer
|
||||
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
|
||||
'created_by' => ($component->adminuser) ? [
|
||||
'id' => (int) $component->adminuser->id,
|
||||
'name'=> e($component->adminuser->present()->fullName()),
|
||||
'name'=> e($component->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'),
|
||||
@@ -76,7 +76,7 @@ class ComponentsTransformer
|
||||
$array[] = [
|
||||
'assigned_pivot_id' => $asset->pivot->id,
|
||||
'id' => (int) $asset->id,
|
||||
'name' => e($asset->model->present()->name).' '.e($asset->present()->name),
|
||||
'name' => e($asset->model->display_name).' '.e($asset->display_name),
|
||||
'qty' => $asset->pivot->assigned_qty,
|
||||
'note' => $asset->pivot->note,
|
||||
'type' => 'asset',
|
||||
|
||||
@@ -25,7 +25,7 @@ class ConsumablesTransformer
|
||||
$array = [
|
||||
'id' => (int) $consumable->id,
|
||||
'name' => e($consumable->name),
|
||||
'image' => ($consumable->image) ? Storage::disk('public')->url('consumables/'.e($consumable->image)) : null,
|
||||
'image' => ($consumable->getImageUrl()) ? ($consumable->getImageUrl()) : null,
|
||||
'category' => ($consumable->category) ? ['id' => $consumable->category->id, 'name' => e($consumable->category->name)] : null,
|
||||
'company' => ($consumable->company) ? ['id' => (int) $consumable->company->id, 'name' => e($consumable->company->name)] : null,
|
||||
'item_no' => e($consumable->item_no),
|
||||
@@ -42,7 +42,7 @@ class ConsumablesTransformer
|
||||
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
|
||||
'created_by' => ($consumable->adminuser) ? [
|
||||
'id' => (int) $consumable->adminuser->id,
|
||||
'name'=> e($consumable->adminuser->present()->fullName()),
|
||||
'name'=> e($consumable->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'),
|
||||
|
||||
@@ -4,6 +4,10 @@ namespace App\Http\Transformers;
|
||||
|
||||
class DatatablesTransformer
|
||||
{
|
||||
|
||||
/**
|
||||
* Transform data for bootstrap tables and API responses for lists of things
|
||||
**/
|
||||
public function transformDatatables($objects, $total = null)
|
||||
{
|
||||
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
|
||||
@@ -11,4 +15,15 @@ class DatatablesTransformer
|
||||
|
||||
return $objects_array;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform data for returning the status of items within a bulk action
|
||||
**/
|
||||
public function transformBulkResponseWithStatusAndObjects($objects, $total)
|
||||
{
|
||||
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
|
||||
$objects_array['rows'] = $objects;
|
||||
|
||||
return $objects_array;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class DepartmentsTransformer
|
||||
] : null,
|
||||
'manager' => ($department->manager) ? [
|
||||
'id' => (int) $department->manager->id,
|
||||
'name' => e($department->manager->getFullNameAttribute()),
|
||||
'name' => e($department->manager->display_name),
|
||||
'first_name'=> e($department->manager->first_name),
|
||||
'last_name'=> e($department->manager->last_name),
|
||||
] : null,
|
||||
|
||||
@@ -26,14 +26,14 @@ class DepreciationsTransformer
|
||||
$array = [
|
||||
'id' => (int) $depreciation->id,
|
||||
'name' => e($depreciation->name),
|
||||
'months' => $depreciation->months.' '.trans('general.months'),
|
||||
'months' => trans_choice('general.months_plural', $depreciation->months),
|
||||
'depreciation_min' => $depreciation->depreciation_type === 'percent' ? $depreciation->depreciation_min.'%' : $depreciation->depreciation_min,
|
||||
'assets_count' => $depreciation->assets_count,
|
||||
'models_count' => $depreciation->models_count,
|
||||
'licenses_count' => $depreciation->licenses_count,
|
||||
'assets_count' => ($depreciation->assets_count > 0) ? (int) $depreciation->assets_count : 0,
|
||||
'models_count' => ($depreciation->models_count > 0) ? (int) $depreciation->models_count : 0,
|
||||
'licenses_count' => ($depreciation->licenses_count > 0) ? (int) $depreciation->licenses_count : 0,
|
||||
'created_by' => ($depreciation->adminuser) ? [
|
||||
'id' => (int) $depreciation->adminuser->id,
|
||||
'name'=> e($depreciation->adminuser->present()->fullName()),
|
||||
'name'=> e($depreciation->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
|
||||
|
||||
@@ -29,7 +29,7 @@ class GroupsTransformer
|
||||
'notes' => Helper::parseEscapedMarkedownInline($group->notes),
|
||||
'created_by' => ($group->adminuser) ? [
|
||||
'id' => (int) $group->adminuser->id,
|
||||
'name'=> e($group->adminuser->present()->fullName()),
|
||||
'name'=> e($group->adminuser->display_name),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class LicenseSeatsTransformer
|
||||
{
|
||||
public function transformLicenseSeats(Collection $seats, $total)
|
||||
@@ -52,6 +51,7 @@ class LicenseSeatsTransformer
|
||||
'reassignable' => (bool) $seat->license->reassignable,
|
||||
'notes' => e($seat->notes),
|
||||
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
|
||||
'disabled' => $seat->unreassignable_seat,
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user