Compare commits
1367 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd805638e | ||
|
|
63629abb93 | ||
|
|
6373ef3283 | ||
|
|
00a7c1e9e2 | ||
|
|
f37d5d3d03 | ||
|
|
57e52f0ba4 | ||
|
|
dc8e06fc65 | ||
|
|
db3f80bb9b | ||
|
|
f5dda06c55 | ||
|
|
c3166d491a | ||
|
|
763e17f491 | ||
|
|
47b2fe571e | ||
|
|
c8f6318aba | ||
|
|
adbb3a8f31 | ||
|
|
1f142fde8a | ||
|
|
44eee019d9 | ||
|
|
f636aac2dd | ||
|
|
54fd1b993b | ||
|
|
a5731a3088 | ||
|
|
6449d0aaf9 | ||
|
|
931e2df3bd | ||
|
|
7f8eddede6 | ||
|
|
4ddab03792 | ||
|
|
ff341caf34 | ||
|
|
5754f0aa3f | ||
|
|
785bc40d9d | ||
|
|
9d50e0e8d0 | ||
|
|
9deb4204c8 | ||
|
|
1ab349a63d | ||
|
|
2e2d087639 | ||
|
|
bfadb2cea6 | ||
|
|
44eb67440a | ||
|
|
8fb97da314 | ||
|
|
12ff465cdb | ||
|
|
f89d789832 | ||
|
|
4c3b46ea88 | ||
|
|
834e0a9dd5 | ||
|
|
681c41bd18 | ||
|
|
74488ddceb | ||
|
|
19820f1b42 | ||
|
|
2a88781cd5 | ||
|
|
f96c867bd3 | ||
|
|
06f8e8620a | ||
|
|
95d907c9e9 | ||
|
|
d990152856 | ||
|
|
05609230b2 | ||
|
|
220c254093 | ||
|
|
02313ce361 | ||
|
|
e70f7c610a | ||
|
|
32f77c3285 | ||
|
|
0a639f4fcc | ||
|
|
f2b55fb641 | ||
|
|
0e443356f0 | ||
|
|
76f7f01398 | ||
|
|
992de0156b | ||
|
|
c96b5f5a85 | ||
|
|
8d2685f0f0 | ||
|
|
eb14cc7f43 | ||
|
|
3dc67cdba6 | ||
|
|
0bbe0c85d7 | ||
|
|
49415806e1 | ||
|
|
5edbb4b229 | ||
|
|
c40b8334fc | ||
|
|
0a37c9564a | ||
|
|
985193ffff | ||
|
|
721add5bc1 | ||
|
|
ff8fa6ec77 | ||
|
|
e0a6f22489 | ||
|
|
030fdd60ff | ||
|
|
fdde844ce5 | ||
|
|
d263990401 | ||
|
|
bf7a856fa6 | ||
|
|
1e062d4fc8 | ||
|
|
ca37de5e45 | ||
|
|
9ba2fd93c1 | ||
|
|
a2e177e754 | ||
|
|
5e6db0b219 | ||
|
|
b09ded2a3b | ||
|
|
124343911f | ||
|
|
462f8c791f | ||
|
|
9a224a07ba | ||
|
|
df4686bc96 | ||
|
|
b6c432a596 | ||
|
|
de9f487664 | ||
|
|
ef668317a9 | ||
|
|
cf368a4577 | ||
|
|
2e71968c04 | ||
|
|
fdb5b3baf1 | ||
|
|
c745fa095b | ||
|
|
70e6a6ced6 | ||
|
|
6772835efc | ||
|
|
fb482b0dd6 | ||
|
|
9f43d3345f | ||
|
|
6e83679528 | ||
|
|
a050aba72f | ||
|
|
0031fab0fe | ||
|
|
585bdff364 | ||
|
|
1d9741a49e | ||
|
|
9f7f1460e9 | ||
|
|
f871759753 | ||
|
|
8e17818f1e | ||
|
|
d19c6ab8e7 | ||
|
|
d14b1e3825 | ||
|
|
ba12ee9954 | ||
|
|
d8bb69533c | ||
|
|
01d3606c42 | ||
|
|
208f1db3b2 | ||
|
|
e5b02da54b | ||
|
|
d6ead5ae17 | ||
|
|
1d7d31b9ae | ||
|
|
2a817c2123 | ||
|
|
f3a7467235 | ||
|
|
2da6f9136f | ||
|
|
79549dbfb9 | ||
|
|
a48d09f37e | ||
|
|
0dc78fdea6 | ||
|
|
75a8639a20 | ||
|
|
380c6171b7 | ||
|
|
d36d6b8e07 | ||
|
|
c00a1fa21b | ||
|
|
bbcd215ea4 | ||
|
|
444f9a81da | ||
|
|
b4eee5a9b7 | ||
|
|
72f9fe444d | ||
|
|
eb423c252a | ||
|
|
382fb31670 | ||
|
|
e6ba4a423d | ||
|
|
13ed6cde67 | ||
|
|
cac78cdbf3 | ||
|
|
8b67326e95 | ||
|
|
f65bc5caee | ||
|
|
0329028e2c | ||
|
|
d3d96c8285 | ||
|
|
5909860c5a | ||
|
|
1023fa3edd | ||
|
|
65e6d56f1f | ||
|
|
bf34385c3e | ||
|
|
55a526a6b3 | ||
|
|
bbf7fbcff4 | ||
|
|
4a5cb94d94 | ||
|
|
cb184a9687 | ||
|
|
fb37dbed92 | ||
|
|
e410696a36 | ||
|
|
45bfec5cd3 | ||
|
|
055522510b | ||
|
|
f1d0d1bfe7 | ||
|
|
c0aa6c153e | ||
|
|
da3451bf0d | ||
|
|
5f76e03616 | ||
|
|
84710eac98 | ||
|
|
81bf41a091 | ||
|
|
134acf3b87 | ||
|
|
82d8b2ab82 | ||
|
|
adc0d3a6ac | ||
|
|
8b15841c4b | ||
|
|
fd4ee60276 | ||
|
|
93358b5872 | ||
|
|
1c4e20c712 | ||
|
|
0e1f6a3fd1 | ||
|
|
71d0e6369e | ||
|
|
b27aeb1952 | ||
|
|
bec2b170ec | ||
|
|
269d3fe509 | ||
|
|
34d5473553 | ||
|
|
4ac15daee7 | ||
|
|
5f3a1f6287 | ||
|
|
bfc60864dd | ||
|
|
ffa2701f89 | ||
|
|
60d269afb5 | ||
|
|
e2cb7a0242 | ||
|
|
f9b1fdc36b | ||
|
|
93cf8d4e0a | ||
|
|
e83bc03d97 | ||
|
|
b0d493ee51 | ||
|
|
4882b01787 | ||
|
|
f9dcf0783a | ||
|
|
985f3658be | ||
|
|
705dd34f3e | ||
|
|
6cf5426540 | ||
|
|
2105a1ec1d | ||
|
|
f475bdbb2d | ||
|
|
96eb623229 | ||
|
|
820a39cc90 | ||
|
|
615051cf66 | ||
|
|
bef42eb43c | ||
|
|
6f99ce2b07 | ||
|
|
76ee5a679b | ||
|
|
26e4354433 | ||
|
|
72fc03aa50 | ||
|
|
bf1f8659cb | ||
|
|
c0d7564658 | ||
|
|
74b26a349c | ||
|
|
e9bfb157bb | ||
|
|
ef957399aa | ||
|
|
973eacf6c3 | ||
|
|
02fef7049f | ||
|
|
b2660002b9 | ||
|
|
2c0b9f959b | ||
|
|
51286d2244 | ||
|
|
712363f861 | ||
|
|
0cdd83aabf | ||
|
|
98dfb9d1b5 | ||
|
|
ae5635ff97 | ||
|
|
8a38b9d018 | ||
|
|
734cb941dd | ||
|
|
c66f8c04c8 | ||
|
|
fa24799d2b | ||
|
|
57d6a7d35e | ||
|
|
9e2d402c3a | ||
|
|
551e28eec9 | ||
|
|
9e30be8a04 | ||
|
|
8457207c8f | ||
|
|
cc4f1a1485 | ||
|
|
2ea805b7ed | ||
|
|
20ef74db42 | ||
|
|
3dda4c9116 | ||
|
|
e805feff9e | ||
|
|
0286cf6d46 | ||
|
|
6ebdfdbabb | ||
|
|
68487e1200 | ||
|
|
f19b9a44fc | ||
|
|
124af6ac6b | ||
|
|
7f126969d0 | ||
|
|
e4b0e9673d | ||
|
|
7ab44ca963 | ||
|
|
4898b58bdb | ||
|
|
9b1e7ba8a6 | ||
|
|
e3d2369151 | ||
|
|
e5f0a12c25 | ||
|
|
de6f3f866f | ||
|
|
8444a60bc9 | ||
|
|
3237411b5d | ||
|
|
5ba1659563 | ||
|
|
6e068ec339 | ||
|
|
4fd666716f | ||
|
|
6eb860ca24 | ||
|
|
903698a7b0 | ||
|
|
513faf2db5 | ||
|
|
9e46bc6c28 | ||
|
|
402fecd408 | ||
|
|
a97b15ec96 | ||
|
|
8504c9e8b9 | ||
|
|
4ebfd6624c | ||
|
|
fbaf6e2494 | ||
|
|
612d3f9b2a | ||
|
|
6e4ab5cd96 | ||
|
|
849966d2c5 | ||
|
|
1f5bcf2475 | ||
|
|
58c476195b | ||
|
|
dcd98a7bf1 | ||
|
|
686ab681e5 | ||
|
|
052f8e2c42 | ||
|
|
e9578ba8a1 | ||
|
|
0c0de5e351 | ||
|
|
10cadecd14 | ||
|
|
644084658a | ||
|
|
07936ea901 | ||
|
|
08784f9cc5 | ||
|
|
a87e615e7f | ||
|
|
df5cc7525e | ||
|
|
75b8c3455c | ||
|
|
81d38a0ded | ||
|
|
b2a8af2fa9 | ||
|
|
9d2363741e | ||
|
|
fc6a33ad38 | ||
|
|
896ce3456e | ||
|
|
9db191f0b2 | ||
|
|
c7d752fb65 | ||
|
|
5026177161 | ||
|
|
d2805442ad | ||
|
|
7765c87387 | ||
|
|
6dccf399a5 | ||
|
|
7432e3fb2d | ||
|
|
caeea9f530 | ||
|
|
0fdfd013e7 | ||
|
|
d537fc5c32 | ||
|
|
9164dda64f | ||
|
|
5ea9c31eab | ||
|
|
c8572deb5c | ||
|
|
57d25ebb20 | ||
|
|
be114176a2 | ||
|
|
1f9b04405c | ||
|
|
4ef11c463c | ||
|
|
5fb31a5a3f | ||
|
|
f0e04ab9e4 | ||
|
|
8a65081768 | ||
|
|
c451fde466 | ||
|
|
5098d69c05 | ||
|
|
b12de13041 | ||
|
|
72126f7d44 | ||
|
|
df000ce32f | ||
|
|
6a2e21f502 | ||
|
|
5ad6234584 | ||
|
|
cc79bb1449 | ||
|
|
c8d588871c | ||
|
|
8890372a69 | ||
|
|
80f2d749a2 | ||
|
|
7f7064c835 | ||
|
|
31d0b4a46c | ||
|
|
5759d4819e | ||
|
|
c49788dd9f | ||
|
|
3641a6d451 | ||
|
|
8c79070cd9 | ||
|
|
f8563bec94 | ||
|
|
fd7c0bc5fb | ||
|
|
7d708572fc | ||
|
|
ea68ff1284 | ||
|
|
66ccf4da03 | ||
|
|
0b4a13156f | ||
|
|
a8a3962008 | ||
|
|
e110a7b15e | ||
|
|
30d68309a9 | ||
|
|
547d1a5a93 | ||
|
|
adf64361e8 | ||
|
|
a43fb060f4 | ||
|
|
c607d89817 | ||
|
|
8c19b11e73 | ||
|
|
178ed82dc4 | ||
|
|
6757df5a2d | ||
|
|
044dfe2620 | ||
|
|
9e319e91d6 | ||
|
|
895a544d4c | ||
|
|
2aa9412565 | ||
|
|
35ab5c7df0 | ||
|
|
8a7cd87644 | ||
|
|
1cdf6f8263 | ||
|
|
b8ad930690 | ||
|
|
ec14a117b7 | ||
|
|
b7cc12a466 | ||
|
|
f4080a7aa9 | ||
|
|
5abfbdd1d2 | ||
|
|
31bbb2d035 | ||
|
|
33dca84ec7 | ||
|
|
460485d843 | ||
|
|
b59269d5e2 | ||
|
|
118931b6fb | ||
|
|
8bbf8409b1 | ||
|
|
b2101a5188 | ||
|
|
ede20523f2 | ||
|
|
89d9b830a0 | ||
|
|
d76eea53c4 | ||
|
|
63a8535de1 | ||
|
|
ef7434b7ac | ||
|
|
148b375ec2 | ||
|
|
d49bc33b54 | ||
|
|
5bff5cb8c0 | ||
|
|
c8634d85f5 | ||
|
|
a5423649f4 | ||
|
|
b026953190 | ||
|
|
3c209b29b1 | ||
|
|
c0fb87c7f9 | ||
|
|
48c3d0fd19 | ||
|
|
67f60127be | ||
|
|
4a8a98a7d3 | ||
|
|
4b8e9a962b | ||
|
|
966179290b | ||
|
|
6618e88a9a | ||
|
|
ab6d2987a4 | ||
|
|
45c140814f | ||
|
|
467ff045aa | ||
|
|
e0103a7fbd | ||
|
|
432263f09f | ||
|
|
911841d188 | ||
|
|
e8fd2a7fe8 | ||
|
|
df5972c4fa | ||
|
|
4d003ac97a | ||
|
|
9f4a212b44 | ||
|
|
12667f41b9 | ||
|
|
a8390ce4bd | ||
|
|
cfc1e1366c | ||
|
|
44e5fec707 | ||
|
|
f138d3b781 | ||
|
|
bbd7a752a0 | ||
|
|
a66a22ffb2 | ||
|
|
b29f794b35 | ||
|
|
410c99da17 | ||
|
|
c800662f0c | ||
|
|
b828985151 | ||
|
|
e6f53a53bc | ||
|
|
0c2800c7dd | ||
|
|
a8f9f5239c | ||
|
|
63bb2de4d4 | ||
|
|
c4b2ef5660 | ||
|
|
296655542d | ||
|
|
a55694da2f | ||
|
|
33bda9b6d1 | ||
|
|
42347d0f0c | ||
|
|
6554903aeb | ||
|
|
502322be4b | ||
|
|
72facffcbe | ||
|
|
001bb3cbe2 | ||
|
|
f42bd3a144 | ||
|
|
b9330cf6b1 | ||
|
|
2844800caf | ||
|
|
b7e8b9bad7 | ||
|
|
a7ecaa6ebc | ||
|
|
7937258f6e | ||
|
|
cce0739bb7 | ||
|
|
c93f4ef0d5 | ||
|
|
89e36dbc42 | ||
|
|
d317052ede | ||
|
|
8ee9e5e059 | ||
|
|
2602e4d602 | ||
|
|
f357d9fc90 | ||
|
|
0eda53c484 | ||
|
|
79a4acae1a | ||
|
|
91e0b26bbb | ||
|
|
5f82214703 | ||
|
|
d517e2fd61 | ||
|
|
28612d8b61 | ||
|
|
6ee3d0eb97 | ||
|
|
7987a4eca4 | ||
|
|
2aa8e1e76b | ||
|
|
be0e327221 | ||
|
|
9a1acced58 | ||
|
|
d74df93c48 | ||
|
|
b9a9949570 | ||
|
|
4ccba5337a | ||
|
|
8aae2b46cd | ||
|
|
d03d4deef9 | ||
|
|
21ceea0aed | ||
|
|
2219c9ccb5 | ||
|
|
bf9e53fbe7 | ||
|
|
f68580b482 | ||
|
|
aec2f2a249 | ||
|
|
831da2d6d1 | ||
|
|
f2aebe5f9a | ||
|
|
40c0ba9a95 | ||
|
|
f5a0726f98 | ||
|
|
2263dae8f4 | ||
|
|
f182ce1c89 | ||
|
|
c6dde67e86 | ||
|
|
88f03e6b55 | ||
|
|
8827d33a43 | ||
|
|
c3e8f64064 | ||
|
|
68cfbaab2e | ||
|
|
66ac5d05ad | ||
|
|
18012279f9 | ||
|
|
2abfd8da74 | ||
|
|
ec7d70cea0 | ||
|
|
6c285b0273 | ||
|
|
e65269e9c7 | ||
|
|
5d112be49a | ||
|
|
d3f5fde84a | ||
|
|
496b176d4e | ||
|
|
fd66f7aeb4 | ||
|
|
c71086d2f3 | ||
|
|
153f849ef7 | ||
|
|
fcdb945f5f | ||
|
|
377c92a290 | ||
|
|
5824ac3b28 | ||
|
|
fad0ed6d5b | ||
|
|
ec9c69323a | ||
|
|
338106734a | ||
|
|
81fcb4c7b3 | ||
|
|
5ae65d5329 | ||
|
|
d04b0e4d4b | ||
|
|
a6d0209e93 | ||
|
|
810f6a3970 | ||
|
|
1379821876 | ||
|
|
33236f16ac | ||
|
|
90dddee923 | ||
|
|
7869ffcc79 | ||
|
|
1687e4b850 | ||
|
|
4a3d8f2cc0 | ||
|
|
c19f1b77b0 | ||
|
|
132f296f03 | ||
|
|
870b097f03 | ||
|
|
3e2996a46a | ||
|
|
1090b291c3 | ||
|
|
12ae408830 | ||
|
|
a62e8413ba | ||
|
|
c1443aa6eb | ||
|
|
e80b63b5c1 | ||
|
|
6b1f1a29ef | ||
|
|
36436f7929 | ||
|
|
569905ad06 | ||
|
|
b626b7895a | ||
|
|
eebb463601 | ||
|
|
8f0b3ace92 | ||
|
|
2dc027e479 | ||
|
|
51d804c971 | ||
|
|
fa41b74856 | ||
|
|
701f35e063 | ||
|
|
a39069bc45 | ||
|
|
be4aa07d8c | ||
|
|
714ad071ca | ||
|
|
0954ff0659 | ||
|
|
33cf4896d8 | ||
|
|
c0f9a35dea | ||
|
|
8a1136d80a | ||
|
|
292370b02f | ||
|
|
d7e58a35bf | ||
|
|
aad14b4d7a | ||
|
|
01a98c38dc | ||
|
|
4dbf1f58e3 | ||
|
|
f5116d4cb5 | ||
|
|
b015cff8bd | ||
|
|
0e632cc7fb | ||
|
|
b49c5dbf06 | ||
|
|
6234c6e5fc | ||
|
|
0cd19c87bf | ||
|
|
14a10f60d9 | ||
|
|
186e229190 | ||
|
|
dafac4ceb3 | ||
|
|
98b9ee9b25 | ||
|
|
01548ba39d | ||
|
|
695774a3fb | ||
|
|
98246de4c7 | ||
|
|
da9896b59a | ||
|
|
b0e9875a50 | ||
|
|
7314bc4c4e | ||
|
|
ba70e07637 | ||
|
|
83dfa2a28c | ||
|
|
471e0fd2fc | ||
|
|
4bd140c324 | ||
|
|
0e823a962b | ||
|
|
002ca52c83 | ||
|
|
9793d75947 | ||
|
|
8331be05aa | ||
|
|
e8dfb71682 | ||
|
|
7de685a1c1 | ||
|
|
b508482441 | ||
|
|
30f8431bfb | ||
|
|
81548e581d | ||
|
|
a3e738b283 | ||
|
|
ed3afd1e2e | ||
|
|
74b96a7413 | ||
|
|
3ceff29e04 | ||
|
|
7736f12eb4 | ||
|
|
b1dda88c9d | ||
|
|
440ca686e4 | ||
|
|
4fd5358aa8 | ||
|
|
2855a9b2e8 | ||
|
|
e8befe9afd | ||
|
|
d5f8e93d7c | ||
|
|
303388e1f5 | ||
|
|
9601bf6edd | ||
|
|
3b6fb68ff1 | ||
|
|
bfd024fdfd | ||
|
|
4d6a8d48ee | ||
|
|
e464f0edf3 | ||
|
|
426897c902 | ||
|
|
cfd78fa870 | ||
|
|
501d5680ea | ||
|
|
291997c915 | ||
|
|
912401a0a0 | ||
|
|
ec5daa387b | ||
|
|
24a52eaeae | ||
|
|
e448b626e2 | ||
|
|
ac2403e8fc | ||
|
|
cf545f0ed1 | ||
|
|
52848ca8d8 | ||
|
|
a91e81fc42 | ||
|
|
e750b2b2da | ||
|
|
d3d64bd85c | ||
|
|
514e7ca072 | ||
|
|
0f22cf6807 | ||
|
|
2f435a57c1 | ||
|
|
f949a95cfb | ||
|
|
f3f7d714a6 | ||
|
|
b258f34bce | ||
|
|
3d4e1d4641 | ||
|
|
cb5104c27d | ||
|
|
5e3ccd1004 | ||
|
|
82e0b1ffd8 | ||
|
|
251ef82741 | ||
|
|
327583f055 | ||
|
|
acd15bc3e4 | ||
|
|
aabf9b6f9c | ||
|
|
10648de9af | ||
|
|
97743160ef | ||
|
|
1bea83ee5a | ||
|
|
d9e7a457d4 | ||
|
|
5f5b80acae | ||
|
|
ba06507e10 | ||
|
|
69d7000922 | ||
|
|
96abe54aad | ||
|
|
249b9b7509 | ||
|
|
25cb489b76 | ||
|
|
0eeeb24cae | ||
|
|
0503a51cae | ||
|
|
2e0f7921a8 | ||
|
|
c29ec6a100 | ||
|
|
a57ab29af6 | ||
|
|
b8ddbadd2b | ||
|
|
3e120812e3 | ||
|
|
d0124b900a | ||
|
|
3ea3e8b856 | ||
|
|
0ace7d16fb | ||
|
|
fb59405a87 | ||
|
|
3fcd83c499 | ||
|
|
9820bc6a58 | ||
|
|
ed79eacb9d | ||
|
|
408435057e | ||
|
|
7cab9f48e5 | ||
|
|
addb2bde23 | ||
|
|
a64e1be278 | ||
|
|
13216b8860 | ||
|
|
2a5cd58ad9 | ||
|
|
6a90ce523b | ||
|
|
453fc718d9 | ||
|
|
9c8ca6af21 | ||
|
|
e321aeabae | ||
|
|
8ec99ff433 | ||
|
|
f2c60d055b | ||
|
|
e1cc003cec | ||
|
|
5aaa2430b4 | ||
|
|
bc5a82e734 | ||
|
|
f83acf8378 | ||
|
|
242af2c90b | ||
|
|
fdafaf2802 | ||
|
|
9ba290e507 | ||
|
|
9f5669e70a | ||
|
|
c23e28d0df | ||
|
|
5abd85003c | ||
|
|
14da328dea | ||
|
|
6e9f36be58 | ||
|
|
67a1d2fa5f | ||
|
|
24be73f76b | ||
|
|
a86dc52695 | ||
|
|
1ff7e6b834 | ||
|
|
fa2dfc3e87 | ||
|
|
d25a05d748 | ||
|
|
139c874308 | ||
|
|
db385b5f6b | ||
|
|
f385f3e928 | ||
|
|
de21f00771 | ||
|
|
6c23e881c7 | ||
|
|
b721a977c0 | ||
|
|
53c46ac8de | ||
|
|
3cba174dfb | ||
|
|
a90d2ba077 | ||
|
|
e5dd6035b9 | ||
|
|
66686f8080 | ||
|
|
041fc20955 | ||
|
|
b97ff4e907 | ||
|
|
232e3f30aa | ||
|
|
5aeac3c9e0 | ||
|
|
0658f17359 | ||
|
|
fa291beaeb | ||
|
|
4b7f6a95e3 | ||
|
|
262a964760 | ||
|
|
19d8d7b146 | ||
|
|
09190e05ae | ||
|
|
4e0a9c7a94 | ||
|
|
6a089f86f8 | ||
|
|
502b3c50a9 | ||
|
|
2bb5ac61a9 | ||
|
|
3e62a6ea2d | ||
|
|
0238c88a3e | ||
|
|
145dfe5847 | ||
|
|
68263ff806 | ||
|
|
318da67230 | ||
|
|
9c9fc2754c | ||
|
|
8fd4529fad | ||
|
|
5b02cfade4 | ||
|
|
567a9639f4 | ||
|
|
7a433f8598 | ||
|
|
3d8823ede5 | ||
|
|
a7eb89f6b6 | ||
|
|
1267c68f28 | ||
|
|
262eb79471 | ||
|
|
7a785b4952 | ||
|
|
3c0a6292c4 | ||
|
|
8ccc1c6515 | ||
|
|
d54acd908c | ||
|
|
42fe1ea398 | ||
|
|
bb062b20a1 | ||
|
|
a93810e301 | ||
|
|
f137c1ca09 | ||
|
|
f196b5bd94 | ||
|
|
e0d9cc4e73 | ||
|
|
5b4e4c8dcd | ||
|
|
dee1f5ec35 | ||
|
|
850bfc40d4 | ||
|
|
b3fe47bfa7 | ||
|
|
1e5d40f32e | ||
|
|
f0546bf689 | ||
|
|
136df9418f | ||
|
|
7ea862787c | ||
|
|
25d6ceee6e | ||
|
|
4fef065010 | ||
|
|
fe74394ae6 | ||
|
|
7a76cb08a0 | ||
|
|
c70bd62a0f | ||
|
|
ad1db2c640 | ||
|
|
2e936a9883 | ||
|
|
3db4480bba | ||
|
|
6a80f95769 | ||
|
|
edd4797178 | ||
|
|
7f8c6fe282 | ||
|
|
8cc9e61983 | ||
|
|
c5560e466e | ||
|
|
157a2ee869 | ||
|
|
de7ee40863 | ||
|
|
a6f90cb3fc | ||
|
|
1321304720 | ||
|
|
e4888b23e2 | ||
|
|
274f82893f | ||
|
|
730632e2eb | ||
|
|
b03f8a2425 | ||
|
|
d22be8ee0e | ||
|
|
572185ac96 | ||
|
|
3d8fce50d0 | ||
|
|
6018a9c6ee | ||
|
|
c3d8024e1f | ||
|
|
c1c37d521c | ||
|
|
b2930d6069 | ||
|
|
8ecb7969df | ||
|
|
85712a1960 | ||
|
|
e9cb17394c | ||
|
|
8507bcd16b | ||
|
|
e47c44d055 | ||
|
|
b488cffc7e | ||
|
|
8bb3c01f78 | ||
|
|
82e02490fd | ||
|
|
bfba30058c | ||
|
|
debdccf5f1 | ||
|
|
441049d8ac | ||
|
|
b28a4f3108 | ||
|
|
68224757f4 | ||
|
|
358609720a | ||
|
|
42e1e9a0bd | ||
|
|
1f8c3cc670 | ||
|
|
f8d18a8eb0 | ||
|
|
c34650458f | ||
|
|
c65b8f97bb | ||
|
|
61a216a531 | ||
|
|
3e7fbb02b7 | ||
|
|
9520ccae47 | ||
|
|
05187eb27f | ||
|
|
bb50828ca0 | ||
|
|
c74b904f14 | ||
|
|
82de51f23e | ||
|
|
c2f1fd4942 | ||
|
|
412caebf69 | ||
|
|
3613da3685 | ||
|
|
b433b49469 | ||
|
|
72a1616b3c | ||
|
|
f172747495 | ||
|
|
6427f46a6b | ||
|
|
b76b97d89b | ||
|
|
6847a331a7 | ||
|
|
bea3a7b982 | ||
|
|
88d39b4287 | ||
|
|
f66ce02756 | ||
|
|
c7b300a50d | ||
|
|
44962d1eb9 | ||
|
|
e69461d797 | ||
|
|
796e693dda | ||
|
|
c9ed205bb0 | ||
|
|
b01e8720dc | ||
|
|
e9135eceb3 | ||
|
|
c50b81ff19 | ||
|
|
b5cc845216 | ||
|
|
599cf23d63 | ||
|
|
b0d848caec | ||
|
|
94e8343f63 | ||
|
|
cdd0e405be | ||
|
|
eb8c20880a | ||
|
|
af6dc0068b | ||
|
|
a160da6619 | ||
|
|
3435485a46 | ||
|
|
3a446e6396 | ||
|
|
b725e788ec | ||
|
|
fa5134603f | ||
|
|
a44d1cc0cf | ||
|
|
69a1732245 | ||
|
|
7e9e5214ef | ||
|
|
e1784394b3 | ||
|
|
edc29bf6d5 | ||
|
|
6103524ebc | ||
|
|
2db527f4d2 | ||
|
|
60851c2eb2 | ||
|
|
905fb09c5b | ||
|
|
82b6d2b31b | ||
|
|
c5e587f33b | ||
|
|
7a33e335d4 | ||
|
|
bbc2ac2f9d | ||
|
|
c02adace61 | ||
|
|
5dc64ea91b | ||
|
|
1c5486e588 | ||
|
|
05e9861b71 | ||
|
|
cff2a65f62 | ||
|
|
7a6ca5cb40 | ||
|
|
63259418b2 | ||
|
|
1d8c3a73ea | ||
|
|
e786ef8290 | ||
|
|
64b0dbdafe | ||
|
|
381d2369b6 | ||
|
|
29b05d2020 | ||
|
|
c38bbfc8bd | ||
|
|
be446eedb0 | ||
|
|
dc6e329d96 | ||
|
|
72b3228d82 | ||
|
|
f6446bae79 | ||
|
|
aaaf5f8b42 | ||
|
|
4fe32e1c9c | ||
|
|
066e5588b5 | ||
|
|
c17106d1b3 | ||
|
|
cb71bcc4af | ||
|
|
58f6b2da5a | ||
|
|
b670371e0b | ||
|
|
f532e9b462 | ||
|
|
3cb4672ffe | ||
|
|
87464e6ec0 | ||
|
|
bbeb51461f | ||
|
|
039f5da0e1 | ||
|
|
9aed12c5aa | ||
|
|
663c0f3713 | ||
|
|
dfb9e430fa | ||
|
|
03c5a42f5a | ||
|
|
c33970e3e3 | ||
|
|
0ea8125f31 | ||
|
|
841d0cdbbe | ||
|
|
46a138c64b | ||
|
|
60b271c6a8 | ||
|
|
5becb93e6c | ||
|
|
56582614b6 | ||
|
|
313cacdb71 | ||
|
|
ab36784edd | ||
|
|
159acf6788 | ||
|
|
7b7e097922 | ||
|
|
5ef40340fb | ||
|
|
aa4d0c4a6b | ||
|
|
7f53462d0f | ||
|
|
94ce8cad64 | ||
|
|
4d38c44a93 | ||
|
|
c31e150935 | ||
|
|
a5b180a9c6 | ||
|
|
a8bbb951bc | ||
|
|
df778cdb80 | ||
|
|
b55d9113d4 | ||
|
|
d7c51f1a2c | ||
|
|
b5db0b22f8 | ||
|
|
296de34e8a | ||
|
|
9291e3ada3 | ||
|
|
2a6a381bc7 | ||
|
|
6ad1f51673 | ||
|
|
3fbfb0c658 | ||
|
|
7225b53986 | ||
|
|
9a2378586c | ||
|
|
e820d4bc4d | ||
|
|
8e3e48da14 | ||
|
|
04cf8d7fbf | ||
|
|
7f8f1ff35a | ||
|
|
c635db0a76 | ||
|
|
704c696711 | ||
|
|
5065164c40 | ||
|
|
8127fdc5bc | ||
|
|
38ed73a2b4 | ||
|
|
5290c47e2a | ||
|
|
957c7f4e79 | ||
|
|
53eae6fbfd | ||
|
|
a90149940a | ||
|
|
ab05a44e0b | ||
|
|
3dcef9aac9 | ||
|
|
4c8b26f732 | ||
|
|
7b0b28aed0 | ||
|
|
0544164015 | ||
|
|
b6a188a87b | ||
|
|
60bb385cf7 | ||
|
|
47724d7268 | ||
|
|
c9f9a60354 | ||
|
|
da52511bf9 | ||
|
|
7899d2284c | ||
|
|
c5e6f06e9b | ||
|
|
6db915b7f0 | ||
|
|
756361584a | ||
|
|
a6d486ea8a | ||
|
|
f1143d701a | ||
|
|
b65fd4adba | ||
|
|
441ae69f5c | ||
|
|
1608dba7dd | ||
|
|
f721dac2ca | ||
|
|
a4629977e7 | ||
|
|
f407b86aa8 | ||
|
|
8a1f6b74e8 | ||
|
|
af1857b6ee | ||
|
|
4c61d330e6 | ||
|
|
c6039cbc1d | ||
|
|
27dcb4d27b | ||
|
|
f85ac97d8c | ||
|
|
e40a5a70a5 | ||
|
|
407445456a | ||
|
|
c6d3c031c7 | ||
|
|
a86409868e | ||
|
|
e4a298ca2a | ||
|
|
ab8792a13d | ||
|
|
00a8a2aff3 | ||
|
|
7497eaf302 | ||
|
|
7d416d1175 | ||
|
|
e9b9301efb | ||
|
|
7b194c678c | ||
|
|
4369d6adc6 | ||
|
|
69614a7db4 | ||
|
|
8154d1116c | ||
|
|
05a85c628f | ||
|
|
0f0ffd39a8 | ||
|
|
996a4cc29b | ||
|
|
0e234bac70 | ||
|
|
ab060288fa | ||
|
|
52c9e8ad96 | ||
|
|
e46cccdf90 | ||
|
|
ded1a59914 | ||
|
|
fe553aec02 | ||
|
|
37b8029d04 | ||
|
|
b1f96448af | ||
|
|
b779e274cc | ||
|
|
83257af267 | ||
|
|
109a29b5fc | ||
|
|
d01f9a701c | ||
|
|
53419b33a5 | ||
|
|
d429a1c423 | ||
|
|
fe0bda0d04 | ||
|
|
acb90c7aee | ||
|
|
bca82684a1 | ||
|
|
335e8ffaaa | ||
|
|
8d0ee9e531 | ||
|
|
cc5c7d33e7 | ||
|
|
69c61420f6 | ||
|
|
5699e021de | ||
|
|
cf92618c70 | ||
|
|
a874dbb0d0 | ||
|
|
0614ab4362 | ||
|
|
8ed268819e | ||
|
|
1d26195821 | ||
|
|
bebdbdfb87 | ||
|
|
26c0cf5d33 | ||
|
|
239f21b0d7 | ||
|
|
0b927623e8 | ||
|
|
96f069c417 | ||
|
|
60e52cc927 | ||
|
|
9f79028391 | ||
|
|
c7f48951a9 | ||
|
|
8d63533205 | ||
|
|
8c65214b1f | ||
|
|
e3e0d57f56 | ||
|
|
55f727ef28 | ||
|
|
6c71e72dcd | ||
|
|
79f4f46e3a | ||
|
|
fddf0cdd15 | ||
|
|
8efbde61c9 | ||
|
|
2c79ed5fc1 | ||
|
|
1b0d855c00 | ||
|
|
f403db274a | ||
|
|
a462e91983 | ||
|
|
7e8d337ed5 | ||
|
|
400907cc40 | ||
|
|
74f33441d3 | ||
|
|
61afdeac2e | ||
|
|
55cbbf2b00 | ||
|
|
daf6d98a3b | ||
|
|
ab1f34604e | ||
|
|
0840ab41d5 | ||
|
|
5293cb30bd | ||
|
|
0b0813b260 | ||
|
|
6f1a70ee09 | ||
|
|
3b41aa52c8 | ||
|
|
c211cc733c | ||
|
|
6696685d0b | ||
|
|
f065bd7784 | ||
|
|
de39906cae | ||
|
|
27280b8866 | ||
|
|
288cc01c41 | ||
|
|
348c13f318 | ||
|
|
e1907e30b1 | ||
|
|
16bb784e78 | ||
|
|
c47fa5ad1c | ||
|
|
d3c320e56f | ||
|
|
983a9a4e0e | ||
|
|
d0659bed35 | ||
|
|
f7e976cce9 | ||
|
|
1573e2a91f | ||
|
|
86a6322562 | ||
|
|
5f3a0bdea5 | ||
|
|
b91ba47b0e | ||
|
|
0242b3ec74 | ||
|
|
2b6675138e | ||
|
|
2624b8c827 | ||
|
|
bfc1ebc4ba | ||
|
|
6b106e48e9 | ||
|
|
18c591f993 | ||
|
|
0000f487ac | ||
|
|
c3c4ad1ae6 | ||
|
|
a4420efb7d | ||
|
|
d24851c5d8 | ||
|
|
794bc45cc0 | ||
|
|
d893837c86 | ||
|
|
230c9da1df | ||
|
|
b5aadd4b74 | ||
|
|
9183de578d | ||
|
|
cd1ef1d316 | ||
|
|
9247dc592b | ||
|
|
35898e4ca0 | ||
|
|
cd333fa93b | ||
|
|
533649f24e | ||
|
|
3b525f0009 | ||
|
|
f1fa5bdaa9 | ||
|
|
2768ea692e | ||
|
|
83c511e1c8 | ||
|
|
35e60fd151 | ||
|
|
471fcd1272 | ||
|
|
1e1d32dc85 | ||
|
|
b317fb8d83 | ||
|
|
245b3ca09f | ||
|
|
da34b82b3e | ||
|
|
8e358faebc | ||
|
|
3f7d2aebc7 | ||
|
|
5624ea14e7 | ||
|
|
3530603797 | ||
|
|
4fd469e07b | ||
|
|
59cb1e561e | ||
|
|
4fe63d2966 | ||
|
|
9ac40f705c | ||
|
|
ef8e20f66b | ||
|
|
e01e0c5596 | ||
|
|
9d21fc85bc | ||
|
|
79b41ee662 | ||
|
|
f8d9301bd4 | ||
|
|
c1a4fbee16 | ||
|
|
8c632f63b0 | ||
|
|
7d982c9ea6 | ||
|
|
ec4161a959 | ||
|
|
b326d8593b | ||
|
|
d1fe7abb18 | ||
|
|
ffc51d6db6 | ||
|
|
394e51029e | ||
|
|
4b8f9d810b | ||
|
|
89a2ce1c6c | ||
|
|
c6ad7f80a8 | ||
|
|
7e6a59bdcc | ||
|
|
8ebd9afd15 | ||
|
|
d1b9eddb34 | ||
|
|
0394dd7ee5 | ||
|
|
53c6c74a43 | ||
|
|
65c5378a10 | ||
|
|
1c6cbbf8ee | ||
|
|
10cb4ec401 | ||
|
|
5281713fd9 | ||
|
|
7982b3f237 | ||
|
|
07eead2dbf | ||
|
|
3fa5976315 | ||
|
|
66f557d436 | ||
|
|
fdd6ddf61b | ||
|
|
4129463923 | ||
|
|
310ed0f1d3 | ||
|
|
1eace04ad9 | ||
|
|
74c099f0b3 | ||
|
|
f0500e9797 | ||
|
|
3e19509a49 | ||
|
|
125938762b | ||
|
|
61c619660d | ||
|
|
1de9087427 | ||
|
|
c23cdb0e31 | ||
|
|
5e06d53d8c | ||
|
|
89a325c0ea | ||
|
|
1fdb057199 | ||
|
|
876ff2ef72 | ||
|
|
f5ba2106cd | ||
|
|
67f6df2be3 | ||
|
|
71f2440ddf | ||
|
|
426dae0310 | ||
|
|
f91fd3c91f | ||
|
|
86cc1a228d | ||
|
|
394d265b96 | ||
|
|
6a6923b1d8 | ||
|
|
28edf13457 | ||
|
|
93947b09c5 | ||
|
|
8b8ce256f5 | ||
|
|
4cc82a808f | ||
|
|
8313a069bf | ||
|
|
b38d07064b | ||
|
|
886b6dd265 | ||
|
|
3d6bd73ed3 | ||
|
|
a874cc32a8 | ||
|
|
34246ee4ef | ||
|
|
3ed2f55696 | ||
|
|
09c4dd4891 | ||
|
|
81f9f717dc | ||
|
|
fb502df089 | ||
|
|
f91e340178 | ||
|
|
f6c0e7cc9c | ||
|
|
9f73fcf308 | ||
|
|
0bbe499414 | ||
|
|
dd7db95135 | ||
|
|
79d979f47f | ||
|
|
b6daad7573 | ||
|
|
67d2953080 | ||
|
|
0d2eef5894 | ||
|
|
88b1da4260 | ||
|
|
0730685c29 | ||
|
|
ea91d59ffc | ||
|
|
ce8d47b2b4 | ||
|
|
65aef11ae3 | ||
|
|
8f22cf7c3c | ||
|
|
7835cc34c8 | ||
|
|
2cecd5e056 | ||
|
|
5da9120870 | ||
|
|
9d5cd27575 | ||
|
|
5faf8bfcd0 | ||
|
|
f0e6a5c905 | ||
|
|
a483e9365b | ||
|
|
e443a576f7 | ||
|
|
20b26effdb | ||
|
|
f1f7694835 | ||
|
|
0e66c3cb56 | ||
|
|
b3672d6365 | ||
|
|
a975117eaf | ||
|
|
465b69516d | ||
|
|
2ded2ff53a | ||
|
|
74c20e62d5 | ||
|
|
0dbce93d1b | ||
|
|
9797412d23 | ||
|
|
6b6cf06530 | ||
|
|
539c3023b9 | ||
|
|
78b6e84774 | ||
|
|
704209de9c | ||
|
|
b8f8d49927 | ||
|
|
63a2727077 | ||
|
|
5516978f2e | ||
|
|
19ee62f704 | ||
|
|
dff674b4ab | ||
|
|
73788817ff | ||
|
|
d715ecf41d | ||
|
|
6818901f7d | ||
|
|
19bc158ff4 | ||
|
|
a516e4278e | ||
|
|
c21524f240 | ||
|
|
ecb8204c3c | ||
|
|
476b58632b | ||
|
|
d4a97d0431 | ||
|
|
ade9a0c995 | ||
|
|
7e33051c7a | ||
|
|
4a448ba5f9 | ||
|
|
8e90ed00d8 | ||
|
|
81d8c1cc52 | ||
|
|
4920dced6e | ||
|
|
4a4fc38c43 | ||
|
|
907d1d0f61 | ||
|
|
6f71abd0e0 | ||
|
|
c1b0b8dee2 | ||
|
|
a6449fa16e | ||
|
|
4cc1351bdd | ||
|
|
701beeac2a | ||
|
|
6489081cd6 | ||
|
|
408f9978e9 | ||
|
|
4a7f61b554 | ||
|
|
db765e4a0b | ||
|
|
54d916fd05 | ||
|
|
8d680d071b | ||
|
|
252341fd5c | ||
|
|
c8210131b4 | ||
|
|
3843764d15 | ||
|
|
accafbf1eb | ||
|
|
ef9817e12d | ||
|
|
b5211b2dd5 | ||
|
|
0a72751b37 | ||
|
|
3c59452e33 | ||
|
|
84848fabd7 | ||
|
|
4a88e155c4 | ||
|
|
66a9421fac | ||
|
|
be609f4a8f | ||
|
|
90b8acdd3b | ||
|
|
c8ad45b11e | ||
|
|
8937edb97e | ||
|
|
80393d73ae | ||
|
|
1742867df1 | ||
|
|
ce0a40a20c | ||
|
|
e5e8c068ea | ||
|
|
366c1f81cc | ||
|
|
4975f9100c | ||
|
|
a481fa2921 | ||
|
|
79367642b1 | ||
|
|
fc8096f8dc | ||
|
|
22c57c2862 | ||
|
|
5cb6744c1b | ||
|
|
153a711de4 | ||
|
|
71f60990db | ||
|
|
32951bbc90 | ||
|
|
aee15e8af4 | ||
|
|
3016dcc616 | ||
|
|
c090a2f0b6 | ||
|
|
11f128a84f | ||
|
|
991a00b38f | ||
|
|
e80dfc886e | ||
|
|
b84eb836f4 | ||
|
|
b09afef46f | ||
|
|
ecf77e60df | ||
|
|
a5abb3e6a6 | ||
|
|
ea63936947 | ||
|
|
68a04c7a23 | ||
|
|
b64924440f | ||
|
|
e5d0f74ba7 | ||
|
|
309f102745 | ||
|
|
16e56646b8 | ||
|
|
82affd5154 | ||
|
|
73e88faa00 | ||
|
|
bcf9d2f75c | ||
|
|
f85e3edfc7 | ||
|
|
115f718cd9 | ||
|
|
9108ff8caa | ||
|
|
d5cf0f1fbd | ||
|
|
c97db3259f | ||
|
|
4b2093b485 | ||
|
|
24aaa36532 | ||
|
|
d84366c6c5 | ||
|
|
2604d3c1fd | ||
|
|
b692f67779 | ||
|
|
05b03df600 | ||
|
|
e445e201f3 | ||
|
|
bbd1d6059a | ||
|
|
86f49d34c3 | ||
|
|
007e8fbdf9 | ||
|
|
e45b4efa1a | ||
|
|
10d19be66c | ||
|
|
b903ca05f7 | ||
|
|
1dff71e4df | ||
|
|
827295a989 | ||
|
|
ff879e2018 | ||
|
|
e89b33f865 | ||
|
|
181e75adb4 | ||
|
|
827a86b2ef | ||
|
|
451f4c3320 | ||
|
|
77cdb2f409 | ||
|
|
26fd7f7e79 | ||
|
|
b80b91514d | ||
|
|
60459d07ce | ||
|
|
84cc90e427 | ||
|
|
bd40bd5a22 | ||
|
|
0e46b6fd42 | ||
|
|
161271b53a | ||
|
|
a865f79d5e | ||
|
|
e9f6cbc97f | ||
|
|
59559b1966 | ||
|
|
a8b5fbde91 | ||
|
|
0014ef054b | ||
|
|
652548957d | ||
|
|
761ea4e581 | ||
|
|
9c24d4300c | ||
|
|
39ab488d90 | ||
|
|
ec67d03a7c | ||
|
|
bfb5920677 | ||
|
|
0c02ff70f6 | ||
|
|
cf2d2ecdfc | ||
|
|
3831ee9f5a | ||
|
|
bc8fa31eb2 | ||
|
|
678ba228cb | ||
|
|
5b8cbe29e1 | ||
|
|
a77b8c2023 | ||
|
|
40dd34b5bf | ||
|
|
daba90b346 | ||
|
|
7b70afa2a1 | ||
|
|
0b8d70c7ec | ||
|
|
8d3b201a49 | ||
|
|
49ecc08398 | ||
|
|
e4f6aefdad | ||
|
|
771265113e | ||
|
|
3c23745508 | ||
|
|
f6971b8ab2 | ||
|
|
aed769c0be | ||
|
|
1543cdbc61 | ||
|
|
ce45c3af4e | ||
|
|
25097bce31 | ||
|
|
62195a805a | ||
|
|
8c96e8fd4b | ||
|
|
1bdf71b584 | ||
|
|
8648d53d25 | ||
|
|
6b05106dcb | ||
|
|
43437aac14 | ||
|
|
830a6cf67e | ||
|
|
39e6b59335 | ||
|
|
72b43b6526 | ||
|
|
e0423418d2 | ||
|
|
e24f292a1a | ||
|
|
17fc59f989 | ||
|
|
775e46288e | ||
|
|
722f032895 | ||
|
|
112a532618 | ||
|
|
ef76908fce | ||
|
|
4a71542f23 | ||
|
|
ea64abc607 | ||
|
|
f0acf47101 | ||
|
|
92a2a5ccbc | ||
|
|
2d0df24ef3 | ||
|
|
1a660911e7 | ||
|
|
84f20b6287 | ||
|
|
134bddf4c7 | ||
|
|
e368a20427 | ||
|
|
2637ce56a1 | ||
|
|
ffed306ecd | ||
|
|
f5a5d830a5 | ||
|
|
9168979d9e | ||
|
|
6f8680efa6 | ||
|
|
3f394f42c7 | ||
|
|
b2c99c88bb | ||
|
|
9a3683ce7c | ||
|
|
2fa66f4551 | ||
|
|
a4019c9f63 | ||
|
|
694166862e | ||
|
|
911c2398ef | ||
|
|
a209a92de8 | ||
|
|
462878a307 | ||
|
|
3941437ad4 | ||
|
|
664cc24481 | ||
|
|
2c38036123 | ||
|
|
6ae096a56f | ||
|
|
436bf152ec | ||
|
|
b1b5eeecba | ||
|
|
400913631c | ||
|
|
006a3adea0 | ||
|
|
c2bb3892b7 | ||
|
|
5c820dd7b8 | ||
|
|
248fcfa869 | ||
|
|
b58c77c8b8 | ||
|
|
94c79fa69a | ||
|
|
a3811f632d | ||
|
|
d8f0102204 | ||
|
|
6b013724aa | ||
|
|
8774f0cf45 | ||
|
|
8762e158c4 | ||
|
|
49d95892e3 | ||
|
|
9618878023 | ||
|
|
b267b34762 | ||
|
|
a5daee811b | ||
|
|
1fd0b14b98 | ||
|
|
ce44744dc6 | ||
|
|
c90dee4cf4 | ||
|
|
373885ebd1 | ||
|
|
a9fd9c9e59 | ||
|
|
9535c68dfe | ||
|
|
63bf71b071 | ||
|
|
2e92ef7d77 | ||
|
|
068ae5805b | ||
|
|
054de06522 | ||
|
|
9a99626b9e | ||
|
|
7a9a78ec53 | ||
|
|
684a892e8b | ||
|
|
91a9e410df | ||
|
|
e64e1207cf | ||
|
|
999c4e781d | ||
|
|
3df19d267f | ||
|
|
501f096a2c | ||
|
|
47ed328f0e | ||
|
|
4cc7ed9ec7 | ||
|
|
7af633177a | ||
|
|
86c1f11bec | ||
|
|
0714ac4248 | ||
|
|
e6dd90e055 | ||
|
|
b2968ea6c6 | ||
|
|
d81975e168 | ||
|
|
24c949c886 | ||
|
|
de84e1bd88 | ||
|
|
60c4fa1d90 | ||
|
|
3d3ed6ab03 | ||
|
|
14735057a8 | ||
|
|
75b3c66908 | ||
|
|
52bd7e4dfb | ||
|
|
b58314493a | ||
|
|
6ebc653620 | ||
|
|
f69e34f6ca | ||
|
|
7bb50a61a7 | ||
|
|
6b31e2aca9 | ||
|
|
a55a9ca4cd | ||
|
|
e332442132 | ||
|
|
64d649be7f |
@@ -135,7 +135,8 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/10137?v=3",
|
||||
"profile": "https://github.com/ghost",
|
||||
"contributions": [
|
||||
"translation"
|
||||
"translation",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1651,6 +1652,186 @@
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jwhulette",
|
||||
"name": "Wes Hulette",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/4930051?v=4",
|
||||
"profile": "http://macfoo.wordpress.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "patrict",
|
||||
"name": "patrict",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/8134591?v=4",
|
||||
"profile": "https://github.com/patrict",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "VELIKII-DIVAN",
|
||||
"name": "Dmitriy Minaev",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2611616?v=4",
|
||||
"profile": "https://github.com/VELIKII-DIVAN",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "liquidhorse",
|
||||
"name": "liquidhorse",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/5132245?v=4",
|
||||
"profile": "https://github.com/liquidhorse",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Seldaek",
|
||||
"name": "Jordi Boggiano",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/183678?v=4",
|
||||
"profile": "https://seld.be/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "inietov",
|
||||
"name": "Ivan Nieto",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/653557?v=4",
|
||||
"profile": "https://github.com/inietov",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "benrubson",
|
||||
"name": "Ben RUBSON",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/6764151?v=4",
|
||||
"profile": "https://github.com/benrubson",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "NMathar",
|
||||
"name": "NMathar",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/8554558?v=4",
|
||||
"profile": "https://github.com/NMathar",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "smb",
|
||||
"name": "Steffen",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/139566?v=4",
|
||||
"profile": "https://github.com/smb",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Sxderp",
|
||||
"name": "Sxderp",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/6609453?v=4",
|
||||
"profile": "https://github.com/Sxderp",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fanta8897",
|
||||
"name": "fanta8897",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/4807843?v=4",
|
||||
"profile": "https://github.com/fanta8897",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andreybolonin",
|
||||
"name": "Andrey Bolonin",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/2576509?v=4",
|
||||
"profile": "https://andreybolonin.com/phpconsulting/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "shinayoshi",
|
||||
"name": "shinayoshi",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2173307?v=4",
|
||||
"profile": "http://www.shinayoshi.net/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "reuser",
|
||||
"name": "Hubert",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2130159?v=4",
|
||||
"profile": "https://github.com/reuser",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "KeenRivals",
|
||||
"name": "KeenRivals",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/6865789?v=4",
|
||||
"profile": "https://brashear.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "omyno",
|
||||
"name": "omyno",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2902513?v=4",
|
||||
"profile": "https://github.com/omyno",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jackka",
|
||||
"name": "Evgeny",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6271335?v=4",
|
||||
"profile": "https://github.com/jackka",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "colin-campbell",
|
||||
"name": "Colin Campbell",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1169963?v=4",
|
||||
"profile": "https://digitalist.se",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lubo",
|
||||
"name": "Ľubomír Kučera",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2872098?v=4",
|
||||
"profile": "https://github.com/lubo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Mezzle",
|
||||
"name": "Martin Meredith",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/570639?v=4",
|
||||
"profile": "https://www.sourceguru.net",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "timothyfarmer",
|
||||
"name": "Tim Farmer",
|
||||
@@ -1723,14 +1904,230 @@
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "johnson-yi",
|
||||
"name": "johnson-yi",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/63399474?v=4",
|
||||
"profile": "https://github.com/johnson-yi",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sanjay900",
|
||||
"name": "Sanjay Govind",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1862720?v=4",
|
||||
"profile": "https://tangentmc.net",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PeterUpfold",
|
||||
"name": "Peter Upfold",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1255375?v=4",
|
||||
"profile": "https://peter.upfold.org.uk/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jbiel",
|
||||
"name": "Jared Biel",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/961717?v=4",
|
||||
"profile": "https://github.com/jbiel",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dampfklon",
|
||||
"name": "Dampfklon",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1733625?v=4",
|
||||
"profile": "https://github.com/dampfklon",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chamilton-ccn",
|
||||
"name": "Charles Hamilton",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/52973156?v=4",
|
||||
"profile": "https://communityclosing.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "giannello",
|
||||
"name": "Giuseppe Iannello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/551789?v=4",
|
||||
"profile": "https://github.com/giannello",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PeterDaveHello",
|
||||
"name": "Peter Dave Hello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3691490?v=4",
|
||||
"profile": "https://www.peterdavehello.org/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sigmoidal",
|
||||
"name": "sigmoidal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6106332?v=4",
|
||||
"profile": "https://github.com/sigmoidal",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "phenixdotnet",
|
||||
"name": "Vincent Lainé",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2082554?v=4",
|
||||
"profile": "https://github.com/phenixdotnet",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "derlucas",
|
||||
"name": "Lucas Pleß",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1943040?v=4",
|
||||
"profile": "http://www.lucas-pless.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "iansltx",
|
||||
"name": "Ian Littman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/472804?v=4",
|
||||
"profile": "http://twitter.com/iansltx",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PauloLuna",
|
||||
"name": "João Paulo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3519029?v=4",
|
||||
"profile": "https://github.com/PauloLuna",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ThoBur",
|
||||
"name": "ThoBur",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/70443365?v=4",
|
||||
"profile": "https://github.com/ThoBur",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "alek13",
|
||||
"name": "Alexander Chibrikin",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1972329?v=4",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1972329?v=4",
|
||||
"profile": "http://phpprofi.ru/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "winstan",
|
||||
"name": "Anthony Winstanley",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/438332?v=4",
|
||||
"profile": "https://github.com/winstan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fashberg",
|
||||
"name": "Folke",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3075214?v=4",
|
||||
"profile": "https://github.com/fashberg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "benwa",
|
||||
"name": "Bennett Blodinger",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1351571?v=4",
|
||||
"profile": "https://github.com/benwa",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ncareau",
|
||||
"name": "NMC",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2974631?v=4",
|
||||
"profile": "https://nmc.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andres-baller",
|
||||
"name": "andres-baller",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/52182449?v=4",
|
||||
"profile": "https://github.com/andres-baller",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sean-borg",
|
||||
"name": "sean-borg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67109348?v=4",
|
||||
"profile": "https://github.com/sean-borg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "EDVLeer",
|
||||
"name": "EDVLeer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32170051?v=4",
|
||||
"profile": "https://github.com/EDVLeer",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Kurokat",
|
||||
"name": "Kurokat",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23075196?v=4",
|
||||
"profile": "https://github.com/Kurokat",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "koelle25",
|
||||
"name": "Kevin Köllmann",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/915514?v=4",
|
||||
"profile": "https://www.kevinkoellmann.de",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sw-mreyes",
|
||||
"name": "sw-mreyes",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/49025941?v=4",
|
||||
"profile": "https://github.com/sw-mreyes",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
.git
|
||||
.github
|
||||
.gitattributes
|
||||
.gitignore
|
||||
.dockerignore
|
||||
app/storage/logs/*
|
||||
app/storage/views/*
|
||||
vendor/*
|
||||
storage/framework/cache/*
|
||||
node_modules
|
||||
.vagrant
|
||||
.idea
|
||||
|
||||
|
||||
43
.env.example
43
.env.example
@@ -9,6 +9,12 @@ APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en
|
||||
MAX_RESULTS=500
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: UPLOADED FILE STORAGE SETTINGS
|
||||
# --------------------------------------------
|
||||
PRIVATE_FILESYSTEM_DISK=local
|
||||
PUBLIC_FILESYSTEM_DISK=local_public
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
@@ -45,7 +51,7 @@ MAIL_FROM_ADDR=you@example.com
|
||||
MAIL_FROM_NAME='Snipe-IT'
|
||||
MAIL_REPLYTO_ADDR=you@example.com
|
||||
MAIL_REPLYTO_NAME='Snipe-IT'
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com
|
||||
MAIL_AUTO_EMBED_METHOD='attachment'
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: IMAGE LIBRARY
|
||||
@@ -53,6 +59,15 @@ MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com
|
||||
# --------------------------------------------
|
||||
IMAGE_LIB=gd
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: BACKUP SETTINGS
|
||||
# --------------------------------------------
|
||||
MAIL_BACKUP_NOTIFICATION_DRIVER=null
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
|
||||
BACKUP_ENV=true
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
# --------------------------------------------
|
||||
@@ -62,6 +77,7 @@ ENCRYPT=false
|
||||
COOKIE_NAME=snipeit_session
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=40
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SECURITY HEADER SETTINGS
|
||||
@@ -95,18 +111,31 @@ MEMCACHED_HOST=null
|
||||
MEMCACHED_PORT=null
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: AWS S3 SETTINGS
|
||||
# OPTIONAL: PUBLIC S3 Settings
|
||||
# --------------------------------------------
|
||||
AWS_SECRET=null
|
||||
AWS_KEY=null
|
||||
AWS_REGION=null
|
||||
AWS_BUCKET=null
|
||||
PUBLIC_AWS_SECRET_ACCESS_KEY=null
|
||||
PUBLIC_AWS_ACCESS_KEY_ID=null
|
||||
PUBLIC_AWS_DEFAULT_REGION=null
|
||||
PUBLIC_AWS_BUCKET=null
|
||||
PUBLIC_AWS_URL=null
|
||||
PUBLIC_AWS_BUCKET_ROOT=null
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: PRIVATE S3 Settings
|
||||
# --------------------------------------------
|
||||
PRIVATE_AWS_ACCESS_KEY_ID=null
|
||||
PRIVATE_AWS_SECRET_ACCESS_KEY=null
|
||||
PRIVATE_AWS_DEFAULT_REGION=null
|
||||
PRIVATE_AWS_BUCKET=null
|
||||
PRIVATE_AWS_URL=null
|
||||
PRIVATE_AWS_BUCKET_ROOT=null
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: LOGIN THROTTLING
|
||||
# --------------------------------------------
|
||||
LOGIN_MAX_ATTEMPTS=5
|
||||
LOGIN_LOCKOUT_DURATION=60
|
||||
RESET_PASSWORD_LINK_EXPIRES=900
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
@@ -114,9 +143,7 @@ LOGIN_LOCKOUT_DURATION=60
|
||||
APP_LOG=single
|
||||
APP_LOG_MAX_FILES=10
|
||||
APP_LOCKED=false
|
||||
FILESYSTEM_DISK=local
|
||||
APP_CIPHER=AES-256-CBC
|
||||
GOOGLE_MAPS_API=
|
||||
BACKUP_ENV=true
|
||||
LDAP_MEM_LIM=500M
|
||||
LDAP_TIME_LIM=600
|
||||
|
||||
@@ -40,10 +40,12 @@ IMAGE_LIB=gd
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: AWS S3 SETTINGS
|
||||
# --------------------------------------------
|
||||
AWS_SECRET=null
|
||||
AWS_KEY=null
|
||||
AWS_REGION=null
|
||||
AWS_SECRET_ACCESS_KEY=null
|
||||
AWS_ACCESS_KEY_ID=null
|
||||
AWS_DEFAULT_REGION=null
|
||||
AWS_BUCKET=null
|
||||
AWS_BUCKET_ROOT=null
|
||||
AWS_URL=null
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# REQUIRED: BASIC APP SETTINGS
|
||||
# --------------------------------------------
|
||||
APP_ENV=testing-ci
|
||||
APP_DEBUG=true
|
||||
APP_DEBUG=false
|
||||
APP_KEY=ChangeMe
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='US/Pacific'
|
||||
@@ -40,10 +40,12 @@ IMAGE_LIB=gd
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: AWS S3 SETTINGS
|
||||
# --------------------------------------------
|
||||
AWS_SECRET=null
|
||||
AWS_KEY=null
|
||||
AWS_REGION=null
|
||||
AWS_SECRET_ACCESS_KEY=null
|
||||
AWS_ACCESS_KEY_ID=null
|
||||
AWS_DEFAULT_REGION=null
|
||||
AWS_BUCKET=null
|
||||
AWS_BUCKET_ROOT=null
|
||||
AWS_URL=null
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
* text=auto
|
||||
* text=auto
|
||||
public/js/** binary
|
||||
public/css/** binary
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -48,6 +48,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Any errors that appear in your browser's error console.
|
||||
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
|
||||
- Include any additional information you can find in `storage/logs` and your webserver's logs.
|
||||
- Include the output from `php -m` (this should display what modules you have enabled.)
|
||||
|
||||
**Additional context**
|
||||
- Is this a fresh install or an upgrade?
|
||||
|
||||
4
.github/autolabeler.yml
vendored
Normal file
4
.github/autolabeler.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*"]
|
||||
backend: ["/app", "*.php"]
|
||||
legal: ["LICENSE*", "NOTICES*"]
|
||||
config: .github
|
||||
33
.github/config.yml
vendored
Normal file
33
.github/config.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
|
||||
|
||||
# Comment to be posted to on first time issues
|
||||
newIssueWelcomeComment: |
|
||||
👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
|
||||
|
||||
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
|
||||
|
||||
# Comment to be posted to on PRs from first time contributors in your repository
|
||||
newPRWelcomeComment: |
|
||||
💖 Thanks for this pull request! 💖
|
||||
|
||||
We use [semantic commit messages](https://snipe-it.readme.io/docs/contributing-overview#section-pull-request-guidelines) to streamline the release process and easily generate changelogs between versions. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix if it doesn't have one already.
|
||||
|
||||
Examples of commit messages with semantic prefixes:
|
||||
|
||||
- `Fixed #<issue number>: don't overwrite prevent_default if default wasn't prevented`
|
||||
- `Added #<issue number>: add checkout functionality to assets`
|
||||
- `Improved Asset Checkout: use new notification method for checkout`
|
||||
|
||||
Things that will help get your PR across the finish line:
|
||||
|
||||
- Document any user-facing changes you've made.
|
||||
- Include tests when adding/changing behavior.
|
||||
- Include screenshots and animated GIFs whenever possible.
|
||||
|
||||
We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.
|
||||
|
||||
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
|
||||
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
firstPRMergeComment: >
|
||||
Congrats on merging your first pull request! 🎉🎉🎉
|
||||
7
.github/weekly-digest.yml
vendored
Normal file
7
.github/weekly-digest.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Configuration for weekly-digest - https://github.com/apps/weekly-digest
|
||||
publishDay: sun
|
||||
canPublishIssues: true
|
||||
canPublishPullRequests: true
|
||||
canPublishContributors: true
|
||||
canPublishStargazers: true
|
||||
canPublishCommits: true
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -50,4 +50,14 @@ tests/_support/_generated/*
|
||||
/storage/oauth-public.key
|
||||
|
||||
*.cache
|
||||
|
||||
.vagrant
|
||||
|
||||
\.php_cs\.dist
|
||||
|
||||
phpmd\.xml
|
||||
/public/storage
|
||||
_ide_helper.php
|
||||
.phpstorm.meta.php
|
||||
_ide_helper_models.php
|
||||
/.phplint-cache
|
||||
|
||||
10
.htaccess
10
.htaccess
@@ -5,7 +5,15 @@
|
||||
|
||||
# Make sure .env files not not browseable if in a sub-directory.
|
||||
<FilesMatch "\.env$">
|
||||
Deny from all
|
||||
# Apache 2.2
|
||||
<IfModule !authz_core_module>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
|
||||
# Apache 2.4+
|
||||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
</IfModule>
|
||||
|
||||
82
.travis.yml
82
.travis.yml
@@ -1,82 +0,0 @@
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token:
|
||||
secure: "C/bUAEpwfZB82dkzI2Nxx3PW5w/BzbKkSyCkp6YjT046jD2/QKvz6ngCFlt3tAWV11TXWFI6D8DzkMmdWOrQl3SGlPZXRD8QOvCiz0HiGMDvlxjAaPaQecGaQZdx/H4m6xTUXRNUVaYmxlMgkkFCWhAp+HZDs0iyOEVamp0Jszg="
|
||||
hosts:
|
||||
- localhost
|
||||
sudo: false
|
||||
|
||||
# see http://about.travis-ci.org/docs/user/languages/php/ for more hints
|
||||
language: php
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
# list any PHP version you want to test against
|
||||
php:
|
||||
- 7.1.2
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: 7.3
|
||||
|
||||
# execute any number of scripts before the test run, custom env's are available as variables
|
||||
before_script:
|
||||
- phpenv config-add .github/travis-memory.ini
|
||||
- phantomjs --webdriver=4444 &
|
||||
- sleep 4
|
||||
- mysql -e 'CREATE DATABASE snipeit_unit;'
|
||||
- mysql -e 'CREATE USER "travis'@'localhost";'
|
||||
- mysql -e 'GRANT ALL PRIVILEGES ON * . * TO "travis'@'localhost";'
|
||||
- mysql -e 'FLUSH PRIVILEGES;'
|
||||
- composer self-update
|
||||
- composer install -n --prefer-source
|
||||
- chmod -R 777 storage
|
||||
- php artisan migrate --env=testing-ci --database=mysql --force
|
||||
- ./vendor/bin/codecept build
|
||||
- php artisan --env=testing-ci key:generate
|
||||
- php artisan --env=testing-ci snipeit:travisci-install
|
||||
- php artisan --env=testing-ci db:seed --database=mysql --force
|
||||
- php artisan --env=testing-ci snipeit:create-admin --first_name=Alison --last_name=Foobar --email=me@example.com --username=snipe --password=password
|
||||
- php artisan --env=testing-ci passport:install
|
||||
- php artisan serve --env=testing-ci --port=8000 --host=localhost &
|
||||
- sleep 5
|
||||
- pip install --user codecov
|
||||
- sleep 5
|
||||
|
||||
|
||||
|
||||
# omitting "script:" will default to phpunit
|
||||
# use the $DB env variable to determine the phpunit.xml to use
|
||||
# script: ./vendor/bin/codecept run --env testing-ci
|
||||
script:
|
||||
- ./vendor/bin/codecept run unit
|
||||
# - ./vendor/bin/codecept run acceptance --env=testing-ci
|
||||
- ./vendor/bin/codecept run functional --env=functional-travis -g func1
|
||||
- ./vendor/bin/codecept run functional --env=functional-travis -g func2
|
||||
- ./vendor/bin/codecept run api --env=functional-travis
|
||||
|
||||
after_script:
|
||||
- vendor/bin/test-reporter
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
|
||||
after_failure:
|
||||
- cat tests/_output/*.fail.html
|
||||
- curl http://localhost:8000/login
|
||||
- cat storage/logs/laravel.log
|
||||
|
||||
# configure notifications (email, IRC, campfire etc)
|
||||
notifications:
|
||||
email: false
|
||||
slack:
|
||||
secure: vv9we1RxB9RsrMbomSdq6D7vz/okobw87pEkgIZjB+hj1QpQ2by90gsPsOa+NgsJEFaEP7e4KlT6SH8kK+zhbmuKaUd3d1//XdcancE22LZXi6tkiB5yuR/Jhhb1LLDqyGJTB4D92hMnnCPiUjpxNA3r437ttNeYRdYIEEP3drA=
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/5e136eb0c1965f3918d0
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: change # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
69
Dockerfile
69
Dockerfile
@@ -1,36 +1,59 @@
|
||||
FROM ubuntu:xenial
|
||||
LABEL maintainer="uberbrady, hinchk"
|
||||
FROM ubuntu:bionic
|
||||
LABEL maintainer Brady Wetherington <uberbrady@gmail.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y software-properties-common
|
||||
RUN LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php
|
||||
RUN apt-get update && apt-get install -y \
|
||||
RUN export DEBIAN_FRONTEND=noninteractive; \
|
||||
export DEBCONF_NONINTERACTIVE_SEEN=true; \
|
||||
echo 'tzdata tzdata/Areas select Etc' | debconf-set-selections; \
|
||||
echo 'tzdata tzdata/Zones/Etc select UTC' | debconf-set-selections; \
|
||||
apt-get update -qqy \
|
||||
&& apt-get install -qqy --no-install-recommends \
|
||||
apt-utils \
|
||||
apache2 \
|
||||
apache2-bin \
|
||||
libapache2-mod-php7.1 \
|
||||
php7.1-curl \
|
||||
php7.1-ldap \
|
||||
php7.1-mysql \
|
||||
php7.1-mcrypt \
|
||||
php7.1-gd \
|
||||
php7.1-xml \
|
||||
php7.1-mbstring \
|
||||
php7.1-zip \
|
||||
php7.1-bcmath \
|
||||
libapache2-mod-php7.2 \
|
||||
php7.2-curl \
|
||||
php7.2-ldap \
|
||||
php7.2-mysql \
|
||||
php7.2-gd \
|
||||
php7.2-xml \
|
||||
php7.2-mbstring \
|
||||
php7.2-zip \
|
||||
php7.2-bcmath \
|
||||
patch \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
git \
|
||||
cron \
|
||||
mysql-client \
|
||||
supervisor \
|
||||
cron \
|
||||
gcc \
|
||||
make \
|
||||
autoconf \
|
||||
libc-dev \
|
||||
pkg-config \
|
||||
libmcrypt-dev \
|
||||
php7.2-dev \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
|
||||
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
|
||||
RUN php go-pear.phar
|
||||
|
||||
RUN pecl install mcrypt-1.0.2
|
||||
|
||||
RUN bash -c "echo extension=/usr/lib/php/20170718/mcrypt.so > /etc/php/7.2/mods-available/mcrypt.ini"
|
||||
|
||||
RUN phpenmod mcrypt
|
||||
RUN phpenmod gd
|
||||
RUN phpenmod bcmath
|
||||
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.1/apache2/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.1/cli/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/apache2/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/cli/php.ini
|
||||
|
||||
RUN useradd -m --uid 1000 --gid 50 docker
|
||||
|
||||
@@ -41,11 +64,11 @@ COPY docker/000-default.conf /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
#SSL
|
||||
RUN mkdir -p /var/lib/snipeit/ssl
|
||||
COPY docker/001-default-ssl.conf /etc/apache2/sites-enabled/001-default-ssl.conf
|
||||
#COPY docker/001-default-ssl.conf /etc/apache2/sites-available/001-default-ssl.conf
|
||||
#COPY docker/001-default-ssl.conf /etc/apache2/sites-enabled/001-default-ssl.conf
|
||||
COPY docker/001-default-ssl.conf /etc/apache2/sites-available/001-default-ssl.conf
|
||||
|
||||
RUN a2enmod ssl
|
||||
#RUN a2ensite 001-default-ssl.conf
|
||||
RUN a2ensite 001-default-ssl.conf
|
||||
|
||||
COPY . /var/www/html
|
||||
|
||||
@@ -68,7 +91,7 @@ RUN \
|
||||
rm -r "/var/www/html/storage/private_uploads" && ln -fs "/var/lib/snipeit/data/private_uploads" "/var/www/html/storage/private_uploads" \
|
||||
&& rm -rf "/var/www/html/public/uploads" && ln -fs "/var/lib/snipeit/data/uploads" "/var/www/html/public/uploads" \
|
||||
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
|
||||
&& mkdir "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
|
||||
&& mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
|
||||
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
|
||||
&& chown docker "/var/lib/snipeit/keys/" \
|
||||
&& chmod +x /var/www/html/artisan \
|
||||
@@ -77,11 +100,11 @@ RUN \
|
||||
############## DEPENDENCIES via COMPOSER ###################
|
||||
|
||||
#global install of composer
|
||||
RUN cd /tmp;curl -sS https://getcomposer.org/installer | php;mv /tmp/composer.phar /usr/local/bin/composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Get dependencies
|
||||
USER docker
|
||||
RUN cd /var/www/html;composer install && rm -rf /home/docker/.composer/cache
|
||||
RUN composer install --no-dev --working-dir=/var/www/html
|
||||
USER root
|
||||
|
||||
############### APPLICATION INSTALL/INIT #################
|
||||
|
||||
82
Dockerfile.alpine
Normal file
82
Dockerfile.alpine
Normal file
@@ -0,0 +1,82 @@
|
||||
FROM alpine:3.12
|
||||
# Apache + PHP
|
||||
RUN apk add --no-cache \
|
||||
apache2 \
|
||||
php7 \
|
||||
php7-common \
|
||||
php7-apache2 \
|
||||
php7-curl \
|
||||
php7-ldap \
|
||||
php7-mysqli \
|
||||
php7-gd \
|
||||
php7-xml \
|
||||
php7-mbstring \
|
||||
php7-zip \
|
||||
php7-ctype \
|
||||
php7-tokenizer \
|
||||
php7-pdo_mysql \
|
||||
php7-openssl \
|
||||
php7-bcmath \
|
||||
php7-phar \
|
||||
php7-json \
|
||||
php7-iconv \
|
||||
php7-fileinfo \
|
||||
php7-simplexml \
|
||||
php7-session \
|
||||
php7-dom \
|
||||
php7-xmlwriter \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
git \
|
||||
mysql-client \
|
||||
tini
|
||||
|
||||
# Where apache's PID lives
|
||||
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
|
||||
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php7/php.ini
|
||||
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
|
||||
|
||||
# Enable mod_rewrite
|
||||
RUN sed -i '/LoadModule rewrite_module/s/^#//g' /etc/apache2/httpd.conf
|
||||
|
||||
COPY . /var/www/html
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY docker/docker.env /var/www/html/.env
|
||||
|
||||
RUN chown -R apache:apache /var/www/html
|
||||
|
||||
RUN \
|
||||
rm -r "/var/www/html/storage/private_uploads" \
|
||||
&& mkdir -p "/var/lib/snipeit/data/private_uploads" && ln -fs "/var/lib/snipeit/data/private_uploads" "/var/www/html/storage/private_uploads" \
|
||||
&& rm -rf "/var/www/html/public/uploads" \
|
||||
&& mkdir -p "/var/lib/snipeit/data/uploads" && ln -fs "/var/lib/snipeit/data/uploads" "/var/www/html/public/uploads" \
|
||||
&& mkdir -p "/var/lib/snipeit/dumps" && rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
|
||||
&& mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
|
||||
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
|
||||
&& chown -R apache "/var/lib/snipeit"
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
RUN mkdir -p /var/www/.composer && chown apache /var/www/.composer
|
||||
|
||||
# Install dependencies
|
||||
USER apache
|
||||
RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html
|
||||
|
||||
USER root
|
||||
|
||||
VOLUME ["/var/lib/snipeit"]
|
||||
|
||||
# Entrypoints
|
||||
COPY docker/entrypoint_alpine.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
40
README.md
40
README.md
@@ -1,12 +1,11 @@
|
||||
[](https://travis-ci.org/snipe/snipe-it) [](https://crowdin.com/project/snipe-it) [](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](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://www.codetriage.com/snipe/snipe-it)
|
||||
|
||||
 [](https://crowdin.com/project/snipe-it) [](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](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)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
|
||||
|
||||
It is built on [Laravel 5.5](http://laravel.com).
|
||||
It is built on [Laravel 6](http://laravel.com).
|
||||
|
||||
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
|
||||
@@ -63,11 +62,21 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
|
||||
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
-----
|
||||
|
||||
### Contributing
|
||||
|
||||
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
||||
|
||||
|
||||
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.
|
||||
|
||||
-----
|
||||
|
||||
### Security
|
||||
|
||||
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
|
||||
@@ -81,7 +90,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
|
||||
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
|
||||
@@ -105,17 +114,16 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars2.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [<img src="https://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
-----
|
||||
|
||||
### Contributing
|
||||
|
||||
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
|
||||
|
||||
|
||||
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.
|
||||
|
||||
18
Vagrantfile
vendored
18
Vagrantfile
vendored
@@ -81,4 +81,22 @@ Vagrant.configure("2") do |config|
|
||||
fedora26.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
fedora26.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "freebsd" do |freebsd|
|
||||
freebsd.vm.box = "freebsd/FreeBSD-11.2-RELEASE"
|
||||
freebsd.vm.hostname = 'freebsd12'
|
||||
freebsd.vm.network "forwarded_port", guest: 80, host: 8080
|
||||
freebsd.vm.network "forwarded_port", guest:3306, host:3306 # mysql
|
||||
freebsd.vm.network "private_network", type: "dhcp"
|
||||
freebsd.ssh.shell = "sh"
|
||||
freebsd.vm.base_mac = "080027D14C66"
|
||||
freebsd.vm.synced_folder ".", "/vagrant", :nfs => true, id: "vagrant-root",
|
||||
:mount_options => ['rw', 'vers=3', 'tcp', 'actimeo=2']
|
||||
freebsd.vm.provision "shell", inline: <<-SHELL
|
||||
pkg install -y python27;
|
||||
SHELL
|
||||
freebsd.vm.provision "ansible" do |ansible|
|
||||
ansible.playbook = "ansible/freebsd/vagrant_playbook.yml"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
260
ansible/freebsd/vagrant_playbook.yml
Normal file
260
ansible/freebsd/vagrant_playbook.yml
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
- name: Set up local server
|
||||
hosts: all
|
||||
remote_user: vagrant
|
||||
become_user: root
|
||||
become_method: sudo
|
||||
vars:
|
||||
- ansible_python_interpreter: /usr/local/bin/python2.7
|
||||
gather_facts: no
|
||||
|
||||
# Tasks
|
||||
tasks:
|
||||
|
||||
#
|
||||
# Update the PKG database
|
||||
#
|
||||
- name: Upgrade PKG database
|
||||
raw: sudo pkg upgrade -y
|
||||
|
||||
#
|
||||
# Mount the shared folders
|
||||
#
|
||||
- name: Update Vagrant Shared Folders
|
||||
command: "{{ item }}"
|
||||
with_items:
|
||||
- sysrc rpc_lockd_enable=YES
|
||||
- sysrc rpc_statd_enable=YES
|
||||
become: true
|
||||
|
||||
#
|
||||
# Install required utilities
|
||||
#
|
||||
- name: Install Utilities
|
||||
pkgng:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
with_items:
|
||||
- openssl
|
||||
- node
|
||||
- npm
|
||||
- git
|
||||
- nano
|
||||
- wget
|
||||
- bash
|
||||
become: true
|
||||
|
||||
#
|
||||
# Install php and php dependancies
|
||||
#
|
||||
- name: Install PHP dependancies
|
||||
pkgng:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
with_items:
|
||||
- php72
|
||||
- php72-zip
|
||||
- php72-zlib
|
||||
- php72-extensions
|
||||
- php72-mbstring
|
||||
- php72-openssl
|
||||
# - php72-mysqli
|
||||
- php72-curl
|
||||
- php72-soap
|
||||
- php72-pdo_mysql
|
||||
# - php72-pdo_pgsql
|
||||
- php72-ldap
|
||||
- php72-curl
|
||||
- php72-fileinfo
|
||||
- php72-bcmath
|
||||
- php72-gd
|
||||
become: true
|
||||
|
||||
#
|
||||
# Create a php.ini file
|
||||
#
|
||||
- name: PHP INI check
|
||||
stat:
|
||||
path: /usr/local/etc/php.ini
|
||||
register: php_ini_exits
|
||||
|
||||
- name: Create PHP ini
|
||||
command: cp /usr/local/etc/php.ini-development /usr/local/etc/php.ini
|
||||
become: true
|
||||
when: not php_ini_exits.stat.exists
|
||||
|
||||
- name: Enable PHP-FPM auto-start
|
||||
command: sysrc php_fpm_enable=YES
|
||||
become: true
|
||||
|
||||
- name: Start PHP-FPM service
|
||||
service:
|
||||
name: php-fpm
|
||||
state: started
|
||||
become: true
|
||||
|
||||
#
|
||||
# Install the lastest version of composer
|
||||
#
|
||||
- name: Composer check
|
||||
stat:
|
||||
path: /usr/local/bin/composer
|
||||
register: composer_exits
|
||||
|
||||
- name: Install Composer
|
||||
shell: |
|
||||
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
|
||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
|
||||
|
||||
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
|
||||
then
|
||||
>&2 echo 'ERROR: Invalid installer signature'
|
||||
rm composer-setup.php
|
||||
exit 1
|
||||
fi
|
||||
|
||||
php composer-setup.php --quiet
|
||||
RESULT=$?
|
||||
rm composer-setup.php
|
||||
mv composer.phar /usr/local/bin/composer
|
||||
exit $RESULT
|
||||
when: not composer_exits.stat.exists
|
||||
become: true
|
||||
|
||||
#
|
||||
# Install MySQL Server
|
||||
|
||||
- name: Install MySQL 5.7
|
||||
pkgng:
|
||||
name: mysql57-server
|
||||
state: present
|
||||
become: true
|
||||
register: sql_server
|
||||
|
||||
- name: Start MySQL server
|
||||
service:
|
||||
name: mysql-server
|
||||
state: started
|
||||
become: true
|
||||
|
||||
- name: MySQL 5.7 auto-start
|
||||
command: sysrc mysql_enable=YES
|
||||
become: true
|
||||
when: sql_server.changed == true
|
||||
|
||||
- name: Get MySQL root password
|
||||
command: tail -1 /root/.mysql_secret
|
||||
register: myql_root_pwd
|
||||
become: true
|
||||
when: sql_server.changed == true
|
||||
|
||||
- name: Change MySQL root password
|
||||
command: mysqladmin -u root -p'{{myql_root_pwd.stdout}}' password vagrant
|
||||
when: sql_server.changed == true
|
||||
|
||||
- name: Enable remote mysql
|
||||
replace:
|
||||
path: /usr/local/etc/mysql/my.cnf
|
||||
regexp: "127.0.0.1"
|
||||
replace: "0.0.0.0"
|
||||
become: true
|
||||
when: sql_server.changed == true
|
||||
|
||||
- name: Grant user vagrant privelages
|
||||
shell: mysql -u root -pvagrant -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'vagrant' WITH GRANT OPTION; FLUSH PRIVILEGES;"
|
||||
become: true
|
||||
when: sql_server.changed == true
|
||||
ignore_errors: true
|
||||
|
||||
- name: Restart MySQL server
|
||||
service:
|
||||
name: mysql-server
|
||||
state: restarted
|
||||
become: true
|
||||
|
||||
|
||||
#
|
||||
# Install Apache Web Server
|
||||
#
|
||||
- name: Install Apache 2.4
|
||||
pkgng:
|
||||
name: apache24
|
||||
state: present
|
||||
become: true
|
||||
register: apache24_server
|
||||
|
||||
- name: Apache 2.4 auto-start
|
||||
command: sysrc apache24_enable=YES
|
||||
become: true
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Enable Apache modules
|
||||
replace:
|
||||
path: /usr/local/etc/apache24/httpd.conf
|
||||
regexp: "#{{ item }}"
|
||||
replace: "{{ item }}"
|
||||
become: true
|
||||
with_items:
|
||||
- LoadModule rewrite_module libexec/apache24/mod_rewrite.so
|
||||
- LoadModule vhost_alias_module libexec/apache24/mod_vhost_alias.so
|
||||
- LoadModule deflate_module libexec/apache24/mod_deflate.so
|
||||
- LoadModule expires_module libexec/apache24/mod_expires.so
|
||||
- LoadModule mpm_worker_module libexec/apache24/mod_mpm_worker.so
|
||||
- LoadModule proxy_fcgi_module libexec/apache24/mod_proxy_fcgi.so
|
||||
- LoadModule proxy_module libexec/apache24/mod_proxy.so
|
||||
- Include etc/apache24/extra/httpd-vhosts.conf
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Disable Apache modules
|
||||
replace:
|
||||
path: /usr/local/etc/apache24/httpd.conf
|
||||
regexp: "{{ item }}"
|
||||
replace: "#{{ item }}"
|
||||
become: true
|
||||
with_items:
|
||||
- LoadModule mpm_prefork_module libexec/apache24/mod_mpm_prefork.so
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Backup vhosts
|
||||
command: cp /usr/local/etc/apache24/extra/httpd-vhosts.conf /usr/local/etc/apache24/extra/httpd-vhosts.conf.bak
|
||||
become: true
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Truncate vhosts
|
||||
command: truncate -s 0 /usr/local/etc/apache24/extra/httpd-vhosts.conf
|
||||
become: true
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Set up vhost
|
||||
blockinfile:
|
||||
path: "/usr/local/etc/apache24/extra/httpd-vhosts.conf"
|
||||
block: |
|
||||
<VirtualHost *>
|
||||
DocumentRoot /usr/local/www/apache24/data/public
|
||||
ServerName vagrant.app
|
||||
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/usr/local/www/apache24/data/public/$1
|
||||
DirectoryIndex /index.php index.php
|
||||
<Directory /usr/local/www/apache24/data/public>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
become: true
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Map apache dir to local folder
|
||||
shell: |
|
||||
if ! [ -L /var/www ]; then
|
||||
rm -rf /usr/local/www/apache24/data;
|
||||
ln -fs /vagrant /usr/local/www/apache24/data;
|
||||
fi
|
||||
become: true
|
||||
when: apache24_server.changed == true
|
||||
|
||||
- name: Start Apache 2.4 server
|
||||
service:
|
||||
name: apache24
|
||||
state: started
|
||||
become: true
|
||||
@@ -76,10 +76,4 @@ class CreateAdmin extends Command
|
||||
|
||||
}
|
||||
|
||||
// protected function getArguments()
|
||||
// {
|
||||
// return array(
|
||||
// array('username', InputArgument::REQUIRED, 'Username'),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DisableLDAP extends Command
|
||||
{
|
||||
@@ -41,7 +41,7 @@ class DisableLDAP extends Command
|
||||
|
||||
if ($this->confirm("\n****************************************************\nThis will disable LDAP support. You will not be able \nto login with an account that does not exist \nlocally in the Snipe-IT local database. \n****************************************************\n\nDo you wish to continue? [y|N]")) {
|
||||
|
||||
$setting = Setting::first();
|
||||
$setting = Setting::getSettings();
|
||||
$setting->ldap_enabled = 0;
|
||||
if ($setting->save()) {
|
||||
$this->info('LDAP has been set to disabled.');
|
||||
|
||||
@@ -71,7 +71,7 @@ class FixDoubleEscape extends Command
|
||||
|
||||
foreach($classname::where("$field",'LIKE','%&%')->get() as $row) {
|
||||
$this->info('Updating '.$field.' for '.$classname);
|
||||
$row->{$field} = html_entity_decode($row->{$field});
|
||||
$row->{$field} = html_entity_decode($row->{$field},ENT_QUOTES);
|
||||
$row->save();
|
||||
$count[$classname][$field]++;
|
||||
|
||||
|
||||
105
app/Console/Commands/FixMismatchedAssetsAndLogs.php
Normal file
105
app/Console/Commands/FixMismatchedAssetsAndLogs.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FixMismatchedAssetsAndLogs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:fix-assets-and-logs {--dryrun : Run the sync process but don\'t update the database}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This script attempts to check the log table and check that the assets.assigned_to matches the last checkout.';
|
||||
|
||||
/**
|
||||
* Is dry-run?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $dryrun = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('This is a DRY RUN - no changes will be saved.' );
|
||||
}
|
||||
|
||||
$mismatch_count = 0;
|
||||
$assets = Asset::whereNotNull('assigned_to')
|
||||
->where('assigned_type', '=', 'App\\Models\\User')
|
||||
->orderBy('id', 'ASC')->get();
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
// get the last checkout of the asset
|
||||
if ($checkout_log = Actionlog::where('target_type', '=', 'App\\Models\\User')
|
||||
->where('action_type', '=', 'checkout')
|
||||
->where('item_id', '=', $asset->id)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first()) {
|
||||
|
||||
// Now check for a subsequent checkin log - we want to ignore those
|
||||
if (!$checkin_log = Actionlog::where('target_type', '=', 'App\\Models\\User')
|
||||
->where('action_type', '=', 'checkin from')
|
||||
->where('item_id', '=', $asset->id)
|
||||
->whereDate('created_at', '>', $checkout_log->created_at)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first()) {
|
||||
|
||||
//print_r($asset);
|
||||
if ($checkout_log->target_id != $asset->assigned_to) {
|
||||
$this->error('Log ID: '.$checkout_log->id.' -- Asset ID '. $checkout_log->item_id.' SHOULD BE checked out to User '.$checkout_log->target_id.' but its assigned_to is '.$asset->assigned_to );
|
||||
|
||||
if (!$this->dryrun) {
|
||||
$asset->assigned_to = $checkout_log->target_id;
|
||||
if ($asset->save()) {
|
||||
$this->info('Asset record updated.');
|
||||
} else {
|
||||
$this->error('Error updating asset: '.$asset->getErrors());
|
||||
}
|
||||
}
|
||||
$mismatch_count++;
|
||||
}
|
||||
} else {
|
||||
//$this->info('Asset ID '.$asset->id.': There is a checkin '.$checkin_log->created_at.' after this checkout '.$checkout_log->created_at);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$this->info($mismatch_count.' mismatched assets.');
|
||||
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,13 @@ class ImportLocations extends Command
|
||||
|
||||
if (array_key_exists('Parent Name', $row)) {
|
||||
$parent_name = trim($row['Parent Name']);
|
||||
} else {
|
||||
$parent_name = null;
|
||||
}
|
||||
|
||||
// Set the location attributes to save
|
||||
if (array_key_exists('Name', $row)) {
|
||||
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
|
||||
$location = Location::firstOrCreate(array('name' => trim($row['Name'])));
|
||||
$location->name = trim($row['Name']);
|
||||
$this->info('Checking location: '.$location->name);
|
||||
} else {
|
||||
@@ -120,7 +122,7 @@ class ImportLocations extends Command
|
||||
if (array_key_exists('Country', $row)) {
|
||||
$location->country = trim($row['Country']);
|
||||
}
|
||||
if (array_key_exists('Country', $row)) {
|
||||
if (array_key_exists('OU', $row)) {
|
||||
$location->ldap_ou = trim($row['OU']);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
/* Determine which location to assign users to by default. */
|
||||
$location = NULL;
|
||||
$location = NULL; // FIXME - this would be better called "$default_location", which is more explicit about its purpose
|
||||
|
||||
if ($this->option('location')!='') {
|
||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
||||
@@ -106,8 +106,8 @@ class LdapSync extends Command
|
||||
$ldap_ou_locations = Location::where('ldap_ou', '!=', '')->get()->toArray();
|
||||
$ldap_ou_lengths = array();
|
||||
|
||||
foreach ($ldap_ou_locations as $location) {
|
||||
$ldap_ou_lengths[] = strlen($location["ldap_ou"]);
|
||||
foreach ($ldap_ou_locations as $ou_loc) {
|
||||
$ldap_ou_lengths[] = strlen($ou_loc["ldap_ou"]);
|
||||
}
|
||||
|
||||
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
|
||||
|
||||
399
app/Console/Commands/LdapSyncNg.php
Normal file
399
app/Console/Commands/LdapSyncNg.php
Normal file
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Log;
|
||||
use Exception;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Console\Command;
|
||||
use Adldap\Models\User as AdldapUser;
|
||||
|
||||
/**
|
||||
* LDAP / AD sync command.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class LdapSyncNg extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:ldap-sync-ng
|
||||
{--location= : A location name }
|
||||
{--location_id= : A location id}
|
||||
{--base_dn= : A diffrent base DN to use }
|
||||
{--summary : Print summary }
|
||||
{--json_summary : Print summary in json format }
|
||||
{--dryrun : Run the sync process but don\'t update the database}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command line LDAP/AD sync';
|
||||
|
||||
/**
|
||||
* An LdapAd instance.
|
||||
*
|
||||
* @var \App\Models\LdapAd
|
||||
*/
|
||||
private $ldap;
|
||||
|
||||
/**
|
||||
* LDAP settings collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* A default location collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $defaultLocation = null;
|
||||
|
||||
/**
|
||||
* Mapped locations collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $mappedLocations = null;
|
||||
|
||||
/**
|
||||
* The summary collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $summary;
|
||||
|
||||
/**
|
||||
* Is dry-run?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $dryrun = false;
|
||||
|
||||
/**
|
||||
* Show users to be imported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $userlist = [];
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(LdapAd $ldap)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->ldap = $ldap;
|
||||
$this->settings = $this->ldap->ldapSettings;
|
||||
$this->summary = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$dispatcher = \Adldap\Adldap::getEventDispatcher();
|
||||
|
||||
// Listen for all model events.
|
||||
$dispatcher->listen('Adldap\Models\Events\*', function ($eventName, array $data) {
|
||||
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
|
||||
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
|
||||
\Log::debug("Event: ".$eventName." data - ".print_r($data, true));
|
||||
});
|
||||
$dispatcher->listen('Adldap\Auth\Events\*', function ($eventName, array $data) {
|
||||
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
|
||||
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
|
||||
\Log::debug("Event: ".$eventName." data - ".print_r($data, true));
|
||||
});
|
||||
|
||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', "600")); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', '500M');
|
||||
$old_error_reporting = error_reporting(); // grab old error_reporting .ini setting, for later re-enablement
|
||||
error_reporting($old_error_reporting & ~E_DEPRECATED); // disable deprecation warnings, for LDAP in PHP 7.4 (and greater)
|
||||
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
$this->checkIfLdapIsEnabled();
|
||||
$this->checkLdapConnection();
|
||||
$this->setBaseDn();
|
||||
$this->getUserDefaultLocation();
|
||||
/*
|
||||
* Use the default location if set, this is needed for the LDAP users sync page
|
||||
*/
|
||||
if (!$this->option('base_dn') && null == $this->defaultLocation) {
|
||||
$this->getMappedLocations();
|
||||
}
|
||||
$this->processLdapUsers();
|
||||
// Print table of users
|
||||
if ($this->dryrun) {
|
||||
$this->info('The following users will be synced!');
|
||||
$headers = ['First Name', 'Last Name', 'Username', 'Email', 'Employee #', 'Location Id', 'Status'];
|
||||
$this->table($headers, $this->summary->toArray());
|
||||
}
|
||||
|
||||
error_reporting($old_error_reporting); // re-enable deprecation warnings.
|
||||
return $this->getSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the LDAP sync summary.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getSummary(): string
|
||||
{
|
||||
if ($this->option('summary') && null === $this->dryrun) {
|
||||
$this->summary->each(function ($item) {
|
||||
$this->info('USER: '.$item['note']);
|
||||
|
||||
if ('ERROR' === $item['status']) {
|
||||
$this->error('ERROR: '.$item['note']);
|
||||
}
|
||||
});
|
||||
} elseif ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => false,
|
||||
'error_message' => '',
|
||||
'summary' => $this->summary->toArray(),
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user or update an existing user.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $snipeUser
|
||||
*/
|
||||
private function updateCreateUser(AdldapUser $snipeUser): void
|
||||
{
|
||||
$user = $this->ldap->processUser($snipeUser, $this->defaultLocation, $this->mappedLocations);
|
||||
$summary = [
|
||||
'firstname' => $user->first_name,
|
||||
'lastname' => $user->last_name,
|
||||
'username' => $user->username,
|
||||
'employee_number' => $user->employee_num,
|
||||
'email' => $user->email,
|
||||
'location_id' => $user->location_id,
|
||||
];
|
||||
// Only update the database if is not a dry run
|
||||
if (!$this->dryrun) {
|
||||
if ($user->isDirty()) { //if nothing on the user changed, don't bother trying to save anything nor put anything in the summary
|
||||
if ($user->save()) {
|
||||
$summary['note'] = ($user->wasRecentlyCreated ? 'CREATED' : 'UPDATED');
|
||||
$summary['status'] = 'SUCCESS';
|
||||
} else {
|
||||
$errors = '';
|
||||
foreach ($user->getErrors()->getMessages() as $error) {
|
||||
$errors .= implode(", ",$error);
|
||||
}
|
||||
$summary['note'] = $snipeUser->getDN().' was not imported. REASON: '.$errors;
|
||||
$summary['status'] = 'ERROR';
|
||||
}
|
||||
} else {
|
||||
$summary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// $summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED'); // this seems, kinda, like, superfluous, relative to the $summary['note'] thing above, yeah?
|
||||
if($summary) { //if the $user wasn't dirty, $summary was set to null so that we will skip the following push()
|
||||
$this->summary->push($summary);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the users to update / create.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
*/
|
||||
private function processLdapUsers(): void
|
||||
{
|
||||
try {
|
||||
\Log::debug("CAL:LING GET LDAP SUSERS");
|
||||
$ldapUsers = $this->ldap->getLdapUsers();
|
||||
\Log::debug("END CALLING GET LDAP USERS");
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit($e->getMessage());
|
||||
}
|
||||
|
||||
if (0 == $ldapUsers->count()) {
|
||||
$msg = 'ERROR: No users found!';
|
||||
Log::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
}
|
||||
exit($msg);
|
||||
}
|
||||
|
||||
// Process each individual users
|
||||
foreach ($ldapUsers->getResults() as $user) { // AdLdap2's paginate() method is weird, it gets *everything* and ->getResults() returns *everything*
|
||||
$this->updateCreateUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mapped locations if a base_dn is provided.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getMappedLocations()
|
||||
{
|
||||
$ldapOuLocation = Location::where('ldap_ou', '!=', '')->select(['id', 'ldap_ou'])->get();
|
||||
$locations = $ldapOuLocation->sortBy(function ($ou, $key) {
|
||||
return strlen($ou->ldap_ou);
|
||||
});
|
||||
if ($locations->count() > 0) {
|
||||
$msg = 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.';
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->mappedLocations = $locations->pluck('ldap_ou', 'id'); // TODO: this seems ok-ish, but the key-> value is going location_id -> OU name, and the primary action here is the opposite of that - going from OU's to location ID's.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base dn if supplied.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function setBaseDn(): void
|
||||
{
|
||||
if ($this->option('base_dn')) {
|
||||
$this->ldap->baseDn = $this->option('base_dn');
|
||||
$msg = sprintf('Importing users from specified base DN: "%s"', $this->ldap->baseDn);
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default location id for imported users.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getUserDefaultLocation(): void
|
||||
{
|
||||
$location = $this->option('location_id') ?? $this->option('location');
|
||||
if ($location) {
|
||||
$userLocation = Location::where('name', '=', $location)
|
||||
->orWhere('id', '=', intval($location))
|
||||
->select(['name', 'id'])
|
||||
->first();
|
||||
if ($userLocation) {
|
||||
$msg = 'Importing users with default location: '.$userLocation->name.' ('.$userLocation->id.')';
|
||||
LOG::debug($msg);
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->defaultLocation = collect([
|
||||
$userLocation->id => $userLocation->name,
|
||||
]);
|
||||
} else {
|
||||
$msg = 'The supplied location is invalid!';
|
||||
LOG::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LDAP intergration is enabled.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkIfLdapIsEnabled(): void
|
||||
{
|
||||
if (false === $this->settings['ldap_enabled']) {
|
||||
$msg = 'LDAP intergration is not enabled. Exiting sync process.';
|
||||
$this->info($msg);
|
||||
Log::info($msg);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure we can access the server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkLdapConnection(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->testLdapAdUserConnection();
|
||||
$this->ldap->testLdapAdBindConnection();
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the json summary to the screen if enabled.
|
||||
*
|
||||
* @param Exception $error
|
||||
*/
|
||||
private function outputError($error): void
|
||||
{
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => true,
|
||||
'error_message' => $error->getMessage(),
|
||||
'summary' => [],
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
$this->error($error->getMessage());
|
||||
LOG::error($error);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,10 @@ class MergeUsersByUsername extends Command
|
||||
foreach ($bad_user->assets as $asset) {
|
||||
$this->info( 'Updating asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
|
||||
$asset->assigned_to = $user->id;
|
||||
$asset->save();
|
||||
if (!$asset->save()) {
|
||||
$this->error( 'Could not update assigned_to field on asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
|
||||
$this->error( 'Error saving: '.$asset->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the list of licenses
|
||||
|
||||
183
app/Console/Commands/MoveUploadsToNewDisk.php
Normal file
183
app/Console/Commands/MoveUploadsToNewDisk.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MoveUploadsToNewDisk extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:move-uploads {delete_local?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This will move your locally uploaded files to whatever your current disk is.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if (config('filesystems.default')=='local') {
|
||||
$this->error('Your current disk is set to local so we cannot proceed.');
|
||||
$this->warn("Please configure your .env settings for S3. \nChange your PUBLIC_FILESYSTEM_DISK value to 's3_public' and your PRIVATE_FILESYSTEM_DISK to s3_private.");
|
||||
return false;
|
||||
}
|
||||
$delete_local = $this->argument('delete_local');
|
||||
|
||||
$public_uploads['accessories'] = glob('public/accessories'."/*.*");
|
||||
$public_uploads['assets'] = glob('public/assets'."/*.*");
|
||||
$public_uploads['avatars'] = glob('public/avatars'."/*.*");
|
||||
$public_uploads['categories'] = glob('public/categories'."/*.*");
|
||||
$public_uploads['companies'] = glob('public/companies'."/*.*");
|
||||
$public_uploads['components'] = glob('public/components'."/*.*");
|
||||
$public_uploads['consumables'] = glob('public/consumables'."/*.*");
|
||||
$public_uploads['departments'] = glob('public/departments'."/*.*");
|
||||
$public_uploads['locations'] = glob('public/locations'."/*.*");
|
||||
$public_uploads['manufacturers'] = glob('public/manufacturers'."/*.*");
|
||||
$public_uploads['suppliers'] = glob('public/suppliers'."/*.*");
|
||||
$public_uploads['assetmodels'] = glob('public/models'."/*.*");
|
||||
|
||||
|
||||
// iterate files
|
||||
foreach($public_uploads as $public_type => $public_upload)
|
||||
{
|
||||
$type_count = 0;
|
||||
$this->info("- There are ".count($public_upload).' PUBLIC '.$public_type.' files.');
|
||||
|
||||
for ($i = 0; $i < count($public_upload); $i++) {
|
||||
$type_count++;
|
||||
$filename = basename($public_upload[$i]);
|
||||
|
||||
try {
|
||||
Storage::disk('public')->put('uploads/'.public_type.'/'.$filename, file_get_contents($public_upload[$i]));
|
||||
$new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename);
|
||||
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
$this->error($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$logos = glob("public/uploads/setting*.*");
|
||||
$this->info("- There are ".count($logos).' files that might be logos.');
|
||||
$type_count = 0;
|
||||
|
||||
foreach ($logos as $logo) {
|
||||
$this->info($logo);
|
||||
$type_count++;
|
||||
$filename = basename($logo);
|
||||
Storage::disk('public')->put('uploads/'.$filename, file_get_contents($logo));
|
||||
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.env('PUBLIC_AWS_URL').'/uploads/'.$filename);
|
||||
}
|
||||
|
||||
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
|
||||
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
|
||||
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
|
||||
$private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*");
|
||||
$private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*");
|
||||
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
|
||||
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
|
||||
$private_uploads['backups'] = glob('storage/private_uploads/users'."/*.*");
|
||||
|
||||
|
||||
foreach($private_uploads as $private_type => $private_upload)
|
||||
{
|
||||
$this->info("- There are ".count($private_upload).' PRIVATE '.$private_type.' files.');
|
||||
|
||||
$type_count = 0;
|
||||
for ($x = 0; $x < count($private_upload); $x++) {
|
||||
$type_count++;
|
||||
$filename = basename($private_upload[$x]);
|
||||
|
||||
try {
|
||||
Storage::put($private_type.'/'.$filename, file_get_contents($private_upload[$i]));
|
||||
$new_url = Storage::url($private_type.'/'.$filename, $filename);
|
||||
$this->info($type_count.'. PRIVATE: '.$filename.' was copied to '.$new_url);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
$this->error($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ($delete_local=='true') {
|
||||
$public_delete_count = 0;
|
||||
$private_delete_count = 0;
|
||||
|
||||
$this->info("\n\n");
|
||||
$this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
$this->warn("\nTHIS WILL DELETE ALL OF YOUR LOCAL UPLOADED FILES. \n\nThis cannot be undone, so you should take a backup of your system before you proceed.\n");
|
||||
$this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
|
||||
if ($this->confirm("Do you wish to continue?")) {
|
||||
|
||||
foreach($public_uploads as $public_type => $public_upload) {
|
||||
|
||||
for ($i = 0; $i < count($public_upload); $i++) {
|
||||
$filename = $public_upload[$i];
|
||||
try {
|
||||
unlink($filename);
|
||||
$public_delete_count++;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
$this->error($e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
foreach($private_uploads as $private_type => $private_upload)
|
||||
{
|
||||
|
||||
for ($i = 0; $i < count($private_upload); $i++) {
|
||||
$filename = $private_upload[$i];
|
||||
try {
|
||||
unlink($filename);
|
||||
$private_delete_count++;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
$this->error($e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->info($public_delete_count." PUBLIC local files and ".$private_delete_count." PRIVATE local files were deleted from your filesystem.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,9 @@
|
||||
<?php
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Importer\AccessoryImporter;
|
||||
use App\Importer\AssetImporter;
|
||||
use App\Importer\ConsumableImporter;
|
||||
use App\Importer\Importer;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use ForceUTF8\Encoding;
|
||||
|
||||
ini_set('max_execution_time', 600); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', '500M');
|
||||
@@ -77,8 +55,10 @@ class ObjectImportCommand extends Command
|
||||
->setShouldNotify($this->option('send-welcome'))
|
||||
->setUsernameFormat($this->option('username_format'));
|
||||
|
||||
$logFile = $this->option('logfile');
|
||||
\Log::useFiles($logFile);
|
||||
|
||||
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
|
||||
// $logFile = $this->option('logfile');
|
||||
// \Log::useFiles($logFile);
|
||||
$this->comment('======= Importing Items from '.$filename.' =========');
|
||||
$importer->import();
|
||||
|
||||
|
||||
@@ -83,11 +83,20 @@ class PaveIt extends Command
|
||||
DB::statement('delete from asset_uploads');
|
||||
DB::statement('delete from action_logs');
|
||||
DB::statement('delete from checkout_requests');
|
||||
DB::statement('delete from checkout_acceptances');
|
||||
DB::statement('delete from consumables_users');
|
||||
DB::statement('delete from custom_field_custom_fieldset');
|
||||
DB::statement('delete from custom_fields');
|
||||
DB::statement('delete from custom_fieldsets');
|
||||
DB::statement('delete from components_assets');
|
||||
DB::statement('delete from kits');
|
||||
DB::statement('delete from kits_accessories');
|
||||
DB::statement('delete from kits_consumables');
|
||||
DB::statement('delete from kits_licenses');
|
||||
DB::statement('delete from kits_models');
|
||||
DB::statement('delete from login_attempts');
|
||||
DB::statement('delete from models_custom_fields');
|
||||
DB::statement('delete from permission_groups');
|
||||
DB::statement('delete from password_resets');
|
||||
DB::statement('delete from requested_assets');
|
||||
DB::statement('delete from requests');
|
||||
@@ -104,7 +113,10 @@ class PaveIt extends Command
|
||||
\DB::statement('drop table IF EXISTS assets');
|
||||
\DB::statement('drop table IF EXISTS categories');
|
||||
\DB::statement('drop table IF EXISTS checkout_requests');
|
||||
\DB::statement('drop table IF EXISTS checkout_acceptances');
|
||||
\DB::statement('drop table IF EXISTS companies');
|
||||
\DB::statement('drop table IF EXISTS components');
|
||||
\DB::statement('drop table IF EXISTS components_assets');
|
||||
\DB::statement('drop table IF EXISTS consumables_users');
|
||||
\DB::statement('drop table IF EXISTS consumables');
|
||||
\DB::statement('drop table IF EXISTS custom_field_custom_fieldset');
|
||||
@@ -114,11 +126,17 @@ class PaveIt extends Command
|
||||
\DB::statement('drop table IF EXISTS departments');
|
||||
\DB::statement('drop table IF EXISTS groups');
|
||||
\DB::statement('drop table IF EXISTS history');
|
||||
\DB::statement('drop table IF EXISTS components');
|
||||
\DB::statement('drop table IF EXISTS components_assets');
|
||||
\DB::statement('drop table IF EXISTS kits');
|
||||
\DB::statement('drop table IF EXISTS kits_accessories');
|
||||
\DB::statement('drop table IF EXISTS kits_consumables');
|
||||
\DB::statement('drop table IF EXISTS kits_licenses');
|
||||
\DB::statement('drop table IF EXISTS kits_models');
|
||||
\DB::statement('drop table IF EXISTS models_custom_fields');
|
||||
\DB::statement('drop table IF EXISTS permission_groups');
|
||||
\DB::statement('drop table IF EXISTS license_seats');
|
||||
\DB::statement('drop table IF EXISTS licenses');
|
||||
\DB::statement('drop table IF EXISTS locations');
|
||||
\DB::statement('drop table IF EXISTS login_attempts');
|
||||
\DB::statement('drop table IF EXISTS manufacturers');
|
||||
\DB::statement('drop table IF EXISTS models');
|
||||
\DB::statement('drop table IF EXISTS migrations');
|
||||
|
||||
@@ -2,22 +2,19 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use DB;
|
||||
use \App\Models\Asset;
|
||||
use \App\Models\AssetModel;
|
||||
use \App\Models\Location;
|
||||
use \App\Models\Company;
|
||||
use \App\Models\License;
|
||||
use \App\Models\Accessory;
|
||||
use \App\Models\Component;
|
||||
use \App\Models\Consumable;
|
||||
use \App\Models\Category;
|
||||
use \App\Models\User;
|
||||
use \App\Models\Supplier;
|
||||
use \App\Models\Manufacturer;
|
||||
use \App\Models\Depreciation;
|
||||
use \App\Models\Statuslabel;
|
||||
|
||||
class Purge extends Command
|
||||
{
|
||||
|
||||
44
app/Console/Commands/PurgeLoginAttempts.php
Normal file
44
app/Console/Commands/PurgeLoginAttempts.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PurgeLoginAttempts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:purge-logins';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clears the login_attempts table';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE YOUR LOGIN ATTEMPT RECORDS. \nThere is NO undo! \n****************************************************\n\nDo you wish to continue? No backsies! [y|N]")) {
|
||||
\DB::statement('delete from login_attempts');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Console\Command;
|
||||
use App\LegacyEncrypter\McryptEncrypter;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class RecryptFromMcrypt extends Command
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use DB;
|
||||
use Artisan;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RegenerateAssetTags extends Command
|
||||
{
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ResetDemoSettings extends Command
|
||||
{
|
||||
@@ -51,9 +51,9 @@ class ResetDemoSettings extends Command
|
||||
$settings->header_color = null;
|
||||
$settings->barcode_type = 'QRCODE';
|
||||
$settings->default_currency = 'USD';
|
||||
$settings->brand = 3;
|
||||
$settings->brand = 2;
|
||||
$settings->ldap_enabled = 0;
|
||||
$settings->full_multiple_companies_support = 1;
|
||||
$settings->full_multiple_companies_support = 0;
|
||||
$settings->alt_barcode = 'C128';
|
||||
$settings->skin = '';
|
||||
$settings->email_domain = 'snipeitapp.com';
|
||||
@@ -64,7 +64,16 @@ class ResetDemoSettings extends Command
|
||||
$settings->thumbnail_max_h = '30';
|
||||
$settings->locale = 'en';
|
||||
$settings->version_footer = 'on';
|
||||
$settings->support_footer = 'on';
|
||||
$settings->support_footer = null;
|
||||
$settings->saml_enabled = '0';
|
||||
$settings->saml_sp_x509cert = null;
|
||||
$settings->saml_idp_metadata = null;
|
||||
$settings->saml_attr_mapping_username = null;
|
||||
$settings->saml_forcelogin = '0';
|
||||
$settings->saml_slo = null;
|
||||
$settings->saml_custom_settings = null;
|
||||
|
||||
|
||||
$settings->save();
|
||||
|
||||
if ($user = User::where('username', '=', 'admin')->first()) {
|
||||
@@ -72,7 +81,7 @@ class ResetDemoSettings extends Command
|
||||
$user->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\License;
|
||||
use DB;
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RestoreDeletedUsers extends Command
|
||||
{
|
||||
|
||||
60
app/Console/Commands/SendCurrentInventoryToUsers.php
Normal file
60
app/Console/Commands/SendCurrentInventoryToUsers.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendCurrentInventoryToUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:user-inventory';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This will send users a report of all of the items currently checked out to them.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$users = User::whereNull('deleted_at')->whereNotNull('email')->with('assets', 'accessories', 'licenses')->get();
|
||||
|
||||
$count = 0;
|
||||
foreach ($users as $user) {
|
||||
|
||||
if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0))
|
||||
{
|
||||
$count++;
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->info($count.' users notified.');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,16 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Notifications\ExpectedCheckinNotification;
|
||||
use App\Notifications\ExpectedCheckinAdminNotification;
|
||||
use App\Notifications\ExpectedCheckinNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
|
||||
class SendExpectedCheckinAlerts extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
@@ -29,8 +28,6 @@ class SendExpectedCheckinAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -44,15 +41,15 @@ class SendExpectedCheckinAlerts extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$settings = Setting::getSettings();
|
||||
$whenNotify = Carbon::now()->addDays(7);
|
||||
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
|
||||
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
|
||||
|
||||
$this->info($whenNotify.' is deadline');
|
||||
$this->info($assets->count().' assets');
|
||||
$this->info($whenNotify . ' is deadline');
|
||||
$this->info($assets->count() . ' assets');
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->assigned && $asset->checkedOutToUser()) {
|
||||
if ($asset->assigned && $asset->checkedOutToUser()) {
|
||||
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
|
||||
}
|
||||
}
|
||||
@@ -60,14 +57,9 @@ class SendExpectedCheckinAlerts extends Command
|
||||
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new \App\Models\Recipients\AlertRecipient($item);
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,14 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
use App\Models\Setting;
|
||||
use DB;
|
||||
use App\Notifications\ExpiringLicenseNotification;
|
||||
use App\Notifications\ExpiringAssetsNotification;
|
||||
|
||||
use App\Notifications\ExpiringLicenseNotification;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendExpirationAlerts extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
@@ -30,8 +28,6 @@ class SendExpirationAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -45,48 +41,35 @@ class SendExpirationAlerts extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
$settings = Setting::getSettings();
|
||||
$threshold = $settings->alert_interval;
|
||||
|
||||
|
||||
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
|
||||
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new \App\Models\Recipients\AlertRecipient($item);
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
// Expiring Assets
|
||||
$assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
|
||||
$assets = Asset::getExpiringWarrantee($threshold);
|
||||
if ($assets->count() > 0) {
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(),
|
||||
['count' => $assets->count(), 'threshold' => $threshold]));
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
|
||||
\Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
|
||||
}
|
||||
|
||||
// Expiring licenses
|
||||
$licenses = License::getExpiringLicenses($threshold);
|
||||
|
||||
|
||||
if ($licenses->count() > 0) {
|
||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
|
||||
\Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
if ($settings->alert_email=='') {
|
||||
if ($settings->alert_email == '') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif ($settings->alerts_enabled!=1) {
|
||||
} elseif (1 != $settings->alerts_enabled) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Setting;
|
||||
use DB;
|
||||
use Mail;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\InventoryAlert;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class SendInventoryAlerts extends Command
|
||||
{
|
||||
@@ -28,8 +27,6 @@ class SendInventoryAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -45,29 +42,24 @@ class SendInventoryAlerts extends Command
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if (($settings->alert_email!='') && ($settings->alerts_enabled==1)) {
|
||||
|
||||
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
|
||||
$items = Helper::checkLowInventory();
|
||||
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
|
||||
if (($items) && (count($items) > 0)) {
|
||||
$this->info(trans_choice('mail.low_inventory_alert', count($items)));
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new \App\Models\Recipients\AlertRecipient($item);
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (Setting::getSettings()->alert_email=='') {
|
||||
if ($settings->alert_email == '') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif (Setting::getSettings()->alerts_enabled!=1) {
|
||||
} elseif (1 != $settings->alerts_enabled) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncAssetCounters extends Command
|
||||
{
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncAssetLocations extends Command
|
||||
{
|
||||
|
||||
@@ -19,7 +19,6 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
|
||||
$schedule->command('snipeit:inventory-alerts')->daily();
|
||||
$schedule->command('snipeit:expiring-alerts')->daily();
|
||||
$schedule->command('snipeit:expected-checkin')->daily();
|
||||
@@ -28,6 +27,10 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command('snipeit:upcoming-audits')->daily();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by Laravel to handle any console routes
|
||||
* that are defined in routes/console.php.
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
require base_path('routes/console.php');
|
||||
|
||||
23
app/Events/CheckoutAccepted.php
Normal file
23
app/Events/CheckoutAccepted.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Contracts\Acceptable;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutAccepted
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(CheckoutAcceptance $acceptance)
|
||||
{
|
||||
$this->acceptance = $acceptance;
|
||||
}
|
||||
}
|
||||
23
app/Events/CheckoutDeclined.php
Normal file
23
app/Events/CheckoutDeclined.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Contracts\Acceptable;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutDeclined
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(CheckoutAcceptance $acceptance)
|
||||
{
|
||||
$this->acceptance = $acceptance;
|
||||
}
|
||||
}
|
||||
32
app/Events/CheckoutableCheckedIn.php
Normal file
32
app/Events/CheckoutableCheckedIn.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutableCheckedIn
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public $checkoutable;
|
||||
public $checkedOutTo;
|
||||
public $checkedInBy;
|
||||
public $note;
|
||||
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null)
|
||||
{
|
||||
$this->checkoutable = $checkoutable;
|
||||
$this->checkedOutTo = $checkedOutTo;
|
||||
$this->checkedInBy = $checkedInBy;
|
||||
$this->note = $note;
|
||||
$this->action_date = $action_date ?? date('Y-m-d');
|
||||
}
|
||||
}
|
||||
30
app/Events/CheckoutableCheckedOut.php
Normal file
30
app/Events/CheckoutableCheckedOut.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutableCheckedOut
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public $checkoutable;
|
||||
public $checkedOutTo;
|
||||
public $checkedOutBy;
|
||||
public $note;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
|
||||
{
|
||||
$this->checkoutable = $checkoutable;
|
||||
$this->checkedOutTo = $checkedOutTo;
|
||||
$this->checkedOutBy = $checkedOutBy;
|
||||
$this->note = $note;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CheckoutNotAllowed extends Exception
|
||||
{
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Helpers\Helper;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Log;
|
||||
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ class Handler extends ExceptionHandler
|
||||
\Illuminate\Session\TokenMismatchException::class,
|
||||
\Illuminate\Validation\ValidationException::class,
|
||||
\Intervention\Image\Exception\NotSupportedException::class,
|
||||
\League\OAuth2\Server\Exception\OAuthServerException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -104,7 +106,7 @@ class Handler extends ExceptionHandler
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthorized.'], 401);
|
||||
return response()->json(['error' => 'Unauthorized or unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
use DB;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Location;
|
||||
use App\Models\Department;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\Category;
|
||||
use App\Models\Depreciation;
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Component;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\Depreciation;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use Crypt;
|
||||
use Image;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
|
||||
class Helper
|
||||
@@ -64,27 +55,313 @@ class Helper
|
||||
|
||||
/**
|
||||
* Static colors for pie charts.
|
||||
* This is inelegant, and could be refactored later.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.3]
|
||||
* @return Array
|
||||
*/
|
||||
public static function chartColors()
|
||||
public static function defaultChartColors($index = 0)
|
||||
{
|
||||
$colors = [
|
||||
'#f56954',
|
||||
'#00a65a',
|
||||
'#f39c12',
|
||||
'#00c0ef',
|
||||
'#3c8dbc',
|
||||
'#d2d6de',
|
||||
'#3c8dbc',
|
||||
'#3c8dbc',
|
||||
'#3c8dbc',
|
||||
|
||||
"#008941",
|
||||
"#FF4A46",
|
||||
"#006FA6",
|
||||
"#A30059",
|
||||
"#1CE6FF",
|
||||
"#FFDBE5",
|
||||
"#7A4900",
|
||||
"#0000A6",
|
||||
"#63FFAC",
|
||||
"#B79762",
|
||||
"#004D43",
|
||||
"#8FB0FF",
|
||||
"#997D87",
|
||||
"#5A0007",
|
||||
"#809693",
|
||||
"#FEFFE6",
|
||||
"#1B4400",
|
||||
"#4FC601",
|
||||
"#3B5DFF",
|
||||
"#4A3B53",
|
||||
"#FF2F80",
|
||||
"#61615A",
|
||||
"#BA0900",
|
||||
"#6B7900",
|
||||
"#00C2A0",
|
||||
"#FFAA92",
|
||||
"#FF90C9",
|
||||
"#B903AA",
|
||||
"#D16100",
|
||||
"#DDEFFF",
|
||||
"#000035",
|
||||
"#7B4F4B",
|
||||
"#A1C299",
|
||||
"#300018",
|
||||
"#0AA6D8",
|
||||
"#013349",
|
||||
"#00846F",
|
||||
"#372101",
|
||||
"#FFB500",
|
||||
"#C2FFED",
|
||||
"#A079BF",
|
||||
"#CC0744",
|
||||
"#C0B9B2",
|
||||
"#C2FF99",
|
||||
"#001E09",
|
||||
"#00489C",
|
||||
"#6F0062",
|
||||
"#0CBD66",
|
||||
"#EEC3FF",
|
||||
"#456D75",
|
||||
"#B77B68",
|
||||
"#7A87A1",
|
||||
"#788D66",
|
||||
"#885578",
|
||||
"#FAD09F",
|
||||
"#FF8A9A",
|
||||
"#D157A0",
|
||||
"#BEC459",
|
||||
"#456648",
|
||||
"#0086ED",
|
||||
"#886F4C",
|
||||
"#34362D",
|
||||
"#B4A8BD",
|
||||
"#00A6AA",
|
||||
"#452C2C",
|
||||
"#636375",
|
||||
"#A3C8C9",
|
||||
"#FF913F",
|
||||
"#938A81",
|
||||
"#575329",
|
||||
"#00FECF",
|
||||
"#B05B6F",
|
||||
"#8CD0FF",
|
||||
"#3B9700",
|
||||
"#04F757",
|
||||
"#C8A1A1",
|
||||
"#1E6E00",
|
||||
"#7900D7",
|
||||
"#A77500",
|
||||
"#6367A9",
|
||||
"#A05837",
|
||||
"#6B002C",
|
||||
"#772600",
|
||||
"#D790FF",
|
||||
"#9B9700",
|
||||
"#549E79",
|
||||
"#FFF69F",
|
||||
"#201625",
|
||||
"#72418F",
|
||||
"#BC23FF",
|
||||
"#99ADC0",
|
||||
"#3A2465",
|
||||
"#922329",
|
||||
"#5B4534",
|
||||
"#FDE8DC",
|
||||
"#404E55",
|
||||
"#0089A3",
|
||||
"#CB7E98",
|
||||
"#A4E804",
|
||||
"#324E72",
|
||||
"#6A3A4C",
|
||||
"#83AB58",
|
||||
"#001C1E",
|
||||
"#D1F7CE",
|
||||
"#004B28",
|
||||
"#C8D0F6",
|
||||
"#A3A489",
|
||||
"#806C66",
|
||||
"#222800",
|
||||
"#BF5650",
|
||||
"#E83000",
|
||||
"#66796D",
|
||||
"#DA007C",
|
||||
"#FF1A59",
|
||||
"#8ADBB4",
|
||||
"#1E0200",
|
||||
"#5B4E51",
|
||||
"#C895C5",
|
||||
"#320033",
|
||||
"#FF6832",
|
||||
"#66E1D3",
|
||||
"#CFCDAC",
|
||||
"#D0AC94",
|
||||
"#7ED379",
|
||||
"#012C58",
|
||||
"#7A7BFF",
|
||||
"#D68E01",
|
||||
"#353339",
|
||||
"#78AFA1",
|
||||
"#FEB2C6",
|
||||
"#75797C",
|
||||
"#837393",
|
||||
"#943A4D",
|
||||
"#B5F4FF",
|
||||
"#D2DCD5",
|
||||
"#9556BD",
|
||||
"#6A714A",
|
||||
"#001325",
|
||||
"#02525F",
|
||||
"#0AA3F7",
|
||||
"#E98176",
|
||||
"#DBD5DD",
|
||||
"#5EBCD1",
|
||||
"#3D4F44",
|
||||
"#7E6405",
|
||||
"#02684E",
|
||||
"#962B75",
|
||||
"#8D8546",
|
||||
"#9695C5",
|
||||
"#E773CE",
|
||||
"#D86A78",
|
||||
"#3E89BE",
|
||||
"#CA834E",
|
||||
"#518A87",
|
||||
"#5B113C",
|
||||
"#55813B",
|
||||
"#E704C4",
|
||||
"#00005F",
|
||||
"#A97399",
|
||||
"#4B8160",
|
||||
"#59738A",
|
||||
"#FF5DA7",
|
||||
"#F7C9BF",
|
||||
"#643127",
|
||||
"#513A01",
|
||||
"#6B94AA",
|
||||
"#51A058",
|
||||
"#A45B02",
|
||||
"#1D1702",
|
||||
"#E20027",
|
||||
"#E7AB63",
|
||||
"#4C6001",
|
||||
"#9C6966",
|
||||
"#64547B",
|
||||
"#97979E",
|
||||
"#006A66",
|
||||
"#391406",
|
||||
"#F4D749",
|
||||
"#0045D2",
|
||||
"#006C31",
|
||||
"#DDB6D0",
|
||||
"#7C6571",
|
||||
"#9FB2A4",
|
||||
"#00D891",
|
||||
"#15A08A",
|
||||
"#BC65E9",
|
||||
"#FFFFFE",
|
||||
"#C6DC99",
|
||||
"#203B3C",
|
||||
"#671190",
|
||||
"#6B3A64",
|
||||
"#F5E1FF",
|
||||
"#FFA0F2",
|
||||
"#CCAA35",
|
||||
"#374527",
|
||||
"#8BB400",
|
||||
"#797868",
|
||||
"#C6005A",
|
||||
"#3B000A",
|
||||
"#C86240",
|
||||
"#29607C",
|
||||
"#402334",
|
||||
"#7D5A44",
|
||||
"#CCB87C",
|
||||
"#B88183",
|
||||
"#AA5199",
|
||||
"#B5D6C3",
|
||||
"#A38469",
|
||||
"#9F94F0",
|
||||
"#A74571",
|
||||
"#B894A6",
|
||||
"#71BB8C",
|
||||
"#00B433",
|
||||
"#789EC9",
|
||||
"#6D80BA",
|
||||
"#953F00",
|
||||
"#5EFF03",
|
||||
"#E4FFFC",
|
||||
"#1BE177",
|
||||
"#BCB1E5",
|
||||
"#76912F",
|
||||
"#003109",
|
||||
"#0060CD",
|
||||
"#D20096",
|
||||
"#895563",
|
||||
"#29201D",
|
||||
"#5B3213",
|
||||
"#A76F42",
|
||||
"#89412E",
|
||||
"#1A3A2A",
|
||||
"#494B5A",
|
||||
"#A88C85",
|
||||
"#F4ABAA",
|
||||
"#A3F3AB",
|
||||
"#00C6C8",
|
||||
"#EA8B66",
|
||||
"#958A9F",
|
||||
"#BDC9D2",
|
||||
"#9FA064",
|
||||
"#BE4700",
|
||||
"#658188",
|
||||
"#83A485",
|
||||
"#453C23",
|
||||
"#47675D",
|
||||
"#3A3F00",
|
||||
"#061203",
|
||||
"#DFFB71",
|
||||
"#868E7E",
|
||||
"#98D058",
|
||||
"#6C8F7D",
|
||||
"#D7BFC2",
|
||||
"#3C3E6E",
|
||||
"#D83D66",
|
||||
"#2F5D9B",
|
||||
"#6C5E46",
|
||||
"#D25B88",
|
||||
"#5B656C",
|
||||
"#00B57F",
|
||||
"#545C46",
|
||||
"#866097",
|
||||
"#365D25",
|
||||
"#252F99",
|
||||
"#00CCFF",
|
||||
"#674E60",
|
||||
"#FC009C",
|
||||
"#92896B",
|
||||
];
|
||||
return $colors;
|
||||
|
||||
|
||||
|
||||
return $colors[$index];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||
*
|
||||
* @param string $hexCode Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
|
||||
* @param float $adjustPercent A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function adjustBrightness($hexCode, $adjustPercent) {
|
||||
$hexCode = ltrim($hexCode, '#');
|
||||
|
||||
if (strlen($hexCode) == 3) {
|
||||
$hexCode = $hexCode[0] . $hexCode[0] . $hexCode[1] . $hexCode[1] . $hexCode[2] . $hexCode[2];
|
||||
}
|
||||
|
||||
$hexCode = array_map('hexdec', str_split($hexCode, 2));
|
||||
|
||||
foreach ($hexCode as & $color) {
|
||||
$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
|
||||
$adjustAmount = ceil($adjustableLimit * $adjustPercent);
|
||||
|
||||
$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return '#' . implode($hexCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +428,26 @@ class Helper
|
||||
return $statuslabel_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of deployable status labels in an array to make a dropdown menu
|
||||
*
|
||||
* @todo This should probably be a selectlist, same as the other endpoints
|
||||
* and we should probably add to the API controllers to make sure that
|
||||
* the status_id submitted is actually really deployable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.1.0]
|
||||
* @return Array
|
||||
*/
|
||||
public static function deployableStatusLabelList()
|
||||
{
|
||||
$statuslabel_list = Statuslabel::where('deployable', '=', '1')->orderBy('default_label', 'desc')
|
||||
->orderBy('name','asc')
|
||||
->orderBy('deployable','desc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $statuslabel_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of status label types in an array to make a dropdown menu
|
||||
*
|
||||
@@ -225,8 +522,9 @@ class Helper
|
||||
*/
|
||||
public static function predefined_formats()
|
||||
{
|
||||
$keys = array_keys(CustomField::$PredefinedFormats);
|
||||
$keys = array_keys(CustomField::PREDEFINED_FORMATS);
|
||||
$stuff = array_combine($keys, $keys);
|
||||
|
||||
return $stuff;
|
||||
}
|
||||
|
||||
@@ -614,38 +912,32 @@ class Helper
|
||||
|
||||
$extension = substr(strrchr($filename,'.'),1);
|
||||
|
||||
if ($extension) {
|
||||
switch ($extension) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'gif':
|
||||
case 'png':
|
||||
return "fa fa-file-image-o";
|
||||
break;
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return "fa fa-file-word-o";
|
||||
break;
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return "fa fa-file-excel-o";
|
||||
break;
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
return "fa fa-file-archive-o";
|
||||
break;
|
||||
case 'pdf':
|
||||
return "fa fa-file-pdf-o";
|
||||
break;
|
||||
case 'txt':
|
||||
return "fa fa-file-text-o";
|
||||
break;
|
||||
case 'lic':
|
||||
return "fa fa-floppy-o";
|
||||
break;
|
||||
default:
|
||||
return "fa fa-file-o";
|
||||
}
|
||||
$allowedExtensionMap = [
|
||||
// Images
|
||||
'jpg' => 'fa fa-file-image-o',
|
||||
'jpeg' => 'fa fa-file-image-o',
|
||||
'gif' => 'fa fa-file-image-o',
|
||||
'png' => 'fa fa-file-image-o',
|
||||
// word
|
||||
'doc' => 'fa fa-file-word-o',
|
||||
'docx' => 'fa fa-file-word-o',
|
||||
// Excel
|
||||
'xls' => 'fa fa-file-excel-o',
|
||||
'xlsx' => 'fa fa-file-excel-o',
|
||||
// archive
|
||||
'zip' => 'fa fa-file-archive-o',
|
||||
'rar' => 'fa fa-file-archive-o',
|
||||
//Text
|
||||
'txt' => 'fa fa-file-text-o',
|
||||
'rtf' => 'fa fa-file-text-o',
|
||||
'xml' => 'fa fa-file-text-o',
|
||||
// Misc
|
||||
'pdf' => 'fa fa-file-pdf-o',
|
||||
'lic' => 'fa fa-file-floppy-o',
|
||||
];
|
||||
|
||||
if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
|
||||
return $allowedExtensionMap[$extension];
|
||||
}
|
||||
return "fa fa-file-o";
|
||||
}
|
||||
@@ -669,7 +961,80 @@ class Helper
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random encrypted password.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateEncyrptedPassword(): string
|
||||
{
|
||||
return bcrypt(Helper::generateUnencryptedPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random unencrypted password.
|
||||
*
|
||||
* @author Steffen Buehl <sb@sbuehl.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateUnencryptedPassword(): string
|
||||
{
|
||||
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
$password = '';
|
||||
for ( $i = 0; $i < 20; $i++ ) {
|
||||
$password .= substr( $chars, random_int( 0, strlen( $chars ) - 1 ), 1 );
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process base64 encoded image data and save it on supplied path
|
||||
*
|
||||
* @param string $image_data base64 encoded image data with mime type
|
||||
* @param string $save_path path to a folder where the image should be saved
|
||||
* @return string path to uploaded image or false if something went wrong
|
||||
*/
|
||||
public static function processUploadedImage(String $image_data, String $save_path) {
|
||||
if ($image_data != null && $save_path != null) {
|
||||
// After modification, the image is prefixed by mime info like the following:
|
||||
// data:image/jpeg;base64,; This causes the image library to be unhappy, so we need to remove it.
|
||||
$header = explode(';', $image_data, 2)[0];
|
||||
// Grab the image type from the header while we're at it.
|
||||
$extension = substr($header, strpos($header, '/')+1);
|
||||
// Start reading the image after the first comma, postceding the base64.
|
||||
$image = substr($image_data, strpos($image_data, ',')+1);
|
||||
|
||||
$file_name = str_random(25).".".$extension;
|
||||
|
||||
$directory= public_path($save_path);
|
||||
// Check if the uploads directory exists. If not, try to create it.
|
||||
if (!file_exists($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
$path = public_path($save_path.$file_name);
|
||||
|
||||
try {
|
||||
Image::make($image)->resize(500, 500, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $file_name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
207
app/Http/Controllers/Accessories/AccessoriesController.php
Executable file
207
app/Http/Controllers/Accessories/AccessoriesController.php
Executable file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Redirect;
|
||||
|
||||
/** This controller handles all actions related to Accessories for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class AccessoriesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the accessories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see AccessoriesController::getDatatable() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', Accessory::class);
|
||||
return view('accessories/index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view with a form to create a new Accessory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Accessory::class);
|
||||
$category_type = 'accessory';
|
||||
return view('accessories/edit')->with('category_type', $category_type)
|
||||
->with('item', new Accessory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and save new Accessory from form post
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param ImageUploadRequest $request
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize(Accessory::class);
|
||||
// create a new model instance
|
||||
$accessory = new Accessory();
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->user_id = Auth::user()->id;
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory created?
|
||||
if ($accessory->save()) {
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return view for the Accessory update form, prepopulated with existing data
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($accessoryId = null)
|
||||
{
|
||||
|
||||
if ($item = Accessory::find($accessoryId)) {
|
||||
$this->authorize($item);
|
||||
return view('accessories/edit', compact('item'))->with('category_type', 'accessory');
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save edited Accessory from form post
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$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 = request('purchase_cost');
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
|
||||
$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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given accessory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($accessoryId)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
|
||||
if ($accessory->hasUsers() > 0) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', array('count'=> $accessory->hasUsers())));
|
||||
}
|
||||
|
||||
if ($accessory->image) {
|
||||
try {
|
||||
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$accessory->delete();
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax table which contains
|
||||
* the content for the accessory detail view, which is generated in getDataView.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryID
|
||||
* @see AccessoriesController::getDataView() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($accessoryID = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories/view', compact('accessory'));
|
||||
}
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AccessoryCheckinController extends Controller
|
||||
{
|
||||
/**
|
||||
* Check the accessory back into inventory
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param integer $accessoryUserId
|
||||
* @param string $backto
|
||||
* @return View
|
||||
* @internal param int $accessoryId
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($accessoryUserId = null, $backto = null)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
$this->authorize('checkin', $accessory);
|
||||
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the item so that it can be checked out again to someone else
|
||||
*
|
||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param null $accessoryUserId
|
||||
* @param string $backto
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @internal param int $accessoryId
|
||||
*/
|
||||
public function store(Request $request, $accessoryUserId = null, $backto = null)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
$checkin_at = date('Y-m-d');
|
||||
if($request->filled('checkin_at')){
|
||||
$checkin_at = $request->input('checkin_at');
|
||||
}
|
||||
|
||||
// Was the accessory updated?
|
||||
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
||||
$return_to = e($accessory_user->assigned_to);
|
||||
|
||||
event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at));
|
||||
|
||||
return redirect()->route("accessories.show", $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
|
||||
class AccessoryCheckoutController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Return the form to checkout an Accessory to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
if ($accessory->category) {
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
// Get the dropdown of users and then pass it to the checkout view
|
||||
return view('accessories/checkout', compact('accessory'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the Accessory checkout information.
|
||||
*
|
||||
* If Slack is enabled and/or asset acceptance is enabled, it will also
|
||||
* trigger a Slack message and send an email.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(Request $request, $accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
if (!$user = User::find($request->input('assigned_to'))) {
|
||||
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->assigned_to = e($request->input('assigned_to'));
|
||||
|
||||
$accessory->users()->attach($accessory->id, [
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $request->get('assigned_to'),
|
||||
'note' => $request->input('note')
|
||||
]);
|
||||
|
||||
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
|
||||
|
||||
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Redirect;
|
||||
use Illuminate\Http\Request;
|
||||
use Slack;
|
||||
use Str;
|
||||
use View;
|
||||
use Image;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
|
||||
/** This controller handles all actions related to Accessories for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class AccessoriesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the accessories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see AccessoriesController::getDatatable() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('index', Accessory::class);
|
||||
return view('accessories/index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view with a form to create a new Accessory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return View
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->authorize('create', Accessory::class);
|
||||
$category_type = 'accessory';
|
||||
return view('accessories/edit')->with('category_type', $category_type)
|
||||
->with('item', new Accessory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and save new Accessory from form post
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize(Accessory::class);
|
||||
// create a new model instance
|
||||
$accessory = new Accessory();
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->user_id = Auth::user()->id;
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory = $request->handleImages($accessory,600, public_path().'/uploads/accessories');
|
||||
|
||||
|
||||
// Was the accessory created?
|
||||
if ($accessory->save()) {
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return view for the Accessory update form, prepopulated with existing data
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Request $request, $accessoryId = null)
|
||||
{
|
||||
|
||||
if ($item = Accessory::find($accessoryId)) {
|
||||
$this->authorize($item);
|
||||
$category_type = 'accessory';
|
||||
return view('accessories/edit', compact('item'))->with('category_type', $category_type);
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save edited Accessory from form post
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$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 = request('purchase_cost');
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
|
||||
$accessory = $request->handleImages($accessory,600, public_path().'/uploads/accessories');
|
||||
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given accessory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
*/
|
||||
public function destroy(Request $request, $accessoryId)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
|
||||
if ($accessory->hasUsers() > 0) {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', array('count'=> $accessory->hasUsers())));
|
||||
}
|
||||
$accessory->delete();
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax table which contains
|
||||
* the content for the accessory detail view, which is generated in getDataView.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryID
|
||||
* @see AccessoriesController::getDataView() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function show(Request $request, $accessoryID = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories/view', compact('accessory'));
|
||||
}
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the form to checkout an Accessory to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return View
|
||||
*/
|
||||
public function getCheckout(Request $request, $accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
if ($accessory->category) {
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
// Get the dropdown of users and then pass it to the checkout view
|
||||
return view('accessories/checkout', compact('accessory'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the Accessory checkout information.
|
||||
*
|
||||
* If Slack is enabled and/or asset acceptance is enabled, it will also
|
||||
* trigger a Slack message and send an email.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @return Redirect
|
||||
*/
|
||||
public function postCheckout(Request $request, $accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
if (!$user = User::find(Input::get('assigned_to'))) {
|
||||
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->assigned_to = e(Input::get('assigned_to'));
|
||||
|
||||
$accessory->users()->attach($accessory->id, [
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $request->get('assigned_to')
|
||||
]);
|
||||
|
||||
$logaction = $accessory->logCheckout(e(Input::get('note')), $user);
|
||||
|
||||
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
|
||||
|
||||
$data['log_id'] = $logaction->id;
|
||||
$data['eula'] = $accessory->getEula();
|
||||
$data['first_name'] = $user->first_name;
|
||||
$data['item_name'] = $accessory->name;
|
||||
$data['checkout_date'] = $logaction->created_at;
|
||||
$data['item_tag'] = '';
|
||||
$data['expected_checkin'] = '';
|
||||
$data['note'] = $logaction->note;
|
||||
$data['require_acceptance'] = $accessory->requireAcceptance();
|
||||
|
||||
// Redirect to the new accessory page
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the accessory back into inventory
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param integer $accessoryUserId
|
||||
* @param string $backto
|
||||
* @return View
|
||||
* @internal param int $accessoryId
|
||||
*/
|
||||
public function getCheckin(Request $request, $accessoryUserId = null, $backto = null)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
$this->authorize('checkin', $accessory);
|
||||
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check in the item so that it can be checked out again to someone else
|
||||
*
|
||||
* @uses Accessory::checkin_email() to determine if an email can and should be sent
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @param integer $accessoryUserId
|
||||
* @param string $backto
|
||||
* @return Redirect
|
||||
* @internal param int $accessoryId
|
||||
*/
|
||||
public function postCheckin(Request $request, $accessoryUserId = null, $backto = null)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
$return_to = e($accessory_user->assigned_to);
|
||||
$logaction = $accessory->logCheckin(User::find($return_to), e(Input::get('note')));
|
||||
|
||||
// Was the accessory updated?
|
||||
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
|
||||
if (!is_null($accessory_user->assigned_to)) {
|
||||
$user = User::find($accessory_user->assigned_to);
|
||||
}
|
||||
|
||||
$data['log_id'] = $logaction->id;
|
||||
$data['first_name'] = e($user->first_name);
|
||||
$data['last_name'] = e($user->last_name);
|
||||
$data['item_name'] = e($accessory->name);
|
||||
$data['checkin_date'] = e($logaction->created_at);
|
||||
$data['item_tag'] = '';
|
||||
$data['note'] = e($logaction->note);
|
||||
|
||||
if ($backto=='user') {
|
||||
return redirect()->route("users.show", $return_to)->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
}
|
||||
return redirect()->route("accessories.show", $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
127
app/Http/Controllers/Account/AcceptanceController.php
Normal file
127
app/Http/Controllers/Account/AcceptanceController.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\Events\CheckoutAccepted;
|
||||
use App\Events\CheckoutDeclined;
|
||||
use App\Events\ItemAccepted;
|
||||
use App\Events\ItemDeclined;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contracts\Acceptable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AcceptanceController extends Controller {
|
||||
|
||||
/**
|
||||
* Show a listing of pending checkout acceptances for the current user
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index() {
|
||||
$acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
|
||||
|
||||
return view('account/accept.index', compact('acceptances'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a form to either accept or decline the checkout acceptance
|
||||
*
|
||||
* @param int $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function create($id) {
|
||||
|
||||
$acceptance = CheckoutAcceptance::find($id);
|
||||
|
||||
if (is_null($acceptance)) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if (! $acceptance->isPending()) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
||||
}
|
||||
|
||||
if (! $acceptance->isCheckedOutTo(Auth::user())) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
||||
}
|
||||
|
||||
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
||||
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
||||
}
|
||||
|
||||
return view('account/accept.create', compact('acceptance'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the accept/decline of the checkout acceptance
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Redirect
|
||||
*/
|
||||
public function store(Request $request, $id) {
|
||||
|
||||
$acceptance = CheckoutAcceptance::find($id);
|
||||
|
||||
if (is_null($acceptance)) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if (! $acceptance->isPending()) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
||||
}
|
||||
|
||||
if (! $acceptance->isCheckedOutTo(Auth::user())) {
|
||||
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
||||
}
|
||||
|
||||
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
||||
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
||||
}
|
||||
|
||||
if (!$request->filled('asset_acceptance')) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signature and save it
|
||||
*/
|
||||
|
||||
if (!Storage::exists('private_uploads/signatures')) Storage::makeDirectory('private_uploads/signatures', 775);
|
||||
|
||||
|
||||
$sig_filename = '';
|
||||
if ($request->filled('signature_output')) {
|
||||
$sig_filename = "siglog-" .Str::uuid() . '-'.date('Y-m-d-his').".png";
|
||||
$data_uri = e($request->input('signature_output'));
|
||||
$encoded_image = explode(",", $data_uri);
|
||||
$decoded_image = base64_decode($encoded_image[1]);
|
||||
Storage::put('private_uploads/signatures/'.$sig_filename, (string)$decoded_image);
|
||||
}
|
||||
|
||||
|
||||
if ($request->input('asset_acceptance') == 'accepted') {
|
||||
|
||||
$acceptance->accept($sig_filename);
|
||||
|
||||
event(new CheckoutAccepted($acceptance));
|
||||
|
||||
$return_msg = trans('admin/users/message.accepted');
|
||||
|
||||
} else {
|
||||
|
||||
$acceptance->decline($sig_filename);
|
||||
|
||||
event(new CheckoutDeclined($acceptance));
|
||||
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
|
||||
}
|
||||
|
||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Accessory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AccessoriesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AccessoriesController extends Controller
|
||||
{
|
||||
@@ -141,18 +142,36 @@ class AccessoriesController extends Controller
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function checkedout($id)
|
||||
public function checkedout($id, Request $request)
|
||||
{
|
||||
$this->authorize('view', Accessory::class);
|
||||
|
||||
$accessory = Accessory::findOrFail($id);
|
||||
$accessory = Accessory::with('lastCheckout')->findOrFail($id);
|
||||
if (!Company::isCurrentUserHasAccess($accessory)) {
|
||||
return ['total' => 0, 'rows' => []];
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$accessory_users = $accessory->users;
|
||||
$total = $accessory_users->count();
|
||||
|
||||
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory_users, $total);
|
||||
if($total < $offset){
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessory_users = $accessory->users()
|
||||
->where('first_name', 'like', '%'.$request->input('search').'%')
|
||||
->orWhere('last_name', 'like', '%'.$request->input('search').'%')
|
||||
->get();
|
||||
$total = $accessory_users->count();
|
||||
}
|
||||
|
||||
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +186,7 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Accessory::class);
|
||||
$this->authorize('update', Accessory::class);
|
||||
$accessory = Accessory::findOrFail($id);
|
||||
$accessory->fill($request->all());
|
||||
|
||||
@@ -290,4 +309,31 @@ class AccessoriesController extends Controller
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
* @see \App\Http\Transformers\SelectlistTransformer
|
||||
*
|
||||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$accessories = Accessory::select([
|
||||
'accessories.id',
|
||||
'accessories.name'
|
||||
]);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessories = $accessories->where('accessories.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
$accessories = $accessories->orderBy('name', 'ASC')->paginate(50);
|
||||
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($accessories);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Models\AssetMaintenance;
|
||||
use App\Models\Company;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
|
||||
@@ -34,10 +33,10 @@ class AssetMaintenancesController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$maintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company', 'admin');
|
||||
$maintenances = AssetMaintenance::with('asset', 'asset.model','asset.location', 'supplier', 'asset.company', 'admin');
|
||||
|
||||
if (Input::has('search')) {
|
||||
$maintenances = $maintenances->TextSearch(e($request->input('search')));
|
||||
if ($request->filled('search')) {
|
||||
$maintenances = $maintenances->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_id')) {
|
||||
@@ -65,8 +64,8 @@ class AssetMaintenancesController extends Controller
|
||||
'asset_name',
|
||||
'user_id'
|
||||
];
|
||||
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
|
||||
switch ($sort) {
|
||||
case 'user_id':
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Asset;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetModelsTransformer;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to asset models for
|
||||
@@ -30,7 +30,20 @@ class AssetModelsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', AssetModel::class);
|
||||
$allowed_columns = ['id','image','name','model_number','eol','notes','created_at','manufacturer','assets_count'];
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'image',
|
||||
'name',
|
||||
'model_number',
|
||||
'eol',
|
||||
'notes',
|
||||
'created_at',
|
||||
'manufacturer',
|
||||
'requestable',
|
||||
'assets_count',
|
||||
'category'
|
||||
];
|
||||
|
||||
$assetmodels = AssetModel::select([
|
||||
'models.id',
|
||||
@@ -38,6 +51,7 @@ class AssetModelsController extends Controller
|
||||
'models.name',
|
||||
'model_number',
|
||||
'eol',
|
||||
'requestable',
|
||||
'models.notes',
|
||||
'models.created_at',
|
||||
'category_id',
|
||||
@@ -74,13 +88,14 @@ class AssetModelsController extends Controller
|
||||
case 'manufacturer':
|
||||
$assetmodels->OrderManufacturer($order);
|
||||
break;
|
||||
case 'category':
|
||||
$assetmodels->OrderCategory($order);
|
||||
break;
|
||||
default:
|
||||
$assetmodels->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$total = $assetmodels->count();
|
||||
$assetmodels = $assetmodels->skip($offset)->take($limit)->get();
|
||||
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
|
||||
@@ -153,7 +168,19 @@ class AssetModelsController extends Controller
|
||||
$this->authorize('update', AssetModel::class);
|
||||
$assetmodel = AssetModel::findOrFail($id);
|
||||
$assetmodel->fill($request->all());
|
||||
$assetmodel->fieldset_id = $request->get("custom_fieldset_id");
|
||||
|
||||
/**
|
||||
* Allow custom_fieldset_id to override and populate fieldset_id.
|
||||
* This is stupid, but required for legacy API support.
|
||||
*
|
||||
* I have no idea why we manually overrode that field name
|
||||
* in previous versions. I assume there was a good reason for
|
||||
* it, but I'll be damned if I can think of one. - snipe
|
||||
*/
|
||||
if ($request->filled('custom_fieldset_id')) {
|
||||
$assetmodel->fieldset_id = $request->get("custom_fieldset_id");
|
||||
}
|
||||
|
||||
|
||||
if ($assetmodel->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
|
||||
@@ -182,7 +209,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
if ($assetmodel->image) {
|
||||
try {
|
||||
unlink(public_path().'/uploads/models/'.$assetmodel->image);
|
||||
Storage::disk('public')->delete('assetmodels/'.$assetmodel->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::info($e);
|
||||
}
|
||||
@@ -226,20 +253,20 @@ class AssetModelsController extends Controller
|
||||
$assetmodel->use_text = '';
|
||||
|
||||
if ($settings->modellistCheckedValue('category')) {
|
||||
$assetmodel->use_text .= (($assetmodel->category) ? e($assetmodel->category->name).' - ' : '');
|
||||
$assetmodel->use_text .= (($assetmodel->category) ? $assetmodel->category->name.' - ' : '');
|
||||
}
|
||||
|
||||
if ($settings->modellistCheckedValue('manufacturer')) {
|
||||
$assetmodel->use_text .= (($assetmodel->manufacturer) ? e($assetmodel->manufacturer->name).' ' : '');
|
||||
$assetmodel->use_text .= (($assetmodel->manufacturer) ? $assetmodel->manufacturer->name.' ' : '');
|
||||
}
|
||||
|
||||
$assetmodel->use_text .= e($assetmodel->name);
|
||||
$assetmodel->use_text .= $assetmodel->name;
|
||||
|
||||
if (($settings->modellistCheckedValue('model_number')) && ($assetmodel->model_number!='')) {
|
||||
$assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')';
|
||||
$assetmodel->use_text .= ' (#'.$assetmodel->model_number.')';
|
||||
}
|
||||
|
||||
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? url('/').'/uploads/models/'.$assetmodel->image : null;
|
||||
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($assetmodels);
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetRequest;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\LicensesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Paginator;
|
||||
use Response;
|
||||
use Slack;
|
||||
use Str;
|
||||
use TCPDF;
|
||||
use Validator;
|
||||
use View;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
|
||||
|
||||
/**
|
||||
@@ -148,7 +142,6 @@ class AssetsController extends Controller
|
||||
|
||||
$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
|
||||
// case we override with the actual count, so we should return 0 items.
|
||||
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('offset', 0);
|
||||
@@ -181,7 +174,7 @@ class AssetsController extends Controller
|
||||
// I am sad. :(
|
||||
switch ($request->input('status')) {
|
||||
case 'Deleted':
|
||||
$assets->withTrashed()->Deleted();
|
||||
$assets->onlyTrashed();
|
||||
break;
|
||||
case 'Pending':
|
||||
$assets->join('status_labels AS status_alias',function ($join) {
|
||||
@@ -299,6 +292,7 @@ class AssetsController extends Controller
|
||||
|
||||
$total = $assets->count();
|
||||
$assets = $assets->skip($offset)->take($limit)->get();
|
||||
// dd($assets);
|
||||
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||
}
|
||||
|
||||
@@ -313,7 +307,7 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function showByTag($tag)
|
||||
{
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->where('asset_tag',$tag)->first()) {
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag',$tag)->first()) {
|
||||
$this->authorize('view', $asset);
|
||||
return (new AssetsTransformer)->transformAsset($asset);
|
||||
}
|
||||
@@ -334,8 +328,7 @@ class AssetsController extends Controller
|
||||
$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 (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
|
||||
@@ -352,13 +345,22 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as userRequests_count')->findOrFail($id)) {
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
|
||||
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
|
||||
$this->authorize('view', $asset);
|
||||
return (new AssetsTransformer)->transformAsset($asset);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public function licenses($id)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$this->authorize('view', License::class);
|
||||
$asset = Asset::where('id', $id)->withTrashed()->first();
|
||||
$licenses = $asset->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -380,7 +382,7 @@ class AssetsController extends Controller
|
||||
'assets.assigned_to',
|
||||
'assets.assigned_type',
|
||||
'assets.status_id'
|
||||
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(),'company_id', 'assets');
|
||||
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets');
|
||||
|
||||
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
|
||||
$assets = $assets->RTD();
|
||||
@@ -426,7 +428,7 @@ class AssetsController extends Controller
|
||||
* @since [v4.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function store(AssetRequest $request)
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('create', Asset::class);
|
||||
@@ -455,6 +457,22 @@ class AssetsController extends Controller
|
||||
$asset->rtd_location_id = $request->get('rtd_location_id', null);
|
||||
$asset->location_id = $request->get('rtd_location_id', null);
|
||||
|
||||
if ($request->has('image_source') && $request->input('image_source') != "") {
|
||||
$saved_image_path = Helper::processUploadedImage(
|
||||
$request->input('image_source'), 'uploads/assets/'
|
||||
);
|
||||
|
||||
if (!$saved_image_path) {
|
||||
return response()->json(Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.create.error')
|
||||
), 200);
|
||||
}
|
||||
|
||||
$asset->image = $saved_image_path;
|
||||
}
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handled through the AssetRequest form request
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
@@ -482,6 +500,11 @@ class AssetsController extends Controller
|
||||
if (isset($target)) {
|
||||
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
|
||||
}
|
||||
|
||||
if ($asset->image) {
|
||||
$asset->image = $asset->getImageUrl();
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
|
||||
}
|
||||
|
||||
@@ -502,15 +525,40 @@ class AssetsController extends Controller
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if ($asset = Asset::find($id)) {
|
||||
|
||||
$asset->fill($request->all());
|
||||
|
||||
($request->filled('model_id')) ?
|
||||
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
|
||||
($request->filled('rtd_location_id')) ?
|
||||
$asset->location_id = $request->get('rtd_location_id') : '';
|
||||
($request->filled('company_id')) ?
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : null;
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
|
||||
|
||||
($request->filled('rtd_location_id')) ?
|
||||
$asset->location_id = $request->get('rtd_location_id') : null;
|
||||
|
||||
|
||||
if ($request->filled('image_source')) {
|
||||
if ($request->input('image_source') == "") {
|
||||
($request->filled('rtd_location_id')) ?
|
||||
$asset->location_id = $request->get('rtd_location_id') : null;
|
||||
$asset->image = null;
|
||||
} else {
|
||||
$saved_image_path = Helper::processUploadedImage(
|
||||
$request->input('image_source'), 'uploads/assets/'
|
||||
);
|
||||
|
||||
if (!$saved_image_path) {
|
||||
return response()->json(Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.update.error')
|
||||
), 200);
|
||||
}
|
||||
|
||||
$asset->image = $saved_image_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Update custom fields
|
||||
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
|
||||
@@ -531,7 +579,7 @@ class AssetsController extends Controller
|
||||
if ($asset->save()) {
|
||||
|
||||
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
|
||||
$location = $target->location_id;
|
||||
$location = $target->location_id;
|
||||
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
|
||||
$location = $target->location_id;
|
||||
|
||||
@@ -546,6 +594,10 @@ class AssetsController extends Controller
|
||||
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
|
||||
}
|
||||
|
||||
if ($asset->image) {
|
||||
$asset->image = $asset->getImageUrl();
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
@@ -650,6 +702,7 @@ class AssetsController extends Controller
|
||||
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
|
||||
// TODO: Follow up here. WTF. Commented out for now.
|
||||
|
||||
|
||||
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
|
||||
// $asset->location_id = $target->rtd_location_id;
|
||||
// }
|
||||
@@ -700,12 +753,12 @@ class AssetsController extends Controller
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
if (Input::has('status_id')) {
|
||||
$asset->status_id = Input::get('status_id');
|
||||
if ($request->has('status_id')) {
|
||||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
if ($asset->save()) {
|
||||
$asset->logCheckin($target, e(request('note')));
|
||||
$asset->logCheckin($target, e($request->input('note')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
|
||||
}
|
||||
|
||||
@@ -736,21 +789,35 @@ class AssetsController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||
}
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
|
||||
$asset = Asset::where('asset_tag','=', $request->input('asset_tag'))->first();
|
||||
|
||||
|
||||
if ($asset) {
|
||||
// We don't want to log this as a normal update, so let's bypass that
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d h:i:s');
|
||||
$asset->next_audit_date = $dt;
|
||||
|
||||
if ($request->filled('next_audit_date')) {
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
}
|
||||
|
||||
// Check to see if they checked the box to update the physical location,
|
||||
// not just note it in the audit notes
|
||||
if ($request->input('update_location')=='1') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
if ($asset->save()) {
|
||||
$log = $asset->logAudit(request('note'),request('location_id'));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||
'asset_tag'=> e($asset->asset_tag),
|
||||
'note'=> e($request->input('note')),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($log->calcNextAuditDate())
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date)
|
||||
], trans('admin/hardware/message.audit.success')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Category;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\CategoriesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Category;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
{
|
||||
@@ -121,14 +122,10 @@ class CategoriesController extends Controller
|
||||
$this->authorize('delete', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
|
||||
if ($category->has_models() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'model'])));
|
||||
} elseif ($category->accessories()->count() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory'])));
|
||||
} elseif ($category->consumables()->count() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable'])));
|
||||
} elseif ($category->components()->count() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'component'])));
|
||||
if (!$category->isDeletable()) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>$category->category_type]))
|
||||
);
|
||||
}
|
||||
$category->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
||||
@@ -163,7 +160,7 @@ class CategoriesController extends Controller
|
||||
// This lets us have more flexibility in special cases like assets, where
|
||||
// they may not have a ->name value but we want to display something anyway
|
||||
foreach ($categories as $category) {
|
||||
$category->use_image = ($category->image) ? url('/').'/uploads/categories/'.$category->image : null;
|
||||
$category->use_image = ($category->image) ? Storage::disk('public')->url('categories/'.$category->image, $category->image) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($categories);
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Transformers\CompaniesTransformer;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Company;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\CompaniesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CompaniesController extends Controller
|
||||
{
|
||||
@@ -47,7 +48,7 @@ class CompaniesController extends Controller
|
||||
|
||||
// Check to make sure the limit is not higher than the max allowed
|
||||
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
|
||||
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$companies->orderBy($sort, $order);
|
||||
@@ -132,28 +133,17 @@ class CompaniesController extends Controller
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
$this->authorize('delete', $company);
|
||||
$this->authorize('delete', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
$this->authorize('delete', $company);
|
||||
|
||||
try {
|
||||
$company->delete();
|
||||
if ( !$company->isDeletable() ) {
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('success', null, trans('admin/companies/message.delete.success')));
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
/*
|
||||
* NOTE: This happens when there's a foreign key constraint violation
|
||||
* For example when rows in other tables are referencing this company
|
||||
*/
|
||||
if ($exception->getCode() == 23000) {
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
|
||||
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
$company->delete();
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('success', null, trans('admin/companies/message.delete.success')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +173,7 @@ class CompaniesController extends Controller
|
||||
// This lets us have more flexibility in special cases like assets, where
|
||||
// they may not have a ->name value but we want to display something anyway
|
||||
foreach ($companies as $company) {
|
||||
$company->use_image = ($company->image) ? url('/').'/uploads/companies/'.$company->image : null;
|
||||
$company->use_image = ($company->image) ? Storage::disk('public')->url('companies/'.$company->image, $company->image) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($companies);
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\ComponentsTransformer;
|
||||
use App\Http\Transformers\ComponentsAssetsTransformer;
|
||||
use App\Models\Component;
|
||||
use App\Models\Company;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ComponentsTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ComponentsController extends Controller
|
||||
{
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ConsumablesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use App\Http\Transformers\ConsumablesTransformer;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ConsumablesController extends Controller
|
||||
{
|
||||
@@ -162,7 +163,7 @@ class ConsumablesController extends Controller
|
||||
* Returns a JSON response containing details on the users associated with this consumable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ConsumablesController::getView() method that returns the form.
|
||||
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
|
||||
* @since [v1.0]
|
||||
* @param int $consumableId
|
||||
* @return array
|
||||
@@ -248,4 +249,28 @@ class ConsumablesController extends Controller
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'No consumables remaining'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
* @see \App\Http\Transformers\SelectlistTransformer
|
||||
*
|
||||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$consumables = Consumable::select([
|
||||
'consumables.id',
|
||||
'consumables.name'
|
||||
]);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$consumables = $consumables->where('consumables.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
$consumables = $consumables->orderBy('name', 'ASC')->paginate(50);
|
||||
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($consumables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Models\CustomField;
|
||||
use App\Models\CustomFieldset;
|
||||
use Illuminate\Http\Request;
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CustomFieldsController extends Controller
|
||||
{
|
||||
@@ -59,9 +58,9 @@ class CustomFieldsController extends Controller
|
||||
{
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($id);
|
||||
|
||||
|
||||
/**
|
||||
* Updated values for the field,
|
||||
* Updated values for the field,
|
||||
* without the "field_encrypted" flag, preventing the change of encryption status
|
||||
* @var array
|
||||
*/
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use View;
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\CustomField;
|
||||
use Input;
|
||||
use Validator;
|
||||
use Redirect;
|
||||
use App\Models\AssetModel;
|
||||
use Lang;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Transformers\CustomFieldsTransformer;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\CustomFieldsetsTransformer;
|
||||
use App\Http\Requests\AssetRequest;
|
||||
use App\Http\Transformers\CustomFieldsTransformer;
|
||||
use App\Models\CustomFieldset;
|
||||
use Illuminate\Http\Request;
|
||||
use Redirect;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Custom Asset Fieldsets for
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Department;
|
||||
use App\Http\Transformers\DepartmentsTransformer;
|
||||
use App\Helpers\Helper;
|
||||
use Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\DepartmentsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Department;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DepartmentsController extends Controller
|
||||
{
|
||||
@@ -105,14 +106,36 @@ class DepartmentsController extends Controller
|
||||
return (new DepartmentsTransformer)->transformDepartment($department);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', Department::class);
|
||||
$department = Department::findOrFail($id);
|
||||
$department->fill($request->all());
|
||||
|
||||
if ($department->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Validates and deletes selected location.
|
||||
* Validates and deletes selected department.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $locationId
|
||||
* @since [v1.0]
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy($id)
|
||||
@@ -157,34 +180,11 @@ class DepartmentsController extends Controller
|
||||
// This lets us have more flexibility in special cases like assets, where
|
||||
// they may not have a ->name value but we want to display something anyway
|
||||
foreach ($departments as $department) {
|
||||
$department->use_image = ($department->image) ? url('/').'/uploads/departments/'.$department->image : null;
|
||||
$department->use_image = ($department->image) ? Storage::disk('public')->url('departments/'.$department->image, $department->image) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($departments);
|
||||
|
||||
}
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
|
||||
* @since [v4.0]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', Department::class);
|
||||
$departments = Department::findOrFail($id);
|
||||
$departments->fill($request->all());
|
||||
|
||||
if ($departments->save()) {
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer())->transformdepartment($departments), trans('admin/departments/message.update.success')));
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('error', null, $departments->getErrors()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Depreciation;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\DepreciationsTransformer;
|
||||
use App\Models\Depreciation;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DepreciationsController extends Controller
|
||||
{
|
||||
@@ -20,7 +20,7 @@ class DepreciationsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Depreciation::class);
|
||||
$allowed_columns = ['id','name','created_at'];
|
||||
$allowed_columns = ['id','name','months','created_at'];
|
||||
|
||||
$depreciations = Depreciation::select('id','name','months','user_id','created_at','updated_at');
|
||||
|
||||
@@ -115,10 +115,10 @@ class DepreciationsController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Depreciation::class);
|
||||
$depreciation = Depreciation::findOrFail($id);
|
||||
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);
|
||||
$this->authorize('delete', $depreciation);
|
||||
|
||||
if ($depreciation->has_models() > 0) {
|
||||
if ($depreciation->models_count > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('admin/depreciations/message.assoc_users')));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Group;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\GroupsTransformer;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GroupsController extends Controller
|
||||
{
|
||||
|
||||
@@ -6,15 +6,15 @@ use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ItemImportRequest;
|
||||
use App\Http\Transformers\ImportsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Import;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Artisan;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Artisan;
|
||||
use App\Models\Asset;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
@@ -41,7 +41,7 @@ class ImportController extends Controller
|
||||
{
|
||||
$this->authorize('import');
|
||||
if (!config('app.lock_passwords')) {
|
||||
$files = Input::file('files');
|
||||
$files = Request::file('files');
|
||||
$path = config('app.private_uploads').'/imports';
|
||||
$results = [];
|
||||
$import = new Import;
|
||||
@@ -112,14 +112,14 @@ class ImportController extends Controller
|
||||
/**
|
||||
* Processes the specified Import.
|
||||
*
|
||||
* @param \App\Import $import
|
||||
* @param int $import_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function process(ItemImportRequest $request, $import_id)
|
||||
{
|
||||
$this->authorize('import');
|
||||
// Run a backup immediately before processing
|
||||
|
||||
// Run a backup immediately before processing
|
||||
if ($request->has('run-backup')) {
|
||||
\Log::debug('Backup manually requested via importer');
|
||||
Artisan::call('backup:run');
|
||||
@@ -162,19 +162,26 @@ class ImportController extends Controller
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Import $import
|
||||
* @param int $import_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($import_id)
|
||||
{
|
||||
$this->authorize('import');
|
||||
$import = Import::find($import_id);
|
||||
try {
|
||||
unlink(config('app.private_uploads').'/imports/'.$import->file_path);
|
||||
$import->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.import.file_delete_success')));
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.import.file_delete_error')), 500);
|
||||
$this->authorize('create', Asset::class);
|
||||
|
||||
if ($import = Import::find($import_id)) {
|
||||
try {
|
||||
// Try to delete the file
|
||||
Storage::delete('imports/'.$import->file_path);
|
||||
$import->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.import.file_delete_success')));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// If the file delete didn't work, remove it from the database anyway and return a warning
|
||||
$import->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\LicenseSeatsTransformer;
|
||||
use App\Http\Transformers\LicensesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
@@ -102,11 +103,33 @@ class LicensesController extends Controller
|
||||
case 'category':
|
||||
$licenses = $licenses->leftJoin('categories', 'licenses.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
|
||||
break;
|
||||
case 'depreciation':
|
||||
$licenses = $licenses->leftJoin('depreciations', 'licenses.depreciation_id', '=', 'depreciations.id')->orderBy('depreciations.name', $order);
|
||||
break;
|
||||
case 'company':
|
||||
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
|
||||
break;
|
||||
default:
|
||||
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','category','license_name','license_email','free_seats_count','seats'];
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'name',
|
||||
'purchase_cost',
|
||||
'expiration_date',
|
||||
'purchase_order',
|
||||
'order_number',
|
||||
'notes',
|
||||
'purchase_date',
|
||||
'serial',
|
||||
'company',
|
||||
'category',
|
||||
'license_name',
|
||||
'license_email',
|
||||
'free_seats_count',
|
||||
'seats',
|
||||
'termination_date',
|
||||
'depreciation_id'
|
||||
];
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$licenses = $licenses->orderBy($sort, $order);
|
||||
break;
|
||||
@@ -155,7 +178,7 @@ class LicensesController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', License::class);
|
||||
$license = License::findOrFail($id);
|
||||
$license = License::withCount('freeSeats')->findOrFail($id);
|
||||
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
|
||||
return (new LicensesTransformer)->transformLicense($license);
|
||||
}
|
||||
@@ -230,8 +253,8 @@ class LicensesController extends Controller
|
||||
|
||||
$this->authorize('view', $license);
|
||||
|
||||
$seats = LicenseSeat::where('license_seats.license_id', $licenseId)
|
||||
->with('license', 'user', 'asset', 'user.department');
|
||||
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
|
||||
->where('license_seats.license_id', $licenseId);
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
@@ -241,10 +264,11 @@ class LicensesController extends Controller
|
||||
$seats->orderBy('id', $order);
|
||||
}
|
||||
|
||||
$total = $seats->count();
|
||||
$offset = (($seats) && (request('offset') > $total)) ? 0 : request('offset', 0);
|
||||
$offset = (($seats) && (request('offset') > $seats->count())) ? 0 : request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$total = $seats->count();
|
||||
|
||||
$seats = $seats->skip($offset)->take($limit)->get();
|
||||
|
||||
if ($seats) {
|
||||
@@ -257,5 +281,29 @@ class LicensesController extends Controller
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
* @see \App\Http\Transformers\SelectlistTransformer
|
||||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$licenses = License::select([
|
||||
'licenses.id',
|
||||
'licenses.name'
|
||||
]);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$licenses = $licenses->where('licenses.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
$licenses = $licenses->orderBy('name', 'ASC')->paginate(50);
|
||||
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($licenses);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$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'];
|
||||
'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'];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
'locations.id',
|
||||
@@ -42,19 +42,19 @@ class LocationsController extends Controller
|
||||
'locations.created_at',
|
||||
'locations.updated_at',
|
||||
'locations.image',
|
||||
'locations.ldap_ou',
|
||||
'locations.currency'
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count');
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$locations = $locations->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
// case we override with the actual count, so we should return 0 items.
|
||||
$offset = (($locations) && ($request->get('offset') > $locations->count())) ? $locations->count() : $request->get('offset', 0);
|
||||
|
||||
$offset = (($locations) && (request('offset') > $locations->count())) ? 0 : request('offset', 0);
|
||||
|
||||
// Check to make sure the limit is not higher than the max allowed
|
||||
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
|
||||
@@ -143,22 +143,19 @@ class LocationsController extends Controller
|
||||
* @since [v4.0]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', Location::class);
|
||||
$location = Location::findOrFail($id);
|
||||
|
||||
if ($request->input('parent_id') == $id) {
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'A location cannot be its own parent. Please select a different parent ID.'));
|
||||
}
|
||||
|
||||
|
||||
$location->fill($request->all());
|
||||
|
||||
if ($location->save()) {
|
||||
|
||||
if ($location->isValid()) {
|
||||
|
||||
$location->save();
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'success',
|
||||
@@ -183,6 +180,10 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('delete', Location::class);
|
||||
$location = Location::findOrFail($id);
|
||||
if(!$location->isDeletable()) {
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
|
||||
}
|
||||
$this->authorize('delete', $location);
|
||||
$location->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/locations/message.delete.success')));
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Http\Transformers\DatatablesTransformer;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ManufacturersTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Manufacturer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ManufacturersController extends Controller
|
||||
{
|
||||
@@ -123,11 +123,21 @@ class ManufacturersController extends Controller
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
|
||||
$this->authorize('delete', Manufacturer::class);
|
||||
$manufacturer = Manufacturer::findOrFail($id);
|
||||
$this->authorize('delete', $manufacturer);
|
||||
$manufacturer->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||
|
||||
if ($manufacturer->isDeletable()) {
|
||||
$manufacturer->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -159,7 +169,7 @@ class ManufacturersController extends Controller
|
||||
// they may not have a ->name value but we want to display something anyway
|
||||
foreach ($manufacturers as $manufacturer) {
|
||||
$manufacturer->use_text = $manufacturer->name;
|
||||
$manufacturer->use_image = ($manufacturer->image) ? url('/').'/uploads/manufacturers/'.$manufacturer->image : null;
|
||||
$manufacturer->use_image = ($manufacturer->image) ? Storage::disk('public')->url('manufacturers/'.$manufacturer->image, $manufacturer->image) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($manufacturers);
|
||||
|
||||
449
app/Http/Controllers/Api/PredefinedKitsController.php
Normal file
449
app/Http/Controllers/Api/PredefinedKitsController.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\PredefinedKitsTransformer;
|
||||
use App\Models\PredefinedKit;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
|
||||
*/
|
||||
class PredefinedKitsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$allowed_columns = ['id', 'name'];
|
||||
|
||||
$kits = PredefinedKit::query();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$kits = $kits->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
|
||||
$kits->orderBy($sort, $order);
|
||||
|
||||
$total = $kits->count();
|
||||
$kits = $kits->skip($offset)->take($limit)->get();
|
||||
return (new PredefinedKitsTransformer)->transformPredefinedKits($kits, $total);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', PredefinedKit::class);
|
||||
$kit = new PredefinedKit;
|
||||
$kit->fill($request->all());
|
||||
|
||||
if ($kit->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.create_success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $kit->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($id);
|
||||
return (new PredefinedKitsTransformer)->transformPredefinedKit($kit);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id kit id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($id);
|
||||
$kit->fill($request->all());
|
||||
|
||||
if ($kit->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.update_success'))); // TODO: trans
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $kit->getErrors()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($id);
|
||||
|
||||
// Delete childs
|
||||
$kit->models()->detach();
|
||||
$kit->licenses()->detach();
|
||||
$kit->consumables()->detach();
|
||||
$kit->accessories()->detach();
|
||||
|
||||
$kit->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/kits/general.delete_success'))); // TODO: trans
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
* @see \App\Http\Transformers\SelectlistTransformer
|
||||
*
|
||||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$kits = PredefinedKit::select([
|
||||
'id',
|
||||
'name'
|
||||
]);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$kits = $kits->where('name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
$kits = $kits->orderBy('name', 'ASC')->paginate(50);
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($kits);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function indexLicenses($kit_id) {
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$licenses = $kit->licenses;
|
||||
return (new PredefinedKitsTransformer)->transformElements($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function storeLicense(Request $request, $kit_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
|
||||
$license_id = $request->get('license');
|
||||
$relation = $kit->licenses();
|
||||
if( $relation->find($license_id) ) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['license' => 'License already attached to kit']));
|
||||
}
|
||||
|
||||
$relation->attach( $license_id, ['quantity' => $quantity]);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License added successfull')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateLicense(Request $request, $kit_id, $license_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
$kit->licenses()->syncWithoutDetaching([$license_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License updated')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function detachLicense($kit_id, $license_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
$kit->licenses()->detach($license_id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.delete_success')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function indexModels($kit_id) {
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$models = $kit->models;
|
||||
return (new PredefinedKitsTransformer)->transformElements($models, $models->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function storeModel(Request $request, $kit_id)
|
||||
{
|
||||
|
||||
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
$model_id = $request->get('model');
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
|
||||
$relation = $kit->models();
|
||||
if( $relation->find($model_id) ) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
|
||||
}
|
||||
$relation->attach($model_id, ['quantity' => $quantity]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Model added successfull'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateModel(Request $request, $kit_id, $model_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
$kit->models()->syncWithoutDetaching([$model_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License updated')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function detachModel($kit_id, $model_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
$kit->models()->detach($model_id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.model_removed_success')));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function indexConsumables($kit_id) {
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$consumables = $kit->consumables;
|
||||
return (new PredefinedKitsTransformer)->transformElements($consumables, $consumables->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function storeConsumable(Request $request, $kit_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
|
||||
$consumable_id = $request->get('consumable');
|
||||
$relation = $kit->consumables();
|
||||
if( $relation->find($consumable_id) ) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['consumable' => 'Consumable already attached to kit']));
|
||||
}
|
||||
|
||||
$relation->attach( $consumable_id, ['quantity' => $quantity]);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Consumable added successfull')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateConsumable(Request $request, $kit_id, $consumable_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
$kit->consumables()->syncWithoutDetaching([$consumable_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Consumable updated')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function detachConsumable($kit_id, $consumable_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
$kit->consumables()->detach($consumable_id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Delete was successfull')); // TODO: trans
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function indexAccessories($kit_id) {
|
||||
$this->authorize('view', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$accessories = $kit->accessories;
|
||||
return (new PredefinedKitsTransformer)->transformElements($accessories, $accessories->count());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store the specified resource.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function storeAccessory(Request $request, $kit_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
|
||||
$accessory_id = $request->get('accessory');
|
||||
$relation = $kit->accessories();
|
||||
if( $relation->find($accessory_id) ) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['accessory' => 'Accessory already attached to kit']));
|
||||
}
|
||||
|
||||
$relation->attach( $accessory_id, ['quantity' => $quantity]);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Accessory added successfull')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateAccessory(Request $request, $kit_id, $accessory_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
$quantity = $request->input('quantity', 1);
|
||||
if( $quantity < 1) {
|
||||
$quantity = 1;
|
||||
}
|
||||
$kit->accessories()->syncWithoutDetaching([$accessory_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Accessory updated')); // TODO: trans
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $kit_id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function detachAccessory($kit_id, $accessory_id)
|
||||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
$kit = PredefinedKit::findOrFail($kit_id);
|
||||
|
||||
$kit->accessories()->detach($accessory_id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Delete was successfull')); // TODO: trans
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\CheckoutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Auth;
|
||||
use App\Helpers\Helper;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CheckoutRequest;
|
||||
use Auth;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Transformers\ActionlogsTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ReportsController extends Controller
|
||||
{
|
||||
@@ -49,6 +49,7 @@ class ReportsController extends Controller
|
||||
'created_at',
|
||||
'target_id',
|
||||
'user_id',
|
||||
'accept_signature',
|
||||
'action_type',
|
||||
'note'
|
||||
];
|
||||
|
||||
@@ -2,50 +2,113 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Ldap;
|
||||
use Validator;
|
||||
use App\Http\Transformers\LoginAttemptsTransformer;
|
||||
use App\Models\Setting;
|
||||
use Mail;
|
||||
use App\Notifications\SlackTest;
|
||||
use Notification;
|
||||
use App\Notifications\MailTest;
|
||||
use App\Services\LdapAd;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Models\Ldap; // forward-port of v4 LDAP model for Sync
|
||||
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
||||
|
||||
public function ldaptest()
|
||||
/**
|
||||
* Test the ldap settings
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param App\Models\LdapAd $ldap
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function ldapAdSettingsTest(LdapAd $ldap): JsonResponse
|
||||
{
|
||||
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
\Log::debug('LDAP is not enabled so cannot test.');
|
||||
return response()->json(['message' => 'LDAP is not enabled, so we cannot test LDAP connections.'], 400);
|
||||
if(!$ldap->init()) {
|
||||
Log::info('LDAP is not enabled cannot test.');
|
||||
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
|
||||
}
|
||||
|
||||
\Log::debug('Preparing to test LDAP connection');
|
||||
// The connect, bind and resulting users message
|
||||
$message = [];
|
||||
|
||||
Log::info('Preparing to test LDAP user login');
|
||||
// Test user can connect to the LDAP server
|
||||
try {
|
||||
$connection = Ldap::connectToLdap();
|
||||
try {
|
||||
\Log::debug('attempting to bind to LDAP for LDAP test');
|
||||
Ldap::bindAdminToLdap($connection);
|
||||
return response()->json(['message' => 'It worked!'], 200);
|
||||
} catch (\Exception $e) {
|
||||
$ldap->testLdapAdUserConnection();
|
||||
$message['login'] = [
|
||||
'message' => 'Successfully connected to LDAP server.'
|
||||
];
|
||||
} catch (\Exception $ex) {
|
||||
\Log::debug('LDAP connected but Bind failed. Please check your LDAP settings and try again.');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
return response()->json([
|
||||
'message' => 'Error logging into LDAP server, error: ' . $ex->getMessage() . ' - Verify your that your username and password are correct']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::info('LDAP connection failed but we cannot debug it any further on our end.');
|
||||
return response()->json(['message' => 'The LDAP connection failed but we cannot debug it any further on our end. The error from the server is: '.$e->getMessage()], 500);
|
||||
}
|
||||
|
||||
Log::info('Preparing to test LDAP bind connection');
|
||||
// Test user can bind to the LDAP server
|
||||
try {
|
||||
Log::info('Testing Bind');
|
||||
$ldap->testLdapAdBindConnection();
|
||||
$message['bind'] = [
|
||||
'message' => 'Successfully binded to LDAP server.'
|
||||
];
|
||||
} catch (\Exception $ex) {
|
||||
Log::info('LDAP Bind failed');
|
||||
return response()->json([
|
||||
'message' => 'Error binding to LDAP server, error: ' . $ex->getMessage()
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
||||
Log::info('Preparing to get sample user set from LDAP directory');
|
||||
// Get a sample of 10 users so user can verify the data is correct
|
||||
$settings = Setting::getSettings();
|
||||
try {
|
||||
Log::info('Testing LDAP sync');
|
||||
error_reporting(E_ALL & ~E_DEPRECATED); // workaround for php7.4, which deprecates ldap_control_paged_result
|
||||
// $users = $ldap->testUserImportSync(); // from AdLdap2 from v5, disabling and falling back to v4's sync code
|
||||
$users = collect(Ldap::findLdapUsers())->slice(0, 11)->filter(function ($value, $key) { //choosing ELEVEN because one is going to be the count, which we're about to filter out in the next line
|
||||
return is_int($key);
|
||||
})->map(function ($item) use ($settings) {
|
||||
return (object) [
|
||||
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
||||
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
||||
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
||||
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
||||
'email' => $item[$settings['ldap_email']][0] ?? null,
|
||||
];
|
||||
});
|
||||
$message['user_sync'] = [
|
||||
'users' => $users
|
||||
];
|
||||
} catch (\Exception $ex) {
|
||||
Log::info('LDAP sync failed');
|
||||
$message['user_sync'] = [
|
||||
'message' => 'Error getting users from LDAP directory, error: ' . $ex->getMessage()
|
||||
];
|
||||
return response()->json($message, 400);
|
||||
}
|
||||
|
||||
return response()->json($message, 200);
|
||||
}
|
||||
|
||||
public function ldaptestlogin(Request $request)
|
||||
public function ldaptestlogin(Request $request, LdapAd $ldap)
|
||||
{
|
||||
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
@@ -69,55 +132,54 @@ class SettingsController extends Controller
|
||||
|
||||
\Log::debug('Preparing to test LDAP login');
|
||||
try {
|
||||
$connection = Ldap::connectToLdap();
|
||||
try {
|
||||
Ldap::bindAdminToLdap($connection);
|
||||
\Log::debug('Attempting to bind to LDAP for LDAP test');
|
||||
try {
|
||||
$ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password'));
|
||||
if ($ldap_user) {
|
||||
\Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
|
||||
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
|
||||
}
|
||||
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
|
||||
DB::beginTransaction(); //this was the easiest way to invoke a full test of an LDAP login without adding new users to the DB (which may not be desired)
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('LDAP login failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
}
|
||||
// $results = $ldap->ldap->auth()->attempt($request->input('ldaptest_username'), $request->input('ldaptest_password'), true);
|
||||
// can't do this because that's a protected property.
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Bind failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
$results = $ldap->ldapLogin($request->input('ldaptest_user'), $request->input('ldaptest_password')); // this would normally create a user on success (if they didn't already exist), but for the transaction
|
||||
if($results) {
|
||||
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
|
||||
} else {
|
||||
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed');
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
} finally {
|
||||
DB::rollBack(); // ALWAYS rollback, whether success or failure
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function slacktest()
|
||||
public function slacktest(Request $request)
|
||||
{
|
||||
|
||||
if ($settings = Setting::getSettings()->slack_channel=='') {
|
||||
\Log::debug('Slack is not enabled. Cannot test.');
|
||||
return response()->json(['message' => 'Slack is not enabled, cannot test.'], 400);
|
||||
}
|
||||
$slack = new Client([
|
||||
'base_url' => e($request->input('slack_endpoint')),
|
||||
'defaults' => [
|
||||
'exceptions' => false
|
||||
]
|
||||
]);
|
||||
|
||||
\Log::debug('Preparing to test slack connection');
|
||||
|
||||
$payload = json_encode(
|
||||
[
|
||||
'channel' => e($request->input('slack_channel')),
|
||||
'text' => trans('general.slack_test_msg'),
|
||||
'username' => e($request->input('slack_botname')),
|
||||
'icon_emoji' => ':heart:'
|
||||
]);
|
||||
|
||||
try {
|
||||
Notification::send($settings = Setting::getSettings(), new SlackTest());
|
||||
$slack->post($request->input('slack_endpoint'),['body' => $payload]);
|
||||
return response()->json(['message' => 'Success'], 200);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Slack connection failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
return response()->json(['message' => 'Oops! Please check the channel name and webhook endpoint URL. Slack responded with: '.$e->getMessage()], 400);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Something went wrong :( '], 400);
|
||||
|
||||
}
|
||||
|
||||
@@ -135,7 +197,7 @@ class SettingsController extends Controller
|
||||
try {
|
||||
Notification::send(Setting::first(), new MailTest());
|
||||
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
@@ -144,5 +206,74 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete server-cached barcodes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0.0]
|
||||
* @return Response
|
||||
*/
|
||||
public function purgeBarcodes()
|
||||
{
|
||||
|
||||
$file_count = 0;
|
||||
$files = Storage::disk('public')->files('barcodes');
|
||||
|
||||
foreach ($files as $file) { // iterate files
|
||||
|
||||
$file_parts = explode(".", $file);
|
||||
$extension = end($file_parts);
|
||||
\Log::debug($extension);
|
||||
|
||||
// Only generated barcodes would have a .png file extension
|
||||
if ($extension =='png') {
|
||||
|
||||
\Log::debug('Deleting: '.$file);
|
||||
|
||||
|
||||
try {
|
||||
Storage::disk('public')->delete($file);
|
||||
\Log::debug('Deleting: '.$file);
|
||||
$file_count++;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Deleted '.$file_count.' barcodes'], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of login attempts
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0.0]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function showLoginAttempts(Request $request)
|
||||
{
|
||||
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent','successful','created_at'];
|
||||
|
||||
$login_attempts = DB::table('login_attempts');
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'created_at';
|
||||
|
||||
$total = $login_attempts->count();
|
||||
$login_attempts->orderBy($sort, $order);
|
||||
$login_attempt_results = $login_attempts->skip(request('offset', 0))->take(request('limit', 20))->get();
|
||||
|
||||
return (new LoginAttemptsTransformer)->transformLoginAttempts($login_attempt_results, $total);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Asset;
|
||||
use App\Http\Transformers\StatuslabelsTransformer;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\StatuslabelsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Statuslabel;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StatuslabelsController extends Controller
|
||||
{
|
||||
@@ -167,25 +167,30 @@ class StatuslabelsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$statuslabels = Statuslabel::with('assets')->groupBy('id')->withCount('assets as assets_count')->get();
|
||||
$statuslabels = Statuslabel::with('assets')
|
||||
->groupBy('id')
|
||||
->withCount('assets as assets_count')
|
||||
->get();
|
||||
|
||||
$labels=[];
|
||||
$points=[];
|
||||
$colors=[];
|
||||
$default_color_count = 0;
|
||||
|
||||
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[]=$statuslabel->color;
|
||||
$colors_array[] = $statuslabel->color;
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
$default_color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$colors_array = array_merge($colors, Helper::chartColors());
|
||||
|
||||
$result= [
|
||||
"labels" => $labels,
|
||||
"datasets" => [ [
|
||||
@@ -213,7 +218,7 @@ class StatuslabelsController extends Controller
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'name'
|
||||
'name',
|
||||
];
|
||||
|
||||
$offset = request('offset', 0);
|
||||
@@ -243,8 +248,6 @@ class StatuslabelsController extends Controller
|
||||
*/
|
||||
public function checkIfDeployable($id) {
|
||||
$statuslabel = Statuslabel::findOrFail($id);
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
if ($statuslabel->getStatuslabelType()=='deployable') {
|
||||
return '1';
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Supplier;
|
||||
use App\Http\Transformers\SuppliersTransformer;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
|
||||
use App\Http\Transformers\SuppliersTransformer;
|
||||
use App\Models\Supplier;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class SuppliersController extends Controller
|
||||
{
|
||||
@@ -22,7 +22,7 @@ class SuppliersController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Supplier::class);
|
||||
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count'];
|
||||
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count','url'];
|
||||
|
||||
$suppliers = Supplier::select(
|
||||
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes')
|
||||
@@ -169,7 +169,7 @@ class SuppliersController extends Controller
|
||||
// they may not have a ->name value but we want to display something anyway
|
||||
foreach ($suppliers as $supplier) {
|
||||
$supplier->use_text = $supplier->name;
|
||||
$supplier->use_image = ($supplier->image) ? url('/').'/uploads/suppliers/'.$supplier->image : null;
|
||||
$supplier->use_image = ($supplier->image) ? Storage::disk('public')->url('suppliers/'.$supplier->image, $supplier->image) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($suppliers);
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\UsersTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\SaveUserRequest;
|
||||
use App\Models\Asset;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Http\Transformers\AccessoriesTransformer;
|
||||
use App\Http\Transformers\AssetsTransformer;
|
||||
use App\Http\Transformers\LicensesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Http\Transformers\UsersTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
@@ -58,6 +60,7 @@ class UsersController extends Controller
|
||||
'users.updated_at',
|
||||
'users.username',
|
||||
'users.zip',
|
||||
'users.ldap_import',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department','assets','licenses','accessories','consumables')
|
||||
->withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count');
|
||||
@@ -65,7 +68,9 @@ class UsersController extends Controller
|
||||
|
||||
|
||||
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
|
||||
$users = $users->GetDeleted();
|
||||
$users = $users->onlyTrashed();
|
||||
} elseif (($request->filled('all')) && ($request->input('all')=='true')) {
|
||||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
@@ -97,6 +102,7 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
// case we override with the actual count, so we should return 0 items.
|
||||
@@ -126,7 +132,7 @@ class UsersController extends Controller
|
||||
'assets','accessories', 'consumables','licenses','groups','activated','created_at',
|
||||
'two_factor_enrolled','two_factor_optin','last_login', 'assets_count', 'licenses_count',
|
||||
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
|
||||
'country', 'zip', 'id'
|
||||
'country', 'zip', 'id', 'ldap_import'
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -179,16 +185,16 @@ class UsersController extends Controller
|
||||
foreach ($users as $user) {
|
||||
$name_str = '';
|
||||
if ($user->last_name!='') {
|
||||
$name_str .= e($user->last_name).', ';
|
||||
$name_str .= $user->last_name.', ';
|
||||
}
|
||||
$name_str .= e($user->first_name);
|
||||
$name_str .= $user->first_name;
|
||||
|
||||
if ($user->username!='') {
|
||||
$name_str .= ' ('.e($user->username).')';
|
||||
$name_str .= ' ('.$user->username.')';
|
||||
}
|
||||
|
||||
if ($user->employee_num!='') {
|
||||
$name_str .= ' - #'.e($user->employee_num);
|
||||
$name_str .= ' - #'.$user->employee_num;
|
||||
}
|
||||
|
||||
$user->use_text = $name_str;
|
||||
@@ -230,6 +236,7 @@ class UsersController extends Controller
|
||||
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
|
||||
$user->password = bcrypt($request->get('password', $tmp_pass));
|
||||
|
||||
|
||||
if ($user->save()) {
|
||||
if ($request->filled('groups')) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
@@ -271,6 +278,16 @@ class UsersController extends Controller
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
|
||||
// Thanks, jerks. You are why we can't have nice things. - snipe
|
||||
|
||||
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
|
||||
}
|
||||
|
||||
|
||||
$user->fill($request->all());
|
||||
|
||||
if ($user->id == $request->input('manager_id')) {
|
||||
@@ -356,8 +373,16 @@ class UsersController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.'));
|
||||
}
|
||||
|
||||
|
||||
if ($user->delete()) {
|
||||
|
||||
// Remove the user's avatar if they have one
|
||||
if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('avatars/'.$user->avatar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
|
||||
@@ -414,6 +439,8 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
||||
* Reset the user's two-factor status
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetMaintenance;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Company;
|
||||
use DB;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Response;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Slack;
|
||||
use Str;
|
||||
use TCPDF;
|
||||
use Validator;
|
||||
use View;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Asset;
|
||||
use App\Helpers\Helper;
|
||||
use Auth;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Asset Maintenance for
|
||||
@@ -109,9 +100,9 @@ class AssetMaintenancesController extends Controller
|
||||
$assetMaintenance = new AssetMaintenance();
|
||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
||||
$assetMaintenance->is_warranty = $request->input('is_warranty');
|
||||
$assetMaintenance->cost = e($request->input('cost'));
|
||||
$assetMaintenance->notes = e($request->input('notes'));
|
||||
$asset = Asset::find(e($request->input('asset_id')));
|
||||
$assetMaintenance->cost = $request->input('cost');
|
||||
$assetMaintenance->notes = $request->input('notes');
|
||||
$asset = Asset::find($request->input('asset_id'));
|
||||
|
||||
if ((!Company::isCurrentUserHasAccess($asset)) && ($asset!=null)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
@@ -165,6 +156,7 @@ class AssetMaintenancesController extends Controller
|
||||
} elseif (!$assetMaintenance->asset) {
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', 'The asset associated with this maintenance does not exist.');
|
||||
|
||||
} elseif (!Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Image;
|
||||
use Input;
|
||||
use Lang;
|
||||
use App\Models\AssetModel;
|
||||
use Redirect;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Str;
|
||||
use Validator;
|
||||
use View;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use Config;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Storage;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
@@ -31,13 +23,14 @@ use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
class AssetModelsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the accessories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the accessories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', AssetModel::class);
|
||||
@@ -45,29 +38,31 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view containing the asset model creation form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
* Returns a view containing the asset model creation form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', AssetModel::class);
|
||||
$category_type = 'asset';
|
||||
return view('models/edit')->with('category_type',$category_type)
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', new AssetModel);
|
||||
return view('models/edit')->with('category_type', 'asset')
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', new AssetModel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and process the new Asset Model data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
* Validate and process the new Asset Model data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param ImageUploadRequest $request
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
|
||||
@@ -84,13 +79,13 @@ class AssetModelsController extends Controller
|
||||
$model->category_id = $request->input('category_id');
|
||||
$model->notes = $request->input('notes');
|
||||
$model->user_id = Auth::id();
|
||||
$model->requestable = Input::has('requestable');
|
||||
$model->requestable = Request::has('requestable');
|
||||
|
||||
if ($request->input('custom_fieldset')!='') {
|
||||
$model->fieldset_id = e($request->input('custom_fieldset'));
|
||||
}
|
||||
|
||||
$model = $request->handleImages($model,600, public_path().'/uploads/models');
|
||||
$model = $request->handleImages($model);
|
||||
|
||||
// Was it created?
|
||||
if ($model->save()) {
|
||||
@@ -105,13 +100,14 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view containing the asset model edit form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
*/
|
||||
* Returns a view containing the asset model edit form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($modelId = null)
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
@@ -128,14 +124,16 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Validates and processes form data from the edit
|
||||
* Asset Model form based on the model ID passed.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
*/
|
||||
* Validates and processes form data from the edit
|
||||
* Asset Model form based on the model ID passed.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $modelId = null)
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
@@ -145,6 +143,8 @@ class AssetModelsController extends Controller
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$model = $request->handleImages($model);
|
||||
|
||||
$model->depreciation_id = $request->input('depreciation_id');
|
||||
$model->eol = $request->input('eol');
|
||||
$model->name = $request->input('name');
|
||||
@@ -154,6 +154,8 @@ class AssetModelsController extends Controller
|
||||
$model->notes = $request->input('notes');
|
||||
$model->requestable = $request->input('requestable', '0');
|
||||
|
||||
|
||||
|
||||
$this->removeCustomFieldsDefaultValues($model);
|
||||
|
||||
if ($request->input('custom_fieldset')=='') {
|
||||
@@ -166,7 +168,6 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$model = $request->handleImages($model,600, public_path().'/uploads/models');
|
||||
|
||||
if ($model->save()) {
|
||||
return redirect()->route("models.index")->with('success', trans('admin/models/message.update.success'));
|
||||
@@ -175,14 +176,15 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and delete the given Asset Model. An Asset Model
|
||||
* cannot be deleted if there are associated assets.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
*/
|
||||
* Validate and delete the given Asset Model. An Asset Model
|
||||
* cannot be deleted if there are associated assets.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($modelId)
|
||||
{
|
||||
$this->authorize('delete', AssetModel::class);
|
||||
@@ -198,7 +200,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
if ($model->image) {
|
||||
try {
|
||||
unlink(public_path().'/uploads/models/'.$model->image);
|
||||
Storage::disk('public')->delete('models/'.$model->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::info($e);
|
||||
}
|
||||
@@ -213,13 +215,14 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Restore a given Asset Model (mark as un-deleted)
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
*/
|
||||
* Restore a given Asset Model (mark as un-deleted)
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getRestore($modelId = null)
|
||||
{
|
||||
$this->authorize('create', AssetModel::class);
|
||||
@@ -227,16 +230,8 @@ class AssetModelsController extends Controller
|
||||
$model = AssetModel::withTrashed()->find($modelId);
|
||||
|
||||
if (isset($model->id)) {
|
||||
|
||||
// Restore the model
|
||||
$model->restore();
|
||||
|
||||
// Prepare the success message
|
||||
$success = trans('admin/models/message.restore.success');
|
||||
|
||||
// Redirect back
|
||||
return redirect()->route('models.index')->with('success', $success);
|
||||
|
||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
||||
}
|
||||
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
|
||||
|
||||
@@ -244,13 +239,14 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Get the model information to present to the model view page
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
*/
|
||||
* Get the model information to present to the model view page
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($modelId = null)
|
||||
{
|
||||
$this->authorize('view', AssetModel::class);
|
||||
@@ -282,12 +278,10 @@ class AssetModelsController extends Controller
|
||||
$model->id = null;
|
||||
|
||||
// Show the page
|
||||
$view = View::make('models/edit');
|
||||
$view->with('depreciation_list', Helper::depreciationList());
|
||||
$view->with('item', $model);
|
||||
$view->with('clone_model', $model_to_clone);
|
||||
return $view;
|
||||
|
||||
return view('models/edit')
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', $model)
|
||||
->with('clone_model', $model_to_clone);
|
||||
}
|
||||
|
||||
|
||||
@@ -301,8 +295,7 @@ class AssetModelsController extends Controller
|
||||
*/
|
||||
public function getCustomFields($modelId)
|
||||
{
|
||||
$model = AssetModel::find($modelId);
|
||||
return view("models.custom_fields_form")->with("model", $model);
|
||||
return view("models.custom_fields_form")->with("model", AssetModel::find($modelId));
|
||||
}
|
||||
|
||||
|
||||
@@ -318,7 +311,7 @@ class AssetModelsController extends Controller
|
||||
public function postBulkEdit(Request $request)
|
||||
{
|
||||
|
||||
$models_raw_array = Input::get('ids');
|
||||
$models_raw_array = $request->input('ids');
|
||||
|
||||
// Make sure some IDs have been selected
|
||||
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
|
||||
@@ -367,7 +360,7 @@ class AssetModelsController extends Controller
|
||||
public function postBulkEditSave(Request $request)
|
||||
{
|
||||
|
||||
$models_raw_array = Input::get('ids');
|
||||
$models_raw_array = $request->input('ids');
|
||||
$update_array = array();
|
||||
|
||||
|
||||
@@ -408,7 +401,7 @@ class AssetModelsController extends Controller
|
||||
*/
|
||||
public function postBulkDelete(Request $request)
|
||||
{
|
||||
$models_raw_array = Input::get('ids');
|
||||
$models_raw_array = $request->input('ids');
|
||||
|
||||
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
|
||||
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetCheckinRequest;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class AssetCheckinController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to check an asset back into inventory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param string $backto
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
* Returns a view that presents a form to check an asset back into inventory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param string $backto
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function create($assetId, $backto = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
@@ -40,6 +44,7 @@ class AssetCheckinController extends Controller
|
||||
* @param int $assetId
|
||||
* @param null $backto
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
|
||||
@@ -50,14 +55,14 @@ class AssetCheckinController extends Controller
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if (is_null($target = $asset->assignedTo)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
|
||||
}
|
||||
$this->authorize('checkin', $asset);
|
||||
|
||||
if ($asset->assignedType() == Asset::USER) {
|
||||
$user = $asset->assignedTo;
|
||||
}
|
||||
if (is_null($target = $asset->assignedTo)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
@@ -71,27 +76,43 @@ class AssetCheckinController extends Controller
|
||||
$asset->status_id = e($request->get('status_id'));
|
||||
}
|
||||
|
||||
// This is just meant to correct legacy issues where some user data would have 0
|
||||
// as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
|
||||
// rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
|
||||
// people (and their data) in the long run
|
||||
|
||||
if ($asset->rtd_location_id=='0') {
|
||||
\Log::debug('Manually override the RTD location IDs');
|
||||
\Log::debug('Original RTD Location ID: '.$asset->rtd_location_id);
|
||||
$asset->rtd_location_id = '';
|
||||
\Log::debug('New RTD Location ID: '.$asset->rtd_location_id);
|
||||
}
|
||||
|
||||
if ($asset->location_id=='0') {
|
||||
\Log::debug('Manually override the location IDs');
|
||||
\Log::debug('Original Location ID: '.$asset->location_id);
|
||||
$asset->location_id = '';
|
||||
\Log::debug('New RTD Location ID: '.$asset->location_id);
|
||||
}
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
\Log::debug('After Location ID: '.$asset->location_id);
|
||||
\Log::debug('After RTD Location ID: '.$asset->rtd_location_id);
|
||||
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
\Log::debug('NEW Location ID: '.$request->get('location_id'));
|
||||
$asset->location_id = e($request->get('location_id'));
|
||||
}
|
||||
|
||||
$checkin_at = date('Y-m-d');
|
||||
if($request->filled('checkin_at')){
|
||||
$checkin_at = $request->input('checkin_at');
|
||||
}
|
||||
|
||||
// Was the asset updated?
|
||||
if ($asset->save()) {
|
||||
$logaction = $asset->logCheckin($target, e(request('note')));
|
||||
|
||||
$data['log_id'] = $logaction->id;
|
||||
$data['first_name'] = get_class($target) == User::class ? $target->first_name : '';
|
||||
$data['last_name'] = get_class($target) == User::class ? $target->last_name : '';
|
||||
$data['item_name'] = $asset->present()->name();
|
||||
$data['checkin_date'] = $logaction->created_at;
|
||||
$data['item_tag'] = $asset->asset_tag;
|
||||
$data['item_serial'] = $asset->serial;
|
||||
$data['note'] = $logaction->note;
|
||||
$data['manufacturer_name'] = $asset->model->manufacturer->name;
|
||||
$data['model_name'] = $asset->model->name;
|
||||
$data['model_number'] = $asset->model->model_number;
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
|
||||
if ((isset($user)) && ($backto =='user')) {
|
||||
return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
@@ -99,6 +120,6 @@ class AssetCheckinController extends Controller
|
||||
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route("hardware.index")->with('error', trans('admin/hardware/message.checkin.error'));
|
||||
return redirect()->route("hardware.index")->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AssetCheckoutController extends Controller
|
||||
@@ -34,7 +34,8 @@ class AssetCheckoutController extends Controller
|
||||
$this->authorize('checkout', $asset);
|
||||
|
||||
if ($asset->availableForCheckout()) {
|
||||
return view('hardware/checkout', compact('asset'));
|
||||
return view('hardware/checkout', compact('asset'))
|
||||
->with('statusLabel_list', Helper::deployableStatusLabelList());
|
||||
}
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
|
||||
|
||||
@@ -63,9 +64,7 @@ class AssetCheckoutController extends Controller
|
||||
$admin = Auth::user();
|
||||
|
||||
$target = $this->determineCheckoutTarget($asset);
|
||||
if ($asset->is($target)) {
|
||||
throw new CheckoutNotAllowed('You cannot check an asset out to itself.');
|
||||
}
|
||||
|
||||
$asset = $this->updateAssetLocation($asset, $target);
|
||||
|
||||
$checkout_at = date("Y-m-d H:i:s");
|
||||
@@ -78,12 +77,16 @@ class AssetCheckoutController extends Controller
|
||||
$expected_checkin = $request->get('expected_checkin');
|
||||
}
|
||||
|
||||
if ($request->filled('status_id')) {
|
||||
$asset->status_id = $request->get('status_id');
|
||||
}
|
||||
|
||||
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
|
||||
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
|
||||
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
|
||||
} catch (CheckoutNotAllowed $e) {
|
||||
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AssetFilesController extends Controller
|
||||
{
|
||||
@@ -19,6 +20,7 @@ class AssetFilesController extends Controller
|
||||
* @param int $assetId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $assetId = null)
|
||||
{
|
||||
@@ -28,15 +30,15 @@ class AssetFilesController extends Controller
|
||||
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
$destinationPath = config('app.private_uploads').'/assets';
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
|
||||
if (!Storage::exists('private_uploads/assets')) Storage::makeDirectory('private_uploads/assets', 775);
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$filename = 'hardware-'.$asset->id.'-'.str_random(8);
|
||||
$filename .= '-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
$file->move($destinationPath, $filename);
|
||||
$asset->logUpload($filename, e($request->get('notes')));
|
||||
$file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
|
||||
$asset->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
|
||||
}
|
||||
@@ -45,14 +47,15 @@ class AssetFilesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($assetId = null, $fileId = null, $download = true)
|
||||
{
|
||||
$asset = Asset::find($assetId);
|
||||
@@ -65,24 +68,25 @@ class AssetFilesController extends Controller
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = $log->get_src('assets');
|
||||
$file = 'private_uploads/assets/'.$log->filename;
|
||||
\Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type =='audit') {
|
||||
$file = $log->get_src('audits');
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
if (!Storage::exists($file)) {
|
||||
return response('File '.$file.' not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
if ($download != 'true') {
|
||||
if ($contents = file_get_contents($file)) {
|
||||
return Response::make($contents)->header('Content-Type', mime_content_type($file));
|
||||
if ($contents = file_get_contents(Storage::url($file))) {
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
return JsonResponse::create(["error" => "Failed validation: "], 500);
|
||||
}
|
||||
return Response::download($file);
|
||||
return Storage::download($file);
|
||||
}
|
||||
// Prepare the error message
|
||||
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
|
||||
@@ -92,35 +96,35 @@ class AssetFilesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the associated file
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
* Delete the associated file
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($assetId = null, $fileId = null)
|
||||
{
|
||||
$asset = Asset::find($assetId);
|
||||
$this->authorize('update', $asset);
|
||||
$destinationPath = config('app.private_uploads').'/imports/assets';
|
||||
$rel_path = 'storage/private_uploads/assets';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
$full_filename = $destinationPath.'/'.$log->filename;
|
||||
if (file_exists($full_filename)) {
|
||||
unlink($destinationPath.'/'.$log->filename);
|
||||
if (file_exists(base_path().'/'.$rel_path.'/'.$log->filename)) {
|
||||
Storage::disk('public')->delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
$log->delete();
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
return redirect()->back()->with('error', 'Could not find matching upload log.');
|
||||
|
||||
$log->delete();
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the hardware management page
|
||||
@@ -1,44 +1,34 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\AssetCheckinRequest;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\AssetRequest;
|
||||
use App\Http\Requests\ItemImportRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CheckoutRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Import;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Image;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Input;
|
||||
use Lang;
|
||||
use League\Csv\Reader;
|
||||
use Log;
|
||||
use Mail;
|
||||
use League\Csv\Statement;
|
||||
use Paginator;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use Slack;
|
||||
use Str;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use TCPDF;
|
||||
use Validator;
|
||||
use View;
|
||||
use App\Models\CheckoutRequest;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to assets for
|
||||
@@ -66,16 +56,14 @@ class AssetsController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see AssetController::getDatatable() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @param Request $request
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('index', Asset::class);
|
||||
if ($request->filled('company_id')) {
|
||||
$company = Company::find($request->input('company_id'));
|
||||
} else {
|
||||
$company = null;
|
||||
}
|
||||
$company = Company::find($request->input('company_id'));
|
||||
return view('hardware/index')->with('company', $company);
|
||||
}
|
||||
|
||||
@@ -110,117 +98,121 @@ class AssetsController extends Controller
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function store(AssetRequest $request)
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize(Asset::class);
|
||||
|
||||
// Handle asset tags - there could be one, or potentially many.
|
||||
// This is only necessary on create, not update, since bulk editing is handled
|
||||
// differently
|
||||
$asset_tags = $request->input('asset_tags');
|
||||
|
||||
$asset = new Asset();
|
||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$asset->name = $request->input('name');
|
||||
$asset->serial = $request->input('serial');
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$asset->model_id = $request->input('model_id');
|
||||
$asset->order_number = $request->input('order_number');
|
||||
$asset->notes = $request->input('notes');
|
||||
$asset->asset_tag = $request->input('asset_tag');
|
||||
$asset->user_id = Auth::id();
|
||||
$asset->archived = '0';
|
||||
$asset->physical = '1';
|
||||
$asset->depreciate = '0';
|
||||
$asset->status_id = request('status_id', 0);
|
||||
$asset->warranty_months = request('warranty_months', null);
|
||||
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
|
||||
$asset->purchase_date = request('purchase_date', null);
|
||||
$asset->assigned_to = request('assigned_to', null);
|
||||
$asset->supplier_id = request('supplier_id', 0);
|
||||
$asset->requestable = request('requestable', 0);
|
||||
$asset->rtd_location_id = request('rtd_location_id', null);
|
||||
|
||||
$success = false;
|
||||
$serials = $request->input('serials');
|
||||
|
||||
if ($asset->assigned_to=='') {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
}
|
||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
||||
|
||||
// Create the image (if one was chosen.)
|
||||
if ($request->filled('image')) {
|
||||
$image = $request->input('image');
|
||||
$asset = new Asset();
|
||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
||||
$asset->name = $request->input('name');
|
||||
|
||||
// After modification, the image is prefixed by mime info like the following:
|
||||
// data:image/jpeg;base64,; This causes the image library to be unhappy, so we need to remove it.
|
||||
$header = explode(';', $image, 2)[0];
|
||||
// Grab the image type from the header while we're at it.
|
||||
$extension = substr($header, strpos($header, '/')+1);
|
||||
// Start reading the image after the first comma, postceding the base64.
|
||||
$image = substr($image, strpos($image, ',')+1);
|
||||
|
||||
$file_name = str_random(25).".".$extension;
|
||||
|
||||
$directory= public_path('uploads/assets/');
|
||||
// Check if the uploads directory exists. If not, try to create it.
|
||||
if (!file_exists($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
// Check for a corresponding serial
|
||||
if (($serials) && (array_key_exists($a, $serials))) {
|
||||
$asset->serial = $serials[$a];
|
||||
}
|
||||
$path = public_path('uploads/assets/'.$file_name);
|
||||
try {
|
||||
Image::make($image)->resize(800, 800, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path);
|
||||
$asset->image = $file_name;
|
||||
} catch (\Exception $e) {
|
||||
\Input::flash();
|
||||
$messageBag = new \Illuminate\Support\MessageBag();
|
||||
$messageBag->add('image', $e->getMessage());
|
||||
\Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag)
|
||||
->put('default', $messageBag));
|
||||
return response()->json(['image' => $e->getMessage()], 422);
|
||||
|
||||
if (($asset_tags) && (array_key_exists($a, $asset_tags))) {
|
||||
$asset->asset_tag = $asset_tags[$a];
|
||||
}
|
||||
}
|
||||
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$asset->model_id = $request->input('model_id');
|
||||
$asset->order_number = $request->input('order_number');
|
||||
$asset->notes = $request->input('notes');
|
||||
$asset->user_id = Auth::id();
|
||||
$asset->archived = '0';
|
||||
$asset->physical = '1';
|
||||
$asset->depreciate = '0';
|
||||
$asset->status_id = request('status_id', 0);
|
||||
$asset->warranty_months = request('warranty_months', null);
|
||||
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
|
||||
$asset->purchase_date = request('purchase_date', null);
|
||||
$asset->assigned_to = request('assigned_to', null);
|
||||
$asset->supplier_id = request('supplier_id', 0);
|
||||
$asset->requestable = request('requestable', 0);
|
||||
$asset->rtd_location_id = request('rtd_location_id', null);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handled through the AssetRequest form request
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
if (!empty($settings->audit_interval)) {
|
||||
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
}
|
||||
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
if ($asset->assigned_to=='') {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
}
|
||||
|
||||
// Create the image (if one was chosen.)
|
||||
if ($request->has('image')) {
|
||||
$asset = $request->handleImages($asset);
|
||||
}
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handled through the AssetRequest form request
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e(implode(', ', $request->input($field->convertUnicodeDbSlug()))));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
} }
|
||||
} else {
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the asset before saving
|
||||
if ($asset->isValid() && $asset->save()) {
|
||||
|
||||
if (request('assigned_user')) {
|
||||
$target = User::find(request('assigned_user'));
|
||||
$location = $target->location_id;
|
||||
} elseif (request('assigned_asset')) {
|
||||
$target = Asset::find(request('assigned_asset'));
|
||||
$location = $target->location_id;
|
||||
} elseif (request('assigned_location')) {
|
||||
$target = Location::find(request('assigned_location'));
|
||||
$location = $target->id;
|
||||
}
|
||||
|
||||
if (isset($target)) {
|
||||
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', e($request->get('name')), $location);
|
||||
}
|
||||
|
||||
$success = true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Was the asset created?
|
||||
if ($asset->save()) {
|
||||
|
||||
|
||||
if (request('assigned_user')) {
|
||||
$target = User::find(request('assigned_user'));
|
||||
$location = $target->location_id;
|
||||
} elseif (request('assigned_asset')) {
|
||||
$target = Asset::find(request('assigned_asset'));
|
||||
$location = $target->location_id;
|
||||
} elseif (request('assigned_location')) {
|
||||
$target = Location::find(request('assigned_location'));
|
||||
$location = $target->id;
|
||||
}
|
||||
|
||||
if (isset($target)) {
|
||||
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')), $location);
|
||||
}
|
||||
if ($success) {
|
||||
// Redirect to the asset listing page
|
||||
\Session::flash('success', trans('admin/hardware/message.create.success'));
|
||||
return response()->json(['redirect_url' => route('hardware.index')]);
|
||||
return redirect()->route('hardware.index')
|
||||
->with('success', trans('admin/hardware/message.create.success'));
|
||||
}
|
||||
\Input::flash();
|
||||
\Session::flash('errors', $asset->getErrors());
|
||||
return response()->json(['errors' => $asset->getErrors()], 500);
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +290,7 @@ class AssetsController extends Controller
|
||||
* @return Redirect
|
||||
*/
|
||||
|
||||
public function update(AssetRequest $request, $assetId = null)
|
||||
public function update(ImageUploadRequest $request, $assetId = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (!$asset = Asset::find($assetId)) {
|
||||
@@ -312,6 +304,7 @@ class AssetsController extends Controller
|
||||
$asset->purchase_cost = Helper::ParseFloat($request->input('purchase_cost', null));
|
||||
$asset->purchase_date = $request->input('purchase_date', null);
|
||||
$asset->supplier_id = $request->input('supplier_id', null);
|
||||
$asset->expected_checkin = $request->input('expected_checkin', null);
|
||||
|
||||
// If the box isn't checked, it's not in the request at all.
|
||||
$asset->requestable = $request->filled('requestable');
|
||||
@@ -327,54 +320,25 @@ class AssetsController extends Controller
|
||||
unlink(public_path().'/uploads/assets/'.$asset->image);
|
||||
$asset->image = '';
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
\Log::info($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Update the asset data
|
||||
$asset_tag = $request->input('asset_tags');
|
||||
$serial = $request->input('serials');
|
||||
$asset->name = $request->input('name');
|
||||
$asset->serial = $request->input('serial');
|
||||
$asset->serial = $serial[1];
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$asset->model_id = $request->input('model_id');
|
||||
$asset->order_number = $request->input('order_number');
|
||||
$asset->asset_tag = $request->input('asset_tag');
|
||||
$asset->asset_tag = $asset_tag[1];
|
||||
$asset->notes = $request->input('notes');
|
||||
$asset->physical = '1';
|
||||
|
||||
// Update the image
|
||||
if ($request->filled('image')) {
|
||||
$image = $request->input('image');
|
||||
// See postCreate for more explaination of the following.
|
||||
$header = explode(';', $image, 2)[0];
|
||||
$extension = substr($header, strpos($header, '/')+1);
|
||||
$image = substr($image, strpos($image, ',')+1);
|
||||
|
||||
$directory= public_path('uploads/assets/');
|
||||
// Check if the uploads directory exists. If not, try to create it.
|
||||
if (!file_exists($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
$file_name = str_random(25).".".$extension;
|
||||
$path = public_path('uploads/assets/'.$file_name);
|
||||
try {
|
||||
Image::make($image)->resize(800, 800, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path);
|
||||
$asset->image = $file_name;
|
||||
} catch (\Exception $e) {
|
||||
\Input::flash();
|
||||
$messageBag = new \Illuminate\Support\MessageBag();
|
||||
$messageBag->add('image', $e->getMessage());
|
||||
\Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag)
|
||||
->put('default', $messageBag));
|
||||
return response()->json(['image' => $e->getMessage()], 422);
|
||||
}
|
||||
$asset->image = $file_name;
|
||||
}
|
||||
$asset = $request->handleImages($asset);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handlded through the AssetRequest form request
|
||||
@@ -385,29 +349,29 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e(implode(', ', $request->input($field->convertUnicodeDbSlug()))));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($asset->save()) {
|
||||
|
||||
// Update any assigned assets with the new location_id from the parent asset
|
||||
|
||||
Asset::where('assigned_type', '\\App\\Models\\Asset')->where('assigned_to', $asset->id)
|
||||
->update(['location_id' => $asset->location_id]);
|
||||
|
||||
// Redirect to the new asset page
|
||||
\Session::flash('success', trans('admin/hardware/message.update.success'));
|
||||
return response()->json(['redirect_url' => route("hardware.show", $assetId)]);
|
||||
return redirect()->route("hardware.show", $assetId)
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
\Input::flash();
|
||||
\Session::flash('errors', $asset->getErrors());
|
||||
return response()->json(['errors' => $asset->getErrors()], 500);
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,6 +396,14 @@ class AssetsController extends Controller
|
||||
->where('id', $asset->id)
|
||||
->update(array('assigned_to' => null));
|
||||
|
||||
if ($asset->image) {
|
||||
try {
|
||||
Storage::disk('public')->delete('assets'.'/'.$asset->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$asset->delete();
|
||||
|
||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success'));
|
||||
@@ -440,63 +412,22 @@ class AssetsController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Searches the assets table by tag, and redirects if it finds one.
|
||||
*
|
||||
* This is used by the top search box in Snipe-IT, but as of 4.9.x
|
||||
* can also be used as a url segment.
|
||||
*
|
||||
* https://yoursnipe.com/hardware/bytag/?assetTag=foo
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* https://yoursnipe.com/hardware/bytag/foo
|
||||
*
|
||||
* The latter is useful if you're doing home-grown barcodes, or
|
||||
* some other automation where you don't always know the internal ID of
|
||||
* an asset and don't want to query for it.
|
||||
* Searches the assets table by asset tag, and redirects if it finds one
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $tag
|
||||
* @since [v3.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function getAssetByTag(Request $request, $tag = null)
|
||||
public function getAssetByTag(Request $request)
|
||||
{
|
||||
|
||||
$topsearch = ($request->get('topsearch')=="true");
|
||||
|
||||
// We need this part to determine whether a url query parameter has been passed, OR
|
||||
// whether it's the url fragment we need to look at
|
||||
$tag = ($request->get('assetTag')) ? $request->get('assetTag') : $tag;
|
||||
|
||||
if (!$asset = Asset::where('asset_tag', '=', $tag)->first()) {
|
||||
if (!$asset = Asset::where('asset_tag', '=', $request->get('assetTag'))->first()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
$this->authorize('view', $asset);
|
||||
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches the assets table by serial, and redirects if it finds one
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $serial
|
||||
* @since [v4.9.1]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function getAssetBySerial(Request $request, $serial = null)
|
||||
{
|
||||
|
||||
$serial = ($request->get('serial')) ? $request->get('serial') : $serial;
|
||||
if (!$asset = Asset::where('serial', '=', $serial)->first()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
$this->authorize('view', $asset);
|
||||
return redirect()->route('hardware.show', $asset->id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a QR code for the asset
|
||||
*
|
||||
@@ -547,7 +478,6 @@ class AssetsController extends Controller
|
||||
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
|
||||
|
||||
if (isset($asset->id, $asset->asset_tag)) {
|
||||
|
||||
if (file_exists($barcode_file)) {
|
||||
$header = ['Content-type' => 'image/png'];
|
||||
return response()->file($barcode_file, $header);
|
||||
@@ -556,26 +486,37 @@ class AssetsController extends Controller
|
||||
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 96.000000000001;
|
||||
|
||||
$barcode = new \Com\Tecnick\Barcode\Barcode();
|
||||
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode,$asset->asset_tag,($barcode_width < 300 ? $barcode_width : 300),50);
|
||||
|
||||
try {
|
||||
|
||||
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode,$asset->asset_tag,($barcode_width < 300 ? $barcode_width : 300),50);
|
||||
|
||||
file_put_contents($barcode_file, $barcode_obj->getPngData());
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Error creating barcode: '.$e->getMessage());
|
||||
\Log::debug('This usually happens because the asset tags are of a format that is not compatible with the selected barcode type.');
|
||||
$img = file_get_contents(public_path().'/uploads/barcodes/invalid_barcode.gif');
|
||||
return response($img)->header('Content-type', 'image/gif');
|
||||
}
|
||||
|
||||
|
||||
file_put_contents($barcode_file, $barcode_obj->getPngData());
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a label for an individual asset.
|
||||
*
|
||||
* @author [L. Swartzendruber] [<logan.swartzendruber@gmail.com>
|
||||
* @param int $assetId
|
||||
* @return View
|
||||
*/
|
||||
public function getLabel($assetId = null)
|
||||
{
|
||||
if (isset($assetId)) {
|
||||
$asset = Asset::find($assetId);
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
return view('hardware/labels')
|
||||
->with('assets', Asset::find($asset))
|
||||
->with('settings', Setting::getSettings())
|
||||
->with('bulkedit', false)
|
||||
->with('count', 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to clone an asset.
|
||||
*
|
||||
@@ -615,7 +556,7 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function getImportHistory()
|
||||
{
|
||||
$this->authorize('checkout', Asset::class);
|
||||
$this->authorize('admin');
|
||||
return view('hardware/history');
|
||||
}
|
||||
|
||||
@@ -639,16 +580,13 @@ class AssetsController extends Controller
|
||||
if (!ini_get("auto_detect_line_endings")) {
|
||||
ini_set("auto_detect_line_endings", '1');
|
||||
}
|
||||
|
||||
$csv = Reader::createFromPath(Input::file('user_import_csv'));
|
||||
$csv = Reader::createFromPath($request->file('user_import_csv'));
|
||||
$csv->setHeaderOffset(0);
|
||||
$results = $csv->getRecords();
|
||||
$item = array();
|
||||
$status = array();
|
||||
$status['error'] = array();
|
||||
$status['success'] = array();
|
||||
|
||||
|
||||
foreach ($results as $row) {
|
||||
if (is_array($row)) {
|
||||
$row = array_change_key_case($row, CASE_LOWER);
|
||||
@@ -657,29 +595,23 @@ class AssetsController extends Controller
|
||||
$item[$asset_tag] = array();
|
||||
}
|
||||
$batch_counter = count($item[$asset_tag]);
|
||||
|
||||
$item[$asset_tag][$batch_counter]['checkout_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkout date"))->format('Y-m-d H:i:s');
|
||||
$item[$asset_tag][$batch_counter]['checkin_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkin date"))->format('Y-m-d H:i:s');
|
||||
\Log::debug($item[$asset_tag][$batch_counter]['checkin_date']);
|
||||
|
||||
$item[$asset_tag][$batch_counter]['asset_tag'] = Helper::array_smart_fetch($row, "asset tag");
|
||||
$item[$asset_tag][$batch_counter]['name'] = Helper::array_smart_fetch($row, "name");
|
||||
$item[$asset_tag][$batch_counter]['email'] = Helper::array_smart_fetch($row, "email");
|
||||
|
||||
if ($asset = Asset::where('asset_tag', '=', $asset_tag)->first()) {
|
||||
$item[$asset_tag][$batch_counter]['asset_id'] = $asset->id;
|
||||
|
||||
$base_username = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $item[$asset_tag][$batch_counter]['name']);
|
||||
$user = User::where('username', '=', $base_username['username']);
|
||||
$user_query = ' on username '.$base_username['username'];
|
||||
|
||||
if ($request->input('match_firstnamelastname')=='1') {
|
||||
$firstnamedotlastname = User::generateFormattedNameFromFullName('firstname.lastname', $item[$asset_tag][$batch_counter]['name']);
|
||||
$item[$asset_tag][$batch_counter]['username'][] = $firstnamedotlastname['username'];
|
||||
$user->orWhere('username', '=', $firstnamedotlastname['username']);
|
||||
$user_query .= ', or on username '.$firstnamedotlastname['username'];
|
||||
}
|
||||
|
||||
if ($request->input('match_flastname')=='1') {
|
||||
$flastname = User::generateFormattedNameFromFullName('filastname', $item[$asset_tag][$batch_counter]['name']);
|
||||
$item[$asset_tag][$batch_counter]['username'][] = $flastname['username'];
|
||||
@@ -699,12 +631,10 @@ class AssetsController extends Controller
|
||||
$user_query .= ', or on username '.$user_email;
|
||||
}
|
||||
}
|
||||
|
||||
// A matching user was found
|
||||
if ($user = $user->first()) {
|
||||
$item[$asset_tag][$batch_counter]['checkedout_to'] = $user->id;
|
||||
$item[$asset_tag][$batch_counter]['user_id'] = $user->id;
|
||||
|
||||
Actionlog::firstOrCreate(array(
|
||||
'item_id' => $asset->id,
|
||||
'item_type' => Asset::class,
|
||||
@@ -715,9 +645,7 @@ class AssetsController extends Controller
|
||||
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
||||
'action_type' => 'checkout',
|
||||
));
|
||||
|
||||
$asset->assigned_to = $user->id;
|
||||
|
||||
if ($asset->save()) {
|
||||
$status['success'][]['asset'][$asset_tag]['msg'] = 'Asset successfully matched for '.Helper::array_smart_fetch($row, "name").$user_query.' on '.$item[$asset_tag][$batch_counter]['checkout_date'];
|
||||
} else {
|
||||
@@ -733,19 +661,16 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through and backfill the checkins
|
||||
foreach ($item as $key => $asset_batch) {
|
||||
$total_in_batch = count($asset_batch);
|
||||
for ($x = 0; $x < $total_in_batch; $x++) {
|
||||
$next = $x + 1;
|
||||
|
||||
// Only do this if a matching user was found
|
||||
if ((array_key_exists('checkedout_to', $asset_batch[$x])) && ($asset_batch[$x]['checkedout_to']!='')) {
|
||||
if (($total_in_batch > 1) && ($x < $total_in_batch) && (array_key_exists($next, $asset_batch))) {
|
||||
$checkin_date = Carbon::parse($asset_batch[$next]['checkin_date'])->format('Y-m-d H:i:s');
|
||||
$asset_batch[$x]['real_checkin'] = $checkin_date;
|
||||
|
||||
\Log::debug($asset_batch[$next]['checkin_date']);
|
||||
\Log::debug($checkin_date);
|
||||
Actionlog::firstOrCreate(array(
|
||||
@@ -764,6 +689,11 @@ class AssetsController extends Controller
|
||||
return view('hardware/history')->with('status', $status);
|
||||
}
|
||||
|
||||
public function sortByName(array $recordA, array $recordB): int
|
||||
{
|
||||
return strcmp($recordB['Full Name'], $recordA['Full Name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retore a deleted asset.
|
||||
*
|
||||
@@ -824,7 +754,7 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
public function auditStore(AssetFileRequest $request, $id)
|
||||
public function auditStore(Request $request, $id)
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
||||
@@ -847,24 +777,28 @@ class AssetsController extends Controller
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d h:i:s');
|
||||
|
||||
// Check to see if they checked the box to update the physical location,
|
||||
// not just note it in the audit notes
|
||||
if ($request->input('update_location')=='1') {
|
||||
\Log::debug('update location in audit');
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
|
||||
if ($asset->save()) {
|
||||
|
||||
|
||||
$filename = '';
|
||||
|
||||
$file_name = '';
|
||||
// Upload an image, if attached
|
||||
if ($request->hasFile('image')) {
|
||||
$file = $request->file('image');
|
||||
try {
|
||||
$destinationPath = config('app.private_uploads').'/audits';
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$filename = 'audit-'.$asset->id.'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
$file->move($destinationPath, $filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::info($e);
|
||||
}
|
||||
$path = 'private_uploads/audits';
|
||||
if (!Storage::exists($path)) Storage::makeDirectory($path, 775);
|
||||
$upload = $image = $request->file('image');
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = 'audit-'.str_random(18).'.'.$ext;
|
||||
Storage::putFileAs($path, $upload, $file_name);
|
||||
}
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $filename);
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
|
||||
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\DB;
|
||||
class BulkAssetsController extends Controller
|
||||
{
|
||||
use CheckInOutRequest;
|
||||
|
||||
/**
|
||||
* Display the bulk edit page.
|
||||
*
|
||||
@@ -21,6 +22,7 @@ class BulkAssetsController extends Controller
|
||||
* @return View
|
||||
* @internal param int $assetId
|
||||
* @since [v2.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(Request $request)
|
||||
{
|
||||
@@ -38,6 +40,7 @@ class BulkAssetsController extends Controller
|
||||
return view('hardware/labels')
|
||||
->with('assets', Asset::find($asset_ids))
|
||||
->with('settings', Setting::getSettings())
|
||||
->with('bulkedit', true)
|
||||
->with('count', 0);
|
||||
case 'delete':
|
||||
$assets = Asset::with('assignedTo', 'location')->find($asset_ids);
|
||||
@@ -75,6 +78,7 @@ class BulkAssetsController extends Controller
|
||||
$assets = array_keys($request->input('ids'));
|
||||
|
||||
if (($request->filled('purchase_date'))
|
||||
|| ($request->filled('expected_checkin'))
|
||||
|| ($request->filled('purchase_cost'))
|
||||
|| ($request->filled('supplier_id'))
|
||||
|| ($request->filled('order_number'))
|
||||
@@ -89,6 +93,7 @@ class BulkAssetsController extends Controller
|
||||
$this->update_array = [];
|
||||
|
||||
$this->conditionallyAddItem('purchase_date')
|
||||
->conditionallyAddItem('expected_checkin')
|
||||
->conditionallyAddItem('model_id')
|
||||
->conditionallyAddItem('order_number')
|
||||
->conditionallyAddItem('requestable')
|
||||
@@ -130,10 +135,11 @@ class BulkAssetsController extends Controller
|
||||
* @var Array
|
||||
*/
|
||||
private $update_array;
|
||||
|
||||
/**
|
||||
* Adds parameter to update array for an item if it exists in request
|
||||
* @param String $field field name
|
||||
* @return this Model for Chaining
|
||||
* @param String $field field name
|
||||
* @return BulkAssetsController Model for Chaining
|
||||
*/
|
||||
protected function conditionallyAddItem($field)
|
||||
{
|
||||
@@ -147,7 +153,9 @@ class BulkAssetsController extends Controller
|
||||
* Save bulk deleted.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param Request $request
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @internal param array $assets
|
||||
* @since [v2.0]
|
||||
*/
|
||||
@@ -200,9 +208,11 @@ class BulkAssetsController extends Controller
|
||||
|
||||
$asset_ids = array_filter($request->get('selected_assets'));
|
||||
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
if ($target->id == $asset_id && request('checkout_to_type') =='asset') {
|
||||
return redirect()->back()->with('error', 'You cannot check an asset out to itself.');
|
||||
if(request('checkout_to_type') =='asset') {
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
if ($target->id == $asset_id) {
|
||||
return redirect()->back()->with('error', 'You cannot check an asset out to itself.');
|
||||
}
|
||||
}
|
||||
}
|
||||
$checkout_at = date("Y-m-d H:i:s");
|
||||
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
@@ -58,7 +57,12 @@ class ForgotPasswordController extends Controller
|
||||
* buffer overflow issues with attackers sending very large
|
||||
* payloads through.
|
||||
*/
|
||||
$this->validate($request, ['email' => 'required|email|max:250']);
|
||||
|
||||
$request->validate([
|
||||
'username' => ['required', 'max:255'],
|
||||
]);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If we find a matching email with an activated user, we will
|
||||
@@ -69,13 +73,16 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
$response = $this->broker()->sendResetLink(
|
||||
array_merge(
|
||||
$request->only('email'),
|
||||
['activated' => '1']
|
||||
$request->only('username'),
|
||||
['activated' => '1'],
|
||||
['ldap_import' => '0']
|
||||
)
|
||||
);
|
||||
|
||||
if ($response === \Password::RESET_LINK_SENT) {
|
||||
return redirect()->route('login')->with('status', trans($response));
|
||||
\Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
|
||||
} else {
|
||||
\Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive');
|
||||
}
|
||||
|
||||
|
||||
@@ -92,13 +99,10 @@ class ForgotPasswordController extends Controller
|
||||
* It's bad UX, but better security. The compromises we sometimes have to make.
|
||||
*/
|
||||
|
||||
if ($response == 'passwords.user') {
|
||||
\Log::debug('User with email '.$request->input('email').' attempted a password reset request but was not found. No email was sent.');
|
||||
return redirect()->route('login')->with('success', trans('passwords.user_inactive'));
|
||||
// Regardless of response, we do not want to disclose the status of a user account,
|
||||
// so we give them a generic "If this exists, we're TOTALLY gonna email you" response
|
||||
return redirect()->route('login')->with('success',trans('passwords.sent'));
|
||||
}
|
||||
|
||||
return back()->withErrors(
|
||||
['email' => trans($response)]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Validator;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ThrottlesLogins;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Config;
|
||||
use App\Services\LdapAd;
|
||||
use App\Services\Saml;
|
||||
use Com\Tecnick\Barcode\Barcode;
|
||||
use Google2FA;
|
||||
use Illuminate\Foundation\Auth\ThrottlesLogins;
|
||||
use Illuminate\Http\Request;
|
||||
use Input;
|
||||
use Redirect;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Log;
|
||||
use View;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Redirect;
|
||||
|
||||
/**
|
||||
* This controller handles authentication for the user, including local
|
||||
@@ -39,22 +40,43 @@ class LoginController extends Controller
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* @var LdapAd
|
||||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* @var Saml
|
||||
*/
|
||||
protected $saml;
|
||||
|
||||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*
|
||||
* @param LdapAd $ldap
|
||||
* @param Saml $saml
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(LdapAd $ldap, Saml $saml)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest', ['except' => ['logout','postTwoFactorAuth','getTwoFactorAuth','getTwoFactorEnroll']]);
|
||||
\Session::put('backUrl', \URL::previous());
|
||||
Session::put('backUrl', \URL::previous());
|
||||
$this->ldap = $ldap;
|
||||
$this->saml = $saml;
|
||||
}
|
||||
|
||||
function showLoginForm(Request $request)
|
||||
{
|
||||
$this->loginViaRemoteUser($request);
|
||||
$this->loginViaSaml($request);
|
||||
if (Auth::check()) {
|
||||
return redirect()->intended('dashboard');
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
if ($this->saml->isEnabled() && Setting::getSettings()->saml_forcelogin == "1" && !($request->has('nosaml') || $request->session()->has('error'))) {
|
||||
return redirect()->route('saml.login');
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->login_common_disabled == "1") {
|
||||
@@ -64,15 +86,97 @@ class LoginController extends Controller
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in a user by SAML
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return User
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loginViaSaml(Request $request)
|
||||
{
|
||||
$saml = $this->saml;
|
||||
$samlData = $request->session()->get('saml_login');
|
||||
if ($saml->isEnabled() && !empty($samlData)) {
|
||||
try {
|
||||
Log::debug("Attempting to log user in by SAML authentication.");
|
||||
$user = $saml->samlLogin($samlData);
|
||||
if(!is_null($user)) {
|
||||
Auth::login($user, true);
|
||||
} else {
|
||||
$username = $saml->getUsername();
|
||||
Log::error("SAML user '$username' could not be found in database.");
|
||||
$request->session()->flash('error', trans('auth/message.signin.error'));
|
||||
$saml->clearData();
|
||||
}
|
||||
|
||||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->save();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("There was an error authenticating the SAML user: " . $e->getMessage());
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in a user by LDAP
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return User
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loginViaLdap(Request $request): User
|
||||
{
|
||||
try {
|
||||
return $this->ldap->ldapLogin($request->input('username'), $request->input('password'));
|
||||
} catch (\Exception $ex) {
|
||||
LOG::debug("LDAP user login: " . $ex->getMessage());
|
||||
throw new \Exception($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function loginViaRemoteUser(Request $request)
|
||||
{
|
||||
$remote_user = $request->server('REMOTE_USER');
|
||||
$header_name = Setting::getSettings()->login_remote_user_header_name ?: 'REMOTE_USER';
|
||||
$remote_user = $request->server($header_name);
|
||||
if (Setting::getSettings()->login_remote_user_enabled == "1" && isset($remote_user) && !empty($remote_user)) {
|
||||
Log::debug("Authenticatiing via REMOTE_USER.");
|
||||
Log::debug("Authenticating via HTTP header $header_name.");
|
||||
|
||||
$strip_prefixes = [
|
||||
// IIS/AD
|
||||
// https://github.com/snipe/snipe-it/pull/5862
|
||||
'\\',
|
||||
|
||||
// Google Cloud IAP
|
||||
// https://cloud.google.com/iap/docs/identity-howto#getting_the_users_identity_with_signed_headers
|
||||
'accounts.google.com:',
|
||||
];
|
||||
|
||||
$pos = 0;
|
||||
foreach ($strip_prefixes as $needle) {
|
||||
if (($pos = strpos($remote_user, $needle)) !== FALSE) {
|
||||
$pos += strlen($needle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$pos = strpos($remote_user, '\\');
|
||||
if ($pos > 0) {
|
||||
$remote_user = substr($remote_user, $pos + 1);
|
||||
$remote_user = substr($remote_user, $pos);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -85,53 +189,6 @@ class LoginController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function loginViaLdap(Request $request)
|
||||
{
|
||||
Log::debug("Binding user to LDAP.");
|
||||
$ldap_user = Ldap::findAndBindUserLdap($request->input('username'), $request->input('password'));
|
||||
if (!$ldap_user) {
|
||||
Log::debug("LDAP user ".$request->input('username')." not found in LDAP or could not bind");
|
||||
throw new \Exception("Could not find user in LDAP directory");
|
||||
} else {
|
||||
Log::debug("LDAP user ".$request->input('username')." successfully bound to LDAP");
|
||||
}
|
||||
|
||||
// Check if the user already exists in the database and was imported via LDAP
|
||||
$user = User::where('username', '=', Input::get('username'))->whereNull('deleted_at')->where('ldap_import', '=', 1)->where('activated', '=', '1')->first();
|
||||
Log::debug("Local auth lookup complete");
|
||||
|
||||
// The user does not exist in the database. Try to get them from LDAP.
|
||||
// If user does not exist and authenticates successfully with LDAP we
|
||||
// will create it on the fly and sign in with default permissions
|
||||
if (!$user) {
|
||||
Log::debug("Local user ".Input::get('username')." does not exist");
|
||||
Log::debug("Creating local user ".Input::get('username'));
|
||||
|
||||
if ($user = Ldap::createUserFromLdap($ldap_user)) { //this handles passwords on its own
|
||||
Log::debug("Local user created.");
|
||||
} else {
|
||||
Log::debug("Could not create local user.");
|
||||
throw new \Exception("Could not create local user");
|
||||
}
|
||||
// If the user exists and they were imported from LDAP already
|
||||
} else {
|
||||
Log::debug("Local user ".$request->input('username')." exists in database. Updating existing user against LDAP.");
|
||||
|
||||
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
||||
|
||||
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$user->email = $ldap_attr['email'];
|
||||
$user->first_name = $ldap_attr['firstname'];
|
||||
$user->last_name = $ldap_attr['lastname'];
|
||||
$user->save();
|
||||
} // End if(!user)
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Account sign in form processing.
|
||||
*
|
||||
@@ -143,14 +200,14 @@ class LoginController extends Controller
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
$validator = $this->validator(Input::all());
|
||||
$validator = $this->validator($request->all());
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
$this->maxLoginAttempts = config('auth.throttle.max_attempts');
|
||||
$this->lockoutTime = config('auth.throttle.lockout_duration');
|
||||
$this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
$this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
|
||||
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
@@ -160,9 +217,10 @@ class LoginController extends Controller
|
||||
$user = null;
|
||||
|
||||
// Should we even check for LDAP users?
|
||||
if (Setting::getSettings()->ldap_enabled=='1') {
|
||||
Log::debug("LDAP is enabled.");
|
||||
if ($this->ldap->init()) {
|
||||
LOG::debug("LDAP is enabled.");
|
||||
try {
|
||||
LOG::debug("Attempting to log user in by LDAP authentication.");
|
||||
$user = $this->loginViaLdap($request);
|
||||
Auth::login($user, true);
|
||||
|
||||
@@ -193,6 +251,7 @@ class LoginController extends Controller
|
||||
|
||||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->activated = 1;
|
||||
$user->save();
|
||||
}
|
||||
// Redirect to the users page
|
||||
@@ -227,15 +286,27 @@ class LoginController extends Controller
|
||||
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.already_enrolled'));
|
||||
}
|
||||
|
||||
$google2fa = new Google2FA();
|
||||
$secret = $google2fa->generateSecretKey();
|
||||
$secret = Google2FA::generateSecretKey();
|
||||
$user->two_factor_secret = $secret;
|
||||
$user->save();
|
||||
|
||||
$barcode = new \Com\Tecnick\Barcode\Barcode();
|
||||
$barcode_obj = $barcode->getBarcodeObj('QRCODE', 'otpauth://totp/'.urlencode($settings->site_name).':'.urlencode($user->username).'?secret='.urlencode($secret).'&issuer=Snipe-IT&period=30', 300, 300, 'black', array(-2, -2, -2, -2));
|
||||
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
|
||||
$barcode = new Barcode();
|
||||
$barcode_obj =
|
||||
$barcode->getBarcodeObj(
|
||||
'QRCODE',
|
||||
sprintf(
|
||||
'otpauth://totp/%s:%s?secret=%s&issuer=Snipe-IT&period=30',
|
||||
urlencode($settings->site_name),
|
||||
urlencode($user->username),
|
||||
urlencode($secret)
|
||||
),
|
||||
300,
|
||||
300,
|
||||
'black',
|
||||
[-2, -2, -2, -2]
|
||||
);
|
||||
|
||||
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -266,6 +337,8 @@ class LoginController extends Controller
|
||||
/**
|
||||
* Two factor code submission
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function postTwoFactorAuth(Request $request)
|
||||
@@ -279,11 +352,14 @@ class LoginController extends Controller
|
||||
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
|
||||
}
|
||||
|
||||
if (!$request->has('two_factor_secret')) {
|
||||
return redirect()->route('two-factor')->with('error', 'Two-factor code is required.');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$google2fa = new Google2FA();
|
||||
$secret = $request->input('two_factor_secret');
|
||||
|
||||
if ($google2fa->verifyKey($user->two_factor_secret, $secret)) {
|
||||
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
||||
$user->two_factor_enrolled = 1;
|
||||
$user->save();
|
||||
$request->session()->put('2fa_authed', 'true');
|
||||
@@ -299,21 +375,47 @@ class LoginController extends Controller
|
||||
/**
|
||||
* Logout page.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$saml = $this->saml;
|
||||
$sloRedirectUrl = null;
|
||||
$sloRequestUrl = null;
|
||||
|
||||
if ($saml->isEnabled()) {
|
||||
$auth = $saml->getAuth();
|
||||
$sloRedirectUrl = $request->session()->get('saml_slo_redirect_url');
|
||||
|
||||
if (!empty($auth->getSLOurl()) && $settings->saml_slo == '1' && $saml->isAuthenticated() && empty($sloRedirectUrl)) {
|
||||
$sloRequestUrl = $auth->logout(null, array(), $saml->getNameId(), $saml->getSessionIndex(), true, $saml->getNameIdFormat(), $saml->getNameIdNameQualifier(), $saml->getNameIdSPNameQualifier());
|
||||
}
|
||||
|
||||
$saml->clearData();
|
||||
}
|
||||
|
||||
if (!empty($sloRequestUrl)) {
|
||||
return redirect()->away($sloRequestUrl);
|
||||
}
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
Auth::logout();
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
if (!empty($sloRedirectUrl)) {
|
||||
return redirect()->away($sloRedirectUrl);
|
||||
}
|
||||
|
||||
$customLogoutUrl = $settings->login_remote_user_custom_logout_url ;
|
||||
if ($settings->login_remote_user_enabled == '1' && $customLogoutUrl != '') {
|
||||
return redirect()->away($customLogoutUrl);
|
||||
}
|
||||
|
||||
return redirect()->route('login')->with('success', trans('auth/message.logout.success'));
|
||||
return redirect()->route('login')->with(['success' => trans('auth/message.logout.success'), 'loggedout' => true]);
|
||||
}
|
||||
|
||||
|
||||
@@ -367,8 +469,8 @@ class LoginController extends Controller
|
||||
*/
|
||||
protected function hasTooManyLoginAttempts(Request $request)
|
||||
{
|
||||
$lockoutTime = config('auth.throttle.lockout_duration');
|
||||
$maxLoginAttempts = config('auth.throttle.max_attempts');
|
||||
$lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
$maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
|
||||
return $this->limiter()->tooManyAttempts(
|
||||
$this->throttleKey($request),
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use App\Http\Requests\SaveUserRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
@@ -29,6 +33,8 @@ class ResetPasswordController extends Controller
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
protected $username = 'username';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
@@ -39,7 +45,76 @@ class ResetPasswordController extends Controller
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
'username' => 'required',
|
||||
'password' => 'confirmed|'.Setting::passwordComplexityRulesSaving('store'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function credentials(Request $request)
|
||||
{
|
||||
return $request->only(
|
||||
'username', 'password', 'password_confirmation', 'token'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
return view('auth.passwords.reset')->with(
|
||||
[
|
||||
'token' => $token,
|
||||
'username' => $request->input('username')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
|
||||
$messages = [
|
||||
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
|
||||
];
|
||||
|
||||
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
|
||||
|
||||
// Check to see if the user even exists
|
||||
$user = User::where('username', '=', $request->input('username'))->first();
|
||||
|
||||
$broker = $this->broker();
|
||||
if (strpos(Setting::passwordComplexityRulesSaving('store'), 'disallow_same_pwd_as_user_fields') !== FALSE) {
|
||||
$request->validate(
|
||||
[
|
||||
'password' => 'required|notIn:["'.$user->email.'","'.$user->username.'","'.$user->first_name.'","'.$user->last_name.'"'
|
||||
], $messages);
|
||||
|
||||
}
|
||||
|
||||
|
||||
$response = $broker->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
return $response == \Password::PASSWORD_RESET
|
||||
? $this->sendResetResponse($request, $response)
|
||||
: $this->sendResetFailedResponse($request, $response);
|
||||
}
|
||||
|
||||
|
||||
protected function sendResetFailedResponse(Request $request, $response)
|
||||
{
|
||||
return redirect()->back()
|
||||
->withInput(['username'=> $request->input('username')])
|
||||
->withErrors(['username' => trans($response), 'password' => trans($response)]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
143
app/Http/Controllers/Auth/SamlController.php
Normal file
143
app/Http/Controllers/Auth/SamlController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Saml;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* This controller provides the endpoint for SAML communication and metadata.
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class SamlController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var Saml
|
||||
*/
|
||||
protected $saml;
|
||||
|
||||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Saml $saml)
|
||||
{
|
||||
$this->saml = $saml;
|
||||
|
||||
$this->middleware('guest', ['except' => ['metadata','sls']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SAML SP metadata for Snipe-IT
|
||||
*
|
||||
* /saml/metadata
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function metadata(Request $request)
|
||||
{
|
||||
$metadata = $this->saml->getSPMetadata();
|
||||
|
||||
if (empty($metadata)) {
|
||||
return response()->view('errors.403', [], 403);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($metadata) {
|
||||
echo $metadata;
|
||||
}, 'snipe-it-metadata.xml', ['Content-Type' => 'text/xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin the SP-Initiated SSO by sending AuthN to the IdP.
|
||||
*
|
||||
* /login/saml
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$auth = $this->saml->getAuth();
|
||||
$ssoUrl = $auth->login(null, array(), false, false, false, false);
|
||||
return redirect()->away($ssoUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives, parses the assertion from IdP and flashes SAML data
|
||||
* back to the LoginController for authentication.
|
||||
*
|
||||
* /saml/acs
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function acs(Request $request)
|
||||
{
|
||||
$saml = $this->saml;
|
||||
$auth = $saml->getAuth();
|
||||
$auth->processResponse();
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (!empty($errors)) {
|
||||
Log::error("There was an error with SAML ACS: " . implode(', ', $errors));
|
||||
Log::error("Reason: " . $auth->getLastErrorReason());
|
||||
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
|
||||
}
|
||||
|
||||
$samlData = $saml->extractData();
|
||||
|
||||
return redirect()->route('login')->with('saml_login', $samlData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives LogoutRequest/LogoutResponse from IdP and flashes
|
||||
* back to the LoginController for logging out.
|
||||
*
|
||||
* /saml/sls
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Redirect
|
||||
*/
|
||||
public function sls(Request $request)
|
||||
{
|
||||
$auth = $this->saml->getAuth();
|
||||
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
|
||||
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (!empty($errors)) {
|
||||
Log::error("There was an error with SAML SLS: " . implode(', ', $errors));
|
||||
Log::error("Reason: " . $auth->getLastErrorReason());
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
return redirect()->route('logout')->with('saml_slo_redirect_url', $sloUrl);
|
||||
}
|
||||
}
|
||||
138
app/Http/Controllers/BulkAssetModelsController.php
Normal file
138
app/Http/Controllers/BulkAssetModelsController.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
|
||||
class BulkAssetModelsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that allows the user to bulk edit model attrbutes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.7]
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$models_raw_array = $request->input('ids');
|
||||
|
||||
// Make sure some IDs have been selected
|
||||
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
|
||||
|
||||
$models = AssetModel::whereIn('id', $models_raw_array)
|
||||
->withCount('assets as assets_count')
|
||||
->orderBy('assets_count', 'ASC')
|
||||
->get();
|
||||
|
||||
// If deleting....
|
||||
if ($request->input('bulk_actions')=='delete') {
|
||||
$valid_count = 0;
|
||||
foreach ($models as $model) {
|
||||
if ($model->assets_count == 0) {
|
||||
$valid_count++;
|
||||
}
|
||||
}
|
||||
return view('models/bulk-delete', compact('models'))->with('valid_count', $valid_count);
|
||||
|
||||
// Otherwise display the bulk edit screen
|
||||
}
|
||||
|
||||
$nochange = ['NC' => 'No Change'];
|
||||
return view('models/bulk-edit', compact('models'))
|
||||
->with('fieldset_list', $nochange + Helper::customFieldsetList())
|
||||
->with('depreciation_list', $nochange + Helper::depreciationList());
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
->with('error', 'You must select at least one model to edit.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that allows the user to bulk edit model attrbutes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.7]
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
|
||||
$models_raw_array = $request->input('ids');
|
||||
$update_array = array();
|
||||
|
||||
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
|
||||
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
|
||||
}
|
||||
if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) {
|
||||
$update_array['category_id'] = $request->input('category_id');
|
||||
}
|
||||
if ($request->input('fieldset_id')!='NC') {
|
||||
$update_array['fieldset_id'] = $request->input('fieldset_id');
|
||||
}
|
||||
if ($request->input('depreciation_id')!='NC') {
|
||||
$update_array['depreciation_id'] = $request->input('depreciation_id');
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (count($update_array) > 0) {
|
||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||
return redirect()->route('models.index')
|
||||
->with('success', trans('admin/models/message.bulkedit.success'));
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
->with('warning', trans('admin/models/message.bulkedit.error'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and delete the given Asset Models. An Asset Model
|
||||
* cannot be deleted if there are associated assets.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$models_raw_array = $request->input('ids');
|
||||
|
||||
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
|
||||
|
||||
$models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->get();
|
||||
|
||||
$del_error_count = 0;
|
||||
$del_count = 0;
|
||||
|
||||
foreach ($models as $model) {
|
||||
if ($model->assets_count > 0) {
|
||||
$del_error_count++;
|
||||
} else {
|
||||
$model->delete();
|
||||
$del_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($del_error_count == 0) {
|
||||
return redirect()->route('models.index')
|
||||
->with('success', trans('admin/models/message.bulkdelete.success',['success_count'=> $del_count] ));
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
->with('warning', trans('admin/models/message.bulkdelete.success_partial', ['fail_count'=>$del_error_count, 'success_count'=> $del_count]));
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
->with('error', trans('admin/models/message.bulkdelete.error'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,21 +2,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Category as Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Redirect;
|
||||
use Str;
|
||||
use View;
|
||||
use Image;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Category as Category;
|
||||
use Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Str;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to Categories for
|
||||
@@ -29,13 +19,14 @@ class CategoriesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the categories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::getDatatable() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the categories listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::getDatatable() method that generates the JSON response
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
@@ -46,30 +37,32 @@ class CategoriesController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Returns a form view to create a new category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::store() method that stores the data
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns a form view to create a new category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::store() method that stores the data
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// Show the page
|
||||
$this->authorize('create', Category::class);
|
||||
$category_types= Helper::categoryTypeList();
|
||||
return view('categories/edit')->with('item', new Category)
|
||||
->with('category_types', $category_types);
|
||||
->with('category_types', Helper::categoryTypeList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates and stores the new category data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::create() method that makes the form.
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* Validates and stores the new category data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::create() method that makes the form.
|
||||
* @since [v1.0]
|
||||
* @param ImageUploadRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
@@ -83,8 +76,7 @@ class CategoriesController extends Controller
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
$category->user_id = Auth::id();
|
||||
|
||||
$category = $request->handleImages($category,600, public_path().'/uploads/categories');
|
||||
|
||||
$category = $request->handleImages($category);
|
||||
if ($category->save()) {
|
||||
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.create.success'));
|
||||
}
|
||||
@@ -93,24 +85,23 @@ class CategoriesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that makes a form to update a category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::postEdit() method saves the data
|
||||
* @param int $categoryId
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns a view that makes a form to update a category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::postEdit() method saves the data
|
||||
* @param int $categoryId
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($categoryId = null)
|
||||
{
|
||||
$this->authorize('edit', Category::class);
|
||||
$this->authorize('update', Category::class);
|
||||
if (is_null($item = Category::find($categoryId))) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
|
||||
}
|
||||
$category_types= Helper::categoryTypeList();
|
||||
|
||||
return view('categories/edit', compact('item'))
|
||||
->with('category_types', $category_types);
|
||||
->with('category_types', Helper::categoryTypeList());
|
||||
}
|
||||
|
||||
|
||||
@@ -119,14 +110,15 @@ class CategoriesController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::getEdit() method that makes the form.
|
||||
* @param Request $request
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $categoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $categoryId = null)
|
||||
{
|
||||
$this->authorize('edit', Category::class);
|
||||
$this->authorize('update', Category::class);
|
||||
if (is_null($category = Category::find($categoryId))) {
|
||||
// Redirect to the categories management page
|
||||
return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist'));
|
||||
@@ -142,12 +134,8 @@ class CategoriesController extends Controller
|
||||
$category->require_acceptance = $request->input('require_acceptance', '0');
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
|
||||
// Set the model's image property to null if the image is being deleted
|
||||
if ($request->input('image_delete') == 1) {
|
||||
$category->image = null;
|
||||
}
|
||||
|
||||
$category = $request->handleImages($category,600, public_path().'/uploads/categories');
|
||||
$category = $request->handleImages($category);
|
||||
|
||||
if ($category->save()) {
|
||||
// Redirect to the new category page
|
||||
@@ -158,31 +146,27 @@ class CategoriesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and marks a category as deleted.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $categoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* Validates and marks a category as deleted.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $categoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($categoryId)
|
||||
{
|
||||
$this->authorize('delete', Category::class);
|
||||
// Check if the category exists
|
||||
if (is_null($category = Category::find($categoryId))) {
|
||||
if (is_null($category = Category::findOrFail($categoryId))) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
|
||||
}
|
||||
|
||||
if ($category->has_models() > 0) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'model']));
|
||||
} elseif ($category->accessories()->count() > 0) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory']));
|
||||
} elseif ($category->consumables()->count() > 0) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable']));
|
||||
} elseif ($category->components()->count() > 0) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component']));
|
||||
if (!$category->isDeletable()) {
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=> $category->category_type ]));
|
||||
}
|
||||
|
||||
Storage::disk('public')->delete('categories'.'/'.$category->image);
|
||||
$category->delete();
|
||||
// Redirect to the locations management page
|
||||
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
|
||||
@@ -190,14 +174,15 @@ class CategoriesController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the categories detail view, which is generated in getDataView.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::getDataView() method that generates the JSON response
|
||||
* @param int $categoryId
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the categories detail view, which is generated in getDataView.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see CategoriesController::getDataView() method that generates the JSON response
|
||||
* @param $id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v1.8]
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
@@ -221,6 +206,4 @@ class CategoriesController extends Controller
|
||||
|
||||
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Location;
|
||||
use App\Models\SnipeModel;
|
||||
use App\Models\User;
|
||||
|
||||
trait CheckInOutRequest
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Company;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Redirect;
|
||||
use View;
|
||||
use Illuminate\Http\Request;
|
||||
use Image;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Companies for
|
||||
@@ -21,11 +17,12 @@ final class CompaniesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns view to display listing of companies.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns view to display listing of companies.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
@@ -35,11 +32,12 @@ final class CompaniesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns view to create a new company.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Returns view to create a new company.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
@@ -55,6 +53,7 @@ final class CompaniesController extends Controller
|
||||
* @since [v1.8]
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
@@ -63,7 +62,7 @@ final class CompaniesController extends Controller
|
||||
$company = new Company;
|
||||
$company->name = $request->input('name');
|
||||
|
||||
$company = $request->handleImages($company,600, public_path().'/uploads/companies');
|
||||
$company = $request->handleImages($company);
|
||||
|
||||
if ($company->save()) {
|
||||
return redirect()->route('companies.index')
|
||||
@@ -74,12 +73,13 @@ final class CompaniesController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Return form to edit existing company.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* Return form to edit existing company.
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($companyId)
|
||||
{
|
||||
@@ -98,9 +98,10 @@ final class CompaniesController extends Controller
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @param Request $request
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $companyId)
|
||||
{
|
||||
@@ -112,12 +113,8 @@ final class CompaniesController extends Controller
|
||||
|
||||
$company->name = $request->input('name');
|
||||
|
||||
// Set the model's image property to null if the image is being deleted
|
||||
if ($request->input('image_delete') == 1) {
|
||||
$company->image = null;
|
||||
}
|
||||
|
||||
$company = $request->handleImages($company,600, public_path().'/uploads/companies');
|
||||
$company = $request->handleImages($company);
|
||||
|
||||
|
||||
if ($company->save()) {
|
||||
@@ -129,39 +126,38 @@ final class CompaniesController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete company
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* Delete company
|
||||
*
|
||||
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
|
||||
* @since [v1.8]
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($companyId)
|
||||
{
|
||||
if (is_null($company = Company::find($companyId))) {
|
||||
return redirect()->route('companies.index')
|
||||
->with('error', trans('admin/companies/message.not_found'));
|
||||
} else {
|
||||
}
|
||||
|
||||
$this->authorize('delete', $company);
|
||||
$this->authorize('delete', $company);
|
||||
if(!$company->isDeletable()) {
|
||||
return redirect()->route('companies.index')
|
||||
->with('error', trans('admin/companies/message.assoc_users'));
|
||||
}
|
||||
|
||||
try {
|
||||
$company->delete();
|
||||
return redirect()->route('companies.index')
|
||||
->with('success', trans('admin/companies/message.delete.success'));
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
/*
|
||||
* NOTE: This happens when there's a foreign key constraint violation
|
||||
* For example when rows in other tables are referencing this company
|
||||
*/
|
||||
if ($exception->getCode() == 23000) {
|
||||
return redirect()->route('companies.index')
|
||||
->with('error', trans('admin/companies/message.assoc_users'));
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
if ($company->image) {
|
||||
try {
|
||||
Storage::disk('public')->delete('companies'.'/'.$company->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$company->delete();
|
||||
return redirect()->route('companies.index')
|
||||
->with('success', trans('admin/companies/message.delete.success'));
|
||||
}
|
||||
|
||||
public function show($id) {
|
||||
@@ -170,9 +166,8 @@ final class CompaniesController extends Controller
|
||||
if (is_null($company = Company::find($id))) {
|
||||
return redirect()->route('companies.index')
|
||||
->with('error', trans('admin/companies/message.not_found'));
|
||||
} else {
|
||||
return view('companies/view')->with('company',$company);
|
||||
}
|
||||
|
||||
return view('companies/view')->with('company',$company);
|
||||
}
|
||||
}
|
||||
|
||||
108
app/Http/Controllers/Components/ComponentCheckinController.php
Normal file
108
app/Http/Controllers/Components/ComponentCheckinController.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Events\ComponentCheckedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ComponentCheckinController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a view that allows the checkin of a component from an asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentCheckinController::store() method that stores the data.
|
||||
* @since [v4.1.4]
|
||||
* @param $component_asset_id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($component_asset_id)
|
||||
{
|
||||
|
||||
// This could probably be done more cleanly but I am very tired. - @snipe
|
||||
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
||||
if (is_null($component = Component::find($component_assets->component_id))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
|
||||
}
|
||||
if (is_null($asset = Asset::find($component_assets->asset_id))) {
|
||||
return redirect()->route('components.index')->with('error',
|
||||
trans('admin/components/message.not_found'));
|
||||
}
|
||||
$this->authorize('checkin', $component);
|
||||
return view('components/checkin', compact('component_assets','component','asset'));
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and store checkin data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentCheckinController::create() method that returns the form.
|
||||
* @since [v4.1.4]
|
||||
* @param Request $request
|
||||
* @param $component_asset_id
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(Request $request, $component_asset_id)
|
||||
{
|
||||
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
||||
if (is_null($component = Component::find($component_assets->component_id))) {
|
||||
return redirect()->route('components.index')->with('error',
|
||||
trans('admin/components/message.not_found'));
|
||||
}
|
||||
|
||||
|
||||
$this->authorize('checkin', $component);
|
||||
|
||||
$max_to_checkin = $component_assets->assigned_qty;
|
||||
$validator = Validator::make($request->all(), [
|
||||
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Validation passed, so let's figure out what we have to do here.
|
||||
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty'));
|
||||
|
||||
// We have to modify the record to reflect the new qty that's
|
||||
// actually checked out.
|
||||
$component_assets->assigned_qty = $qty_remaining_in_checkout;
|
||||
DB::table('components_assets')->where('id',
|
||||
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
|
||||
|
||||
// If the checked-in qty is exactly the same as the assigned_qty,
|
||||
// we can simply delete the associated components_assets record
|
||||
if ($qty_remaining_in_checkout == 0) {
|
||||
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
|
||||
}
|
||||
|
||||
$asset = Asset::find($component_assets->asset_id);
|
||||
|
||||
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
|
||||
|
||||
return redirect()->route('components.index')->with('success',
|
||||
trans('admin/components/message.checkin.success'));
|
||||
}
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Events\ComponentCheckedOut;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ComponentCheckoutController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that allows the checkout of a component to an asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentCheckoutController::store() method that stores the data.
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($componentId)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
$this->authorize('checkout', $component);
|
||||
return view('components/checkout', compact('component'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and store checkout data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentCheckoutController::create() method that returns the form.
|
||||
* @since [v3.0]
|
||||
* @param Request $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(Request $request, $componentId)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $component);
|
||||
|
||||
$max_to_checkout = $component->numRemaining();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"asset_id" => "required",
|
||||
"assigned_qty" => "required|numeric|between:1,$max_to_checkout"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$admin_user = Auth::user();
|
||||
$asset_id = e($request->input('asset_id'));
|
||||
|
||||
// Check if the user exists
|
||||
if (is_null($asset = Asset::find($asset_id))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.asset_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the component data
|
||||
$component->asset_id = $asset_id;
|
||||
|
||||
$component->assets()->attach($component->id, [
|
||||
'component_id' => $component->id,
|
||||
'user_id' => $admin_user->id,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'assigned_qty' => $request->input('assigned_qty'),
|
||||
'asset_id' => $asset_id
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
|
||||
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
|
||||
}
|
||||
}
|
||||
210
app/Http/Controllers/Components/ComponentsController.php
Normal file
210
app/Http/Controllers/Components/ComponentsController.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to Components for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class ComponentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the components listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getDatatable() method that generates the JSON response
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('view', Component::class);
|
||||
return view('components/index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a form to create a new component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postCreate() method that stores the data
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Component::class);
|
||||
return view('components/edit')->with('category_type', 'component')
|
||||
->with('item', new Component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and store data for new component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getCreate() method that generates the view
|
||||
* @since [v3.0]
|
||||
* @param ImageUploadRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize('create', Component::class);
|
||||
$component = new Component();
|
||||
$component->name = $request->input('name');
|
||||
$component->category_id = $request->input('category_id');
|
||||
$component->location_id = $request->input('location_id');
|
||||
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$component->order_number = $request->input('order_number', null);
|
||||
$component->min_amt = $request->input('min_amt', null);
|
||||
$component->serial = $request->input('serial', null);
|
||||
$component->purchase_date = $request->input('purchase_date', null);
|
||||
$component->purchase_cost = $request->input('purchase_cost', null);
|
||||
$component->qty = $request->input('qty');
|
||||
$component->user_id = Auth::id();
|
||||
|
||||
$component = $request->handleImages($component);
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view to edit a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postEdit() method that stores the data.
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($componentId = null)
|
||||
{
|
||||
if ($item = Component::find($componentId)) {
|
||||
$this->authorize('update', $item);
|
||||
return view('components/edit', compact('item'))->with('category_type', 'component');
|
||||
}
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a view to edit a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getEdit() method presents the form.
|
||||
* @param ImageUploadRequest $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v3.0]
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $componentId = null)
|
||||
{
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
$min = $component->numCHeckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$this->authorize('update', $component);
|
||||
|
||||
// Update the component data
|
||||
$component->name = $request->input('name');
|
||||
$component->category_id = $request->input('category_id');
|
||||
$component->location_id = $request->input('location_id');
|
||||
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$component->order_number = $request->input('order_number');
|
||||
$component->min_amt = $request->input('min_amt');
|
||||
$component->serial = $request->input('serial');
|
||||
$component->purchase_date = $request->input('purchase_date');
|
||||
$component->purchase_cost = request('purchase_cost');
|
||||
$component->qty = $request->input('qty');
|
||||
|
||||
$component = $request->handleImages($component);
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($componentId)
|
||||
{
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('delete', $component);
|
||||
|
||||
// Remove the image if one exists
|
||||
if (Storage::disk('public')->exists('components/'.$component->image)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('components/'.$component->image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$component->delete();
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view to display component information.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getDataView() method that generates the JSON response
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($componentId = null)
|
||||
{
|
||||
$component = Component::find($componentId);
|
||||
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('view', $component);
|
||||
return view('components/view', compact('component'));
|
||||
}
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('components.index')
|
||||
->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Company;
|
||||
use App\Models\Component;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\Asset;
|
||||
use Auth;
|
||||
use Config;
|
||||
use DB;
|
||||
use Input;
|
||||
use Lang;
|
||||
use Mail;
|
||||
use Redirect;
|
||||
use Slack;
|
||||
use Str;
|
||||
use View;
|
||||
use Validator;
|
||||
use Illuminate\Http\Request;
|
||||
use Gate;
|
||||
use Image;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to Components for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class ComponentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the components listing, which is generated in getDatatable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getDatatable() method that generates the JSON response
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('view', Component::class);
|
||||
return view('components/index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a form to create a new component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postCreate() method that stores the data
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Component::class);
|
||||
$category_type = 'component';
|
||||
return view('components/edit')->with('category_type',$category_type)
|
||||
->with('item', new Component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and store data for new component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getCreate() method that generates the view
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize('create', Component::class);
|
||||
$component = new Component();
|
||||
$component->name = $request->input('name');
|
||||
$component->category_id = $request->input('category_id');
|
||||
$component->location_id = $request->input('location_id');
|
||||
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
$component->order_number = $request->input('order_number', null);
|
||||
$component->min_amt = $request->input('min_amt', null);
|
||||
$component->serial = $request->input('serial', null);
|
||||
$component->purchase_date = $request->input('purchase_date', null);
|
||||
$component->purchase_cost = $request->input('purchase_cost', null);
|
||||
$component->qty = $request->input('qty');
|
||||
$component->user_id = Auth::id();
|
||||
|
||||
|
||||
$component = $request->handleImages($component,600, public_path().'/uploads/components');
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view to edit a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postEdit() method that stores the data.
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit($componentId = null)
|
||||
{
|
||||
|
||||
|
||||
if ($item = Component::find($componentId)) {
|
||||
$this->authorize('update', $item);
|
||||
$category_type = 'component';
|
||||
return view('components/edit', compact('item'))->with('category_type', $category_type);
|
||||
}
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a view to edit a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getEdit() method presents the form.
|
||||
* @param int $componentId
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $componentId = null)
|
||||
{
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('update', $component);
|
||||
|
||||
|
||||
// Update the component data
|
||||
$component->name = Input::get('name');
|
||||
$component->category_id = Input::get('category_id');
|
||||
$component->location_id = Input::get('location_id');
|
||||
$component->company_id = Company::getIdForCurrentUser(Input::get('company_id'));
|
||||
$component->order_number = Input::get('order_number');
|
||||
$component->min_amt = Input::get('min_amt');
|
||||
$component->serial = Input::get('serial');
|
||||
$component->purchase_date = Input::get('purchase_date');
|
||||
$component->purchase_cost = request('purchase_cost');
|
||||
$component->qty = Input::get('qty');
|
||||
|
||||
$component = $request->handleImages($component,600, public_path().'/uploads/components');
|
||||
|
||||
if ($component->save()) {
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($component->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a component.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy($componentId)
|
||||
{
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('delete', $component);
|
||||
$component->delete();
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view to display component information.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getDataView() method that generates the JSON response
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show($componentId = null)
|
||||
{
|
||||
$component = Component::find($componentId);
|
||||
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('view', $component);
|
||||
return view('components/view', compact('component'));
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that allows the checkout of a component to an asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postCheckout() method that stores the data.
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getCheckout($componentId)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
$this->authorize('checkout', $component);
|
||||
return view('components/checkout', compact('component'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and store checkout data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getCheckout() method that returns the form.
|
||||
* @since [v3.0]
|
||||
* @param Request $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function postCheckout(Request $request, $componentId)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $component);
|
||||
|
||||
$max_to_checkout = $component->numRemaining();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"asset_id" => "required",
|
||||
"assigned_qty" => "required|numeric|between:1,$max_to_checkout"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$admin_user = Auth::user();
|
||||
$asset_id = e(Input::get('asset_id'));
|
||||
|
||||
// Check if the user exists
|
||||
if (is_null($asset = Asset::find($asset_id))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.asset_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the component data
|
||||
$component->asset_id = $asset_id;
|
||||
|
||||
$component->assets()->attach($component->id, [
|
||||
'component_id' => $component->id,
|
||||
'user_id' => $admin_user->id,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'assigned_qty' => Input::get('assigned_qty'),
|
||||
'asset_id' => $asset_id
|
||||
]);
|
||||
|
||||
$component->logCheckout(e(Input::get('note')), $asset);
|
||||
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that allows the checkin of a component from an asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::postCheckout() method that stores the data.
|
||||
* @since [v4.1.4]
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getCheckin($component_asset_id)
|
||||
{
|
||||
|
||||
// This could probably be done more cleanly but I am very tired. - @snipe
|
||||
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
||||
if (is_null($component = Component::find($component_assets->component_id))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
|
||||
}
|
||||
if (is_null($asset = Asset::find($component_assets->asset_id))) {
|
||||
return redirect()->route('components.index')->with('error',
|
||||
trans('admin/components/message.not_found'));
|
||||
}
|
||||
$this->authorize('checkin', $component);
|
||||
return view('components/checkin', compact('component_assets','component','asset'));
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and store checkin data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentsController::getCheckout() method that returns the form.
|
||||
* @since [v4.1.4]
|
||||
* @param Request $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function postCheckin(Request $request, $component_asset_id)
|
||||
{
|
||||
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
|
||||
if (is_null($component = Component::find($component_assets->component_id))) {
|
||||
return redirect()->route('components.index')->with('error',
|
||||
trans('admin/components/message.not_found'));
|
||||
}
|
||||
|
||||
|
||||
$this->authorize('checkin', $component);
|
||||
|
||||
$max_to_checkin = $component_assets->assigned_qty;
|
||||
$validator = Validator::make($request->all(), [
|
||||
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Validation passed, so let's figure out what we have to do here.
|
||||
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty'));
|
||||
|
||||
// We have to modify the record to reflect the new qty that's
|
||||
// actually checked out.
|
||||
$component_assets->assigned_qty = $qty_remaining_in_checkout;
|
||||
DB::table('components_assets')->where('id',
|
||||
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
|
||||
|
||||
$log = new Actionlog();
|
||||
$log->user_id = Auth::user()->id;
|
||||
$log->action_type = 'checkin from';
|
||||
$log->target_type = Asset::class;
|
||||
$log->target_id = $component_assets->asset_id;
|
||||
$log->item_id = $component_assets->component_id;
|
||||
$log->item_type = Component::class;
|
||||
$log->note = $request->input('note');
|
||||
$log->save();
|
||||
|
||||
// If the checked-in qty is exactly the same as the assigned_qty,
|
||||
// we can simply delete the associated components_assets record
|
||||
if ($qty_remaining_in_checkout == 0) {
|
||||
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('success',
|
||||
trans('admin/components/message.checkout.success'));
|
||||
}
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Consumables;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
|
||||
class ConsumableCheckoutController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Return a view to checkout a consumable to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ConsumableCheckoutController::store() method that stores the data.
|
||||
* @since [v1.0]
|
||||
* @param int $consumableId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($consumableId)
|
||||
{
|
||||
if (is_null($consumable = Consumable::find($consumableId))) {
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
||||
}
|
||||
$this->authorize('checkout', $consumable);
|
||||
return view('consumables/checkout', compact('consumable'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the checkout information
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ConsumableCheckoutController::create() method that returns the form.
|
||||
* @since [v1.0]
|
||||
* @param int $consumableId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(Request $request, $consumableId)
|
||||
{
|
||||
if (is_null($consumable = Consumable::find($consumableId))) {
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $consumable);
|
||||
|
||||
$admin_user = Auth::user();
|
||||
$assigned_to = e($request->input('assigned_to'));
|
||||
|
||||
// Check if the user exists
|
||||
if (is_null($user = User::find($assigned_to))) {
|
||||
// Redirect to the consumable management page with error
|
||||
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the consumable data
|
||||
$consumable->assigned_to = e($request->input('assigned_to'));
|
||||
|
||||
$consumable->users()->attach($consumable->id, [
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $admin_user->id,
|
||||
'assigned_to' => e($request->input('assigned_to'))
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
// Redirect to the new consumable page
|
||||
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user