Compare commits
871 Commits
v6.0.9
...
features/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28e8db400a | ||
|
|
cde1ab241b | ||
|
|
cc1f9451da | ||
|
|
5d16f5d0d8 | ||
|
|
1d47e87d54 | ||
|
|
d2ab358c6a | ||
|
|
6a6c356cf3 | ||
|
|
262204568e | ||
|
|
3340d8ffcf | ||
|
|
dd3fcdf018 | ||
|
|
be404d34c5 | ||
|
|
79b9b097a8 | ||
|
|
1ed57d30e0 | ||
|
|
f7df2ae403 | ||
|
|
793cf4823b | ||
|
|
c0d8ff1676 | ||
|
|
b0b1c96ab5 | ||
|
|
8e00191b69 | ||
|
|
a94f19a68e | ||
|
|
0cd558593d | ||
|
|
d03e042eda | ||
|
|
1520c9c6a7 | ||
|
|
4bdb1cf552 | ||
|
|
2349f54013 | ||
|
|
2bbbc8158b | ||
|
|
2f5b4c8e1b | ||
|
|
99122ccb50 | ||
|
|
6936efd387 | ||
|
|
5714824aa7 | ||
|
|
e79818967d | ||
|
|
32ed70259e | ||
|
|
35ab4a4cce | ||
|
|
1a651b33fc | ||
|
|
73f355f3a8 | ||
|
|
b3881a43a7 | ||
|
|
c2c666aef0 | ||
|
|
5962f1b627 | ||
|
|
a4a3c03a7c | ||
|
|
e883a61a48 | ||
|
|
16fcc6dc44 | ||
|
|
49b6a1cc53 | ||
|
|
75f3cb3079 | ||
|
|
c0074baa26 | ||
|
|
4b2d35e05d | ||
|
|
c1d484b5df | ||
|
|
b0f4015bb4 | ||
|
|
dc414d3552 | ||
|
|
f9ef49f886 | ||
|
|
51f3857a31 | ||
|
|
bbf6e5e69e | ||
|
|
37ce68af5b | ||
|
|
64aea8d374 | ||
|
|
7344bbdd7f | ||
|
|
2d3318feb6 | ||
|
|
78dca7fd32 | ||
|
|
2a8851bbd7 | ||
|
|
2997de2a66 | ||
|
|
0cfeab8c50 | ||
|
|
1432403c9f | ||
|
|
67e6089805 | ||
|
|
ea4d5e69bc | ||
|
|
2da4578aa5 | ||
|
|
1a8fb5bcfe | ||
|
|
0d4ca218c5 | ||
|
|
4a6250a08c | ||
|
|
0ffd40a217 | ||
|
|
33d819fd41 | ||
|
|
90c1bfe03d | ||
|
|
3702dd87ce | ||
|
|
4d9d73483f | ||
|
|
4e5b8fa213 | ||
|
|
a8643e5d35 | ||
|
|
38f3843c23 | ||
|
|
f7ae5db4f3 | ||
|
|
de1141491a | ||
|
|
2d45ebe103 | ||
|
|
656344b1c7 | ||
|
|
7a23398c0c | ||
|
|
02ca5248b1 | ||
|
|
b1179c7b1e | ||
|
|
a4173e3881 | ||
|
|
a2a078015f | ||
|
|
cddae4869f | ||
|
|
a7a578bf51 | ||
|
|
214a1af61a | ||
|
|
ccc7b78f62 | ||
|
|
c2854f1bcb | ||
|
|
22b0b9b090 | ||
|
|
583a0412fe | ||
|
|
a16604285c | ||
|
|
37f5cfa8ce | ||
|
|
79d9cff22a | ||
|
|
cc370084d1 | ||
|
|
cd573c484d | ||
|
|
c5d3b3ab81 | ||
|
|
cfe9c687f9 | ||
|
|
91cf683f3d | ||
|
|
6b729502c6 | ||
|
|
f0b14513db | ||
|
|
ab12ad1140 | ||
|
|
3ba79600fb | ||
|
|
935f5018cc | ||
|
|
cafe8f3074 | ||
|
|
19cf170dd9 | ||
|
|
596846a94a | ||
|
|
4b51424723 | ||
|
|
dc155846ca | ||
|
|
7a9ea91be0 | ||
|
|
02dcb0fbd7 | ||
|
|
690bfbe16a | ||
|
|
cd582be851 | ||
|
|
760844de6f | ||
|
|
e6dc61d2cf | ||
|
|
35dfaddd79 | ||
|
|
d3bcc48ae5 | ||
|
|
25996cbe03 | ||
|
|
4b01909806 | ||
|
|
72295c093a | ||
|
|
cd8cb445ba | ||
|
|
2dc1182582 | ||
|
|
93ad0d02b9 | ||
|
|
48c85966d7 | ||
|
|
a0aed19f95 | ||
|
|
f77d871dff | ||
|
|
3674f46ce0 | ||
|
|
cfa342c81a | ||
|
|
563a252d18 | ||
|
|
2bdf3d3f44 | ||
|
|
60eab08735 | ||
|
|
90c1631820 | ||
|
|
dceb77d4d4 | ||
|
|
46e3e8a3dd | ||
|
|
56090bc645 | ||
|
|
fd73bee850 | ||
|
|
b59dcc7b30 | ||
|
|
69ab42b3f9 | ||
|
|
0080ac92e7 | ||
|
|
9508a97852 | ||
|
|
0fa956403c | ||
|
|
181f96ad33 | ||
|
|
04e28c2d34 | ||
|
|
f045008237 | ||
|
|
fa9116b184 | ||
|
|
155977de2d | ||
|
|
378dc968b7 | ||
|
|
c089f6eff2 | ||
|
|
e506ed7950 | ||
|
|
07744ca7bd | ||
|
|
0c39c7a53d | ||
|
|
fce620b54f | ||
|
|
33fe2b57f7 | ||
|
|
45019c65f1 | ||
|
|
acd182e3d2 | ||
|
|
48b8cfcef9 | ||
|
|
f626413502 | ||
|
|
b80d52a9c3 | ||
|
|
ee4b8f4694 | ||
|
|
ca1198ceb2 | ||
|
|
b31e000165 | ||
|
|
956bd384b0 | ||
|
|
7e22663326 | ||
|
|
c3e4d9d773 | ||
|
|
f08babfa8a | ||
|
|
4ef0005e7c | ||
|
|
dbda288a44 | ||
|
|
eaf9332096 | ||
|
|
5282ca47b3 | ||
|
|
23a1b2d60a | ||
|
|
1f12137245 | ||
|
|
6c89f08193 | ||
|
|
295f0dd1f5 | ||
|
|
1fef992fa8 | ||
|
|
e23ee7540d | ||
|
|
dd8c6e92db | ||
|
|
f4630d67be | ||
|
|
6f6f99f64e | ||
|
|
6f89e8b0d8 | ||
|
|
6815a16a5f | ||
|
|
0c3d03a4ce | ||
|
|
8048d06853 | ||
|
|
50cc69c427 | ||
|
|
021e54adaa | ||
|
|
9d0a15990e | ||
|
|
51da747809 | ||
|
|
0dd7cc9967 | ||
|
|
49f2573f36 | ||
|
|
bc80f672ee | ||
|
|
49383ddbe0 | ||
|
|
9a3a796e17 | ||
|
|
e8c2b84d24 | ||
|
|
4f8951f6f1 | ||
|
|
85b712e915 | ||
|
|
ca244911ed | ||
|
|
942681bee5 | ||
|
|
459c95064e | ||
|
|
b5ea8b8a4f | ||
|
|
aec64fa64a | ||
|
|
03938d0f32 | ||
|
|
296c9a723d | ||
|
|
2eac03db4a | ||
|
|
c5b09ed955 | ||
|
|
4ee3cbf60e | ||
|
|
39ab545cf5 | ||
|
|
e5cb68cc5e | ||
|
|
b8b2543b0d | ||
|
|
bc0f666906 | ||
|
|
581655f756 | ||
|
|
2aa50859b3 | ||
|
|
0b0fdd8aa5 | ||
|
|
8f3a237ea0 | ||
|
|
151719a91c | ||
|
|
825df2cf75 | ||
|
|
b66cd313b9 | ||
|
|
d508374c57 | ||
|
|
f50b622eb8 | ||
|
|
bef4224e14 | ||
|
|
249b188654 | ||
|
|
2170343958 | ||
|
|
17c308f3ca | ||
|
|
35136cea07 | ||
|
|
0db83ad8cb | ||
|
|
62bf7b1bce | ||
|
|
50fdf9c33b | ||
|
|
c5c37fa95f | ||
|
|
9dc016aecc | ||
|
|
1cf7784bb8 | ||
|
|
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 | ||
|
|
8596c54d6a | ||
|
|
9d627936e6 | ||
|
|
309229619b | ||
|
|
157944b774 | ||
|
|
1a908e361e |
@@ -2675,6 +2675,168 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_DATABASE=snipeit-local
|
||||
DB_USERNAME=snipeit-local
|
||||
DB_PASSWORD=snipeit-local
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
DB_PREFIX=null
|
||||
DB_DUMP_PATH='/Applications/MAMP/Library/bin'
|
||||
#DB_DUMP_PATH=
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SSL DATABASE SETTINGS
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
# --------------------------------------------
|
||||
LOG_CHANNEL=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
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
.couscous
|
||||
.DS_Store
|
||||
.env
|
||||
.env.dusk.*
|
||||
!.env.dusk.example
|
||||
.idea
|
||||
/bin/
|
||||
/bootstrap/compiled.php
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
10
README.md
10
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,7 +59,8 @@ 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
|
||||
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
@@ -79,6 +80,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
|
||||
@@ -135,6 +138,9 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<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/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!
|
||||
|
||||
75
TESTING.md
Normal file
75
TESTING.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 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
|
||||
|
||||
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
|
||||
|
||||
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
|
||||
|
||||
`cp .env.dusk.example .env.dusk.local`
|
||||
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
|
||||
|
||||
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
|
||||
|
||||
### Test Setup
|
||||
|
||||
Your application needs to be configured 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`
|
||||
|
||||
Now you are ready 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`
|
||||
|
||||
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
|
||||
|
||||
`php artisan dusk:chrome-driver --detect`
|
||||
|
||||
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.
|
||||
4
app.json
4
app.json
@@ -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;
|
||||
@@ -43,13 +44,19 @@ class LdapSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
|
||||
exit();
|
||||
}
|
||||
|
||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$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 +64,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 +183,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 +210,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 +223,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 +238,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.
|
||||
@@ -247,25 +309,25 @@ class LdapSync extends Command
|
||||
$user->activated = 0;
|
||||
} */
|
||||
$enabled_accounts = [
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
|
||||
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, 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 +346,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 +359,6 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
array_push($summary, $item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('summary')) {
|
||||
|
||||
@@ -149,7 +149,7 @@ class RestoreFromBackup extends Command
|
||||
$boring_files[] = $raw_path;
|
||||
continue;
|
||||
}
|
||||
if (@pathinfo($raw_path)['extension'] == 'sql') {
|
||||
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
||||
\Log::debug("Found a sql file!");
|
||||
$sqlfiles[] = $raw_path;
|
||||
$sqlfile_indices[] = $i;
|
||||
@@ -214,7 +214,7 @@ class RestoreFromBackup extends Command
|
||||
$env_vars['MYSQL_PWD'] = config('database.connections.mysql.password');
|
||||
// TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this)
|
||||
// we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't?
|
||||
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').'/mysql';
|
||||
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').\DIRECTORY_SEPARATOR.'mysql'.(\DIRECTORY_SEPARATOR == '\\' ? ".exe" : "");
|
||||
if( ! file_exists($mysql_binary) ) {
|
||||
return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class AccessoriesController extends Controller
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize(Accessory::class);
|
||||
|
||||
// create a new model instance
|
||||
$accessory = new Accessory();
|
||||
|
||||
@@ -82,7 +83,6 @@ class AccessoriesController extends Controller
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory created?
|
||||
@@ -127,45 +127,47 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$accessory->users_count"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory updated?
|
||||
if ($accessory->save()) {
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$min = $accessory->numCheckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory updated?
|
||||
if ($accessory->save()) {
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
}
|
||||
|
||||
@@ -217,7 +219,7 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function show($accessoryID = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryID);
|
||||
$accessory = Accessory::withCount('users as users_count')->find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories/view', compact('accessory'));
|
||||
|
||||
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]));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -220,8 +222,8 @@ class AcceptanceController extends Controller
|
||||
'item_model' => $display_model,
|
||||
'item_serial' => $item->serial,
|
||||
'eula' => $item->getEula(),
|
||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format),
|
||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
|
||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
||||
@@ -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->declined_at)->format('Y-m-d'),
|
||||
'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');
|
||||
}
|
||||
|
||||
@@ -41,10 +41,13 @@ class AccessoriesController extends Controller
|
||||
'min_amt',
|
||||
'company_id',
|
||||
'notes',
|
||||
'users_count',
|
||||
'qty',
|
||||
];
|
||||
|
||||
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier');
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessories = $accessories->TextSearch($request->input('search'));
|
||||
|
||||
@@ -100,6 +100,7 @@ class AssetsController extends Controller
|
||||
'checkout_counter',
|
||||
'checkin_counter',
|
||||
'requests_counter',
|
||||
'byod',
|
||||
];
|
||||
|
||||
$filter = [];
|
||||
@@ -120,7 +121,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 +141,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');
|
||||
}
|
||||
@@ -182,6 +190,10 @@ class AssetsController extends Controller
|
||||
$assets->ByDepreciationId($request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('byod')) {
|
||||
$assets->where('assets.byod', '=', $request->input('byod'));
|
||||
}
|
||||
|
||||
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
@@ -259,6 +271,11 @@ class AssetsController extends Controller
|
||||
// more sad, horrible workarounds for laravel bugs when doing full text searches
|
||||
$assets->where('assets.assigned_to', '>', '0');
|
||||
break;
|
||||
case 'byod':
|
||||
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
|
||||
// sidebar nav links a little less chaotic
|
||||
$assets->where('assets.byod', '=', '1');
|
||||
break;
|
||||
default:
|
||||
|
||||
if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) {
|
||||
@@ -357,19 +374,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 +415,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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -825,7 +857,8 @@ class AssetsController extends Controller
|
||||
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
|
||||
$expected_checkin = request('expected_checkin', null);
|
||||
$note = request('note', null);
|
||||
$asset_name = request('name', null);
|
||||
// Using `->has` preserves the asset name if the name parameter was not included in request.
|
||||
$asset_name = request()->has('name') ? request('name') : $asset->name;
|
||||
|
||||
// Set the location ID to the RTD location id if there is one
|
||||
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Category;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
{
|
||||
@@ -107,7 +108,7 @@ class CategoriesController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
return (new CategoriesTransformer)->transformCategory($category);
|
||||
|
||||
}
|
||||
@@ -126,8 +127,14 @@ class CategoriesController extends Controller
|
||||
{
|
||||
$this->authorize('update', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
|
||||
// Don't allow the user to change the category_type once it's been created
|
||||
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
|
||||
);
|
||||
}
|
||||
$category->fill($request->all());
|
||||
$category->category_type = strtolower($request->input('category_type'));
|
||||
$category = $request->handleImages($category);
|
||||
|
||||
if ($category->save()) {
|
||||
@@ -148,7 +155,7 @@ class CategoriesController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
|
||||
if (! $category->isDeletable()) {
|
||||
return response()->json(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ComponentsTransformer;
|
||||
use App\Http\Transformers\ConsumablesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Company;
|
||||
@@ -209,35 +210,23 @@ class ConsumablesController extends Controller
|
||||
* @param int $consumableId
|
||||
* @return array
|
||||
*/
|
||||
public function getDataView($consumableId)
|
||||
public function checkedout($consumableId)
|
||||
{
|
||||
$consumable = Consumable::with(['consumableAssignments'=> function ($query) {
|
||||
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
|
||||
},
|
||||
'consumableAssignments.admin'=> function ($query) {
|
||||
},
|
||||
'consumableAssignments.user'=> function ($query) {
|
||||
},
|
||||
])->find($consumableId);
|
||||
|
||||
$consumable = Consumable::with('users')->findOrFail($consumableId);
|
||||
if (! Company::isCurrentUserHasAccess($consumable)) {
|
||||
return ['total' => 0, 'rows' => []];
|
||||
}
|
||||
$this->authorize('view', Consumable::class);
|
||||
$rows = [];
|
||||
|
||||
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
||||
$rows[] = [
|
||||
'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() : '',
|
||||
];
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$consumables_users = $consumable->users;
|
||||
$total = $consumables_users->count();
|
||||
|
||||
if ($total < $offset) {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$consumableCount = $consumable->users->count();
|
||||
$data = ['total' => $consumableCount, 'rows' => $rows];
|
||||
|
||||
return $data;
|
||||
return (new ConsumablesTransformer)->transformCheckedoutConsumables($consumable, $consumables_users, $total);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,6 +262,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'];
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', CustomFieldset::class);
|
||||
$this->authorize('index', CustomField::class);
|
||||
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
|
||||
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
|
||||
@@ -49,7 +49,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
$fieldset->fill($request->all());
|
||||
|
||||
@@ -89,7 +89,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
$fieldset = new CustomFieldset;
|
||||
$fieldset->fill($request->all());
|
||||
|
||||
@@ -109,7 +109,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', CustomFieldset::class);
|
||||
$this->authorize('delete', CustomField::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
|
||||
$modelsCount = $fieldset->models->count();
|
||||
@@ -136,7 +136,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function fields($id)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
$set = CustomFieldset::findOrFail($id);
|
||||
$fields = $set->fields;
|
||||
|
||||
@@ -153,7 +153,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function fieldsWithDefaultValues($fieldsetId, $modelId)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
|
||||
$set = CustomFieldset::findOrFail($fieldsetId);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Import;
|
||||
use Artisan;
|
||||
use Illuminate\Database\Eloquent\JsonEncodingException;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -35,7 +36,7 @@ class ImportController extends Controller
|
||||
* Process and store a CSV upload file.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
@@ -56,7 +57,7 @@ class ImportController extends Controller
|
||||
'text/tsv', ])) {
|
||||
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
|
||||
}
|
||||
|
||||
//TODO: is there a lighter way to do this?
|
||||
@@ -64,7 +65,19 @@ class ImportController extends Controller
|
||||
ini_set('auto_detect_line_endings', '1');
|
||||
}
|
||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
|
||||
try {
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
} catch (JsonEncodingException $e) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.import.header_row_has_malformed_characters')
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
//duplicate headers check
|
||||
$duplicate_headers = [];
|
||||
@@ -82,11 +95,22 @@ class ImportController extends Controller
|
||||
}
|
||||
}
|
||||
if (count($duplicate_headers) > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)), 500); //should this be '4xx'?
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)),422);
|
||||
}
|
||||
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
try {
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
} catch (JsonEncodingException $e) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.import.content_row_has_malformed_characters')
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
$date = date('Y-m-d-his');
|
||||
$fixed_filename = str_slug($file->getClientOriginalName());
|
||||
@@ -108,12 +132,12 @@ class ImportController extends Controller
|
||||
}
|
||||
$results = (new ImportsTransformer)->transformImports($results);
|
||||
|
||||
return [
|
||||
return response()->json([
|
||||
'files' => $results,
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 500);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 422);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +151,7 @@ class ImportController extends Controller
|
||||
$this->authorize('import');
|
||||
|
||||
// Run a backup immediately before processing
|
||||
if ($request->has('run-backup')) {
|
||||
if ($request->get('run-backup')) {
|
||||
\Log::debug('Backup manually requested via importer');
|
||||
Artisan::call('backup:run');
|
||||
} else {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,22 @@ class UsersController extends Controller
|
||||
$users = $users->where('remote', '=', $request->input('remote'));
|
||||
}
|
||||
|
||||
if ($request->filled('two_factor_enrolled')) {
|
||||
$users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled'));
|
||||
}
|
||||
|
||||
if ($request->filled('two_factor_optin')) {
|
||||
$users = $users->where('two_factor_optin', '=', $request->input('two_factor_optin'));
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
@@ -196,11 +215,39 @@ class UsersController extends Controller
|
||||
default:
|
||||
$allowed_columns =
|
||||
[
|
||||
'last_name', 'first_name', 'email', 'jobtitle', 'username', 'employee_num',
|
||||
'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',
|
||||
'last_name',
|
||||
'first_name',
|
||||
'email',
|
||||
'jobtitle',
|
||||
'username',
|
||||
'employee_num',
|
||||
'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',
|
||||
'two_factor_optin',
|
||||
'two_factor_enrolled',
|
||||
'remote',
|
||||
'start_date',
|
||||
'end_date',
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -479,6 +526,27 @@ 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')));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
|
||||
return response()->json(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'));
|
||||
|
||||
@@ -146,6 +146,7 @@ class AssetsController extends Controller
|
||||
$asset->supplier_id = request('supplier_id', null);
|
||||
$asset->requestable = request('requestable', 0);
|
||||
$asset->rtd_location_id = request('rtd_location_id', null);
|
||||
$asset->byod = request('byod', 0);
|
||||
|
||||
if (! empty($settings->audit_interval)) {
|
||||
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
@@ -318,6 +319,7 @@ class AssetsController extends Controller
|
||||
// If the box isn't checked, it's not in the request at all.
|
||||
$asset->requestable = $request->filled('requestable');
|
||||
$asset->rtd_location_id = $request->input('rtd_location_id', null);
|
||||
$asset->byod = $request->input('byod', 0);
|
||||
|
||||
if ($asset->assigned_to == '') {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
|
||||
@@ -33,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
|
||||
@@ -103,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) {
|
||||
|
||||
@@ -117,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'));
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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('consumables.checkout.show', $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,22 +86,32 @@ 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()
|
||||
]);
|
||||
|
||||
|
||||
if ($request->filled('custom_format')) {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
$field->format = $request->get('custom_format');
|
||||
} else {
|
||||
$field->format = e($request->get('format'));
|
||||
$field->format = $request->get('format');
|
||||
}
|
||||
|
||||
if ($field->save()) {
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
return view('custom_fields.fieldsets.edit');
|
||||
return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$cfset = new CustomFieldset([
|
||||
'name' => e($request->get('name')),
|
||||
@@ -110,31 +110,52 @@ class CustomFieldsetsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* What the actual fuck, Brady?
|
||||
* Presents edit form for fieldset
|
||||
*
|
||||
* @todo Uhh, build this?
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $id
|
||||
* @since [v1.8]
|
||||
* @return Fuckall
|
||||
* @since [v6.0.14]
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
//
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
|
||||
}
|
||||
|
||||
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* GET IN THE SEA BRADY.
|
||||
* Saves updated fieldset data
|
||||
*
|
||||
* @todo Uhh, build this too?
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $id
|
||||
* @since [v1.8]
|
||||
* @return Fuckall
|
||||
* @since [v6.0.14]
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update($id)
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
|
||||
$fieldset->name = $request->input('name');
|
||||
|
||||
if ($fieldset->save()) {
|
||||
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +223,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function makeFieldRequired($fieldset_id, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
$fields[$field->id] = ['required' => 1];
|
||||
@@ -220,7 +241,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function makeFieldOptional($fieldset_id, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
$fields[$field->id] = ['required' => 0];
|
||||
|
||||
@@ -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;
|
||||
@@ -64,37 +67,9 @@ class ProfileController extends Controller
|
||||
$user->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
// Handle the avatar upload and/or delete if necessary
|
||||
app('\App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($request->input('avatar_delete') == 1) {
|
||||
$user->avatar = null;
|
||||
}
|
||||
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
$path = 'avatars';
|
||||
|
||||
if (! Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->makeDirectory($path, 775);
|
||||
}
|
||||
|
||||
$upload = $image = $request->file('avatar');
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = 'avatar-'.str_random(18).'.'.$ext;
|
||||
|
||||
if ($image->getClientOriginalExtension() != 'svg') {
|
||||
$upload = Image::make($image->getRealPath())->resize(84, 84);
|
||||
}
|
||||
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
|
||||
|
||||
// Remove Current image if exists
|
||||
if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) {
|
||||
Storage::disk('public')->delete($path.'/'.$user->avatar);
|
||||
}
|
||||
|
||||
$user->avatar = $file_name;
|
||||
}
|
||||
|
||||
if ($user->save()) {
|
||||
return redirect()->route('profile')->with('success', 'Account successfully updated');
|
||||
@@ -133,7 +108,7 @@ class ProfileController extends Controller
|
||||
public function password()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
|
||||
return view('account/change-password', compact('user'));
|
||||
}
|
||||
|
||||
@@ -161,7 +136,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 +161,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 +191,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');
|
||||
@@ -568,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') {
|
||||
@@ -582,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')) {
|
||||
@@ -659,10 +669,17 @@ class ReportsController extends Controller
|
||||
$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 : '';
|
||||
}
|
||||
@@ -834,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);
|
||||
}
|
||||
@@ -991,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(
|
||||
@@ -1048,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
|
||||
@@ -1075,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;
|
||||
@@ -339,6 +340,7 @@ class SettingsController extends Controller
|
||||
$setting->email_format = $request->input('email_format');
|
||||
$setting->username_format = $request->input('username_format');
|
||||
$setting->require_accept_signature = $request->input('require_accept_signature');
|
||||
$setting->display_username = $request->input('display_username', 0);
|
||||
$setting->show_assigned_assets = $request->input('show_assigned_assets', '0');
|
||||
if (! config('app.lock_passwords')) {
|
||||
$setting->login_note = $request->input('login_note');
|
||||
@@ -349,6 +351,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 +913,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 +927,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 +960,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');
|
||||
|
||||
@@ -197,6 +197,7 @@ class BulkUsersController extends Controller
|
||||
'status_id' => e(request('status_id')),
|
||||
'assigned_to' => null,
|
||||
'assigned_type' => null,
|
||||
'expected_checkin' => null,
|
||||
]);
|
||||
|
||||
|
||||
@@ -231,8 +232,14 @@ 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;
|
||||
|
||||
@@ -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');
|
||||
@@ -129,7 +131,7 @@ class UsersController extends Controller
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// we have to invoke the
|
||||
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
if ($request->filled('groups')) {
|
||||
@@ -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)
|
||||
@@ -292,7 +296,7 @@ class UsersController extends Controller
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
//\Log::debug(print_r($user, true));
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -52,6 +52,13 @@ class AssetCountForSidebar
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_byod_sidebar = Asset::where('byod', '=', '1')->count();
|
||||
view()->share('total_byod_sidebar', $total_byod_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +63,13 @@ class ImageUploadRequest extends Request
|
||||
* @param string $path location for uploaded images, defaults to uploads/plural of item type.
|
||||
* @return SnipeModel Target asset is being checked out to.
|
||||
*/
|
||||
public function handleImages($item, $w = 600, $form_fieldname = null, $path = null, $db_fieldname = 'image')
|
||||
public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
|
||||
{
|
||||
|
||||
$type = strtolower(class_basename(get_class($item)));
|
||||
|
||||
if (is_null($path)) {
|
||||
|
||||
$path = str_plural($type);
|
||||
|
||||
if ($type == 'assetmodel') {
|
||||
@@ -79,42 +81,31 @@ class ImageUploadRequest extends Request
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($form_fieldname)) {
|
||||
$form_fieldname = 'image';
|
||||
}
|
||||
|
||||
// This is dumb, but we need it for overriding field names for exceptions like avatars and logo uploads
|
||||
if (is_null($db_fieldname)) {
|
||||
$use_db_field = $form_fieldname;
|
||||
} else {
|
||||
$use_db_field = $db_fieldname;
|
||||
}
|
||||
|
||||
|
||||
// ConvertBase64ToFiles just changes object type,
|
||||
// as it cannot currently insert files to $this->files
|
||||
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
|
||||
$image=$this->offsetGet($form_fieldname);
|
||||
$image = $this->offsetGet($form_fieldname);
|
||||
\Log::debug('Image is an instance of UploadedFile');
|
||||
} elseif ($this->hasFile($form_fieldname)) {
|
||||
$image = $this->file($form_fieldname);
|
||||
\Log::debug('Just use regular upload for '.$form_fieldname);
|
||||
} else {
|
||||
if ($this->hasFile($form_fieldname)) {
|
||||
$image = $this->file($form_fieldname);
|
||||
}
|
||||
\Log::debug('No image found for form fieldname: '.$form_fieldname);
|
||||
}
|
||||
|
||||
if (isset($image)) {
|
||||
\Log::debug($image);
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.str_random(10).'.'.$ext;
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
|
||||
|
||||
\Log::info('File name will be: '.$file_name);
|
||||
\Log::debug('File extension is: '.$ext);
|
||||
|
||||
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
|
||||
|
||||
\Log::debug('Not an SVG or webp - resize');
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
|
||||
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
@@ -122,6 +113,7 @@ class ImageUploadRequest extends Request
|
||||
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
|
||||
|
||||
} else {
|
||||
// If the file is a webp, we need to just move it since webp support
|
||||
// needs to be compiled into gd for resizing to be available
|
||||
@@ -146,30 +138,30 @@ class ImageUploadRequest extends Request
|
||||
}
|
||||
|
||||
// Remove Current image if exists
|
||||
if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) {
|
||||
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
|
||||
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
|
||||
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
|
||||
}
|
||||
}
|
||||
|
||||
$item->{$use_db_field} = $file_name;
|
||||
$item->{$db_fieldname} = $file_name;
|
||||
}
|
||||
|
||||
|
||||
// If the user isn't uploading anything new but wants to delete their old image, do so
|
||||
} else {
|
||||
if ($this->input('image_delete') == '1') {
|
||||
\Log::debug('Deleting image');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
|
||||
$item->{$use_db_field} = null;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
} elseif ($this->input('image_delete') == '1') {
|
||||
\Log::debug('Deleting image');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
|
||||
$item->{$db_fieldname} = null;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $item;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Setting;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -22,6 +23,7 @@ class AccessoriesTransformer
|
||||
|
||||
public function transformAccessory(Accessory $accessory)
|
||||
{
|
||||
|
||||
$array = [
|
||||
'id' => $accessory->id,
|
||||
'name' => e($accessory->name),
|
||||
@@ -38,7 +40,8 @@ class AccessoriesTransformer
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
||||
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
||||
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
||||
'remaining_qty' => $accessory->numRemaining(),
|
||||
'remaining_qty' => (int) $accessory->numRemaining(),
|
||||
'users_count' => $accessory->users_count,
|
||||
|
||||
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
||||
@@ -65,6 +68,8 @@ class AccessoriesTransformer
|
||||
|
||||
public function transformCheckedoutAccessory($accessory, $accessory_users, $total)
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
$array = [];
|
||||
|
||||
foreach ($accessory_users as $user) {
|
||||
@@ -73,7 +78,7 @@ class AccessoriesTransformer
|
||||
'assigned_pivot_id' => $user->pivot->id,
|
||||
'id' => (int) $user->id,
|
||||
'username' => e($user->username),
|
||||
'name' => e($user->getFullNameAttribute()),
|
||||
'name' => ($setting->display_username == '1') ? e($user->getCompleteNameAttribute()) : e($user->getFullNameAttribute()),
|
||||
'first_name'=> e($user->first_name),
|
||||
'last_name'=> e($user->last_name),
|
||||
'employee_number' => e($user->employee_num),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Transformers;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ActionlogsTransformer
|
||||
@@ -85,6 +86,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,
|
||||
@@ -112,9 +114,7 @@ class ActionlogsTransformer
|
||||
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
|
||||
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
|
||||
];
|
||||
//\Log::info("Clean Meta is: ".print_r($clean_meta,true));
|
||||
|
||||
//dd($array);
|
||||
return $array;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class ActionlogsTransformer
|
||||
|
||||
$array = array();
|
||||
foreach ($accessories_users as $user) {
|
||||
$array[] = (new UsersTransformer)->transformUser($user);
|
||||
$array[] = (new UsersTransformer)->transformUserCompact($user);
|
||||
}
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -34,6 +35,8 @@ class AssetsTransformer
|
||||
'id' => (int) $asset->model->id,
|
||||
'name'=> e($asset->model->name),
|
||||
] : null,
|
||||
'byod' => ($asset->byod ? true : false),
|
||||
|
||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||
'eol' => ($asset->purchase_date != '') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null,
|
||||
'status_label' => ($asset->assetstatus) ? [
|
||||
@@ -71,7 +74,7 @@ class AssetsTransformer
|
||||
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
|
||||
'qr' => ($setting->qr_code=='1') ? config('app.url').'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png' : null,
|
||||
'alt_barcode' => ($setting->alt_barcode_enabled=='1') ? config('app.url').'/uploads/barcodes/'.str_slug($setting->alt_barcode).'-'.str_slug($asset->asset_tag).'.png' : null,
|
||||
'assigned_to' => $this->transformAssignedTo($asset),
|
||||
'assigned_to' => $this->transformAssignedTo($asset, $setting),
|
||||
'warranty_months' => ($asset->warranty_months > 0) ? e($asset->warranty_months.' '.trans('admin/hardware/form.months')) : null,
|
||||
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
|
||||
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
|
||||
@@ -80,6 +83,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 +135,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'] = [
|
||||
@@ -176,18 +180,10 @@ class AssetsTransformer
|
||||
return (new DatatablesTransformer)->transformDatatables($assets);
|
||||
}
|
||||
|
||||
public function transformAssignedTo($asset)
|
||||
public function transformAssignedTo($asset, $setting)
|
||||
{
|
||||
if ($asset->checkedOutToUser()) {
|
||||
return $asset->assigned ? [
|
||||
'id' => (int) $asset->assigned->id,
|
||||
'username' => e($asset->assigned->username),
|
||||
'name' => e($asset->assigned->getFullNameAttribute()),
|
||||
'first_name'=> e($asset->assigned->first_name),
|
||||
'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null,
|
||||
'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null,
|
||||
'type' => 'user',
|
||||
] : null;
|
||||
return (new UsersTransformer)->transformUserCompact($asset->assigned);
|
||||
}
|
||||
|
||||
return $asset->assigned ? [
|
||||
|
||||
@@ -22,6 +22,26 @@ class CategoriesTransformer
|
||||
|
||||
public function transformCategory(Category $category = null)
|
||||
{
|
||||
|
||||
// We only ever use item_count for categories in this transformer, so it makes sense to keep it
|
||||
// simple and do this switch here.
|
||||
switch ($category->category_type) {
|
||||
case 'asset':
|
||||
$category->item_count = $category->assets_count;
|
||||
break;
|
||||
case 'accessory':
|
||||
$category->item_count = $category->accessories_count;
|
||||
break;
|
||||
case 'consumable':
|
||||
$category->item_count = $category->consumables_count;
|
||||
break;
|
||||
case 'component':
|
||||
$category->item_count = $category->components_count;
|
||||
break;
|
||||
default:
|
||||
$category->item_count = 0;
|
||||
}
|
||||
|
||||
if ($category) {
|
||||
$array = [
|
||||
'id' => (int) $category->id,
|
||||
@@ -33,7 +53,7 @@ class CategoriesTransformer
|
||||
'eula' => ($category->getEula()),
|
||||
'checkin_email' => ($category->checkin_email == '1'),
|
||||
'require_acceptance' => ($category->require_acceptance == '1'),
|
||||
'item_count' => (int) $category->itemCount(),
|
||||
'item_count' => (int) $category->item_count,
|
||||
'assets_count' => (int) $category->assets_count,
|
||||
'accessories_count' => (int) $category->accessories_count,
|
||||
'consumables_count' => (int) $category->consumables_count,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -60,11 +60,11 @@ class ConsumablesTransformer
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function transformCheckedoutConsumables(Collection $consumables_users, $total)
|
||||
public function transformCheckedoutConsumables(Consumable $consumable, $consumables_users, $total)
|
||||
{
|
||||
$array = [];
|
||||
foreach ($consumables_users as $user) {
|
||||
$array[] = (new UsersTransformer)->transformUser($user);
|
||||
foreach ($consumables_users as $consumables_user) {
|
||||
$array[] = (new UsersTransformer)->transformUserCompact($consumables_user);
|
||||
}
|
||||
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\Setting;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
@@ -23,19 +24,12 @@ class LicenseSeatsTransformer
|
||||
|
||||
public function transformLicenseSeat(LicenseSeat $seat, $seat_count = 0)
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
$array = [
|
||||
'id' => (int) $seat->id,
|
||||
'license_id' => (int) $seat->license->id,
|
||||
'assigned_user' => ($seat->user) ? [
|
||||
'id' => (int) $seat->user->id,
|
||||
'name'=> e($seat->user->present()->fullName),
|
||||
'department'=> ($seat->user->department) ?
|
||||
[
|
||||
'id' => (int) $seat->user->department->id,
|
||||
'name' => e($seat->user->department->name),
|
||||
|
||||
] : null,
|
||||
] : null,
|
||||
'assigned_user' => ($seat->user) ? (new UsersTransformer)->transformUserCompact($seat->user) : null,
|
||||
'assigned_asset' => ($seat->asset) ? [
|
||||
'id' => (int) $seat->asset->id,
|
||||
'name'=> e($seat->asset->present()->fullName),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -21,6 +22,7 @@ class UsersTransformer
|
||||
|
||||
public function transformUser(User $user)
|
||||
{
|
||||
|
||||
$array = [
|
||||
'id' => (int) $user->id,
|
||||
'avatar' => e($user->present()->gravatar),
|
||||
@@ -30,10 +32,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 +45,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),
|
||||
@@ -56,8 +58,8 @@ class UsersTransformer
|
||||
'permissions' => $user->decodePermissions(),
|
||||
'activated' => ($user->activated == '1') ? true : false,
|
||||
'ldap_import' => ($user->ldap_import == '1') ? true : false,
|
||||
'two_factor_activated' => ($user->two_factor_active()) ? true : false,
|
||||
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
|
||||
'two_factor_optin' => ($user->two_factor_active()) ? true : false,
|
||||
'assets_count' => (int) $user->assets_count,
|
||||
'licenses_count' => (int) $user->licenses_count,
|
||||
'accessories_count' => (int) $user->accessories_count,
|
||||
@@ -69,13 +71,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 != '')),
|
||||
];
|
||||
@@ -99,6 +103,34 @@ class UsersTransformer
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function transformUserCompact(User $user) {
|
||||
$setting = Setting::getSettings();
|
||||
return [
|
||||
'new' => 'yep', // remove me
|
||||
'id' => (int) $user->id,
|
||||
'avatar' => e($user->present()->gravatar),
|
||||
'username' => e($user->username),
|
||||
'name' => ($setting->display_username == '1') ? e($user->getCompleteNameAttribute()) : e($user->getFullNameAttribute()),
|
||||
'first_name'=> e($user->first_name),
|
||||
'last_name'=> ($user->last_name) ? e($user->last_name) : null,
|
||||
'email'=> ($user->email) ? e($user->email) : null,
|
||||
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
|
||||
'manager' => ($user->manager) ? [
|
||||
'id' => (int) $user->manager->id,
|
||||
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
|
||||
] : null,
|
||||
'department' => ($user->department) ? [
|
||||
'id' => (int) $user->department->id,
|
||||
'name'=> e($user->department->name),
|
||||
] : null,
|
||||
'location' => ($user->userloc) ? [
|
||||
'id' => (int) $user->userloc->id,
|
||||
'name'=> e($user->userloc->name),
|
||||
] : null,
|
||||
'type' => 'user',
|
||||
];
|
||||
}
|
||||
|
||||
public function transformUsersDatatable($users)
|
||||
{
|
||||
return (new DatatablesTransformer)->transformDatatables($users);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,31 +317,16 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within an accessory are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
$checkedout = $this->users->count();
|
||||
|
||||
return $checkedout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items of an accessory remain
|
||||
@@ -334,11 +337,11 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
$checkedout = $this->users->count();
|
||||
$checkedout = $this->users_count;
|
||||
$total = $this->qty;
|
||||
$remaining = $total - $checkedout;
|
||||
|
||||
return $remaining;
|
||||
return (int) $remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -94,6 +95,7 @@ class Asset extends Depreciable
|
||||
'location_id' => 'integer',
|
||||
'rtd_company_id' => 'integer',
|
||||
'supplier_id' => 'integer',
|
||||
'byod' => 'boolean',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
@@ -105,7 +107,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',
|
||||
@@ -143,6 +144,7 @@ class Asset extends Depreciable
|
||||
'requestable',
|
||||
'last_checkout',
|
||||
'expected_checkin',
|
||||
'byod',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
@@ -314,14 +316,10 @@ class Asset extends Depreciable
|
||||
}
|
||||
|
||||
$this->last_checkout = $checkout_at;
|
||||
$this->name = $name;
|
||||
|
||||
$this->assignedTo()->associate($target);
|
||||
|
||||
|
||||
if ($name != null) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
if ($location != null) {
|
||||
$this->location_id = $location;
|
||||
} else {
|
||||
@@ -694,15 +692,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 +873,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,7 +74,7 @@ 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'),
|
||||
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'),
|
||||
|
||||
@@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Model for Categories. Categories are a higher-level group
|
||||
@@ -96,6 +98,7 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->itemCount() == 0);
|
||||
}
|
||||
@@ -149,7 +152,10 @@ class Category extends SnipeModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the category
|
||||
* Get the number of items in the category. This should NEVER be used in
|
||||
* a collection of categories, as you'll end up with an n+1 query problem.
|
||||
*
|
||||
* It should only be used in a single category context.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
@@ -157,6 +163,11 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function itemCount()
|
||||
{
|
||||
|
||||
if (isset($this->{Str::plural($this->category_type).'_count'})) {
|
||||
return $this->{Str::plural($this->category_type).'_count'};
|
||||
}
|
||||
|
||||
switch ($this->category_type) {
|
||||
case 'asset':
|
||||
return $this->assets()->count();
|
||||
@@ -168,9 +179,10 @@ class Category extends SnipeModel
|
||||
return $this->consumables()->count();
|
||||
case 'license':
|
||||
return $this->licenses()->count();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return '0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,12 +219,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',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user