Compare commits
762 Commits
v6.0.8
...
features/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adec86f1ef | ||
|
|
46e17f7e77 | ||
|
|
772d3c1da9 | ||
|
|
17c308f3ca | ||
|
|
35136cea07 | ||
|
|
0db83ad8cb | ||
|
|
62bf7b1bce | ||
|
|
50fdf9c33b | ||
|
|
c5c37fa95f | ||
|
|
9dc016aecc | ||
|
|
60dcaa9e94 | ||
|
|
2e75bd6960 | ||
|
|
799c9c910e | ||
|
|
005d6d3b62 | ||
|
|
529b005d1e | ||
|
|
dd55aedb87 | ||
|
|
be16fe948a | ||
|
|
7846251d25 | ||
|
|
cc665d50ed | ||
|
|
ae76d46f87 | ||
|
|
bf849128fc | ||
|
|
44be8c8f60 | ||
|
|
bad1253682 | ||
|
|
20a9be2f6c | ||
|
|
39b1a5a8a0 | ||
|
|
2d0ac5b48b | ||
|
|
e67bd5a275 | ||
|
|
8d35583634 | ||
|
|
b39a9e0fe1 | ||
|
|
ae04a8c872 | ||
|
|
cbffd105a2 | ||
|
|
a57315aed5 | ||
|
|
e1263155b1 | ||
|
|
014eab3abc | ||
|
|
f1bc183043 | ||
|
|
2f20945516 | ||
|
|
91d0c383df | ||
|
|
886f2a0dec | ||
|
|
e05764ac34 | ||
|
|
a916c68f95 | ||
|
|
19e703980d | ||
|
|
6e42bce409 | ||
|
|
1a3c947b14 | ||
|
|
4d0fc99773 | ||
|
|
bd3f730cb2 | ||
|
|
381f89c5a8 | ||
|
|
db43628bdc | ||
|
|
ace73acba1 | ||
|
|
301c4fda6e | ||
|
|
5898e384f8 | ||
|
|
9c63f426d9 | ||
|
|
c575798b56 | ||
|
|
7123e7327f | ||
|
|
b36e81836f | ||
|
|
b77d5801fa | ||
|
|
818db58a7b | ||
|
|
c4f900e9af | ||
|
|
baf14c43ee | ||
|
|
fa439a1928 | ||
|
|
d187174f00 | ||
|
|
6d3c5e0659 | ||
|
|
c49d69c911 | ||
|
|
685cc86d71 | ||
|
|
fb10a6f218 | ||
|
|
3a9b8a4993 | ||
|
|
a6c7471bf5 | ||
|
|
128c21a905 | ||
|
|
75a757d6f5 | ||
|
|
e791e6592a | ||
|
|
38575e93e8 | ||
|
|
88291cadc1 | ||
|
|
cc40ecaa44 | ||
|
|
94afe2bc6a | ||
|
|
25b33d657e | ||
|
|
399c7590cd | ||
|
|
d0c5ba70f6 | ||
|
|
133d6ffa50 | ||
|
|
d9a21cce00 | ||
|
|
29c2ff56ec | ||
|
|
3e7975b2c3 | ||
|
|
d870bc3b02 | ||
|
|
227fef76ee | ||
|
|
9d44720ffd | ||
|
|
9f3f0a25ed | ||
|
|
2e228ccb0b | ||
|
|
3ee413f379 | ||
|
|
b142f8e012 | ||
|
|
418ddcfac3 | ||
|
|
c342668f0f | ||
|
|
2f6a26ec7d | ||
|
|
b89979fbec | ||
|
|
f635278010 | ||
|
|
8043b86786 | ||
|
|
d13a237000 | ||
|
|
b114ffd2c3 | ||
|
|
fabefa61b0 | ||
|
|
f3e57d7dc0 | ||
|
|
389ec3a3cb | ||
|
|
6a72c344b7 | ||
|
|
4442b446b9 | ||
|
|
c432fb9d70 | ||
|
|
07ae91b00f | ||
|
|
450ad3dcec | ||
|
|
fa872b09a9 | ||
|
|
9833ba4ab6 | ||
|
|
9e8fff6e5b | ||
|
|
4ac09ff4e4 | ||
|
|
e26036e998 | ||
|
|
1017148bad | ||
|
|
00ed197651 | ||
|
|
66f8ac1cd1 | ||
|
|
775df0ab60 | ||
|
|
687f8971b1 | ||
|
|
bc2a455a89 | ||
|
|
97c28d9adc | ||
|
|
163b3f6c0b | ||
|
|
9b3df40ed6 | ||
|
|
1c8ee0f706 | ||
|
|
d7c5a6af71 | ||
|
|
743264fd0e | ||
|
|
f7da87520c | ||
|
|
ec854ddc1d | ||
|
|
1a8b2a169b | ||
|
|
43acd3b488 | ||
|
|
c3a6874b16 | ||
|
|
eb67d1b064 | ||
|
|
d0d0058e79 | ||
|
|
bbd04f8876 | ||
|
|
97731cd7c8 | ||
|
|
36901d271b | ||
|
|
b7a5467f8b | ||
|
|
2f8a435e18 | ||
|
|
9ca40716ce | ||
|
|
7518cab7c8 | ||
|
|
8d36971c8b | ||
|
|
371a5bf4d6 | ||
|
|
cfc4229308 | ||
|
|
8bf01715a9 | ||
|
|
55f5e7866d | ||
|
|
3206929ee4 | ||
|
|
1fe0bfe17e | ||
|
|
9c205f63ae | ||
|
|
0cc7389b85 | ||
|
|
e21d0729c1 | ||
|
|
c46dc99e86 | ||
|
|
64a8d1d207 | ||
|
|
8de59d117f | ||
|
|
d5e702ac74 | ||
|
|
6113afe36e | ||
|
|
305df42f92 | ||
|
|
9aa0356de7 | ||
|
|
d2dcb29dcb | ||
|
|
39e06a8856 | ||
|
|
8d861cfd82 | ||
|
|
078e7281cd | ||
|
|
3ef578eb56 | ||
|
|
11a1ab971c | ||
|
|
d226cf189b | ||
|
|
c082d8b90e | ||
|
|
b1985a08c4 | ||
|
|
9bafe795b0 | ||
|
|
bb4c084435 | ||
|
|
feeb1c7ac8 | ||
|
|
c2cfd8ff53 | ||
|
|
20ad4fb681 | ||
|
|
06a1fe5b38 | ||
|
|
b6f87633f5 | ||
|
|
e426846c23 | ||
|
|
27898e660c | ||
|
|
2c7364bdfe | ||
|
|
0d9d02c6d6 | ||
|
|
7c854c0a5c | ||
|
|
efc892cf5e | ||
|
|
ba4c37a7f4 | ||
|
|
f0c5560c1b | ||
|
|
774962c122 | ||
|
|
0a9fd0b867 | ||
|
|
5c94ca1403 | ||
|
|
86bc409d3a | ||
|
|
24841a75d4 | ||
|
|
a02a04d601 | ||
|
|
3997085faf | ||
|
|
3ac92e1425 | ||
|
|
5661e69520 | ||
|
|
cdcd19a7d8 | ||
|
|
489895a5fa | ||
|
|
1122562b4e | ||
|
|
65e46c914c | ||
|
|
4ffe13d3c2 | ||
|
|
5df76155ce | ||
|
|
e1c33d4eff | ||
|
|
2fc7a15372 | ||
|
|
9056d48775 | ||
|
|
9fefdea9de | ||
|
|
2239c6f7e2 | ||
|
|
da0efaa278 | ||
|
|
69bde0443e | ||
|
|
6400bdc266 | ||
|
|
ffd252a00c | ||
|
|
7fcf6f2463 | ||
|
|
5103f28598 | ||
|
|
60d7128a5e | ||
|
|
2c5a0d370c | ||
|
|
bcbe517446 | ||
|
|
d9a9bd1c0d | ||
|
|
2166fcec41 | ||
|
|
9a5d431962 | ||
|
|
6dc0846f38 | ||
|
|
50431c046f | ||
|
|
f0ecab7bb1 | ||
|
|
7cc8b39863 | ||
|
|
ae259f36ae | ||
|
|
cd2997b674 | ||
|
|
b4ff07ce9e | ||
|
|
8122236944 | ||
|
|
56c4fa7c27 | ||
|
|
602c2698d2 | ||
|
|
ff0b68724f | ||
|
|
d4ac7530b6 | ||
|
|
4aded5e117 | ||
|
|
8d03ce5e2c | ||
|
|
15da39d44a | ||
|
|
077d343f01 | ||
|
|
47ee2a8153 | ||
|
|
ae95ee49f1 | ||
|
|
2ac558494d | ||
|
|
ede16ad2c2 | ||
|
|
1e34398c99 | ||
|
|
7a5fcfb87a | ||
|
|
1fa624420e | ||
|
|
92ae271292 | ||
|
|
6a9131e771 | ||
|
|
383bd6bb45 | ||
|
|
097821b818 | ||
|
|
2e56c9b521 | ||
|
|
b95d24b5eb | ||
|
|
f91e1d58ad | ||
|
|
7c37c70164 | ||
|
|
52dc5aa4ba | ||
|
|
23cbee9493 | ||
|
|
7c21158680 | ||
|
|
30f4af3ac2 | ||
|
|
b1fa50cde2 | ||
|
|
236684ec92 | ||
|
|
c45e777324 | ||
|
|
53499b1e29 | ||
|
|
04b6f023ae | ||
|
|
3c7d63c060 | ||
|
|
7cb1ca8754 | ||
|
|
52e33a8b0e | ||
|
|
0f5fbb6a04 | ||
|
|
6d5ace0458 | ||
|
|
6f14355cbc | ||
|
|
eb81c290dc | ||
|
|
2f9e097854 | ||
|
|
97aeb1fcec | ||
|
|
e6d259bac0 | ||
|
|
8887d40b86 | ||
|
|
3d8e0b707e | ||
|
|
71a7176a6e | ||
|
|
def89bfa0c | ||
|
|
f1cb7ee410 | ||
|
|
84c0f50266 | ||
|
|
f51b312843 | ||
|
|
3d3a4b02fc | ||
|
|
276f534ded | ||
|
|
1d47d9e52b | ||
|
|
2106b64da6 | ||
|
|
fa79a6c15f | ||
|
|
761da534f3 | ||
|
|
b362951c95 | ||
|
|
25f69a7bd2 | ||
|
|
f6a6478804 | ||
|
|
3282702c73 | ||
|
|
4e5c878b73 | ||
|
|
5711706367 | ||
|
|
a930661150 | ||
|
|
7cb4740359 | ||
|
|
9f8f028484 | ||
|
|
d0b9e956df | ||
|
|
c1eee2cc72 | ||
|
|
f32183d51b | ||
|
|
73f30c0a41 | ||
|
|
c233d7fb1c | ||
|
|
dea0fcefbd | ||
|
|
38678d4646 | ||
|
|
476a5cbc02 | ||
|
|
bc4c6abe06 | ||
|
|
73f61ce032 | ||
|
|
12c7223bcd | ||
|
|
de856d6045 | ||
|
|
b7efb58733 | ||
|
|
28b0d8cf0f | ||
|
|
88ae8858e8 | ||
|
|
0bc0a116aa | ||
|
|
e670ffe349 | ||
|
|
a94f1c4a64 | ||
|
|
b503b672ba | ||
|
|
72296eea5c | ||
|
|
bc023a2566 | ||
|
|
2f7e22be1e | ||
|
|
9409965239 | ||
|
|
f54c92ade2 | ||
|
|
b1fb70cb7b | ||
|
|
9e08936cbc | ||
|
|
cd385e0865 | ||
|
|
1e3281c76c | ||
|
|
af4d62759f | ||
|
|
9452973845 | ||
|
|
8e23787888 | ||
|
|
3b16157d6b | ||
|
|
3ec3d38c9f | ||
|
|
ae430959a4 | ||
|
|
85a85082b7 | ||
|
|
5acb493efd | ||
|
|
515b14a001 | ||
|
|
4eed2baa31 | ||
|
|
453f2c3b0e | ||
|
|
bc78d341a0 | ||
|
|
da579a302c | ||
|
|
edf191b724 | ||
|
|
12d86bd6e2 | ||
|
|
c4f11de90d | ||
|
|
8fb61cf5f8 | ||
|
|
f2d4a61e3c | ||
|
|
3f25a1bf61 | ||
|
|
f9ac447dd1 | ||
|
|
4e83b06d71 | ||
|
|
23e613f903 | ||
|
|
f1c82ca732 | ||
|
|
2543fa6b98 | ||
|
|
7c34652da0 | ||
|
|
aa3f94973f | ||
|
|
1ec303931a | ||
|
|
d359bcb88d | ||
|
|
28059c878a | ||
|
|
b7bcfaccc9 | ||
|
|
f4a3f896bd | ||
|
|
0303df4b25 | ||
|
|
9151868839 | ||
|
|
8c6aaec9ce | ||
|
|
fb228d6ded | ||
|
|
1360ce90a5 | ||
|
|
3549afe157 | ||
|
|
cfb780b20b | ||
|
|
eb9f17122e | ||
|
|
2ef45afb2b | ||
|
|
c8baf4c6ad | ||
|
|
0b20cf6d70 | ||
|
|
ecc6bd8a24 | ||
|
|
3c9c89355e | ||
|
|
43479efed0 | ||
|
|
80279a95ae | ||
|
|
ac055c01dc | ||
|
|
5ff72420a6 | ||
|
|
55b33c1042 | ||
|
|
a499a92bb7 | ||
|
|
e7ae7742b0 | ||
|
|
98f2e0271b | ||
|
|
e721665fd8 | ||
|
|
cd43fc50fb | ||
|
|
06803092c5 | ||
|
|
0312ecf09d | ||
|
|
0ed50f4e58 | ||
|
|
3f105cd3b2 | ||
|
|
ab0c5bcd9d | ||
|
|
5ba01728d9 | ||
|
|
ae0eb378e4 | ||
|
|
ad8a2387f0 | ||
|
|
4fa01350c3 | ||
|
|
d3161fad02 | ||
|
|
36eb51b52f | ||
|
|
8ff109e9da | ||
|
|
15da3b831d | ||
|
|
0c6441ebed | ||
|
|
fbfc9d123c | ||
|
|
cd942196cc | ||
|
|
f1edbc9810 | ||
|
|
66e64cb136 | ||
|
|
1ef3eb94db | ||
|
|
25e4b7d59d | ||
|
|
9605dec22a | ||
|
|
ca937667b4 | ||
|
|
e9c61903f1 | ||
|
|
14b7319b63 | ||
|
|
9d641573b5 | ||
|
|
3605aa8f5a | ||
|
|
0817308822 | ||
|
|
bdfac1a345 | ||
|
|
f7a9be92ca | ||
|
|
56ba8a7e24 | ||
|
|
cf62761d18 | ||
|
|
d23e178c62 | ||
|
|
7353ecd388 | ||
|
|
18663c2599 | ||
|
|
5b8e0b3af1 | ||
|
|
dab83091d2 | ||
|
|
4c0be7ec98 | ||
|
|
018f71a157 | ||
|
|
9f03f0f6f6 | ||
|
|
da40c0ad48 | ||
|
|
c70c8ce856 | ||
|
|
fe49ac79d1 | ||
|
|
687a764fc1 | ||
|
|
81be84568e | ||
|
|
61fa0bda4d | ||
|
|
81d2a3acda | ||
|
|
931d81d825 | ||
|
|
bcebc1e33b | ||
|
|
ba0483b33e | ||
|
|
97b9f96030 | ||
|
|
891b5b2882 | ||
|
|
e21660ae05 | ||
|
|
53392d6069 | ||
|
|
3469e49470 | ||
|
|
e9224f094e | ||
|
|
25c10c3819 | ||
|
|
b9419c7454 | ||
|
|
b07c4caa37 | ||
|
|
aa52bc6c6a | ||
|
|
5a7e35c0e8 | ||
|
|
bae200edd7 | ||
|
|
2fd197c2db | ||
|
|
36484d50ef | ||
|
|
2b7c8cf82b | ||
|
|
0424308863 | ||
|
|
78580136f6 | ||
|
|
420c216973 | ||
|
|
9c6fa18454 | ||
|
|
70c766e8b3 | ||
|
|
0b2ce7be07 | ||
|
|
3d48dd19cb | ||
|
|
08bab4f101 | ||
|
|
95b49f8deb | ||
|
|
4f5d536e32 | ||
|
|
9dbc5070f8 | ||
|
|
055a2f827f | ||
|
|
8d3fe423e1 | ||
|
|
1377725a97 | ||
|
|
58ccfe9226 | ||
|
|
05ac9f4a68 | ||
|
|
0889cd61cf | ||
|
|
8168ca4074 | ||
|
|
de399a9f8f | ||
|
|
af150f0192 | ||
|
|
c194c8a0c8 | ||
|
|
a7b83fc531 | ||
|
|
ef22da622d | ||
|
|
8443796cbe | ||
|
|
ac996a771c | ||
|
|
942eaac2ee | ||
|
|
ada2a2cd43 | ||
|
|
06ba9356ca | ||
|
|
e034f7b674 | ||
|
|
4d14243f57 | ||
|
|
88a371082d | ||
|
|
ee7e26ae8d | ||
|
|
9d93c72534 | ||
|
|
44b72e0f5f | ||
|
|
d91d226652 | ||
|
|
091ef62cbe | ||
|
|
8316a4eb92 | ||
|
|
b4518677bd | ||
|
|
23cf556a8b | ||
|
|
c0f83a7927 | ||
|
|
5a1062f0f4 | ||
|
|
c32676596c | ||
|
|
f75917d34d | ||
|
|
c55f2d5417 | ||
|
|
e267f5491a | ||
|
|
571ce5b741 | ||
|
|
733b0750a2 | ||
|
|
9369165007 | ||
|
|
f75d348dc0 | ||
|
|
dcab1381e7 | ||
|
|
0924d2144b | ||
|
|
9cae4f1d1b | ||
|
|
ca70988879 | ||
|
|
421c9baecc | ||
|
|
5c9e84f663 | ||
|
|
fbb5f1445a | ||
|
|
07e0fec1de | ||
|
|
c6cbf3712e | ||
|
|
0f777b3168 | ||
|
|
62ad3fad1c | ||
|
|
eb1e287c15 | ||
|
|
3eab44f748 | ||
|
|
1885e73704 | ||
|
|
24910433a1 | ||
|
|
1899e4d1e8 | ||
|
|
481eb2c7f0 | ||
|
|
51d7f2b97b | ||
|
|
9b2eda2d70 | ||
|
|
41b75704e3 | ||
|
|
20220764bb | ||
|
|
abd2349604 | ||
|
|
ac4f91918e | ||
|
|
28f0b72bcd | ||
|
|
ad7bc6f38a | ||
|
|
72feaee6c0 | ||
|
|
4ac3650d64 | ||
|
|
3877770160 | ||
|
|
3192a68b06 | ||
|
|
284b4d869f | ||
|
|
6b6a079440 | ||
|
|
a0624fe179 | ||
|
|
d598f75721 | ||
|
|
9b448227f7 | ||
|
|
d51eca20f0 | ||
|
|
9b5790b7e6 | ||
|
|
28bc97f29f | ||
|
|
a8a00f0a79 | ||
|
|
cbd16174d8 | ||
|
|
448aefac28 | ||
|
|
b23cd6d4f0 | ||
|
|
b0ea1a31dc | ||
|
|
11a2f96b4f | ||
|
|
7361977cdc | ||
|
|
659703bd7d | ||
|
|
3aff97ace1 | ||
|
|
6e82de2f47 | ||
|
|
ae505ef44d | ||
|
|
e86e96e159 | ||
|
|
93428e1ed4 | ||
|
|
193b31e427 | ||
|
|
70ac8af9c4 | ||
|
|
61e1650542 | ||
|
|
ab54ac9408 | ||
|
|
dcbd407698 | ||
|
|
4e7fea3468 | ||
|
|
3ef25c3a4d | ||
|
|
22596f4bb8 | ||
|
|
8fdca7f42b | ||
|
|
1f04cfdb44 | ||
|
|
04c130e596 | ||
|
|
7fa9214beb | ||
|
|
ea63049b4b | ||
|
|
0c362e8b57 | ||
|
|
e94b5ac435 | ||
|
|
9cf5f30c77 | ||
|
|
e8f2e50ada | ||
|
|
3527902ba3 | ||
|
|
f60ea9581d | ||
|
|
74fe3dc733 | ||
|
|
96ec96ff7f | ||
|
|
4fd1827576 | ||
|
|
0d4e9c183b | ||
|
|
1ff2d15c4a | ||
|
|
fc6fefdb4e | ||
|
|
482a7b2a3a | ||
|
|
6f0f6c8042 | ||
|
|
4cc9b5a5a2 | ||
|
|
8720a58b51 | ||
|
|
22710afee3 | ||
|
|
968f8fb554 | ||
|
|
6fde72a693 | ||
|
|
afb7fa2e81 | ||
|
|
c9ef49ec65 | ||
|
|
36ab794adc | ||
|
|
2ad9c39090 | ||
|
|
c59fd7b8da | ||
|
|
44e598b0dc | ||
|
|
ffd38292cf | ||
|
|
660c1429b2 | ||
|
|
8ad5fb34d3 | ||
|
|
a5fd9ebbb1 | ||
|
|
49d2aef831 | ||
|
|
74f5980af7 | ||
|
|
1a47e1000d | ||
|
|
ab60fa08df | ||
|
|
8a312f69a6 | ||
|
|
581cae94ab | ||
|
|
6e85c2e5cb | ||
|
|
30d0125cbd | ||
|
|
e2dec5d5d7 | ||
|
|
af34fc207c | ||
|
|
cca531f8ca | ||
|
|
45f17fea8a | ||
|
|
3ac84eab58 | ||
|
|
15af4ea56e | ||
|
|
d649c65508 | ||
|
|
0965ac9592 | ||
|
|
bfadc69d73 | ||
|
|
41be3f7208 | ||
|
|
906a36dc1a | ||
|
|
aba8449dc6 | ||
|
|
861283b874 | ||
|
|
2994c3613a | ||
|
|
ba7d6dde3f | ||
|
|
4b9a91f55b | ||
|
|
94bbf2bf2f | ||
|
|
e5cc6ec972 | ||
|
|
e97c526050 | ||
|
|
7b45328fcc | ||
|
|
40bc13a946 | ||
|
|
368bd5da3f | ||
|
|
1763bbfbd3 | ||
|
|
7b44afc43c | ||
|
|
13b6c281df | ||
|
|
5feea17f8d | ||
|
|
60d1578a01 | ||
|
|
07585809b3 | ||
|
|
98a111796b | ||
|
|
7b3be076b4 | ||
|
|
2c68ec927a | ||
|
|
a0ba33ed7c | ||
|
|
70bb4cd746 | ||
|
|
91e824c5b0 | ||
|
|
491cdd4de6 | ||
|
|
3454cdd2a0 | ||
|
|
db53a00d3f | ||
|
|
8324b94022 | ||
|
|
a33ca39237 | ||
|
|
98bcd82108 | ||
|
|
d2c486bb1e | ||
|
|
8c579d693a | ||
|
|
814992eb7d | ||
|
|
267997f0a6 | ||
|
|
0172fe6403 | ||
|
|
509bbbc685 | ||
|
|
8cee5060af | ||
|
|
a7de97f060 | ||
|
|
c1074000f9 | ||
|
|
c7d9baad8e | ||
|
|
c77d763f05 | ||
|
|
ef099aa644 | ||
|
|
acd666fdf5 | ||
|
|
8de7e9a2ab | ||
|
|
dd0260c3be | ||
|
|
512348cc5a | ||
|
|
fb3d7bf82e | ||
|
|
e3293151a2 | ||
|
|
3ec77724d8 | ||
|
|
024a014298 | ||
|
|
c1e5d03340 | ||
|
|
fb2fe61daf | ||
|
|
0d9eccb7eb | ||
|
|
f9956cc5df | ||
|
|
f7cceaedd5 | ||
|
|
e7c47408a7 | ||
|
|
637604dfaf | ||
|
|
f66df4d468 | ||
|
|
b6adfa294c | ||
|
|
f374f231d4 | ||
|
|
d4e60a46c1 | ||
|
|
a834ac6ec2 | ||
|
|
92e6ea96b2 | ||
|
|
de449fd1c2 | ||
|
|
77a1fcb1fc | ||
|
|
0cf0598936 | ||
|
|
80d91c0c51 | ||
|
|
c39bf7b7e6 | ||
|
|
9db0b038a5 | ||
|
|
4f4c4ed5be | ||
|
|
51f7298720 | ||
|
|
bc4032c2cd | ||
|
|
c055c91739 | ||
|
|
ea8f12579d | ||
|
|
88db2d9e18 | ||
|
|
5f93d7b3be | ||
|
|
1875ed55cc | ||
|
|
9f0ecba9b9 | ||
|
|
9acfceba29 | ||
|
|
92bf8b4436 | ||
|
|
b52c00d49b | ||
|
|
20fcf7a1e9 | ||
|
|
095dd3ecf2 | ||
|
|
5a1b8bc4b6 | ||
|
|
eadb36cb26 | ||
|
|
6eac133caf | ||
|
|
cc92d342ea | ||
|
|
02caa13222 | ||
|
|
975b4dee2f | ||
|
|
65f0a3f535 | ||
|
|
9ae7c66b77 | ||
|
|
77ab4ac1b5 | ||
|
|
b0e66ae445 | ||
|
|
5fe8db541e | ||
|
|
87bed9b294 | ||
|
|
da1c1dbdd9 | ||
|
|
56b6904564 | ||
|
|
10e64deafe | ||
|
|
ac72b69ece | ||
|
|
8e18ce74b2 | ||
|
|
254ce72415 | ||
|
|
1b657ee995 | ||
|
|
71facb1850 | ||
|
|
8d8c502546 | ||
|
|
0a9bd07f20 | ||
|
|
688577c1ed | ||
|
|
8027039754 | ||
|
|
031b7998c9 | ||
|
|
13053b85ef | ||
|
|
e3b13143bf | ||
|
|
e150ab58e9 | ||
|
|
ff080a2d8f | ||
|
|
a63d00bb5c | ||
|
|
96b40a5edd | ||
|
|
8596c54d6a | ||
|
|
e07532dd1b | ||
|
|
04a467bdc7 | ||
|
|
06d60bb1d1 | ||
|
|
9c6c01bab1 | ||
|
|
4133ac6ef0 | ||
|
|
4834c33861 | ||
|
|
904b5c2886 | ||
|
|
e79a819724 | ||
|
|
0fdbd410b6 | ||
|
|
af7ccf3beb | ||
|
|
3e48f436af | ||
|
|
5e488bd81f | ||
|
|
53ff28d2b0 | ||
|
|
4a4636bd03 | ||
|
|
744d0d299e | ||
|
|
b035003546 | ||
|
|
842dafefec | ||
|
|
375963f92d | ||
|
|
a51b446a10 | ||
|
|
371aba9912 | ||
|
|
8b30992a11 | ||
|
|
33de45d8fb | ||
|
|
d7d0056b73 | ||
|
|
7d2fcef807 | ||
|
|
1981b79557 | ||
|
|
0c7d4a709a | ||
|
|
6c8d226b21 | ||
|
|
211bbeda5b | ||
|
|
7057cb0104 | ||
|
|
658edb64d6 | ||
|
|
dc06f14505 | ||
|
|
f446314ce6 | ||
|
|
443b1df5e1 | ||
|
|
3c22e0d203 | ||
|
|
d636ad8e09 | ||
|
|
adfdbf3d44 | ||
|
|
8b03abde8d | ||
|
|
67320dc024 | ||
|
|
c5530104ff | ||
|
|
78097df7ff | ||
|
|
072e6029b6 | ||
|
|
5926441984 | ||
|
|
a1fe2a8d58 | ||
|
|
25cc46e416 | ||
|
|
458d61fafe | ||
|
|
32010d5387 | ||
|
|
7313bca403 | ||
|
|
b7c7e6c623 | ||
|
|
0b4d243bf0 | ||
|
|
377c0e7075 | ||
|
|
0c4c4fc352 | ||
|
|
9d627936e6 | ||
|
|
29a32c4726 | ||
|
|
e72c4f989f | ||
|
|
0d656e3963 | ||
|
|
50ce471dc6 | ||
|
|
8113838eee | ||
|
|
a92bb80055 | ||
|
|
b2112e6792 | ||
|
|
250db10249 | ||
|
|
309229619b | ||
|
|
157944b774 | ||
|
|
1a908e361e |
@@ -2662,8 +2662,179 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7991086?v=4",
|
||||
"profile": "https://github.com/reederda",
|
||||
"contributions": [
|
||||
"translations",
|
||||
"translation",
|
||||
"translation",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vickyjaura183",
|
||||
"name": "vickyjaura183",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/109422491?v=4",
|
||||
"profile": "https://github.com/vickyjaura183",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "julian-piehl",
|
||||
"name": "Peace",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32363424?v=4",
|
||||
"profile": "https://github.com/julian-piehl",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kylegordon",
|
||||
"name": "Kyle Gordon",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/231528?v=4",
|
||||
"profile": "https://github.com/kylegordon",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sunflowerbofh",
|
||||
"name": "Katharina Drexel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53009155?v=4",
|
||||
"profile": "http://www.bfh.ch",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dsferruzza",
|
||||
"name": "David Sferruzza",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1931963?v=4",
|
||||
"profile": "https://david.sferruzza.fr/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rnelsonee",
|
||||
"name": "Rick Nelson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19511639?v=4",
|
||||
"profile": "https://github.com/rnelsonee",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "BasO12",
|
||||
"name": "BasO12",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/94169344?v=4",
|
||||
"profile": "https://github.com/BasO12",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Vautia",
|
||||
"name": "Vautia",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/111710123?v=4",
|
||||
"profile": "https://github.com/Vautia",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chartjes",
|
||||
"name": "Chris Hartjes",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28321?v=4",
|
||||
"profile": "http://www.littlehart.net/atthekeyboard",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "geo-chen",
|
||||
"name": "geo-chen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2404584?v=4",
|
||||
"profile": "https://github.com/geo-chen",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nh314",
|
||||
"name": "Phan Nguyen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6006620?v=4",
|
||||
"profile": "https://github.com/nh314",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "StarlessNights",
|
||||
"name": "Iisakki Jaakkola",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/115993812?v=4",
|
||||
"profile": "https://github.com/StarlessNights",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "eltociear",
|
||||
"name": "Ikko Ashimine",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
|
||||
"profile": "https://bandism.net/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lukasfehling",
|
||||
"name": "Lukas Fehling",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/56871540?v=4",
|
||||
"profile": "https://github.com/lukasfehling",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fernando-almeida",
|
||||
"name": "Fernando Almeida",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1975990?v=4",
|
||||
"profile": "https://github.com/fernando-almeida",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "akemidx",
|
||||
"name": "akemidx",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/116301219?v=4",
|
||||
"profile": "https://github.com/akemidx",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "oguzbilgic",
|
||||
"name": "Oguz Bilgic",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/144778?v=4",
|
||||
"profile": "http://oguz.site",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "scoo73r",
|
||||
"name": "Scooter Crawford",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9262438?v=4",
|
||||
"profile": "https://github.com/scoo73r",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "subdriven",
|
||||
"name": "subdriven",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5957345?v=4",
|
||||
"profile": "https://github.com/subdriven",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ RESET_PASSWORD_LINK_EXPIRES=900
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=stderr
|
||||
APP_LOG_MAX_FILES=10
|
||||
LOG_CHANNEL=stderr
|
||||
LOG_MAX_DAYS=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
GOOGLE_MAPS_API=
|
||||
|
||||
@@ -21,6 +21,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=snipeit-local
|
||||
DB_USERNAME=snipeit-local
|
||||
DB_PASSWORD=snipeit-local
|
||||
@@ -93,7 +94,7 @@ DISABLE_NOSAML_LOCAL_LOGIN=true
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
LOG_CHANNEL=single
|
||||
LOG_LEVEL=debug
|
||||
LOG_CHANNEL=stack
|
||||
LOG_SLACK_WEBHOOK_URL=null
|
||||
|
||||
12
.env.example
12
.env.example
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
@@ -158,8 +159,8 @@ PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
APP_LOG_MAX_FILES=10
|
||||
LOG_CHANNEL=single
|
||||
LOG_MAX_DAYS=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
APP_FORCE_TLS=false
|
||||
@@ -172,3 +173,10 @@ IMPORT_MEMORY_LIMIT=500M
|
||||
REPORT_TIME_LIMIT=12000
|
||||
REQUIRE_SAML=false
|
||||
API_THROTTLE_PER_MINUTE=120
|
||||
CSV_ESCAPE_FORMULAS=true
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SCIM
|
||||
# --------------------------------------------
|
||||
SCIM_TRACE=false
|
||||
SCIM_STANDARDS_COMPLIANCE=false
|
||||
@@ -14,6 +14,7 @@ FILESYSTEM_DISK=local
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=testing.sqlite
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
@@ -70,5 +71,5 @@ SECURE_COOKIES=false
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: APP LOG FORMAT
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
APP_LOG_LEVEL=debug
|
||||
LOG_CHANNEL=single
|
||||
LOG_LEVEL=debug
|
||||
|
||||
@@ -14,6 +14,7 @@ FILESYSTEM_DISK=local
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE='sqlite_testing'
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=null
|
||||
@@ -34,4 +35,4 @@ IMAGE_LIB=gd
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: APP LOG FORMAT
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
LOG_CHANNEL=single
|
||||
|
||||
@@ -4,6 +4,7 @@ APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=mysql
|
||||
DB_DEFAULT=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=snipeittests
|
||||
DB_USERNAME=snipeit
|
||||
DB_PASSWORD=snipe
|
||||
|
||||
@@ -4,6 +4,7 @@ APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_DEFAULT=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
|
||||
|
||||
|
||||
|
||||
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@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
# 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@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
# 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.1.0
|
||||
uses: codacy/codacy-analysis-cli-action@v4.2.0
|
||||
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/docker-alpine.yml
vendored
2
.github/workflows/docker-alpine.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
|
||||
@@ -24,6 +24,7 @@ php7.4-mbstring \
|
||||
php7.4-zip \
|
||||
php7.4-bcmath \
|
||||
php7.4-redis \
|
||||
php-memcached \
|
||||
patch \
|
||||
curl \
|
||||
wget \
|
||||
@@ -139,4 +140,4 @@ RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
|
||||
CMD ["/startup.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
EXPOSE 443
|
||||
|
||||
@@ -28,6 +28,7 @@ RUN apk add --no-cache \
|
||||
php7-xmlreader \
|
||||
php7-sodium \
|
||||
php7-redis \
|
||||
php7-pecl-memcached \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
@@ -85,4 +86,4 @@ ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 80
|
||||
|
||||
@@ -75,14 +75,14 @@ RUN set -eux; \
|
||||
rm snipeit.tar.gz; \
|
||||
# Install composer php dependencies
|
||||
if [ "$ENVIRONMENT" = "production" ]; then \
|
||||
echo "production enviroment detected!"; \
|
||||
echo "production environment detected!"; \
|
||||
composer update \
|
||||
--no-cache \
|
||||
--no-dev \
|
||||
--optimize-autoloader \
|
||||
--working-dir=/var/www/html; \
|
||||
else \
|
||||
echo "development enviroment detected!"; \
|
||||
echo "development environment detected!"; \
|
||||
apk add --no-cache \
|
||||
${DEV_PACKAGES}; \
|
||||
composer update \
|
||||
@@ -100,4 +100,4 @@ COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
|
||||
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
|
||||
14
README.md
14
README.md
@@ -1,5 +1,5 @@
|
||||
 [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
@@ -59,15 +59,16 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@RodneyLeeBrands](https://github.com/RodneyLeeBrands) - Python script to synchronize information between Mosyle and Snipe-IT
|
||||
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
|
||||
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
-----
|
||||
|
||||
@@ -78,6 +79,8 @@ Please see the documentation on [contributing and developing for Snipe-IT](https
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||
|
||||
-----
|
||||
|
||||
### Security
|
||||
@@ -133,7 +136,10 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
65
TESTING.md
Normal file
65
TESTING.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Using the Test Suite
|
||||
|
||||
This document is targeted at developers looking to make modifications to
|
||||
this application's code base and want to run the existing test suite.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Follow the instructions for installing the application locally,
|
||||
making sure to have also run the [database migrations](link to db migrations).
|
||||
|
||||
|
||||
## Unit Tests
|
||||
|
||||
The application will use values in the `.env.testing` file located
|
||||
in the root directory to override the
|
||||
default settings and/or other values that exist in your `.env` files.
|
||||
|
||||
Make sure to modify the section in `.env.testing` that has the
|
||||
database settings. In the example below, it is connecting to the
|
||||
[MariaDB](link-to-maria-db) server that is used if you install the
|
||||
application using [Docker](https://docker.com).
|
||||
|
||||
```dotenv
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_DATABASE=snipeit
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=changeme1234
|
||||
```
|
||||
|
||||
To run the entire unit test suite, use the following command from your terminal:
|
||||
|
||||
`php artisan test --env=testing`
|
||||
|
||||
To run individual test files, you can pass the path to the test that
|
||||
you want to run.
|
||||
|
||||
`php artisan test --env=testing tests/Unit/AccessoryTest.php`
|
||||
|
||||
## Browser Tests
|
||||
|
||||
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them.
|
||||
When troubleshooting any problems, make sure that your `.env` file is configured
|
||||
correctly to run the existing application.
|
||||
|
||||
### Test Setup
|
||||
|
||||
Your application needs to be configued and up and running in order for the browser
|
||||
tests to actually run. When running the tests locally, you can start the application
|
||||
using the following command:
|
||||
|
||||
`php artisan serve`
|
||||
|
||||
|
||||
To run the test suite use the following command from another terminal tab or window:
|
||||
|
||||
`php artisan dusk`
|
||||
|
||||
To run individual test files, you can pass the path to the test that you want to run.
|
||||
|
||||
`php artisan dusk tests/Browser/LoginTest.php`
|
||||
6
app.json
6
app.json
@@ -118,7 +118,7 @@
|
||||
"description": "The duration (in seconds) that the user should be blocked from attempting to authenticate again.",
|
||||
"value": "60"
|
||||
},
|
||||
"APP_LOG": {
|
||||
"LOG_CHANNEL": {
|
||||
"description": "Driver to send logs to. (errorlog for stderr)",
|
||||
"value": "errorlog"
|
||||
},
|
||||
@@ -148,7 +148,7 @@
|
||||
"image": "heroku/php",
|
||||
"addons": [
|
||||
"cleardb:ignite",
|
||||
"heroku-redis:hobby-dev",
|
||||
"heroku-redis:mini",
|
||||
"papertrail:choklad"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class CheckoutLicenseToAllUsers extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
protected $description = 'Checks out licenses to all users';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Ldap;
|
||||
@@ -49,7 +50,7 @@ class LdapSync extends Command
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag_field;
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
$ldap_result_phone = Setting::getSettings()->ldap_phone_field;
|
||||
@@ -57,6 +58,7 @@ class LdapSync extends Command
|
||||
$ldap_result_country = Setting::getSettings()->ldap_country;
|
||||
$ldap_result_dept = Setting::getSettings()->ldap_dept;
|
||||
$ldap_result_manager = Setting::getSettings()->ldap_manager;
|
||||
$ldap_default_group = Setting::getSettings()->ldap_default_group;
|
||||
|
||||
try {
|
||||
$ldapconn = Ldap::connectToLdap();
|
||||
@@ -175,8 +177,19 @@ class LdapSync extends Command
|
||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
|
||||
$pass = bcrypt($tmp_pass);
|
||||
|
||||
$manager_cache = [];
|
||||
|
||||
if($ldap_default_group != null) {
|
||||
|
||||
$default = Group::find($ldap_default_group);
|
||||
if (!$default) {
|
||||
$ldap_default_group = null; // un-set the default group if that group doesn't exist
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
if (empty($ldap_result_active_flag) || $results[$i][$ldap_result_active_flag][0] == 'TRUE') {
|
||||
$item = [];
|
||||
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : '';
|
||||
$item['employee_number'] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : '';
|
||||
@@ -191,6 +204,7 @@ class LdapSync extends Command
|
||||
$item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : '';
|
||||
$item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : '';
|
||||
|
||||
|
||||
$department = Department::firstOrCreate([
|
||||
'name' => $item['department'],
|
||||
]);
|
||||
@@ -203,7 +217,7 @@ class LdapSync extends Command
|
||||
// Creating a new user.
|
||||
$user = new User;
|
||||
$user->password = $pass;
|
||||
$user->activated = 0;
|
||||
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
|
||||
$item['createorupdate'] = 'created';
|
||||
}
|
||||
|
||||
@@ -218,19 +232,61 @@ class LdapSync extends Command
|
||||
$user->department_id = $department->id;
|
||||
|
||||
if($item['manager'] != null) {
|
||||
//Captures only the Canonical Name
|
||||
$item['manager'] = ltrim($item['manager'], "CN=");
|
||||
$item['manager'] = substr($item['manager'],0, strpos($item['manager'], ','));
|
||||
$ldap_manager = User::where('username', $item['manager'])->first();
|
||||
if ( $ldap_manager && isset($ldap_manager->id) ) {
|
||||
$user->manager_id = $ldap_manager->id;
|
||||
// Check Cache first
|
||||
if (isset($manager_cache[$item['manager']])) {
|
||||
// found in cache; use that and avoid extra lookups
|
||||
$user->manager_id = $manager_cache[$item['manager']];
|
||||
} else {
|
||||
// Get the LDAP Manager
|
||||
try {
|
||||
$ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
|
||||
// Hail-mary for Okta manager 'shortnames' - will only work if
|
||||
// Okta configuration is using full email-address-style usernames
|
||||
$ldap_manager = [
|
||||
"count" => 1,
|
||||
0 => [
|
||||
$ldap_result_username => [$item['manager']]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($ldap_manager["count"] > 0) {
|
||||
|
||||
// Get the Manager's username
|
||||
// PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
|
||||
$ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0];
|
||||
|
||||
// Get User from Manager username.
|
||||
$ldap_manager = User::where('username', $ldapManagerUsername)->first();
|
||||
|
||||
if ($ldap_manager && isset($ldap_manager->id)) {
|
||||
// Link user to manager id.
|
||||
$user->manager_id = $ldap_manager->id;
|
||||
}
|
||||
}
|
||||
$manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sync activated state for Active Directory.
|
||||
if (array_key_exists('useraccountcontrol', $results[$i])) {
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
|
||||
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
|
||||
// (Specifically, we don't handle a value of '0.0' correctly)
|
||||
$raw_value = @$results[$i][$ldap_result_active_flag][0];
|
||||
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$boolean_cast = (bool)$raw_value;
|
||||
|
||||
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
|
||||
|
||||
} elseif (array_key_exists('useraccountcontrol', $results[$i]) ) {
|
||||
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
|
||||
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
|
||||
|
||||
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
some users may have been dependent upon the previous behavior, and this
|
||||
could cause additional access to be available to users they don't want
|
||||
to allow to log in.
|
||||
@@ -260,12 +316,11 @@ class LdapSync extends Command
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
];
|
||||
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// If we're not using AD, and there isn't an activated flag set, activate all users
|
||||
elseif (empty($ldap_result_active_flag)) {
|
||||
$user->activated = 1;
|
||||
}
|
||||
} /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
|
||||
already-existing accounts will be however the administrator has set them */
|
||||
|
||||
|
||||
if ($item['ldap_location_override'] == true) {
|
||||
$user->location_id = $item['location_id'];
|
||||
@@ -284,6 +339,10 @@ class LdapSync extends Command
|
||||
if ($user->save()) {
|
||||
$item['note'] = $item['createorupdate'];
|
||||
$item['status'] = 'success';
|
||||
if ( $item['createorupdate'] === 'created' && $ldap_default_group) {
|
||||
$user->groups()->attach($ldap_default_group);
|
||||
}
|
||||
|
||||
} else {
|
||||
foreach ($user->getErrors()->getMessages() as $key => $err) {
|
||||
$errors .= $err[0];
|
||||
@@ -293,7 +352,6 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
array_push($summary, $item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('summary')) {
|
||||
|
||||
@@ -39,33 +39,39 @@ class SyncAssetCounters extends Command
|
||||
public function handle()
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
// We need the whole count of all assets in order to set up the progress bar
|
||||
$assets_count = Asset::withTrashed()->count();
|
||||
$bar = $this->output->createProgressBar($assets_count);
|
||||
|
||||
$assets = Asset::withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')
|
||||
->withTrashed()->get();
|
||||
->withTrashed()->chunk(100, function ($assets) use ($bar) {
|
||||
|
||||
if ($assets) {
|
||||
if ($assets->count() > 0) {
|
||||
$bar = $this->output->createProgressBar($assets->count());
|
||||
if ($assets->count() > 0) {
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$asset->checkin_counter = (int) $asset->checkins_count;
|
||||
$asset->checkout_counter = (int) $asset->checkouts_count;
|
||||
$asset->requests_counter = (int) $asset->user_requests_count;
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->save();
|
||||
$output['info'][] = 'Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests';
|
||||
$bar->advance();
|
||||
}
|
||||
$bar->finish();
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
foreach ($output['info'] as $key => $output_text) {
|
||||
$this->info($output_text);
|
||||
}
|
||||
$asset->checkin_counter = (int) $asset->checkins_count;
|
||||
$asset->checkout_counter = (int) $asset->checkouts_count;
|
||||
$asset->requests_counter = (int) $asset->user_requests_count;
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->save();
|
||||
$bar->advance();
|
||||
|
||||
\Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests');
|
||||
|
||||
}
|
||||
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
$this->info('Sync executed in '.$time_elapsed_secs.' seconds');
|
||||
} else {
|
||||
$this->info('No assets to sync');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$bar->finish();
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
$this->info("\nSync of ".$assets_count.' assets executed in '.$time_elapsed_secs.' seconds');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\Statuslabel;
|
||||
use Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Image;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Helper
|
||||
{
|
||||
@@ -22,12 +23,13 @@ class Helper
|
||||
* @since [v2.0]
|
||||
* @return string
|
||||
*/
|
||||
public static function parseEscapedMarkedown($str)
|
||||
public static function parseEscapedMarkedown($str = null)
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
$Parsedown->setSafeMode(true);
|
||||
|
||||
if ($str) {
|
||||
return $Parsedown->text(e($str));
|
||||
return $Parsedown->text($str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +625,7 @@ class Helper
|
||||
{
|
||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::withCount('assets as assets_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::whereNotNull('min_amt')->get();
|
||||
|
||||
$avail_consumables = 0;
|
||||
$items_array = [];
|
||||
@@ -668,7 +670,7 @@ class Helper
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$avail = $component->qty - $component->assets_count;
|
||||
$avail = $component->numRemaining();
|
||||
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($component->qty > 0) {
|
||||
$percent = number_format((($avail / $component->qty) * 100), 0);
|
||||
@@ -1124,5 +1126,25 @@ class Helper
|
||||
|
||||
return $settings;
|
||||
}
|
||||
public static function AgeFormat($date) {
|
||||
$year = Carbon::parse($date)
|
||||
->diff(now())->y;
|
||||
$month = Carbon::parse($date)
|
||||
->diff(now())->m;
|
||||
$days = Carbon::parse($date)
|
||||
->diff(now())->d;
|
||||
$age='';
|
||||
if ($year) {
|
||||
$age .= $year.'y ';
|
||||
}
|
||||
if ($month) {
|
||||
$age .= $month.'m ';
|
||||
}
|
||||
if ($days) {
|
||||
$age .= $days.'d';
|
||||
}
|
||||
|
||||
return $age;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
186
app/Http/Controllers/Accessories/AccessoriesFilesController.php
Normal file
186
app/Http/Controllers/Accessories/AccessoriesFilesController.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Accessory;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Accessory\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class AccessoriesFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a accessory.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $accessoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $accessoryId = null)
|
||||
{
|
||||
|
||||
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) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\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('private_uploads/accessories/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$accessory->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('general.file_upload_success'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded'));
|
||||
}
|
||||
// Prepare the error message
|
||||
return redirect()->route('accessories.index')
|
||||
->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected accessory file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $accessoryId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($accessoryId = null, $fileId = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
// the asset is valid
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('update', $accessory);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('accessories/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('accessories/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('accessories.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 $accessoryId
|
||||
* @param int $fileId
|
||||
* @return \Symfony\Accessory\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($accessoryId = null, $fileId = null, $download = true)
|
||||
{
|
||||
|
||||
\Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
|
||||
|
||||
// the accessory is valid
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('view', $accessory);
|
||||
$this->authorize('accessories.files', $accessory);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/accessories/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class AccessoryCheckoutController extends Controller
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
if (! $user = User::find($request->input('assigned_to'))) {
|
||||
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
|
||||
@@ -19,6 +19,8 @@ use App\Models\Accessory;
|
||||
use App\Models\License;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -236,12 +238,49 @@ class AcceptanceController extends Controller
|
||||
}
|
||||
|
||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename);
|
||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
||||
event(new CheckoutAccepted($acceptance));
|
||||
|
||||
$return_msg = trans('admin/users/message.accepted');
|
||||
|
||||
} else {
|
||||
// Format the data to send the declined notification
|
||||
$branding_settings = SettingsController::getPDFBranding();
|
||||
|
||||
// This is the most horriblest
|
||||
switch($acceptance->checkoutable_type){
|
||||
case 'App\Models\Asset':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Accessory':
|
||||
$assigned_to = User::find($item->assignedTo);
|
||||
break;
|
||||
|
||||
case 'App\Models\LicenseSeat':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Component':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Consumable':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
}
|
||||
$data = [
|
||||
'item_tag' => $item->asset_tag,
|
||||
'item_model' => $display_model,
|
||||
'item_serial' => $item->serial,
|
||||
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'date_settings' => $branding_settings->date_display_format,
|
||||
];
|
||||
|
||||
$acceptance->decline($sig_filename);
|
||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||
event(new CheckoutDeclined($acceptance));
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ class AssetsController extends Controller
|
||||
|
||||
if ($filter_non_deprecable_assets) {
|
||||
$non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
|
||||
|
||||
$assets->InModelList($non_deprecable_models->toArray());
|
||||
}
|
||||
|
||||
@@ -141,6 +140,14 @@ class AssetsController extends Controller
|
||||
$assets->where('assets.status_id', '=', $request->input('status_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_tag')) {
|
||||
$assets->where('assets.asset_tag', '=', $request->input('asset_tag'));
|
||||
}
|
||||
|
||||
if ($request->filled('serial')) {
|
||||
$assets->where('assets.serial', '=', $request->input('serial'));
|
||||
}
|
||||
|
||||
if ($request->input('requestable') == 'true') {
|
||||
$assets->where('assets.requestable', '=', '1');
|
||||
}
|
||||
@@ -357,19 +364,38 @@ class AssetsController extends Controller
|
||||
/**
|
||||
* Returns JSON with information about an asset (by tag) for detail view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $tag
|
||||
* @since [v4.2.1]
|
||||
* @return JsonResponse
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function showByTag(Request $request, $tag)
|
||||
{
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag', $tag)->first()) {
|
||||
$this->authorize('view', $asset);
|
||||
$this->authorize('index', Asset::class);
|
||||
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
|
||||
|
||||
return (new AssetsTransformer)->transformAsset($asset, $request);
|
||||
// Check if they've passed ?deleted=true
|
||||
if ($request->input('deleted', 'false') == 'true') {
|
||||
$assets = $assets->withTrashed();
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
|
||||
if (($assets = $assets->get()) && ($assets->count()) > 0) {
|
||||
|
||||
// If there is exactly one result and the deleted parameter is not passed, we should pull the first (and only)
|
||||
// asset from the returned collection, since transformAsset() expects an Asset object, NOT a collection
|
||||
if (($assets->count() == 1) && ($request->input('deleted') != 'true')) {
|
||||
return (new AssetsTransformer)->transformAsset($assets->first());
|
||||
|
||||
// If there is more than one result OR if the endpoint is requesting deleted items (even if there is only one
|
||||
// match, return the normal collection transformed.
|
||||
} else {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If there are 0 results, return the "no such asset" response
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
@@ -379,29 +405,25 @@ class AssetsController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $serial
|
||||
* @since [v4.2.1]
|
||||
* @return JsonResponse
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function showBySerial(Request $request, $serial)
|
||||
{
|
||||
$this->authorize('index', Asset::class);
|
||||
if ($assets = Asset::with('assetstatus')->with('assignedTo')
|
||||
->withTrashed()->where('serial', $serial)->get()) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
|
||||
|
||||
$assets = Asset::with('assetstatus')->with('assignedTo');
|
||||
|
||||
if ($request->input('deleted', 'false') === 'true') {
|
||||
// Check if they've passed ?deleted=true
|
||||
if ($request->input('deleted', 'false') == 'true') {
|
||||
$assets = $assets->withTrashed();
|
||||
}
|
||||
|
||||
$assets = $assets->where('serial', $serial)->get();
|
||||
if ($assets) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
}
|
||||
|
||||
if (($assets = $assets->get()) && ($assets->count()) > 0) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
|
||||
// If there are 0 results, return the "no such asset" response
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -885,11 +907,9 @@ class AssetsController extends Controller
|
||||
if ($request->has('status_id')) {
|
||||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
||||
|
||||
$checkin_at = null;
|
||||
if ($request->filled('checkin_at')) {
|
||||
$checkin_at = $request->input('checkin_at');
|
||||
}
|
||||
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
@@ -912,7 +932,7 @@ class AssetsController extends Controller
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first();
|
||||
|
||||
if($asset) {
|
||||
if ($asset) {
|
||||
return $this->checkin($request, $asset->id);
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,8 @@ class ComponentsController extends Controller
|
||||
'created_at' => \Carbon::now(),
|
||||
'assigned_qty' => $request->get('assigned_qty', 1),
|
||||
'user_id' => \Auth::id(),
|
||||
'asset_id' => $request->get('assigned_to')
|
||||
'asset_id' => $request->get('assigned_to'),
|
||||
'note' => $request->get('note'),
|
||||
]);
|
||||
|
||||
$component->logCheckout($request->input('note'), $asset);
|
||||
|
||||
@@ -228,9 +228,11 @@ 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',
|
||||
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
|
||||
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
||||
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -273,6 +275,7 @@ class ConsumablesController extends Controller
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $user->id,
|
||||
'assigned_to' => $assigned_to,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
// Log checkout event
|
||||
|
||||
@@ -96,7 +96,7 @@ class CustomFieldsController extends Controller
|
||||
$data = $request->all();
|
||||
$regex_format = null;
|
||||
|
||||
if (str_contains($data['format'], 'regex:')) {
|
||||
if ((array_key_exists('format', $data)) && (str_contains($data['format'], 'regex:'))) {
|
||||
$regex_format = $data['format'];
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class LicenseSeatsController extends Controller
|
||||
}
|
||||
|
||||
$total = $seats->count();
|
||||
$offset = (($seats) && (request('offset') > $total)) ? 0 : request('offset', 0);
|
||||
$offset = (($seats) && (request('offset') >= $total)) ? 0 : request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$seats = $seats->skip($offset)->take($limit)->get();
|
||||
|
||||
@@ -27,7 +27,7 @@ class LocationsController extends Controller
|
||||
$allowed_columns = [
|
||||
'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
|
||||
'updated_at', 'manager_id', 'image',
|
||||
'assigned_assets_count', 'users_count', 'assets_count', 'currency', 'ldap_ou', ];
|
||||
'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
'locations.id',
|
||||
@@ -47,6 +47,7 @@ class LocationsController extends Controller
|
||||
'locations.currency',
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
@@ -157,7 +158,9 @@ class LocationsController extends Controller
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count')->findOrFail($id);
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('users as users_count')
|
||||
->findOrFail($id);
|
||||
|
||||
return (new LocationsTransformer)->transformLocation($location);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class ReportsController extends Controller
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'target', 'location');
|
||||
$actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
|
||||
|
||||
@@ -9,6 +9,8 @@ use App\Http\Transformers\StatuslabelsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Statuslabel;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Transformers\PieChartTransformer;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class StatuslabelsController extends Controller
|
||||
{
|
||||
@@ -188,43 +190,54 @@ class StatuslabelsController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetCountByStatuslabel()
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$statuslabels = Statuslabel::withCount('assets')->get();
|
||||
|
||||
$labels = [];
|
||||
$points = [];
|
||||
$default_color_count = 0;
|
||||
$colors_array = [];
|
||||
|
||||
foreach ($statuslabels as $statuslabel) {
|
||||
if ($statuslabel->assets_count > 0) {
|
||||
$labels[] = $statuslabel->name.' ('.number_format($statuslabel->assets_count).')';
|
||||
$points[] = $statuslabel->assets_count;
|
||||
|
||||
if ($statuslabel->color != '') {
|
||||
$colors_array[] = $statuslabel->color;
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
}
|
||||
$default_color_count++;
|
||||
$total[$statuslabel->name]['label'] = $statuslabel->name;
|
||||
$total[$statuslabel->name]['count'] = $statuslabel->assets_count;
|
||||
|
||||
if ($statuslabel->color != '') {
|
||||
$total[$statuslabel->name]['color'] = $statuslabel->color;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'labels' => $labels,
|
||||
'datasets' => [[
|
||||
'data' => $points,
|
||||
'backgroundColor' => $colors_array,
|
||||
'hoverBackgroundColor' => $colors_array,
|
||||
]],
|
||||
];
|
||||
return (new PieChartTransformer())->transformPieChartDate($total);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a count of assets by meta status type for pie chart
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.11]
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetCountByMetaStatus()
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$total['rtd']['label'] = trans('general.ready_to_deploy');
|
||||
$total['rtd']['count'] = Asset::RTD()->count();
|
||||
|
||||
$total['deployed']['label'] = trans('general.deployed');
|
||||
$total['deployed']['count'] = Asset::Deployed()->count();
|
||||
|
||||
$total['archived']['label'] = trans('general.archived');
|
||||
$total['archived']['count'] = Asset::Archived()->count();
|
||||
|
||||
$total['pending']['label'] = trans('general.pending');
|
||||
$total['pending']['count'] = Asset::Pending()->count();
|
||||
|
||||
$total['undeployable']['label'] = trans('general.undeployable');
|
||||
$total['undeployable']['count'] = Asset::Undeployable()->count();
|
||||
|
||||
return (new PieChartTransformer())->transformPieChartDate($total);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
@@ -66,6 +67,8 @@ class UsersController extends Controller
|
||||
'users.zip',
|
||||
'users.remote',
|
||||
'users.ldap_import',
|
||||
'users.start_date',
|
||||
'users.end_date',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
|
||||
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
|
||||
@@ -146,6 +149,15 @@ class UsersController extends Controller
|
||||
$users = $users->where('remote', '=', $request->input('remote'));
|
||||
}
|
||||
|
||||
if ($request->filled('start_date')) {
|
||||
$users = $users->where('users.start_date', '=', $request->input('start_date'));
|
||||
}
|
||||
|
||||
if ($request->filled('end_date')) {
|
||||
$users = $users->where('users.end_date', '=', $request->input('end_date'));
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('assets_count')) {
|
||||
$users->has('assets', '=', $request->input('assets_count'));
|
||||
}
|
||||
@@ -200,7 +212,7 @@ class UsersController extends Controller
|
||||
'assets', 'accessories', 'consumables', 'licenses', 'groups', 'activated', 'created_at',
|
||||
'two_factor_enrolled', 'two_factor_optin', 'last_login', 'assets_count', 'licenses_count',
|
||||
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
|
||||
'country', 'zip', 'id', 'ldap_import', 'remote',
|
||||
'country', 'zip', 'id', 'ldap_import', 'remote', 'start_date', 'end_date',
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -479,6 +491,26 @@ class UsersController extends Controller
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a specific user via email with all of their assigned assets.
|
||||
*
|
||||
* @author [Lukas Fehling] [<lukas.fehling@adabay.rocks>]
|
||||
* @since [v6.0.13]
|
||||
* @param Request $request
|
||||
* @param $id
|
||||
* @return string JSON
|
||||
*/
|
||||
public function emailAssetList(Request $request, $id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
if (empty($user->email)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
|
||||
}
|
||||
|
||||
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON containing a list of consumables assigned to a user.
|
||||
|
||||
@@ -463,7 +463,18 @@ class AssetModelsController extends Controller
|
||||
$data[$customField->db_column] = $defaultValue;
|
||||
}
|
||||
|
||||
$rules = $model->fieldset->validation_rules();
|
||||
$fieldsets = $model->fieldset->validation_rules();
|
||||
$rules = array();
|
||||
|
||||
foreach ($fieldsets as $fieldset => $validation){
|
||||
// If the field is marked as required, eliminate the rule so it doesn't interfere with the default values
|
||||
// (we are at model level, the rule still applies when creating a new asset using this model)
|
||||
$index = array_search('required', $validation);
|
||||
if ($index !== false){
|
||||
$validation[$index] = 'nullable';
|
||||
}
|
||||
$rules[$fieldset] = $validation;
|
||||
}
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class AssetModelsFilesController extends Controller
|
||||
$model->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
|
||||
return redirect()->back()->with('success', trans('general.file_upload_success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
|
||||
class BulkAssetsController extends Controller
|
||||
{
|
||||
@@ -32,7 +33,6 @@ class BulkAssetsController extends Controller
|
||||
|
||||
if (! $request->filled('ids')) {
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||
|
||||
}
|
||||
|
||||
// Figure out where we need to send the user after the update is complete, and store that in the session
|
||||
@@ -102,6 +102,8 @@ class BulkAssetsController extends Controller
|
||||
|| ($request->filled('company_id'))
|
||||
|| ($request->filled('status_id'))
|
||||
|| ($request->filled('model_id'))
|
||||
|| ($request->filled('null_purchase_date'))
|
||||
|| ($request->filled('null_expected_checkin_date'))
|
||||
) {
|
||||
foreach ($assets as $assetId) {
|
||||
|
||||
@@ -116,6 +118,14 @@ class BulkAssetsController extends Controller
|
||||
->conditionallyAddItem('supplier_id')
|
||||
->conditionallyAddItem('warranty_months');
|
||||
|
||||
if ($request->input('null_purchase_date')=='1') {
|
||||
$this->update_array['purchase_date'] = null;
|
||||
}
|
||||
|
||||
if ($request->input('null_expected_checkin_date')=='1') {
|
||||
$this->update_array['expected_checkin'] = null;
|
||||
}
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
$this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
|
||||
}
|
||||
@@ -239,7 +249,7 @@ class BulkAssetsController extends Controller
|
||||
* Process Multiple Checkout Request
|
||||
* @return View
|
||||
*/
|
||||
public function storeCheckout(Request $request)
|
||||
public function storeCheckout(AssetCheckoutRequest $request)
|
||||
{
|
||||
|
||||
$this->authorize('checkout', Asset::class);
|
||||
@@ -250,7 +260,7 @@ class BulkAssetsController extends Controller
|
||||
$target = $this->determineCheckoutTarget();
|
||||
|
||||
if (! is_array($request->get('selected_assets'))) {
|
||||
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
}
|
||||
|
||||
$asset_ids = array_filter($request->get('selected_assets'));
|
||||
@@ -297,9 +307,9 @@ class BulkAssetsController extends Controller
|
||||
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to('hardware/bulk-checkout')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->to('hardware/bulk-checkout')->with('error', $e->getErrors());
|
||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +135,7 @@ class LoginController extends Controller
|
||||
} else {
|
||||
|
||||
// Better logging
|
||||
if (!$saml->isEnabled()) {
|
||||
\Log::debug("SAML page requested, but SAML does not seem to enabled.");
|
||||
} else {
|
||||
if (empty($samlData)) {
|
||||
\Log::debug("SAML page requested, but samlData seems empty.");
|
||||
}
|
||||
}
|
||||
@@ -473,6 +471,11 @@ class LoginController extends Controller
|
||||
}
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
|
||||
if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){
|
||||
$request->session()->remove('password_hash_'.Auth::getDefaultDriver());
|
||||
}
|
||||
|
||||
Auth::logout();
|
||||
|
||||
if (! empty($sloRedirectUrl)) {
|
||||
|
||||
@@ -41,6 +41,7 @@ class ResetPasswordController extends Controller
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
$this->middleware('throttle:10,1');
|
||||
}
|
||||
|
||||
protected function rules()
|
||||
@@ -116,7 +117,7 @@ class ResetPasswordController extends Controller
|
||||
}
|
||||
|
||||
\Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
|
||||
return redirect()->back()->withInput($request->only('email'))->with('error', trans('passwords.token'));
|
||||
return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,6 @@ class SamlController extends Controller
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
return redirect()->route('logout')->with(['saml_logout' => true,'saml_slo_redirect_url' => $sloUrl]);
|
||||
return redirect()->route('logout.get')->with(['saml_logout' => true,'saml_slo_redirect_url' => $sloUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ class ComponentCheckoutController extends Controller
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'assigned_qty' => $request->input('assigned_qty'),
|
||||
'asset_id' => $asset_id,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
|
||||
|
||||
180
app/Http/Controllers/Components/ComponentsFilesController.php
Normal file
180
app/Http/Controllers/Components/ComponentsFilesController.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class ComponentsFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a component.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $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) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\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('private_uploads/components/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$component->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('components.show', $component->id)->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()
|
||||
->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, $download = true)
|
||||
{
|
||||
\Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||
$component = Component::find($componentId);
|
||||
|
||||
// the component is valid
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('view', $component);
|
||||
$this->authorize('components.files', $component);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/components/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ConsumableCheckoutController extends Controller
|
||||
// Check if the user exists
|
||||
if (is_null($user = User::find($assigned_to))) {
|
||||
// Redirect to the consumable management page with error
|
||||
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
|
||||
return redirect()->route('consumables.checkout.show', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'))->withInput();
|
||||
}
|
||||
|
||||
// Update the consumable data
|
||||
@@ -66,6 +66,7 @@ class ConsumableCheckoutController extends Controller
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $admin_user->id,
|
||||
'assigned_to' => e($request->input('assigned_to')),
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
180
app/Http/Controllers/Consumables/ConsumablesFilesController.php
Normal file
180
app/Http/Controllers/Consumables/ConsumablesFilesController.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Consumables;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Consumable;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Consumable\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class ConsumablesFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a consumable.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $consumableId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $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) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\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('private_uploads/consumables/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$consumable->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('consumables.show', $consumable->id)->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()
|
||||
->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
|
||||
* @return \Symfony\Consumable\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($consumableId = null, $fileId = null, $download = true)
|
||||
{
|
||||
$consumable = Consumable::find($consumableId);
|
||||
|
||||
// the consumable is valid
|
||||
if (isset($consumable->id)) {
|
||||
$this->authorize('view', $consumable);
|
||||
$this->authorize('consumables.files', $consumable);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/consumables/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -86,14 +86,24 @@ class CustomFieldsController extends Controller
|
||||
{
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$show_in_email = $request->get("show_in_email", 0);
|
||||
$display_in_user_view = $request->get("display_in_user_view", 0);
|
||||
|
||||
// Override the display settings if the field is encrypted
|
||||
if ($request->get("field_encrypted") == '1') {
|
||||
$show_in_email = '0';
|
||||
$display_in_user_view = '0';
|
||||
}
|
||||
|
||||
$field = new CustomField([
|
||||
"name" => trim($request->get("name")),
|
||||
"element" => $request->get("element"),
|
||||
"help_text" => $request->get("help_text"),
|
||||
"field_values" => $request->get("field_values"),
|
||||
"field_encrypted" => $request->get("field_encrypted", 0),
|
||||
"show_in_email" => $request->get("show_in_email", 0),
|
||||
"show_in_email" => $show_in_email,
|
||||
"is_unique" => $request->get("is_unique", 0),
|
||||
"display_in_user_view" => $display_in_user_view,
|
||||
"user_id" => Auth::id()
|
||||
]);
|
||||
|
||||
@@ -221,13 +231,24 @@ class CustomFieldsController extends Controller
|
||||
|
||||
$this->authorize('update', $field);
|
||||
|
||||
|
||||
$show_in_email = $request->get("show_in_email", 0);
|
||||
$display_in_user_view = $request->get("display_in_user_view", 0);
|
||||
|
||||
// Override the display settings if the field is encrypted
|
||||
if ($request->get("field_encrypted") == '1') {
|
||||
$show_in_email = '0';
|
||||
$display_in_user_view = '0';
|
||||
}
|
||||
|
||||
$field->name = trim(e($request->get("name")));
|
||||
$field->element = e($request->get("element"));
|
||||
$field->field_values = e($request->get("field_values"));
|
||||
$field->user_id = Auth::id();
|
||||
$field->help_text = $request->get("help_text");
|
||||
$field->show_in_email = $request->get("show_in_email", 0);
|
||||
$field->show_in_email = $show_in_email;
|
||||
$field->is_unique = $request->get("is_unique", 0);
|
||||
$field->display_in_user_view = $display_in_user_view;
|
||||
|
||||
if ($request->get('format') == 'CUSTOM REGEX') {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
@@ -235,6 +256,10 @@ class CustomFieldsController extends Controller
|
||||
$field->format = e($request->get('format'));
|
||||
}
|
||||
|
||||
if($field->element == 'checkbox' || $field->element == 'radio'){
|
||||
$field->format = 'ANY';
|
||||
}
|
||||
|
||||
if ($field->save()) {
|
||||
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success'));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class DashboardController extends Controller
|
||||
$counts['license'] = \App\Models\License::assetcount();
|
||||
$counts['consumable'] = \App\Models\Consumable::count();
|
||||
$counts['component'] = \App\Models\Component::count();
|
||||
$counts['user'] = \App\Models\User::count();
|
||||
$counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count();
|
||||
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
|
||||
|
||||
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
|
||||
|
||||
@@ -54,7 +54,8 @@ class DepartmentsController extends Controller
|
||||
$department->fill($request->all());
|
||||
$department->user_id = Auth::user()->id;
|
||||
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
|
||||
|
||||
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
|
||||
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
|
||||
$department = $request->handleImages($department);
|
||||
|
||||
if ($department->save()) {
|
||||
@@ -167,6 +168,8 @@ class DepartmentsController extends Controller
|
||||
|
||||
$department->fill($request->all());
|
||||
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
|
||||
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
|
||||
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
|
||||
|
||||
$department = $request->handleImages($department);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||
$licenseSeat->user_id = Auth::id();
|
||||
|
||||
|
||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
||||
if ($this->$checkoutMethod($licenseSeat)) {
|
||||
@@ -76,14 +77,14 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
if (! $licenseSeat) {
|
||||
if ($seatId) {
|
||||
return redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.');
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
|
||||
}
|
||||
|
||||
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
|
||||
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
|
||||
}
|
||||
|
||||
if (! $licenseSeat->license->is($license)) {
|
||||
return redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.');
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
|
||||
}
|
||||
|
||||
return $licenseSeat;
|
||||
|
||||
@@ -135,6 +135,7 @@ class LicenseFilesController extends Controller
|
||||
// the license is valid
|
||||
if (isset($license->id)) {
|
||||
$this->authorize('view', $license);
|
||||
$this->authorize('licenses.files', $license);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
@@ -171,6 +172,6 @@ class LicenseFilesController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('license.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Auth;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@@ -133,7 +136,7 @@ class ProfileController extends Controller
|
||||
public function password()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
|
||||
return view('account/change-password', compact('user'));
|
||||
}
|
||||
|
||||
@@ -161,7 +164,7 @@ class ProfileController extends Controller
|
||||
$validator = \Validator::make($request->all(), $rules);
|
||||
$validator->after(function ($validator) use ($request, $user) {
|
||||
if (! Hash::check($request->input('current_password'), $user->password)) {
|
||||
$validator->errors()->add('current_password', trans('validation.hashed_pass'));
|
||||
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
|
||||
}
|
||||
|
||||
// This checks to make sure that the user's password isn't the same as their username,
|
||||
@@ -186,6 +189,9 @@ class ProfileController extends Controller
|
||||
if (! $validator->fails()) {
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->save();
|
||||
|
||||
// Log the user out of other devices
|
||||
Auth::logoutOtherDevices($request->input('password'));
|
||||
return redirect()->route('account.password.index')->with('success', 'Password updated!');
|
||||
|
||||
}
|
||||
@@ -213,4 +219,47 @@ class ProfileController extends Controller
|
||||
$request->session()->put('menu_state', 'closed');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print inventory
|
||||
*
|
||||
* @author A. Gianotto
|
||||
* @since [v6.0.12]
|
||||
* @return Illuminate\View\View
|
||||
*/
|
||||
public function printInventory()
|
||||
{
|
||||
$show_user = Auth::user();
|
||||
|
||||
return view('users/print')
|
||||
->with('assets', Auth::user()->assets)
|
||||
->with('licenses', $show_user->licenses()->get())
|
||||
->with('accessories', $show_user->accessories()->get())
|
||||
->with('consumables', $show_user->consumables()->get())
|
||||
->with('show_user', $show_user)
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emails user a list of assigned assets
|
||||
*
|
||||
* @author A. Gianotto
|
||||
* @since [v6.0.12]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function emailAssetList()
|
||||
{
|
||||
|
||||
if (!$user = User::find(Auth::user()->id)) {
|
||||
return redirect()->back()
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
}
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ use Illuminate\Support\Facades\View;
|
||||
use Input;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use League\Csv\EscapeFormula;
|
||||
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Reports for
|
||||
@@ -411,6 +413,7 @@ class ReportsController extends Controller
|
||||
$customfields = CustomField::get();
|
||||
$response = new StreamedResponse(function () use ($customfields, $request) {
|
||||
\Log::debug('Starting streamed response');
|
||||
\Log::debug('CSV escaping is set to: '.config('app.escape_formulas'));
|
||||
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
@@ -422,6 +425,9 @@ class ReportsController extends Controller
|
||||
|
||||
$header = [];
|
||||
|
||||
if ($request->filled('id')) {
|
||||
$header[] = trans('general.id');
|
||||
}
|
||||
|
||||
if ($request->filled('company')) {
|
||||
$header[] = trans('general.company');
|
||||
@@ -552,6 +558,10 @@ class ReportsController extends Controller
|
||||
$header[] = trans('general.updated_at');
|
||||
}
|
||||
|
||||
if ($request->filled('deleted_at')) {
|
||||
$header[] = trans('general.deleted');
|
||||
}
|
||||
|
||||
if ($request->filled('last_audit_date')) {
|
||||
$header[] = trans('general.last_audit');
|
||||
}
|
||||
@@ -564,6 +574,10 @@ class ReportsController extends Controller
|
||||
$header[] = trans('general.notes');
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$header[] = trans('admin/manufacturers/table.url');
|
||||
}
|
||||
|
||||
|
||||
foreach ($customfields as $customfield) {
|
||||
if ($request->input($customfield->db_column_name()) == '1') {
|
||||
@@ -578,7 +592,7 @@ class ReportsController extends Controller
|
||||
\Log::debug('Added headers: '.$executionTime);
|
||||
|
||||
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
|
||||
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc', 'assignedTo',
|
||||
'location', 'assetstatus', 'company', 'defaultLoc', 'assignedTo',
|
||||
'model.category', 'model.manufacturer', 'supplier');
|
||||
|
||||
if ($request->filled('by_location_id')) {
|
||||
@@ -586,7 +600,6 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('by_rtd_location_id')) {
|
||||
\Log::debug('RTD location should match: '.$request->input('by_rtd_location_id'));
|
||||
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
|
||||
}
|
||||
|
||||
@@ -607,7 +620,6 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('by_dept_id')) {
|
||||
\Log::debug('Only users in dept '.$request->input('by_dept_id'));
|
||||
$assets->CheckedOutToTargetInDepartment($request->input('by_dept_id'));
|
||||
}
|
||||
|
||||
@@ -642,19 +654,32 @@ class ReportsController extends Controller
|
||||
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
||||
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
|
||||
}
|
||||
if($request->filled('exclude_archived')){
|
||||
if ($request->filled('exclude_archived')) {
|
||||
$assets->notArchived();
|
||||
}
|
||||
if ($request->input('deleted_assets') == '1') {
|
||||
$assets->withTrashed();
|
||||
}
|
||||
if ($request->input('deleted_assets') == '0') {
|
||||
$assets->onlyTrashed();
|
||||
}
|
||||
|
||||
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
\Log::debug('Walking results: '.$executionTime);
|
||||
$count = 0;
|
||||
|
||||
$formatter = new EscapeFormula("`");
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$count++;
|
||||
$row = [];
|
||||
|
||||
|
||||
if ($request->filled('id')) {
|
||||
$row[] = ($asset->id) ? $asset->id : '';
|
||||
}
|
||||
|
||||
if ($request->filled('company')) {
|
||||
$row[] = ($asset->company) ? $asset->company->name : '';
|
||||
}
|
||||
@@ -783,7 +808,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
|
||||
$row[] = $asset->present()->warrantee_expires();
|
||||
$row[] = $asset->present()->warranty_expires();
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
@@ -810,6 +835,10 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->updated_at) ? $asset->updated_at : '';
|
||||
}
|
||||
|
||||
if ($request->filled('deleted_at')) {
|
||||
$row[] = ($asset->deleted_at) ? $asset->deleted_at : '';
|
||||
}
|
||||
|
||||
if ($request->filled('last_audit_date')) {
|
||||
$row[] = ($asset->last_audit_date) ? $asset->last_audit_date : '';
|
||||
}
|
||||
@@ -822,13 +851,27 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->notes) ? $asset->notes : '';
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$row[] = config('app.url').'/hardware/'.$asset->id ;
|
||||
}
|
||||
|
||||
foreach ($customfields as $customfield) {
|
||||
$column_name = $customfield->db_column_name();
|
||||
if ($request->filled($customfield->db_column_name())) {
|
||||
$row[] = $asset->$column_name;
|
||||
}
|
||||
}
|
||||
fputcsv($handle, $row);
|
||||
|
||||
|
||||
// CSV_ESCAPE_FORMULAS is set to false in the .env
|
||||
if (config('app.escape_formulas') === false) {
|
||||
fputcsv($handle, $row);
|
||||
|
||||
// CSV_ESCAPE_FORMULAS is set to true or is not set in the .env
|
||||
} else {
|
||||
fputcsv($handle, $formatter->escapeRecord($row));
|
||||
}
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
\Log::debug('-- Record '.$count.' Asset ID:'.$asset->id.' in '.$executionTime);
|
||||
}
|
||||
@@ -979,7 +1022,11 @@ class ReportsController extends Controller
|
||||
}
|
||||
$assetItem = $acceptance->checkoutable;
|
||||
|
||||
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
|
||||
if (is_null($acceptance->created_at)){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||
} else {
|
||||
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
|
||||
}
|
||||
|
||||
if(!$assetItem->assignedTo->locale){
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
@@ -1036,9 +1083,9 @@ class ReportsController extends Controller
|
||||
* Get all assets with pending checkout acceptances
|
||||
*/
|
||||
if($showDeleted) {
|
||||
$acceptances = CheckoutAcceptance::pending()->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
} else {
|
||||
$acceptances = CheckoutAcceptance::pending()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
}
|
||||
|
||||
$assetsForReport = $acceptances
|
||||
@@ -1063,13 +1110,19 @@ class ReportsController extends Controller
|
||||
$rows[] = implode(',', $header);
|
||||
|
||||
foreach ($assetsForReport as $item) {
|
||||
$row = [ ];
|
||||
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
|
||||
$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')));
|
||||
$rows[] = implode(',', $row);
|
||||
|
||||
if ($item['assetItem'] != null){
|
||||
|
||||
$row = [ ];
|
||||
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
|
||||
$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')));
|
||||
$rows[] = implode(',', $row);
|
||||
} else {
|
||||
// Log the error maybe?
|
||||
}
|
||||
}
|
||||
|
||||
// spit out a csv
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Requests\SettingsSamlRequest;
|
||||
use App\Http\Requests\SetupUserRequest;
|
||||
use App\Models\Group;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
@@ -349,6 +350,7 @@ class SettingsController extends Controller
|
||||
$setting->privacy_policy_link = $request->input('privacy_policy_link');
|
||||
|
||||
$setting->depreciation_method = $request->input('depreciation_method');
|
||||
$setting->dash_chart_type = $request->input('dash_chart_type');
|
||||
|
||||
if ($request->input('per_page') != '') {
|
||||
$setting->per_page = $request->input('per_page');
|
||||
@@ -910,6 +912,8 @@ class SettingsController extends Controller
|
||||
public function getLdapSettings()
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
$groups = Group::pluck('name', 'id');
|
||||
|
||||
|
||||
/**
|
||||
* This validator is only temporary (famous last words.) - @snipe
|
||||
@@ -922,13 +926,13 @@ class SettingsController extends Controller
|
||||
|
||||
$validator = Validator::make($setting->toArray(), [
|
||||
'ldap_username_field' => 'not_in:sAMAccountName',
|
||||
'ldap_auth_filter_query' => 'not_in:uid=samaccountname',
|
||||
'ldap_filter' => 'regex:"^[^(]"',
|
||||
'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1',
|
||||
'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1',
|
||||
], $messages);
|
||||
|
||||
|
||||
|
||||
return view('settings.ldap', compact('setting'))->withErrors($validator);
|
||||
return view('settings.ldap', compact('setting', 'groups'))->withErrors($validator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -955,6 +959,7 @@ class SettingsController extends Controller
|
||||
$setting->ldap_pword = Crypt::encrypt($request->input('ldap_pword'));
|
||||
}
|
||||
$setting->ldap_basedn = $request->input('ldap_basedn');
|
||||
$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_lname_field = $request->input('ldap_lname_field');
|
||||
|
||||
@@ -5,10 +5,13 @@ namespace App\Http\Controllers\Users;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\License;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Group;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\ConsumableAssignment;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -162,13 +165,11 @@ class BulkUsersController extends Controller
|
||||
if ((! $request->filled('ids')) || (count($request->input('ids')) == 0)) {
|
||||
return redirect()->back()->with('error', 'No users selected');
|
||||
}
|
||||
if ((! $request->filled('status_id')) || ($request->input('status_id') == '')) {
|
||||
return redirect()->route('users.index')->with('error', 'No status selected');
|
||||
}
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('users.index')->with('error', 'Bulk delete is not enabled in this installation');
|
||||
}
|
||||
|
||||
$user_raw_array = request('ids');
|
||||
|
||||
if (($key = array_search(Auth::id(), $user_raw_array)) !== false) {
|
||||
@@ -179,27 +180,48 @@ class BulkUsersController extends Controller
|
||||
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
|
||||
$accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
|
||||
if ((($assets->count() > 0) && ((!$request->filled('status_id')) || ($request->input('status_id') == '')))) {
|
||||
return redirect()->route('users.index')->with('error', 'No status selected');
|
||||
}
|
||||
|
||||
|
||||
$this->logItemCheckinAndDelete($assets, Asset::class);
|
||||
$this->logItemCheckinAndDelete($accessories, Accessory::class);
|
||||
$this->logItemCheckinAndDelete($licenses, LicenseSeat::class);
|
||||
$this->logItemCheckinAndDelete($licenses, License::class);
|
||||
$this->logItemCheckinAndDelete($consumables, Consumable::class);
|
||||
|
||||
|
||||
Asset::whereIn('id', $assets->pluck('id'))->update([
|
||||
'status_id' => e(request('status_id')),
|
||||
'assigned_to' => null,
|
||||
'assigned_type' => null,
|
||||
'expected_checkin' => null,
|
||||
]);
|
||||
|
||||
|
||||
LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]);
|
||||
ConsumableAssignment::whereIn('id', $consumables->pluck('id'))->delete();
|
||||
|
||||
|
||||
foreach ($users as $user) {
|
||||
|
||||
$user->consumables()->sync([]);
|
||||
$user->accessories()->sync([]);
|
||||
$user->delete();
|
||||
if ($request->input('delete_user')=='1') {
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('success', 'Your selected users have been deleted and their assets have been updated.');
|
||||
$msg = trans('general.bulk_checkin_success');
|
||||
if ($request->input('delete_user')=='1') {
|
||||
$msg = trans('general.bulk_checkin_delete_success');
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('users.index')->with('success', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,14 +232,20 @@ class BulkUsersController extends Controller
|
||||
protected function logItemCheckinAndDelete($items, $itemType)
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$item_id = $item->id;
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $item->id;
|
||||
|
||||
if ($itemType == License::class){
|
||||
$item_id = $item->license_id;
|
||||
}
|
||||
|
||||
$logAction->item_id = $item_id;
|
||||
// We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays.
|
||||
$logAction->item_type = $itemType;
|
||||
$logAction->target_id = $item->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->note = 'Bulk checkin items and delete user';
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ class UsersController extends Controller
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->created_by = Auth::user()->id;
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
$permissions_array = $request->input('permission');
|
||||
@@ -270,6 +272,8 @@ class UsersController extends Controller
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
@@ -594,7 +598,7 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP form processing.
|
||||
* Print inventory
|
||||
*
|
||||
* @author Aladin Alaily
|
||||
* @since [v1.8]
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\CustomField;
|
||||
use App\Notifications\RequestAssetCancelation;
|
||||
use App\Notifications\RequestAssetNotification;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -29,23 +30,41 @@ class ViewAssetsController extends Controller
|
||||
public function getIndex()
|
||||
{
|
||||
$user = User::with(
|
||||
'assets',
|
||||
'assets.model',
|
||||
'assets.model.fieldset.fields',
|
||||
'consumables',
|
||||
'accessories',
|
||||
'licenses',
|
||||
'userloc',
|
||||
'userlog'
|
||||
)->withTrashed()->find(Auth::user()->id);
|
||||
)->find(Auth::user()->id);
|
||||
|
||||
$userlog = $user->userlog->load('item', 'user', 'target');
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (isset($user->id)) {
|
||||
return view('account/view-assets', compact('user', 'userlog'))
|
||||
return view('account/view-assets', compact('user', 'field_array' ))
|
||||
->with('settings', Setting::getSettings());
|
||||
} else {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', $user->id));
|
||||
@@ -165,7 +184,7 @@ class ViewAssetsController extends Controller
|
||||
$settings->notify(new RequestAssetCancelation($data));
|
||||
|
||||
return redirect()->route('requestable-assets')
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.cancel'));
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
|
||||
}
|
||||
|
||||
$logaction->logaction('requested');
|
||||
|
||||
@@ -43,6 +43,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\CheckForTwoFactor::class,
|
||||
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
|
||||
\App\Http\Middleware\AssetCountForSidebar::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
||||
@@ -49,7 +49,7 @@ class ItemImportRequest extends FormRequest
|
||||
$errorMessage = null;
|
||||
|
||||
if (is_null($fieldValue)) {
|
||||
$errorMessage = trans('validation.import_field_empty');
|
||||
$errorMessage = trans('validation.import_field_empty', ['fieldname' => $field]);
|
||||
$this->errorCallback($import, $field, $errorMessage);
|
||||
|
||||
return $this->errors;
|
||||
|
||||
@@ -85,6 +85,7 @@ class ActionlogsTransformer
|
||||
'id' => (int) $actionlog->item->id,
|
||||
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
|
||||
'type' => e($actionlog->itemType()),
|
||||
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
|
||||
] : null,
|
||||
'location' => ($actionlog->location) ? [
|
||||
'id' => (int) $actionlog->location->id,
|
||||
@@ -95,11 +96,11 @@ class ActionlogsTransformer
|
||||
'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(null, $actionlog->item), 'date'): null,
|
||||
'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item),
|
||||
'action_type' => $actionlog->present()->actionType(),
|
||||
'admin' => ($actionlog->user) ? [
|
||||
'id' => (int) $actionlog->user->id,
|
||||
'name' => e($actionlog->user->getFullNameAttribute()),
|
||||
'first_name'=> e($actionlog->user->first_name),
|
||||
'last_name'=> e($actionlog->user->last_name)
|
||||
'admin' => ($actionlog->admin) ? [
|
||||
'id' => (int) $actionlog->admin->id,
|
||||
'name' => e($actionlog->admin->getFullNameAttribute()),
|
||||
'first_name'=> e($actionlog->admin->first_name),
|
||||
'last_name'=> e($actionlog->admin->last_name)
|
||||
] : null,
|
||||
'target' => ($actionlog->target) ? [
|
||||
'id' => (int) $actionlog->target->id,
|
||||
|
||||
@@ -22,6 +22,22 @@ class AssetModelsTransformer
|
||||
|
||||
public function transformAssetModel(AssetModel $assetmodel)
|
||||
{
|
||||
|
||||
$default_field_values = array();
|
||||
|
||||
// Reach into the custom fields and models_custom_fields pivot table to find the default values for this model
|
||||
if ($assetmodel->fieldset) {
|
||||
foreach($assetmodel->fieldset->fields AS $field) {
|
||||
$default_field_values[] = [
|
||||
'name' => e($field->name),
|
||||
'db_column_name' => e($field->db_column_name()),
|
||||
'default_value' => ($field->defaultValue($assetmodel->id)) ? e($field->defaultValue($assetmodel->id)) : null,
|
||||
'format' => e($field->format),
|
||||
'required' => ($field->pivot->required == '1') ? true : false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$array = [
|
||||
'id' => (int) $assetmodel->id,
|
||||
'name' => e($assetmodel->name),
|
||||
@@ -44,6 +60,7 @@ class AssetModelsTransformer
|
||||
'id' => (int) $assetmodel->fieldset->id,
|
||||
'name'=> e($assetmodel->fieldset->name),
|
||||
] : null,
|
||||
'default_fieldset_values' => $default_field_values,
|
||||
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
|
||||
'requestable' => ($assetmodel->requestable == '1') ? true : false,
|
||||
'notes' => e($assetmodel->notes),
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Setting;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
|
||||
class AssetsTransformer
|
||||
{
|
||||
public function transformAssets(Collection $assets, $total)
|
||||
@@ -80,6 +81,7 @@ class AssetsTransformer
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
|
||||
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
|
||||
'age' => $asset->purchase_date ? Helper::AgeFormat($asset->purchase_date) : '',
|
||||
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
|
||||
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
|
||||
@@ -131,7 +133,7 @@ class AssetsTransformer
|
||||
$array['custom_fields'] = $fields_array;
|
||||
}
|
||||
} else {
|
||||
$array['custom_fields'] = [];
|
||||
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
|
||||
}
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
@@ -185,8 +187,11 @@ class AssetsTransformer
|
||||
'name' => e($asset->assigned->getFullNameAttribute()),
|
||||
'first_name'=> e($asset->assigned->first_name),
|
||||
'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,
|
||||
'type' => 'user',
|
||||
'eula_pdf' => e($asset->acceptanceLog->last()->filename),
|
||||
'signature_file' => e($asset->acceptanceLog->last()->accept_signature),
|
||||
] : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class ComponentsAssetsTransformer
|
||||
'created_at' => $asset->created_at->format('Y-m-d'),
|
||||
'qty' => $asset->components()->count(),
|
||||
'user_can_checkout' => $asset->availableForCheckout(),
|
||||
'note' => e($asset->note),
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
|
||||
@@ -71,6 +71,7 @@ class ComponentsTransformer
|
||||
'id' => (int) $asset->id,
|
||||
'name' => e($asset->model->present()->name).' '.e($asset->present()->name),
|
||||
'qty' => $asset->pivot->assigned_qty,
|
||||
'note' => $asset->pivot->note,
|
||||
'type' => 'asset',
|
||||
'created_at' => Helper::getFormattedDateObject($asset->pivot->created_at, 'datetime'),
|
||||
'available_actions' => ['checkin' => true],
|
||||
|
||||
@@ -47,6 +47,7 @@ class CustomFieldsTransformer
|
||||
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
|
||||
'type' => e($field->element),
|
||||
'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false,
|
||||
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
|
||||
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
|
||||
];
|
||||
|
||||
@@ -63,10 +63,16 @@ class DepreciationReportTransformer
|
||||
*/
|
||||
if (($asset->model) && ($asset->model->depreciation)) {
|
||||
$depreciated_value = Helper::formatCurrencyOutput($asset->getDepreciatedValue());
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0));
|
||||
if($asset->model->eol==0 || $asset->model->eol==null ){
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months);
|
||||
}
|
||||
else {
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0));
|
||||
}
|
||||
$diff = Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
|
||||
}
|
||||
|
||||
|
||||
if ($asset->assigned) {
|
||||
$checkout_target = $asset->assigned->name;
|
||||
if ($asset->checkedOutToUser()) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Depreciable;
|
||||
use App\Models\Depreciation;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
@@ -45,6 +45,7 @@ class LocationsTransformer
|
||||
'zip' => ($location->zip) ? e($location->zip) : null,
|
||||
'assigned_assets_count' => (int) $location->assigned_assets_count,
|
||||
'assets_count' => (int) $location->assets_count,
|
||||
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
||||
'users_count' => (int) $location->users_count,
|
||||
'currency' => ($location->currency) ? e($location->currency) : null,
|
||||
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
||||
|
||||
55
app/Http/Transformers/PieChartTransformer.php
Normal file
55
app/Http/Transformers/PieChartTransformer.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
|
||||
use App\Helpers\Helper;
|
||||
/**
|
||||
* Class PieChartTransformer
|
||||
*
|
||||
* This handles the standardized formatting of the API response we need to provide for
|
||||
* the pie charts
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @since [v6.0.11]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
class PieChartTransformer
|
||||
{
|
||||
public function transformPieChartDate($totals)
|
||||
{
|
||||
|
||||
$labels = [];
|
||||
$counts = [];
|
||||
$default_color_count = 0;
|
||||
$colors_array = [];
|
||||
|
||||
foreach ($totals as $total) {
|
||||
|
||||
if ($total['count'] > 0) {
|
||||
|
||||
$labels[] = $total['label']." (".$total['count'].")";
|
||||
$counts[] = $total['count'];
|
||||
|
||||
if (isset($total['color'])) {
|
||||
$colors_array[] = $total['color'];
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
$default_color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results = [
|
||||
'labels' => $labels,
|
||||
'datasets' => [[
|
||||
'data' => $counts,
|
||||
'backgroundColor' => $colors_array,
|
||||
'hoverBackgroundColor' => $colors_array,
|
||||
]],
|
||||
];
|
||||
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,10 @@ class UsersTransformer
|
||||
'username' => e($user->username),
|
||||
'remote' => ($user->remote == '1') ? true : false,
|
||||
'locale' => ($user->locale) ? e($user->locale) : null,
|
||||
'employee_num' => e($user->employee_num),
|
||||
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
|
||||
'manager' => ($user->manager) ? [
|
||||
'id' => (int) $user->manager->id,
|
||||
'name'=> e($user->manager->username),
|
||||
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
|
||||
] : null,
|
||||
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
|
||||
'phone' => ($user->phone) ? e($user->phone) : null,
|
||||
@@ -43,7 +43,7 @@ class UsersTransformer
|
||||
'state' => ($user->state) ? e($user->state) : null,
|
||||
'country' => ($user->country) ? e($user->country) : null,
|
||||
'zip' => ($user->zip) ? e($user->zip) : null,
|
||||
'email' => e($user->email),
|
||||
'email' => ($user->email) ? e($user->email) : null,
|
||||
'department' => ($user->department) ? [
|
||||
'id' => (int) $user->department->id,
|
||||
'name'=> e($user->department->name),
|
||||
@@ -69,13 +69,15 @@ class UsersTransformer
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
|
||||
'start_date' => Helper::getFormattedDateObject($user->start_date, 'date'),
|
||||
'end_date' => Helper::getFormattedDateObject($user->end_date, 'date'),
|
||||
'last_login' => Helper::getFormattedDateObject($user->last_login, 'datetime'),
|
||||
'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null,
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
|
||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0) && ($user->consumables_count == 0)),
|
||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
|
||||
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
|
||||
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
|
||||
];
|
||||
|
||||
@@ -43,6 +43,7 @@ class AccessoryImporter extends ItemImporter
|
||||
$this->log('No Matching Accessory, Creating a new one');
|
||||
$accessory = new Accessory();
|
||||
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
|
||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
||||
$accessory->fill($this->sanitizeItemForStoring($accessory));
|
||||
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
|
||||
@@ -55,6 +55,11 @@ class AssetImporter extends ItemImporter
|
||||
{
|
||||
$editingAsset = false;
|
||||
$asset_tag = $this->findCsvMatch($row, 'asset_tag');
|
||||
|
||||
if(empty($asset_tag)){
|
||||
$asset_tag = Asset::autoincrement_asset();
|
||||
}
|
||||
|
||||
$asset = Asset::where(['asset_tag'=> $asset_tag])->first();
|
||||
if ($asset) {
|
||||
if (! $this->updating) {
|
||||
|
||||
@@ -43,6 +43,7 @@ class ConsumableImporter extends ItemImporter
|
||||
$consumable = new Consumable();
|
||||
$this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
|
||||
$this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
|
||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
||||
$consumable->fill($this->sanitizeItemForStoring($consumable));
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
$consumable->unsetEventDispatcher();
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class Importer
|
||||
'reassignable' => 'reassignable',
|
||||
'requestable' => 'requestable',
|
||||
'seats' => 'seats',
|
||||
'serial_number' => 'serial number',
|
||||
'serial' => 'serial number',
|
||||
'status' => 'status',
|
||||
'supplier' => 'supplier',
|
||||
'termination_date' => 'termination date',
|
||||
@@ -77,6 +77,7 @@ abstract class Importer
|
||||
'manager_first_name' => 'manager first name',
|
||||
'manager_last_name' => 'manager last name',
|
||||
'min_amt' => 'minimum quantity',
|
||||
'remote' => 'remote',
|
||||
];
|
||||
/**
|
||||
* Map of item fields->csv names
|
||||
@@ -288,6 +289,7 @@ abstract class Importer
|
||||
'department_id' => '',
|
||||
'username' => $this->findCsvMatch($row, 'username'),
|
||||
'activated' => $this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')),
|
||||
'remote' => $this->fetchHumanBoolean(($this->findCsvMatch($row, 'remote'))),
|
||||
];
|
||||
|
||||
// Maybe we're lucky and the user already exists.
|
||||
|
||||
@@ -90,7 +90,7 @@ class ItemImporter extends Importer
|
||||
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
||||
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
||||
$this->item['user_id'] = $this->user_id;
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial number');
|
||||
// NO need to call this method if we're running the user import.
|
||||
// TODO: Merge these methods.
|
||||
$this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class');
|
||||
@@ -112,7 +112,7 @@ class ItemImporter extends Importer
|
||||
return $this->createOrFetchUser($row);
|
||||
}
|
||||
|
||||
if (strtolower($this->item['checkout_class']) === 'location') {
|
||||
if (strtolower($this->item['checkout_class']) === 'location' && $this->findCsvMatch($row, 'checkout_location') != null ) {
|
||||
return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location')));
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class UserImporter extends ItemImporter
|
||||
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
|
||||
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
|
||||
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
|
||||
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
|
||||
|
||||
$user_department = $this->findCsvMatch($row, 'department');
|
||||
if ($this->shouldUpdateField($user_department)) {
|
||||
|
||||
@@ -20,6 +20,8 @@ use App\Notifications\CheckoutConsumableNotification;
|
||||
use App\Notifications\CheckoutLicenseNotification;
|
||||
use App\Notifications\CheckoutLicenseSeatNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
use Log;
|
||||
|
||||
class CheckoutableListener
|
||||
{
|
||||
@@ -43,16 +45,20 @@ class CheckoutableListener
|
||||
*/
|
||||
$acceptance = $this->getCheckoutAcceptance($event);
|
||||
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
try {
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkout notification: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +89,21 @@ class CheckoutableListener
|
||||
}
|
||||
}
|
||||
|
||||
// Use default locale
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
try {
|
||||
// Use default locale
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkin notification: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
@@ -100,6 +101,23 @@ class Accessory extends SnipeModel
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessories -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> supplier relationship
|
||||
*
|
||||
@@ -299,15 +317,14 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return $Parsedown->text(e($this->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,9 @@ class Actionlog extends SnipeModel
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'company' => ['name'],
|
||||
'user' => ['first_name','last_name','username'],
|
||||
'admin' => ['first_name','last_name','username', 'email'],
|
||||
'user' => ['first_name','last_name','username', 'email'],
|
||||
'assets' => ['asset_tag','name'],
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -95,6 +97,19 @@ class Actionlog extends SnipeModel
|
||||
return $this->hasMany(\App\Models\Company::class, 'id', 'company_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> asset relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item type relationship
|
||||
*
|
||||
@@ -154,6 +169,19 @@ class Actionlog extends SnipeModel
|
||||
return $this->target();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> admin user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> user relationship
|
||||
*
|
||||
@@ -163,8 +191,8 @@ class Actionlog extends SnipeModel
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id')
|
||||
->withTrashed();
|
||||
return $this->belongsTo(User::class, 'target_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Events\AssetCheckedOut;
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Traits\UniqueSerialTrait;
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Acceptable;
|
||||
@@ -105,7 +106,6 @@ class Asset extends Depreciable
|
||||
'physical' => 'numeric|max:1|nullable',
|
||||
'checkout_date' => 'date|max:10|min:10|nullable',
|
||||
'checkin_date' => 'date|max:10|min:10|nullable',
|
||||
'supplier_id' => 'exists:suppliers,id|numeric|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'rtd_location_id' => 'exists:locations,id|nullable',
|
||||
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
|
||||
@@ -587,6 +587,24 @@ class Asset extends Depreciable
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log info for acceptances on this asset.
|
||||
* To use this as a single record, invoke it using ->last(), for
|
||||
* example, $asset->lastAcceptedLog->last().
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.14]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function acceptanceLog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'accepted')
|
||||
->orderBy('created_at', 'desc')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of checkouts for this asset
|
||||
*
|
||||
@@ -694,15 +712,15 @@ class Asset extends Depreciable
|
||||
public static function getExpiringWarrantee($days = 30)
|
||||
{
|
||||
$days = (is_null($days)) ? 30 : $days;
|
||||
|
||||
|
||||
return self::where('archived', '=', '0')
|
||||
->whereNotNull('warranty_months')
|
||||
->whereNotNull('purchase_date')
|
||||
->whereNull('deleted_at')
|
||||
->whereRaw(\DB::raw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
|
||||
->whereRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
|
||||
. $days
|
||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()'))
|
||||
->orderBy('purchase_date', 'ASC')
|
||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()')
|
||||
->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)')
|
||||
->get();
|
||||
}
|
||||
|
||||
@@ -875,13 +893,12 @@ class Asset extends Depreciable
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
|
||||
if (($this->model) && ($this->model->category)) {
|
||||
if ($this->model->category->eula_text) {
|
||||
return $Parsedown->text(e($this->model->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->model->category->eula_text);
|
||||
} elseif ($this->model->category->use_default_eula == '1') {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,10 +74,11 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'),
|
||||
trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'),
|
||||
trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
|
||||
'PAT test' => 'PAT test',
|
||||
trans('admin/asset_maintenances/general.pat_test') => trans('admin/asset_maintenances/general.pat_test'),
|
||||
trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'),
|
||||
'Software Support' => trans('admin/asset_maintenances/general.software_support'),
|
||||
'Hardware Support' => trans('admin/asset_maintenances/general.hardware_support'),
|
||||
trans('admin/asset_maintenances/general.software_support') => trans('admin/asset_maintenances/general.software_support'),
|
||||
trans('admin/asset_maintenances/general.hardware_support') => trans('admin/asset_maintenances/general.hardware_support'),
|
||||
trans('admin/asset_maintenances/general.configuration_change') => trans('admin/asset_maintenances/general.configuration_change'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use App\Helpers\Helper;
|
||||
|
||||
/**
|
||||
* Model for Categories. Categories are a higher-level group
|
||||
@@ -207,12 +208,11 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
if ($this->eula_text) {
|
||||
return $Parsedown->text(e($this->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->use_default_eula == '1')) {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,25 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class CheckoutAcceptance extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, Notifiable;
|
||||
|
||||
protected $casts = [
|
||||
'accepted_at' => 'datetime',
|
||||
'declined_at' => 'datetime',
|
||||
];
|
||||
|
||||
// Get the mail recipient from the config
|
||||
public function routeNotificationForMail(): string
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
return (config('mail.reply_to.address')) ? config('mail.reply_to.address') : '' ;
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource that was is out
|
||||
*
|
||||
|
||||
@@ -73,6 +73,10 @@ final class Company extends SnipeModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
if (Auth::user()) {
|
||||
@@ -127,6 +131,11 @@ final class Company extends SnipeModel
|
||||
return false;
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
} elseif (!$companyable instanceof Company && !\Schema::hasColumn($companyable->getModel()->getTable(), 'company_id')) {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
return true;
|
||||
} else {
|
||||
if (Auth::user()) {
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
|
||||
@@ -88,6 +88,24 @@ class Component extends SnipeModel
|
||||
'location' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the components -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the component -> location relationship
|
||||
*
|
||||
@@ -109,7 +127,7 @@ class Component extends SnipeModel
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'user_id');
|
||||
return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'user_id', 'note');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
@@ -95,6 +96,24 @@ class Consumable extends SnipeModel
|
||||
'manufacturer' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the components -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the attribute of whether or not the consumable is requestable
|
||||
*
|
||||
@@ -265,12 +284,10 @@ class Consumable extends SnipeModel
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return $Parsedown->text(e($this->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,11 @@ class CustomField extends Model
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [];
|
||||
protected $rules = [
|
||||
'name' => 'required|unique:custom_fields',
|
||||
'element' => 'required|in:text,listbox,textarea,checkbox,radio',
|
||||
'field_encrypted' => 'nullable|boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -64,6 +68,7 @@ class CustomField extends Model
|
||||
'help_text',
|
||||
'show_in_email',
|
||||
'is_unique',
|
||||
'display_in_user_view',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -338,7 +343,7 @@ class CustomField extends Model
|
||||
$id = $this->id ? $this->id : 'xx';
|
||||
|
||||
if (! function_exists('transliterator_transliterate')) {
|
||||
$long_slug = '_snipeit_'.str_slug(\Patchwork\Utf8::utf8_encode(trim($name)), '_');
|
||||
$long_slug = '_snipeit_'.str_slug(mb_convert_encoding(trim($name),"UTF-8"), '_');
|
||||
} else {
|
||||
$long_slug = '_snipeit_'.Utf8Slugger::slugify($name, '_');
|
||||
}
|
||||
@@ -356,15 +361,9 @@ class CustomField extends Model
|
||||
public function validationRules($regex_format = null)
|
||||
{
|
||||
return [
|
||||
'name' => 'required|unique:custom_fields',
|
||||
'element' => [
|
||||
'required',
|
||||
Rule::in(['text', 'listbox', 'textarea', 'checkbox', 'radio']),
|
||||
],
|
||||
'format' => [
|
||||
Rule::in(array_merge(array_keys(self::PREDEFINED_FORMATS), self::PREDEFINED_FORMATS, [$regex_format])),
|
||||
],
|
||||
'field_encrypted' => 'nullable|boolean',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class CustomFieldset extends Model
|
||||
}
|
||||
|
||||
if ($field->is_unique == '1') {
|
||||
$rule[] = 'unique';
|
||||
$rule[] = 'unique_undeleted';
|
||||
}
|
||||
|
||||
array_push($rule, $field->attributes['format']);
|
||||
|
||||
@@ -29,7 +29,7 @@ class Department extends SnipeModel
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|max:255',
|
||||
'name' => 'required|max:255|is_unique_department',
|
||||
'location_id' => 'numeric|nullable',
|
||||
'company_id' => 'numeric|nullable',
|
||||
'manager_id' => 'numeric|nullable',
|
||||
|
||||
@@ -68,21 +68,37 @@ class Depreciable extends SnipeModel
|
||||
*/
|
||||
public function getLinearDepreciatedValue() // TODO - for testing it might be nice to have an optional $relative_to param here, defaulted to 'now'
|
||||
{
|
||||
$months_remaining = $this->time_until_depreciated()->m + 12 * $this->time_until_depreciated()->y; //UGlY
|
||||
|
||||
$current_value = round(($months_remaining / $this->get_depreciation()->months) * $this->purchase_cost, 2);
|
||||
|
||||
if($this->get_depreciation()->depreciation_min > $current_value) {
|
||||
|
||||
$current_value=round($this->get_depreciation()->depreciation_min,2);
|
||||
if ($this->purchase_date) {
|
||||
$months_passed = ($this->purchase_date->diff(now())->m)+($this->purchase_date->diff(now())->y*12);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if ($current_value < 0) {
|
||||
$current_value = 0;
|
||||
|
||||
if ($months_passed >= $this->get_depreciation()->months){
|
||||
//if there is a floor use it
|
||||
if(!$this->get_depreciation()->depreciation_min == null) {
|
||||
|
||||
$current_value = $this->get_depreciation()->depreciation_min;
|
||||
|
||||
}else{
|
||||
$current_value = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The equation here is (Purchase_Cost-Floor_min)*(Months_passed/Months_til_depreciated)
|
||||
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->get_depreciation()->depreciation_min)) * ($months_passed / $this->get_depreciation()->months)), 2);
|
||||
|
||||
}
|
||||
|
||||
return $current_value;
|
||||
}
|
||||
|
||||
public function getMonthlyDepreciation(){
|
||||
|
||||
return ($this->purchase_cost-$this->get_depreciation()->depreciation_min)/$this->get_depreciation()->months;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyHalfFirstYear Boolean always applied only second half of the first year
|
||||
* @return float|int
|
||||
|
||||
@@ -169,23 +169,29 @@ class Ldap extends Model
|
||||
{
|
||||
$ldap_username = Setting::getSettings()->ldap_uname;
|
||||
|
||||
// Lets return some nicer messages for users who donked their app key, and disable LDAP
|
||||
try {
|
||||
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.');
|
||||
}
|
||||
|
||||
if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
// TODO - this just "falls off the end" but the function states that it should return true or false
|
||||
// unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire
|
||||
// so I don't want to fix this right now.
|
||||
// this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined)
|
||||
// at the next refactor, this should be appropriately modified to be more consistent.
|
||||
}
|
||||
if ( $ldap_username ) {
|
||||
// Lets return some nicer messages for users who donked their app key, and disable LDAP
|
||||
try {
|
||||
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.');
|
||||
}
|
||||
|
||||
if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
// TODO - this just "falls off the end" but the function states that it should return true or false
|
||||
// unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire
|
||||
// so I don't want to fix this right now.
|
||||
// this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined)
|
||||
// at the next refactor, this should be appropriately modified to be more consistent.
|
||||
} else {
|
||||
// LDAP should also work with anonymous bind (no dn, no password available)
|
||||
if (! $ldapbind = @ldap_bind($connection )) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and map LDAP attributes based on settings
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Carbon\Carbon;
|
||||
@@ -122,12 +123,20 @@ class License extends Depreciable
|
||||
static::created(function ($license) {
|
||||
$newSeatCount = $license->getAttributes()['seats'];
|
||||
|
||||
return static::adjustSeatCount($license, $oldSeatCount = 0, $newSeatCount);
|
||||
return static::adjustSeatCount($license, 0, $newSeatCount);
|
||||
});
|
||||
// However, we listen for updating to be able to prevent the edit if we cannot delete enough seats.
|
||||
static::updating(function ($license) {
|
||||
$newSeatCount = $license->getAttributes()['seats'];
|
||||
$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
|
||||
//$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
|
||||
/*
|
||||
That previous method *did* mostly work, but if you ever managed to get your $license->seats value out of whack
|
||||
with your actual count of license_seats *records*, you would never manage to get back 'into whack'.
|
||||
The below method actually grabs a count of existing license_seats records, so it will be more accurate.
|
||||
This means that if your license_seats are out of whack, you can change the quantity and hit 'save' and it
|
||||
will manage to 'true up' and make your counts line up correctly.
|
||||
*/
|
||||
$oldSeatCount = $license->license_seats_count;
|
||||
|
||||
return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount);
|
||||
});
|
||||
@@ -337,12 +346,11 @@ class License extends Depreciable
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return $Parsedown->text(e($this->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ($this->category->use_default_eula == '1') {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,14 @@ class Location extends SnipeModel
|
||||
'parent' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not this location can be deleted
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
@@ -98,12 +106,25 @@ class Location extends SnipeModel
|
||||
&& ($this->users()->count() === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(\App\Models\User::class, 'location_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find assets with this location as their location_id
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'location_id')
|
||||
@@ -114,6 +135,14 @@ class Location extends SnipeModel
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the asset -> rtd_location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function rtd_assets()
|
||||
{
|
||||
/* This used to have an ...->orHas() clause that referred to
|
||||
@@ -123,48 +152,93 @@ class Location extends SnipeModel
|
||||
It is arguable that we should have a '...->whereNull('assigned_to')
|
||||
bit in there, but that isn't always correct either (in the case
|
||||
where a user has no location, for example).
|
||||
|
||||
In all likelyhood, we need to denorm an "effective_location" column
|
||||
into Assets to make this slightly less miserable.
|
||||
*/
|
||||
return $this->hasMany(\App\Models\Asset::class, 'rtd_location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the consumable -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> accessory relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'location_id');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Find the parent of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id')
|
||||
->with('parent');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the manager of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manager()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'manager_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find children of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')
|
||||
->with('children');
|
||||
}
|
||||
|
||||
// I don't think we need this anymore since we de-normed location_id in assets?
|
||||
/**
|
||||
* Establishes the asset -> location assignment relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assignedAssets()
|
||||
{
|
||||
return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed();
|
||||
|
||||
@@ -189,7 +189,7 @@ trait Loggable
|
||||
$params = [
|
||||
'item' => $log->item,
|
||||
'filename' => $log->filename,
|
||||
'admin' => $log->user,
|
||||
'admin' => $log->admin,
|
||||
'location' => ($location) ? $location->name : '',
|
||||
'note' => $note,
|
||||
];
|
||||
|
||||
@@ -8,9 +8,10 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Parsedown;
|
||||
use App\Helpers\Helper;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
|
||||
/**
|
||||
* Settings model.
|
||||
*/
|
||||
@@ -20,11 +21,10 @@ class Setting extends Model
|
||||
use Notifiable, ValidatingTrait;
|
||||
|
||||
/**
|
||||
* The app settings cache key name.
|
||||
*
|
||||
* @var string
|
||||
* The cache property so that multiple invocations of this will only load the Settings record from disk only once
|
||||
* @var self
|
||||
*/
|
||||
const APP_SETTINGS_KEY = 'snipeit_app_settings';
|
||||
public static ?self $_cache = null;
|
||||
|
||||
/**
|
||||
* The setup check cache key name.
|
||||
@@ -97,14 +97,15 @@ class Setting extends Model
|
||||
*/
|
||||
public static function getSettings(): ?self
|
||||
{
|
||||
return Cache::rememberForever(self::APP_SETTINGS_KEY, function () {
|
||||
if (!self::$_cache) {
|
||||
// Need for setup as no tables exist
|
||||
try {
|
||||
return self::first();
|
||||
self::$_cache = self::first();
|
||||
} catch (\Throwable $th) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return self::$_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +136,6 @@ class Setting extends Model
|
||||
public function lar_ver(): string
|
||||
{
|
||||
$app = App::getFacadeApplication();
|
||||
|
||||
return $app::VERSION;
|
||||
}
|
||||
|
||||
@@ -147,9 +147,7 @@ class Setting extends Model
|
||||
public static function getDefaultEula(): ?string
|
||||
{
|
||||
if (self::getSettings()->default_eula_text) {
|
||||
$parsedown = new Parsedown();
|
||||
|
||||
return $parsedown->text(e(self::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(self::getSettings()->default_eula_text);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -41,6 +41,11 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
|
||||
}
|
||||
);
|
||||
|
||||
// externalId support
|
||||
$config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta
|
||||
// note that the mapping is *not* namespaced like the other $mappings
|
||||
$config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid');
|
||||
|
||||
$config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT...
|
||||
$config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here)
|
||||
|
||||
|
||||
@@ -162,6 +162,10 @@ trait Searchable
|
||||
$query->orWhere($table.'.'.$column, 'LIKE', '%'.$term.'%');
|
||||
}
|
||||
}
|
||||
// I put this here because I only want to add the concat one time in the end of the user relation search
|
||||
if($relation == 'user') {
|
||||
$query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%$term%"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'username',
|
||||
'zip',
|
||||
'remote',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'scim_externalid'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -68,6 +71,16 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'company_id' => 'integer',
|
||||
];
|
||||
|
||||
|
||||
protected $dates = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'start_date',
|
||||
'end_date',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Model validation rules
|
||||
*
|
||||
@@ -83,6 +96,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'website' => 'url|nullable|max:191',
|
||||
'manager_id' => 'nullable|exists:users,id|cant_manage_self',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'start_date' => 'nullable|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -308,7 +323,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Consumable::class, 'consumables_users', 'assigned_to', 'consumable_id')->withPivot('id','created_at')->withTrashed();
|
||||
return $this->belongsToMany(\App\Models\Consumable::class, 'consumables_users', 'assigned_to', 'consumable_id')->withPivot('id','created_at','note')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,6 +338,24 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a count of all items assigned
|
||||
*
|
||||
* @author J. Vinsmoke
|
||||
* @since [v6.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
Public function allAssignedCount() {
|
||||
$assetsCount = $this->assets()->count();
|
||||
$licensesCount = $this->licenses()->count();
|
||||
$accessoriesCount = $this->accessories()->count();
|
||||
$consumablesCount = $this->consumables()->count();
|
||||
|
||||
$totalCount = $assetsCount + $licensesCount + $accessoriesCount + $consumablesCount;
|
||||
|
||||
return (int) $totalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> actionlogs relationship
|
||||
*
|
||||
|
||||
72
app/Notifications/AcceptanceAssetAcceptedNotification.php
Normal file
72
app/Notifications/AcceptanceAssetAcceptedNotification.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AcceptanceAssetAcceptedNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
$this->item_tag = $params['item_tag'];
|
||||
$this->item_model = $params['item_model'];
|
||||
$this->item_serial = $params['item_serial'];
|
||||
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
|
||||
$this->assigned_to = $params['assigned_to'];
|
||||
$this->company_name = $params['company_name'];
|
||||
$this->settings = Setting::getSettings();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
|
||||
$notifyBy[] = 'mail';
|
||||
|
||||
return $notifyBy;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail()
|
||||
{
|
||||
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
|
||||
[
|
||||
'item_tag' => $this->item_tag,
|
||||
'item_model' => $this->item_model,
|
||||
'item_serial' => $this->item_serial,
|
||||
'accepted_date' => $this->accepted_date,
|
||||
'assigned_to' => $this->assigned_to,
|
||||
'company_name' => $this->company_name,
|
||||
'intro_text' => trans('mail.acceptance_asset_accepted'),
|
||||
])
|
||||
->subject(trans('mail.acceptance_asset_accepted'));
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Notifications/AcceptanceAssetDeclinedNotification.php
Normal file
70
app/Notifications/AcceptanceAssetDeclinedNotification.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AcceptanceAssetDeclinedNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
$this->item_tag = $params['item_tag'];
|
||||
$this->item_model = $params['item_model'];
|
||||
$this->item_serial = $params['item_serial'];
|
||||
$this->declined_date = Helper::getFormattedDateObject($params['declined_date'], 'date', false);
|
||||
$this->assigned_to = $params['assigned_to'];
|
||||
$this->company_name = $params['company_name'];
|
||||
$this->settings = Setting::getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
$notifyBy[] = 'mail';
|
||||
|
||||
return $notifyBy;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
|
||||
[
|
||||
'item_tag' => $this->item_tag,
|
||||
'item_model' => $this->item_model,
|
||||
'item_serial' => $this->item_serial,
|
||||
'declined_date' => $this->declined_date,
|
||||
'assigned_to' => $this->assigned_to,
|
||||
'company_name' => $this->company_name,
|
||||
'intro_text' => trans('mail.acceptance_asset_declined'),
|
||||
])
|
||||
->subject(trans('mail.acceptance_asset_declined'));
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@ class SettingObserver
|
||||
*/
|
||||
public function saved(Setting $setting)
|
||||
{
|
||||
Cache::forget(Setting::APP_SETTINGS_KEY);
|
||||
Cache::forget(Setting::SETUP_CHECK_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,12 @@ class LicensePolicy extends CheckoutablePermissionsPolicy
|
||||
* @param \App\Models\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function files(User $user)
|
||||
public function files(User $user, $license = null)
|
||||
{
|
||||
return $user->hasAccess($this->columnName().'.files');
|
||||
if ($user->hasAccess('licenses.files')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user