Compare commits
2510 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81fcb4c7b3 | ||
|
|
5ae65d5329 | ||
|
|
d04b0e4d4b | ||
|
|
a6d0209e93 | ||
|
|
810f6a3970 | ||
|
|
1379821876 | ||
|
|
33236f16ac | ||
|
|
90dddee923 | ||
|
|
7869ffcc79 | ||
|
|
1687e4b850 | ||
|
|
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 | ||
|
|
b9e19468e5 | ||
|
|
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 | ||
|
|
5b68a321a6 | ||
|
|
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 | ||
|
|
37568ae9ec | ||
|
|
32ad9050cf | ||
|
|
01a832169c | ||
|
|
3c6883489c | ||
|
|
6c23e881c7 | ||
|
|
b721a977c0 | ||
|
|
53c46ac8de | ||
|
|
3cba174dfb | ||
|
|
a90d2ba077 | ||
|
|
e5dd6035b9 | ||
|
|
66686f8080 | ||
|
|
041fc20955 | ||
|
|
b97ff4e907 | ||
|
|
232e3f30aa | ||
|
|
5aeac3c9e0 | ||
|
|
0658f17359 | ||
|
|
bcad49ce79 | ||
|
|
b5acca89d7 | ||
|
|
e52919cf1b | ||
|
|
29f3a5c48f | ||
|
|
134e8e6fb9 | ||
|
|
714576be45 | ||
|
|
fa291beaeb | ||
|
|
4b7f6a95e3 | ||
|
|
5128992940 | ||
|
|
0291323502 | ||
|
|
262a964760 | ||
|
|
e0f6f9b839 | ||
|
|
f1a6308002 | ||
|
|
19d8d7b146 | ||
|
|
b999c50a2e | ||
|
|
e3906b245c | ||
|
|
9ca20e4964 | ||
|
|
456a74d88c | ||
|
|
799c059070 | ||
|
|
c62d43a778 | ||
|
|
b725bd0fae | ||
|
|
09190e05ae | ||
|
|
e0644dbbf6 | ||
|
|
5b6925b00c | ||
|
|
df17a859bf | ||
|
|
24c43056ba | ||
|
|
4e0a9c7a94 | ||
|
|
6a089f86f8 | ||
|
|
606b7e905d | ||
|
|
d73ddad477 | ||
|
|
502b3c50a9 | ||
|
|
9a39cf721e | ||
|
|
2bb5ac61a9 | ||
|
|
3e62a6ea2d | ||
|
|
7410b16835 | ||
|
|
8994f3e15e | ||
|
|
d23f1a77ca | ||
|
|
e955c983a3 | ||
|
|
b09e7d19b3 | ||
|
|
2fa17ac185 | ||
|
|
3b1e46f72b | ||
|
|
0c1a1de2a2 | ||
|
|
20c9ae5818 | ||
|
|
eed41e4549 | ||
|
|
b750f4754f | ||
|
|
c17a06792a | ||
|
|
4f76cc6cfb | ||
|
|
b905154373 | ||
|
|
daf748e531 | ||
|
|
799a93c46a | ||
|
|
34aa12e229 | ||
|
|
897757bd04 | ||
|
|
c7125c3937 | ||
|
|
0238c88a3e | ||
|
|
145dfe5847 | ||
|
|
68263ff806 | ||
|
|
81a6332889 | ||
|
|
6e563f6e4b | ||
|
|
5320f5c67c | ||
|
|
7f69ae953b | ||
|
|
17f6fbabfa | ||
|
|
318da67230 | ||
|
|
c79f8c1baf | ||
|
|
e7a820f7c9 | ||
|
|
9c9fc2754c | ||
|
|
8fd4529fad | ||
|
|
12c92e30b7 | ||
|
|
5b02cfade4 | ||
|
|
fd10b755b0 | ||
|
|
567a9639f4 | ||
|
|
7a433f8598 | ||
|
|
3d8823ede5 | ||
|
|
dbbb7680d9 | ||
|
|
cf0dd5bbad | ||
|
|
25e53d8c7f | ||
|
|
89d433b41a | ||
|
|
e2570ada6f | ||
|
|
45afe725a1 | ||
|
|
536401fe0f | ||
|
|
ec6ed256fb | ||
|
|
2aaa7bed2d | ||
|
|
cc9f1577a4 | ||
|
|
ab1fe8be0c | ||
|
|
339bdddc38 | ||
|
|
35b9cf4b70 | ||
|
|
7ccb41371e | ||
|
|
2e60a457bf | ||
|
|
2390d2160b | ||
|
|
00b051b8c7 | ||
|
|
05b3a9ad7e | ||
|
|
4fb880384f | ||
|
|
43042ad841 | ||
|
|
a716382ac4 | ||
|
|
36c8f7f4f1 | ||
|
|
b42801f6ae | ||
|
|
946129f206 | ||
|
|
b941ef1e08 | ||
|
|
d1aa11ec89 | ||
|
|
de4934f21d | ||
|
|
b10076b015 | ||
|
|
af06e42056 | ||
|
|
9a2440dc4b | ||
|
|
2ac1c1636c | ||
|
|
a7eb89f6b6 | ||
|
|
004ecad059 | ||
|
|
1267c68f28 | ||
|
|
262eb79471 | ||
|
|
7a785b4952 | ||
|
|
beae8efb21 | ||
|
|
3c0a6292c4 | ||
|
|
8ccc1c6515 | ||
|
|
9839e5e566 | ||
|
|
d54acd908c | ||
|
|
42fe1ea398 | ||
|
|
bb062b20a1 | ||
|
|
a93810e301 | ||
|
|
d14ab7e3e1 | ||
|
|
f137c1ca09 | ||
|
|
f196b5bd94 | ||
|
|
e0d9cc4e73 | ||
|
|
5b4e4c8dcd | ||
|
|
dee1f5ec35 | ||
|
|
850bfc40d4 | ||
|
|
e7f74d94c1 | ||
|
|
e97cf011b6 | ||
|
|
ed23505054 | ||
|
|
b3fe47bfa7 | ||
|
|
1e5d40f32e | ||
|
|
f0546bf689 | ||
|
|
136df9418f | ||
|
|
7ea862787c | ||
|
|
25d6ceee6e | ||
|
|
4fef065010 | ||
|
|
fe74394ae6 | ||
|
|
7a76cb08a0 | ||
|
|
c70bd62a0f | ||
|
|
ad1db2c640 | ||
|
|
2e936a9883 | ||
|
|
3db4480bba | ||
|
|
001e721530 | ||
|
|
8210da6e82 | ||
|
|
6a80f95769 | ||
|
|
edd4797178 | ||
|
|
f88683766b | ||
|
|
e4385c0f8c | ||
|
|
7f8c6fe282 | ||
|
|
8cc9e61983 | ||
|
|
c5560e466e | ||
|
|
157a2ee869 | ||
|
|
de7ee40863 | ||
|
|
a6f90cb3fc | ||
|
|
1321304720 | ||
|
|
e4888b23e2 | ||
|
|
274f82893f | ||
|
|
0550fe0ffa | ||
|
|
730632e2eb | ||
|
|
7fb3a9b82c | ||
|
|
b03f8a2425 | ||
|
|
ecb1e87fe6 | ||
|
|
f43df5f041 | ||
|
|
95cc48e422 | ||
|
|
9a2ed804ca | ||
|
|
d20fad28e5 | ||
|
|
ae813ddf75 | ||
|
|
bb42109c0c | ||
|
|
f46ecf8ec0 | ||
|
|
b9e821c0e6 | ||
|
|
9ee28c7513 | ||
|
|
1a8ba06702 | ||
|
|
0fd232e70d | ||
|
|
ee4d69b1c5 | ||
|
|
d22be8ee0e | ||
|
|
d1ad111949 | ||
|
|
572185ac96 | ||
|
|
3d8fce50d0 | ||
|
|
6018a9c6ee | ||
|
|
c3d8024e1f | ||
|
|
c1c37d521c | ||
|
|
b2930d6069 | ||
|
|
8ecb7969df | ||
|
|
31c5350941 | ||
|
|
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 | ||
|
|
7eb70e17e0 | ||
|
|
3dfcb46991 | ||
|
|
96eb96f964 | ||
|
|
a2f08bd3ba | ||
|
|
e009fbe59f | ||
|
|
5bb4f271aa | ||
|
|
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 | ||
|
|
154db9a416 | ||
|
|
cf9d0201e0 | ||
|
|
7ebd21bc04 | ||
|
|
5707df0239 | ||
|
|
197a84be94 | ||
|
|
b4fa4c77d7 | ||
|
|
cfec142c3b | ||
|
|
48dfc699d7 | ||
|
|
ec723a3da1 | ||
|
|
f8a72db696 | ||
|
|
83ee64f155 | ||
|
|
b7d12ff944 | ||
|
|
0858fec7f1 | ||
|
|
206bd675f2 | ||
|
|
92695782ff | ||
|
|
c447e4d29b | ||
|
|
811f89b1de | ||
|
|
be3e572440 | ||
|
|
824ebc19c0 | ||
|
|
a0f7fdc57a | ||
|
|
450c1b9d56 | ||
|
|
79232fc434 | ||
|
|
0b3f511534 | ||
|
|
7f18983a49 | ||
|
|
b7d9790acb | ||
|
|
1a5785a8d3 | ||
|
|
320d660e83 | ||
|
|
fb903b2fda | ||
|
|
c18646d096 | ||
|
|
7bf398aca4 | ||
|
|
f6bb655383 | ||
|
|
19f71face9 | ||
|
|
d82b94e281 | ||
|
|
893944403e | ||
|
|
0d3c18d1df | ||
|
|
d7873f257d | ||
|
|
7e3f718797 | ||
|
|
be79a1f3d6 | ||
|
|
a8032ac388 | ||
|
|
21d8225696 | ||
|
|
766c2b22cb | ||
|
|
db79f92423 | ||
|
|
bdddab5b8b | ||
|
|
031adc3be4 | ||
|
|
e7c1418314 | ||
|
|
c906026acd | ||
|
|
56c2740b68 | ||
|
|
4688d62b9f | ||
|
|
99686bd73a | ||
|
|
120e224961 | ||
|
|
e27d69a31d | ||
|
|
c492ba7245 | ||
|
|
53658e365f | ||
|
|
4cfa0e36b1 | ||
|
|
9d9b5d3885 | ||
|
|
36f9905be0 | ||
|
|
a815e0ab8c | ||
|
|
6bfec08a8c | ||
|
|
2d2cd68061 | ||
|
|
fd642e95eb | ||
|
|
9ab3370be5 | ||
|
|
4dcc1ffdbc | ||
|
|
7d466f3584 | ||
|
|
7718abaa72 | ||
|
|
59c5a1ea87 | ||
|
|
0cf70c9e16 | ||
|
|
6174f9b93f | ||
|
|
c3d2e8ff26 | ||
|
|
192f703885 | ||
|
|
be93b23488 | ||
|
|
b079d0d6d5 | ||
|
|
c6c75cc11f | ||
|
|
b188285bc9 | ||
|
|
6d659a84b8 | ||
|
|
3e3828229d | ||
|
|
bf6a0f8d2f | ||
|
|
3873c4b253 | ||
|
|
6cc23f69f9 | ||
|
|
a467a6999e | ||
|
|
e0eb10ca1e | ||
|
|
99c4c73c09 | ||
|
|
bde45cbb34 | ||
|
|
c408c27bf4 | ||
|
|
b14f37d966 | ||
|
|
bfa9c0c528 | ||
|
|
9cc9cddd68 | ||
|
|
fe2261c88d | ||
|
|
6aeb3c0a47 | ||
|
|
dfaa1c9578 | ||
|
|
0ef1dfe061 | ||
|
|
ba8bcd6413 | ||
|
|
7854003ec2 | ||
|
|
c71dd9b68a | ||
|
|
dfeabbc85d | ||
|
|
b8b9ac8a1b | ||
|
|
80ac2607cd | ||
|
|
3552fb1fd8 | ||
|
|
54a96b8453 | ||
|
|
03be4e74df | ||
|
|
e9ddd1af81 | ||
|
|
f305885e8e | ||
|
|
f0b9cd7820 | ||
|
|
59accca89d | ||
|
|
e72ebfb94b | ||
|
|
0b7316d548 | ||
|
|
d0cf76989a | ||
|
|
90a2bf7c9c | ||
|
|
95945412b1 | ||
|
|
c299efca0c | ||
|
|
5e4918579a | ||
|
|
db75f0e894 | ||
|
|
5a6c13e364 | ||
|
|
4b22f07dd7 | ||
|
|
57cb5146fc | ||
|
|
07708f530e | ||
|
|
5c68353e62 | ||
|
|
53728e5c71 | ||
|
|
b965d170ab | ||
|
|
34a1bb7152 | ||
|
|
8787f228d9 | ||
|
|
03cde9a72c | ||
|
|
623655b6f6 | ||
|
|
da6830225a | ||
|
|
a729410fe8 | ||
|
|
bba4036e53 | ||
|
|
39c71c6027 | ||
|
|
03a9219a7c | ||
|
|
a8f6bbd86a | ||
|
|
9a2ee2638b | ||
|
|
2a813244a2 | ||
|
|
b50894fca1 | ||
|
|
41fa2d1aa1 | ||
|
|
54d39c04ad | ||
|
|
3c1365b2c8 | ||
|
|
5858c90e71 | ||
|
|
f0ef06ebe1 | ||
|
|
700f7de748 | ||
|
|
af2ea7ac03 | ||
|
|
690d8255c9 | ||
|
|
aded2193a2 | ||
|
|
6d99b2a68c | ||
|
|
55a619778f | ||
|
|
bbeb51461f | ||
|
|
6066c249d5 | ||
|
|
025ea93f05 | ||
|
|
54fd8f81ff | ||
|
|
ca43554327 | ||
|
|
039f5da0e1 | ||
|
|
9aed12c5aa | ||
|
|
61bdb88ba5 | ||
|
|
36696ab56e | ||
|
|
f0f9b93652 | ||
|
|
a2fae76eaf | ||
|
|
8b2f8ef3cb | ||
|
|
5307e57bd9 | ||
|
|
15518852aa | ||
|
|
60fc1d3f6d | ||
|
|
d1a8d76d85 | ||
|
|
803f5ad0ab | ||
|
|
663c0f3713 | ||
|
|
dfb9e430fa | ||
|
|
03c5a42f5a | ||
|
|
c33970e3e3 | ||
|
|
0ea8125f31 | ||
|
|
841d0cdbbe | ||
|
|
0e0fe967e4 | ||
|
|
192917cc84 | ||
|
|
46a138c64b | ||
|
|
60b271c6a8 | ||
|
|
81880645ed | ||
|
|
9eb4b0dda7 | ||
|
|
2f0ed129f0 | ||
|
|
5becb93e6c | ||
|
|
3361b859c0 | ||
|
|
e27a9b137b | ||
|
|
89e2a3ae3c | ||
|
|
5f85d8132b | ||
|
|
ca1285ec08 | ||
|
|
56582614b6 | ||
|
|
313cacdb71 | ||
|
|
75bf8f3d58 | ||
|
|
ab36784edd | ||
|
|
324da7c0c8 | ||
|
|
779fc6d195 | ||
|
|
159acf6788 | ||
|
|
db59106c3e | ||
|
|
7b7e097922 | ||
|
|
5ef40340fb | ||
|
|
aa4d0c4a6b | ||
|
|
7f53462d0f | ||
|
|
94ce8cad64 | ||
|
|
4d38c44a93 | ||
|
|
c31e150935 | ||
|
|
a5b180a9c6 | ||
|
|
a8bbb951bc | ||
|
|
df778cdb80 | ||
|
|
b55d9113d4 | ||
|
|
d7c51f1a2c | ||
|
|
b5db0b22f8 | ||
|
|
296de34e8a | ||
|
|
88fb1370f0 | ||
|
|
943cf40247 | ||
|
|
ff57f10e9f | ||
|
|
91bb76fd8a | ||
|
|
893454dca7 | ||
|
|
9291e3ada3 | ||
|
|
2a6a381bc7 | ||
|
|
de0b5a6149 | ||
|
|
8fd4e35244 | ||
|
|
6ad1f51673 | ||
|
|
3fbfb0c658 | ||
|
|
7225b53986 | ||
|
|
e71e57f16a | ||
|
|
3f5840d390 | ||
|
|
d3f4205f09 | ||
|
|
5b946087c4 | ||
|
|
9a2378586c | ||
|
|
e820d4bc4d | ||
|
|
8e3e48da14 | ||
|
|
ff8d98c97c | ||
|
|
04cf8d7fbf | ||
|
|
7f8f1ff35a | ||
|
|
c635db0a76 | ||
|
|
704c696711 | ||
|
|
2fbbe430b5 | ||
|
|
f0af750b0a | ||
|
|
88cf456386 | ||
|
|
d8049209ca | ||
|
|
dd40ddf5a5 | ||
|
|
5065164c40 | ||
|
|
8127fdc5bc | ||
|
|
38ed73a2b4 | ||
|
|
5290c47e2a | ||
|
|
957c7f4e79 | ||
|
|
a73fd24695 | ||
|
|
70c8ad9797 | ||
|
|
0290257734 | ||
|
|
53eae6fbfd | ||
|
|
a90149940a | ||
|
|
4fe689dc5d | ||
|
|
0769f585ea | ||
|
|
04562e6d4a | ||
|
|
22d2ad9248 | ||
|
|
6deb26fafe | ||
|
|
6c1de7ff05 | ||
|
|
ab05a44e0b | ||
|
|
7f5f4a1297 | ||
|
|
c68c0e1208 | ||
|
|
c256536d21 | ||
|
|
4159a0effa | ||
|
|
b8f7cd81eb | ||
|
|
3dcef9aac9 | ||
|
|
b381528668 | ||
|
|
4c8b26f732 | ||
|
|
7b0b28aed0 | ||
|
|
6d66d7e215 | ||
|
|
0544164015 | ||
|
|
b6a188a87b | ||
|
|
b5bf8e9a37 | ||
|
|
ba197c8857 | ||
|
|
124b249df4 | ||
|
|
2a6919c438 | ||
|
|
60bb385cf7 | ||
|
|
47724d7268 | ||
|
|
c9f9a60354 | ||
|
|
da52511bf9 | ||
|
|
7899d2284c | ||
|
|
c5e6f06e9b | ||
|
|
6db915b7f0 | ||
|
|
756361584a | ||
|
|
a6d486ea8a | ||
|
|
f1143d701a | ||
|
|
b65fd4adba | ||
|
|
441ae69f5c | ||
|
|
8b4a9aa382 | ||
|
|
99cd552d5c | ||
|
|
6c7e5cb9cf | ||
|
|
6ebb01a081 | ||
|
|
5591c861b9 | ||
|
|
d37280567d | ||
|
|
e7b0ee2539 | ||
|
|
1608dba7dd | ||
|
|
f721dac2ca | ||
|
|
a4629977e7 | ||
|
|
c593b3645c | ||
|
|
28ae90fa8a | ||
|
|
f407b86aa8 | ||
|
|
c7be25078e | ||
|
|
3dc2cc9f22 | ||
|
|
ab86e42b2e | ||
|
|
9af9ed9eb9 | ||
|
|
250a797339 | ||
|
|
8a1f6b74e8 | ||
|
|
a0f3fc6d76 | ||
|
|
74e647fea7 | ||
|
|
55ee90b25d | ||
|
|
eec445fcf5 | ||
|
|
af1857b6ee | ||
|
|
cef22c3158 | ||
|
|
61fb38087e | ||
|
|
0e93495ca2 | ||
|
|
444e250609 | ||
|
|
77a6f6f400 | ||
|
|
15bfd07f30 | ||
|
|
fecf8015a1 | ||
|
|
79ab0d8dc2 | ||
|
|
b4b6d6b571 | ||
|
|
8c73a47afb | ||
|
|
f82ffe378c | ||
|
|
984c2a8fd4 | ||
|
|
6736b1c4e7 | ||
|
|
d409be6d43 | ||
|
|
e1b33f3087 | ||
|
|
740d5a6846 | ||
|
|
d19df4ded8 | ||
|
|
03a4512406 | ||
|
|
de992e4df3 | ||
|
|
a85251aa83 | ||
|
|
4c61d330e6 | ||
|
|
26a1181765 | ||
|
|
cef030cf55 | ||
|
|
2bfa05fd2d | ||
|
|
30904dd019 | ||
|
|
1d0d25db37 | ||
|
|
cbff66c9db | ||
|
|
27231d49ea | ||
|
|
765417c0be | ||
|
|
49a255c8fb | ||
|
|
925d3a23c6 | ||
|
|
6966c132d0 | ||
|
|
c29ab90029 | ||
|
|
d2bbc09892 | ||
|
|
c6039cbc1d | ||
|
|
74a2c29bc2 | ||
|
|
2c64739e8f | ||
|
|
37f950ab42 | ||
|
|
b07a254e60 | ||
|
|
087cdd859e | ||
|
|
829d44bd27 | ||
|
|
db76090e10 | ||
|
|
03cf3b5431 | ||
|
|
40f101d471 | ||
|
|
7aa6ef5f6c | ||
|
|
186b94751d | ||
|
|
6f2717a876 | ||
|
|
a014af4c47 | ||
|
|
2dd31544fe | ||
|
|
237acdcff0 | ||
|
|
5db7a7c196 | ||
|
|
68acdff11b | ||
|
|
58e72e5ee6 | ||
|
|
6b43cd10ba | ||
|
|
b2c9a38db8 | ||
|
|
6dcdb5abae | ||
|
|
56576d9e45 | ||
|
|
d5c3ee5ed0 | ||
|
|
18db0a50f1 | ||
|
|
d596ced0a0 | ||
|
|
78fb2b2239 | ||
|
|
1472e9d5b5 | ||
|
|
fcbc7e4540 | ||
|
|
93bf541ce7 | ||
|
|
0e48d7b080 | ||
|
|
1e6c85da41 | ||
|
|
c5a23e8f5e | ||
|
|
b6d2392303 | ||
|
|
9995f1a743 | ||
|
|
d6f251e992 | ||
|
|
4be95eac4b | ||
|
|
8914d14681 | ||
|
|
d4725b61be | ||
|
|
aa0b627fe7 | ||
|
|
5be5e3271d | ||
|
|
dd5d5cc07c | ||
|
|
84c3709161 | ||
|
|
96e2d74ae3 | ||
|
|
27dcb4d27b | ||
|
|
f85ac97d8c | ||
|
|
bf93e8cc32 | ||
|
|
d1a8955ef9 | ||
|
|
149ac4bdf8 | ||
|
|
2d036c64e9 | ||
|
|
8db2470ac4 | ||
|
|
79156ff8f4 | ||
|
|
8e86d780bf | ||
|
|
f6ef139111 | ||
|
|
12ec2d1f7a | ||
|
|
0dfc28b0e8 | ||
|
|
b3132a4a8f | ||
|
|
6cd25fbdeb | ||
|
|
4be8ba9f17 | ||
|
|
df8008f1ed | ||
|
|
77547c528b | ||
|
|
bfb910f375 | ||
|
|
57e80ee317 | ||
|
|
de1189295a | ||
|
|
20d0dce73e | ||
|
|
144a32b1ca | ||
|
|
8244a2ad23 | ||
|
|
77c3b8f8c1 | ||
|
|
7b34cf1a31 | ||
|
|
21ec670531 | ||
|
|
b2eacb147b | ||
|
|
729e3eb70d | ||
|
|
978533b2f4 | ||
|
|
0358d13ddb | ||
|
|
c944304444 | ||
|
|
096393389c | ||
|
|
9eb7b668d1 | ||
|
|
6728089106 | ||
|
|
33b59d7bed | ||
|
|
888bdbdb68 | ||
|
|
e40a5a70a5 | ||
|
|
d67c931f6a | ||
|
|
dbdc511eff | ||
|
|
f47b960566 | ||
|
|
d016076806 | ||
|
|
23fa5d0bf4 | ||
|
|
486c708911 | ||
|
|
407445456a | ||
|
|
e5c2d77c7d | ||
|
|
c6d3c031c7 | ||
|
|
a86409868e | ||
|
|
e4a298ca2a | ||
|
|
ab8792a13d | ||
|
|
00a8a2aff3 | ||
|
|
7497eaf302 | ||
|
|
7d416d1175 | ||
|
|
e9b9301efb | ||
|
|
7b194c678c | ||
|
|
4369d6adc6 | ||
|
|
69614a7db4 | ||
|
|
8154d1116c | ||
|
|
05a85c628f | ||
|
|
0f0ffd39a8 | ||
|
|
996a4cc29b | ||
|
|
0e234bac70 | ||
|
|
ab060288fa | ||
|
|
52c9e8ad96 | ||
|
|
e46cccdf90 | ||
|
|
ce16eae508 | ||
|
|
dc73dbfbfd | ||
|
|
dae26e0378 | ||
|
|
1bb1f7342f | ||
|
|
420e8bc85a | ||
|
|
a521523d45 | ||
|
|
ded1a59914 | ||
|
|
fe553aec02 | ||
|
|
37b8029d04 | ||
|
|
b1f96448af | ||
|
|
b779e274cc | ||
|
|
83257af267 | ||
|
|
109a29b5fc | ||
|
|
d01f9a701c | ||
|
|
25884a893e | ||
|
|
d1e9fbfa24 | ||
|
|
53419b33a5 | ||
|
|
d429a1c423 | ||
|
|
fe0bda0d04 | ||
|
|
acb90c7aee | ||
|
|
bca82684a1 | ||
|
|
da015ec4a8 | ||
|
|
335e8ffaaa | ||
|
|
8d0ee9e531 | ||
|
|
cc5c7d33e7 | ||
|
|
69c61420f6 | ||
|
|
5699e021de | ||
|
|
cf92618c70 | ||
|
|
a874dbb0d0 | ||
|
|
0614ab4362 | ||
|
|
8ed268819e | ||
|
|
1451b4f45d | ||
|
|
b6da68a69c | ||
|
|
dee92cfc6c | ||
|
|
dec77890bd | ||
|
|
0e1289f12f | ||
|
|
1d26195821 | ||
|
|
7b33f95e83 | ||
|
|
ab6744dfba | ||
|
|
0fd940ffa4 | ||
|
|
bebdbdfb87 | ||
|
|
5893e25b43 | ||
|
|
26c0cf5d33 | ||
|
|
239f21b0d7 | ||
|
|
0b927623e8 | ||
|
|
96f069c417 | ||
|
|
60e52cc927 | ||
|
|
9f79028391 | ||
|
|
c7f48951a9 | ||
|
|
8d63533205 | ||
|
|
8c65214b1f | ||
|
|
e3e0d57f56 | ||
|
|
55f727ef28 | ||
|
|
6c71e72dcd | ||
|
|
79f4f46e3a | ||
|
|
fddf0cdd15 | ||
|
|
7c3bbe3097 | ||
|
|
858d382e26 | ||
|
|
de16fee00a | ||
|
|
8efbde61c9 | ||
|
|
2c79ed5fc1 | ||
|
|
1b0d855c00 | ||
|
|
f403db274a | ||
|
|
a462e91983 | ||
|
|
7e8d337ed5 | ||
|
|
400907cc40 | ||
|
|
74f33441d3 | ||
|
|
61afdeac2e | ||
|
|
55cbbf2b00 | ||
|
|
daf6d98a3b | ||
|
|
7deab0f53b | ||
|
|
e59ec8b27f | ||
|
|
ab1f34604e | ||
|
|
0840ab41d5 | ||
|
|
5293cb30bd | ||
|
|
6d98bd6846 | ||
|
|
0b0813b260 | ||
|
|
6f1a70ee09 | ||
|
|
58768e5aee | ||
|
|
3b41aa52c8 | ||
|
|
c211cc733c | ||
|
|
28a450ea25 | ||
|
|
6696685d0b | ||
|
|
f065bd7784 | ||
|
|
de39906cae | ||
|
|
1393f44070 | ||
|
|
27280b8866 | ||
|
|
8016939f31 | ||
|
|
c1ad2f9376 | ||
|
|
288cc01c41 | ||
|
|
9575cd2651 | ||
|
|
348c13f318 | ||
|
|
e1907e30b1 | ||
|
|
16bb784e78 | ||
|
|
c47fa5ad1c | ||
|
|
d3c320e56f | ||
|
|
cf086b711e | ||
|
|
53db96edad | ||
|
|
983a9a4e0e | ||
|
|
3b62c4a83a | ||
|
|
d0659bed35 | ||
|
|
f7e976cce9 | ||
|
|
5f3147cf36 | ||
|
|
1573e2a91f | ||
|
|
86a6322562 | ||
|
|
738896bdc2 | ||
|
|
5f3a0bdea5 | ||
|
|
e2834fab90 | ||
|
|
b91ba47b0e | ||
|
|
d687e1d762 | ||
|
|
6256abddf2 | ||
|
|
0242b3ec74 | ||
|
|
b26fbf986f | ||
|
|
5c9b1ed43a | ||
|
|
2b6675138e | ||
|
|
2624b8c827 | ||
|
|
bfc1ebc4ba | ||
|
|
14eb6b387b | ||
|
|
6b106e48e9 | ||
|
|
18c591f993 | ||
|
|
0000f487ac | ||
|
|
35ebe33e4e | ||
|
|
c3c4ad1ae6 | ||
|
|
a4420efb7d | ||
|
|
d24851c5d8 | ||
|
|
794bc45cc0 | ||
|
|
d893837c86 | ||
|
|
230c9da1df | ||
|
|
b5aadd4b74 | ||
|
|
9183de578d | ||
|
|
cd1ef1d316 | ||
|
|
9035707bd6 | ||
|
|
9247dc592b | ||
|
|
35898e4ca0 | ||
|
|
cd333fa93b | ||
|
|
533649f24e | ||
|
|
3b525f0009 | ||
|
|
f1fa5bdaa9 | ||
|
|
aa1e06f021 | ||
|
|
30b1cfabf5 | ||
|
|
e75d22ab73 | ||
|
|
b1e17743b8 | ||
|
|
2768ea692e | ||
|
|
83c511e1c8 | ||
|
|
35e60fd151 | ||
|
|
471fcd1272 | ||
|
|
1e1d32dc85 | ||
|
|
b317fb8d83 | ||
|
|
e2c0f01a10 | ||
|
|
f88fee0f21 | ||
|
|
245b3ca09f | ||
|
|
c0669150fb | ||
|
|
f3c12f38b6 | ||
|
|
da34b82b3e | ||
|
|
5e19178a30 | ||
|
|
8e358faebc | ||
|
|
90cddb7aee | ||
|
|
3f7d2aebc7 | ||
|
|
5624ea14e7 | ||
|
|
3530603797 | ||
|
|
4fd469e07b | ||
|
|
59cb1e561e | ||
|
|
4fe63d2966 | ||
|
|
6d828964be | ||
|
|
971fcf5800 | ||
|
|
9ac40f705c | ||
|
|
2ad270cf33 | ||
|
|
ef8e20f66b | ||
|
|
8ce78c6b31 | ||
|
|
af3c8195af | ||
|
|
e01e0c5596 | ||
|
|
117b4c59cc | ||
|
|
9d21fc85bc | ||
|
|
194d0733d4 | ||
|
|
79b41ee662 | ||
|
|
f8d9301bd4 | ||
|
|
c1a4fbee16 | ||
|
|
8c632f63b0 | ||
|
|
7d982c9ea6 | ||
|
|
ec4161a959 | ||
|
|
a371e8d53f | ||
|
|
8f09cca043 | ||
|
|
b326d8593b | ||
|
|
d1fe7abb18 | ||
|
|
39bca49e8f | ||
|
|
b8269020ae | ||
|
|
ffc51d6db6 | ||
|
|
394e51029e | ||
|
|
4b8f9d810b | ||
|
|
89a2ce1c6c | ||
|
|
c6ad7f80a8 | ||
|
|
7e6a59bdcc | ||
|
|
8ebd9afd15 | ||
|
|
d1b9eddb34 | ||
|
|
0394dd7ee5 | ||
|
|
53c6c74a43 | ||
|
|
65c5378a10 | ||
|
|
1c6cbbf8ee | ||
|
|
10cb4ec401 | ||
|
|
5281713fd9 | ||
|
|
7982b3f237 | ||
|
|
07eead2dbf | ||
|
|
3fa5976315 | ||
|
|
66f557d436 | ||
|
|
fdd6ddf61b | ||
|
|
4129463923 | ||
|
|
601c129bbf | ||
|
|
310ed0f1d3 | ||
|
|
b293d00699 | ||
|
|
1eace04ad9 | ||
|
|
75a0cf97e2 | ||
|
|
c055e3af21 | ||
|
|
a1f93e733c | ||
|
|
74c099f0b3 | ||
|
|
49073742b5 | ||
|
|
f0500e9797 | ||
|
|
3e19509a49 | ||
|
|
125938762b | ||
|
|
187206cb88 | ||
|
|
8420cb7ec1 | ||
|
|
61c619660d | ||
|
|
75252bce05 | ||
|
|
1de9087427 | ||
|
|
c23cdb0e31 | ||
|
|
794824713e | ||
|
|
5e06d53d8c | ||
|
|
89a325c0ea | ||
|
|
1fdb057199 | ||
|
|
876ff2ef72 | ||
|
|
f5ba2106cd | ||
|
|
67f6df2be3 | ||
|
|
8f6ea84fca | ||
|
|
71f2440ddf | ||
|
|
ea1b792a93 | ||
|
|
426dae0310 | ||
|
|
f91fd3c91f | ||
|
|
86cc1a228d | ||
|
|
394d265b96 | ||
|
|
6a6923b1d8 | ||
|
|
28edf13457 | ||
|
|
93947b09c5 | ||
|
|
8b8ce256f5 | ||
|
|
4cc82a808f | ||
|
|
8313a069bf | ||
|
|
b38d07064b | ||
|
|
886b6dd265 | ||
|
|
3d6bd73ed3 | ||
|
|
4ffb8f14b8 | ||
|
|
a874cc32a8 | ||
|
|
34246ee4ef | ||
|
|
3ed2f55696 | ||
|
|
09c4dd4891 | ||
|
|
81f9f717dc | ||
|
|
fb502df089 | ||
|
|
f91e340178 | ||
|
|
f6c0e7cc9c | ||
|
|
9f73fcf308 | ||
|
|
d023f61bc4 | ||
|
|
0bbe499414 | ||
|
|
dd7db95135 | ||
|
|
dd5ca73602 | ||
|
|
2632f730d1 | ||
|
|
24c158bfe6 | ||
|
|
3d4a5a8066 | ||
|
|
db7e0b56f2 | ||
|
|
f2478d813c | ||
|
|
79d979f47f | ||
|
|
b6daad7573 | ||
|
|
67d2953080 | ||
|
|
192aa9eb71 | ||
|
|
0d2eef5894 | ||
|
|
88b1da4260 | ||
|
|
0730685c29 | ||
|
|
ea91d59ffc | ||
|
|
ce8d47b2b4 | ||
|
|
65aef11ae3 | ||
|
|
8f22cf7c3c | ||
|
|
7835cc34c8 | ||
|
|
0eef0fc1dd | ||
|
|
2cecd5e056 | ||
|
|
5da9120870 | ||
|
|
9d5cd27575 | ||
|
|
81f8fe34cd | ||
|
|
f744696043 | ||
|
|
29b0780c6c | ||
|
|
5faf8bfcd0 | ||
|
|
f0e6a5c905 | ||
|
|
6b3b673daa | ||
|
|
a4876e9f3e | ||
|
|
925258bfb4 | ||
|
|
a483e9365b | ||
|
|
e443a576f7 | ||
|
|
20b26effdb | ||
|
|
1a10aa0dda | ||
|
|
d6f8d1b464 | ||
|
|
f1f7694835 | ||
|
|
0e66c3cb56 | ||
|
|
09a102fea8 | ||
|
|
304fce73fc | ||
|
|
295a68bb7a | ||
|
|
3aeb521782 | ||
|
|
f587d2248b | ||
|
|
b3672d6365 | ||
|
|
a975117eaf | ||
|
|
465b69516d | ||
|
|
2ded2ff53a | ||
|
|
74c20e62d5 | ||
|
|
0dbce93d1b | ||
|
|
8579c5a68a | ||
|
|
9797412d23 | ||
|
|
6b6cf06530 | ||
|
|
539c3023b9 | ||
|
|
78b6e84774 | ||
|
|
704209de9c | ||
|
|
835b461d7d | ||
|
|
b8f8d49927 | ||
|
|
63a2727077 | ||
|
|
5516978f2e | ||
|
|
b8a37a0c73 | ||
|
|
19ee62f704 | ||
|
|
41b5b1dfd0 | ||
|
|
c7596e7741 | ||
|
|
d4fa81301d | ||
|
|
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 | ||
|
|
ec7245965f | ||
|
|
de76e8db5f | ||
|
|
a52575c7bf | ||
|
|
bf6703c2e8 | ||
|
|
b5211b2dd5 | ||
|
|
4db1dd8afc | ||
|
|
c39e3acb59 | ||
|
|
0a72751b37 | ||
|
|
7a44da85a0 | ||
|
|
890b613f71 | ||
|
|
1014bd74e0 | ||
|
|
3c59452e33 | ||
|
|
db385e024b | ||
|
|
84848fabd7 | ||
|
|
4a88e155c4 | ||
|
|
66a9421fac | ||
|
|
be609f4a8f | ||
|
|
90b8acdd3b | ||
|
|
c8ad45b11e | ||
|
|
8937edb97e | ||
|
|
c8bff3ef38 | ||
|
|
80393d73ae | ||
|
|
1742867df1 | ||
|
|
ce0a40a20c | ||
|
|
e5e8c068ea | ||
|
|
366c1f81cc | ||
|
|
4975f9100c | ||
|
|
a481fa2921 | ||
|
|
79367642b1 | ||
|
|
fc8096f8dc | ||
|
|
22c57c2862 | ||
|
|
5cb6744c1b | ||
|
|
153a711de4 | ||
|
|
71f60990db | ||
|
|
32951bbc90 | ||
|
|
aee15e8af4 | ||
|
|
3016dcc616 | ||
|
|
c090a2f0b6 | ||
|
|
11f128a84f | ||
|
|
991a00b38f | ||
|
|
e80dfc886e | ||
|
|
b84eb836f4 | ||
|
|
b09afef46f | ||
|
|
ecf77e60df | ||
|
|
10bc35d604 | ||
|
|
eea65a3f26 | ||
|
|
3b21a19491 | ||
|
|
7a52477294 | ||
|
|
ef0bd72076 | ||
|
|
a5abb3e6a6 | ||
|
|
ff824ec4db | ||
|
|
ea63936947 | ||
|
|
68a04c7a23 | ||
|
|
75032def9e | ||
|
|
b64924440f | ||
|
|
e5d0f74ba7 | ||
|
|
309f102745 | ||
|
|
3a0f738fb0 | ||
|
|
55846cc717 | ||
|
|
1784278a59 | ||
|
|
afac0bc441 | ||
|
|
ffbee77f6f | ||
|
|
16e56646b8 | ||
|
|
82affd5154 | ||
|
|
73e88faa00 | ||
|
|
bcf9d2f75c | ||
|
|
f85e3edfc7 | ||
|
|
115f718cd9 | ||
|
|
9108ff8caa | ||
|
|
d5cf0f1fbd | ||
|
|
c97db3259f | ||
|
|
4b2093b485 | ||
|
|
b69b5fdf84 | ||
|
|
24aaa36532 | ||
|
|
d84366c6c5 | ||
|
|
89e06054bf | ||
|
|
2604d3c1fd | ||
|
|
b692f67779 | ||
|
|
05b03df600 | ||
|
|
3159e7713a | ||
|
|
e445e201f3 | ||
|
|
bbd1d6059a | ||
|
|
86f49d34c3 | ||
|
|
007e8fbdf9 | ||
|
|
adf6e7d1cd | ||
|
|
ba3662a9ed | ||
|
|
05ea61421f | ||
|
|
e45b4efa1a | ||
|
|
10d19be66c | ||
|
|
b903ca05f7 | ||
|
|
1dff71e4df | ||
|
|
827295a989 | ||
|
|
ff879e2018 | ||
|
|
e89b33f865 | ||
|
|
181e75adb4 | ||
|
|
22ef2ce0b6 | ||
|
|
51d3d130e4 | ||
|
|
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 | ||
|
|
d8a8e1cc09 | ||
|
|
5b8cbe29e1 | ||
|
|
a77b8c2023 | ||
|
|
40dd34b5bf | ||
|
|
daba90b346 | ||
|
|
7b70afa2a1 | ||
|
|
0b8d70c7ec | ||
|
|
522dc1db2a | ||
|
|
8d3b201a49 | ||
|
|
49ecc08398 | ||
|
|
e4f6aefdad | ||
|
|
771265113e | ||
|
|
3c23745508 | ||
|
|
db907815ff | ||
|
|
f6971b8ab2 | ||
|
|
aed769c0be | ||
|
|
ae6abdddad | ||
|
|
63c9fbe10c | ||
|
|
101dfd01f2 | ||
|
|
1543cdbc61 | ||
|
|
5db5134ae0 | ||
|
|
5294489b0e | ||
|
|
ce45c3af4e | ||
|
|
05b2b8fb59 | ||
|
|
25097bce31 | ||
|
|
0100c56046 | ||
|
|
e81b221fd1 | ||
|
|
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 | ||
|
|
f374ac1bf7 | ||
|
|
2fa66f4551 | ||
|
|
a4019c9f63 | ||
|
|
694166862e | ||
|
|
911c2398ef | ||
|
|
a209a92de8 | ||
|
|
462878a307 | ||
|
|
3941437ad4 | ||
|
|
664cc24481 | ||
|
|
2c38036123 | ||
|
|
6ae096a56f | ||
|
|
436bf152ec | ||
|
|
b1b5eeecba | ||
|
|
400913631c | ||
|
|
006a3adea0 | ||
|
|
c2bb3892b7 | ||
|
|
5c820dd7b8 | ||
|
|
248fcfa869 | ||
|
|
b58c77c8b8 | ||
|
|
94c79fa69a | ||
|
|
a3811f632d | ||
|
|
d8f0102204 | ||
|
|
524c6c502e | ||
|
|
6b013724aa | ||
|
|
8774f0cf45 | ||
|
|
8762e158c4 | ||
|
|
49d95892e3 | ||
|
|
9618878023 | ||
|
|
614e858e44 | ||
|
|
b267b34762 | ||
|
|
708b1a962c | ||
|
|
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 | ||
|
|
4e55a18a60 | ||
|
|
e332442132 | ||
|
|
64d649be7f | ||
|
|
3de1de9dc6 | ||
|
|
e320d2ba05 | ||
|
|
ed78a4b8a0 | ||
|
|
376eb52f00 | ||
|
|
8ecceeacda | ||
|
|
f4cfb31bf4 | ||
|
|
227dc7e81d | ||
|
|
152d985ebc | ||
|
|
ef1e8df001 | ||
|
|
5c2b1a3b70 | ||
|
|
66c3f5432d | ||
|
|
de413408f5 | ||
|
|
059126f642 | ||
|
|
3bc43210ab | ||
|
|
82194cef8a | ||
|
|
1956a16d1e | ||
|
|
e1c095adca | ||
|
|
45a2932f4b | ||
|
|
b6e3715cd8 | ||
|
|
aa9c0078a1 | ||
|
|
9677115055 | ||
|
|
d45e90e358 | ||
|
|
f692fb5bff | ||
|
|
83692696ba | ||
|
|
bbb15d610f | ||
|
|
7ebb7876c4 | ||
|
|
bd581d01a3 | ||
|
|
de2ebba577 | ||
|
|
351274c633 | ||
|
|
53fab3e9ba | ||
|
|
a0c0b7b1eb | ||
|
|
5a34d43a86 | ||
|
|
86a0f77e6f | ||
|
|
9d00ae6e50 | ||
|
|
8d23398176 | ||
|
|
5aae930e8c | ||
|
|
6db096c336 | ||
|
|
78d27e82c8 | ||
|
|
2a4fef6a61 | ||
|
|
9daeeeb851 | ||
|
|
92671823d8 | ||
|
|
19396b2107 | ||
|
|
f0332a7388 | ||
|
|
70cb5ed215 | ||
|
|
d309f67df0 | ||
|
|
7dc070ec2c | ||
|
|
2ecdf19569 | ||
|
|
c56f2625b7 | ||
|
|
3dc154991a | ||
|
|
a5f57c050f | ||
|
|
36c4cd98e0 | ||
|
|
f6ef6039f4 | ||
|
|
6867563e4a | ||
|
|
21820262ce | ||
|
|
bae1203b64 | ||
|
|
0f2ff7aba2 | ||
|
|
0ce90834f6 | ||
|
|
0e182bb2b9 | ||
|
|
7afff69fab | ||
|
|
136d59e4b9 | ||
|
|
6d9cbac928 | ||
|
|
efcd8d339b | ||
|
|
7e09ce468b | ||
|
|
baf714b41f | ||
|
|
b0808e846d | ||
|
|
0e3e10a707 | ||
|
|
cbf3d5d071 | ||
|
|
68d355a33d | ||
|
|
853923013a | ||
|
|
737061a983 | ||
|
|
0438d55284 | ||
|
|
924962c156 | ||
|
|
a8c42843f6 | ||
|
|
63c4b7b6b6 | ||
|
|
ff06387d90 | ||
|
|
fa46622bad | ||
|
|
fd65780287 | ||
|
|
6478ec86ac | ||
|
|
748e0eb44f | ||
|
|
507c07e9eb | ||
|
|
5185a9e590 | ||
|
|
1ade088a8f | ||
|
|
5c087181be | ||
|
|
08bb314bbc | ||
|
|
7ddb9becd4 | ||
|
|
7d96709ee2 | ||
|
|
4413a9f0b6 | ||
|
|
9611b55fb9 | ||
|
|
dffd535f68 | ||
|
|
4aaf2da651 | ||
|
|
80ac46af78 | ||
|
|
13c58b54fa | ||
|
|
355119187a | ||
|
|
0d96587b04 | ||
|
|
be806def91 | ||
|
|
8acf8027e9 | ||
|
|
893a1fec57 | ||
|
|
d182e40aef | ||
|
|
6118aca949 | ||
|
|
f0ac83179f | ||
|
|
8969ffc14b | ||
|
|
9fb130146a | ||
|
|
d285ff673a | ||
|
|
7f586da856 | ||
|
|
c35f5b3116 | ||
|
|
12927d2d54 | ||
|
|
6b06b87547 | ||
|
|
058f8ae3b8 | ||
|
|
1faedac04b | ||
|
|
666c7321af | ||
|
|
6cbaf7396a | ||
|
|
b706acaa9f | ||
|
|
5870acb193 | ||
|
|
c00b633312 | ||
|
|
1c0ee7c4c5 | ||
|
|
961741b809 | ||
|
|
5a66f0f373 | ||
|
|
5a1e1c73c9 | ||
|
|
3be68ec721 | ||
|
|
0f81e494ee | ||
|
|
d88f66b019 | ||
|
|
027edbdb21 | ||
|
|
cf03d25934 | ||
|
|
75232d2a70 | ||
|
|
bcb966af12 | ||
|
|
0272e58868 | ||
|
|
bcd988bb81 | ||
|
|
aa6c21f38d | ||
|
|
3fb5f6f5be | ||
|
|
5dc2ac9e22 | ||
|
|
0f85d6810b | ||
|
|
bf761946da | ||
|
|
d9fa2f0e91 | ||
|
|
8a25677a8d | ||
|
|
7c2da81700 | ||
|
|
a4799a495a | ||
|
|
b5de5ac19c | ||
|
|
638a7b2d91 | ||
|
|
dcf72ce2da | ||
|
|
8f6e0ad5be | ||
|
|
50e0b9b84e | ||
|
|
b6b93550fe | ||
|
|
baa3be728d | ||
|
|
240e642fe9 | ||
|
|
07a92d20d7 | ||
|
|
3f334406d1 | ||
|
|
dbd177576e | ||
|
|
0fb9f42ba4 | ||
|
|
b4542d4d42 | ||
|
|
4bfd7a7e4e | ||
|
|
3fe1562b92 | ||
|
|
27699aa99c | ||
|
|
48bbbe0f40 | ||
|
|
9dc226e3d6 | ||
|
|
98b20fc1cd | ||
|
|
980dccf31c | ||
|
|
bb2193d481 | ||
|
|
bf8fe316df | ||
|
|
96716626c6 | ||
|
|
e20338ff9e | ||
|
|
78530ae123 | ||
|
|
52d605d13e | ||
|
|
0182615e7e | ||
|
|
fad84d4437 | ||
|
|
c162e9a4de | ||
|
|
bf1e742df6 | ||
|
|
0e88a6b268 | ||
|
|
c1e870528e | ||
|
|
35fc001c58 | ||
|
|
339263a295 | ||
|
|
a44bd9abe0 | ||
|
|
b850d47282 | ||
|
|
4099c06b27 | ||
|
|
e559879f91 | ||
|
|
abb95e7872 | ||
|
|
869de3d251 | ||
|
|
f3526eccb9 | ||
|
|
880faa83a6 | ||
|
|
311f9fcefb | ||
|
|
8732f299e6 | ||
|
|
b30aac536a | ||
|
|
d7dc4ae0c0 | ||
|
|
5bb4c85ccb | ||
|
|
80dda198c5 | ||
|
|
9442736518 | ||
|
|
2fbad52c71 | ||
|
|
5975c9fac7 | ||
|
|
7b0e392ecd | ||
|
|
b51a10b46b | ||
|
|
6c58f59d72 | ||
|
|
cd9caa24ad | ||
|
|
cbb4b4d846 | ||
|
|
ea4cdadc6e | ||
|
|
6638d64d68 | ||
|
|
eb412c2bcb | ||
|
|
fde4a59510 | ||
|
|
1ee394aa69 | ||
|
|
707f90573c | ||
|
|
f8429ad357 | ||
|
|
aa5003d297 | ||
|
|
e9901f5e58 | ||
|
|
f0d04a4a57 | ||
|
|
32e3f748d8 | ||
|
|
fa465a84df | ||
|
|
82cf1a4467 | ||
|
|
3bbd49dbad | ||
|
|
ad21857cae | ||
|
|
2d18b73138 | ||
|
|
e7bc18dad4 | ||
|
|
62e4eabab0 | ||
|
|
d204eebab9 | ||
|
|
ed5823151b | ||
|
|
968d7d1f11 | ||
|
|
086683319a | ||
|
|
e5c1f4847d | ||
|
|
087e114d34 | ||
|
|
5a6b8bb856 | ||
|
|
102f567cb5 | ||
|
|
1a64879b65 | ||
|
|
4a0e5e4b88 | ||
|
|
01857fb056 | ||
|
|
3afe4938f9 | ||
|
|
9e0544e735 | ||
|
|
3993c6ad6b | ||
|
|
649563457d | ||
|
|
6ec75714f0 | ||
|
|
15916e6668 | ||
|
|
76d0562716 | ||
|
|
2ecc4aead7 | ||
|
|
d8210847a4 | ||
|
|
ece916e12f | ||
|
|
1a29d4f60f | ||
|
|
11832daf5c | ||
|
|
79555f5647 | ||
|
|
1868013704 | ||
|
|
39cc13f155 | ||
|
|
e636875797 | ||
|
|
260749337e | ||
|
|
20a3b556bb | ||
|
|
fa0c58e42a | ||
|
|
abbb94239d | ||
|
|
d89ef43834 | ||
|
|
f84ab2beda | ||
|
|
86d398abda | ||
|
|
883c65981b | ||
|
|
8eb96efa13 | ||
|
|
e9973670ea | ||
|
|
ef8d2d06df | ||
|
|
0b5bb520a7 | ||
|
|
233fb23cb8 | ||
|
|
fa89f45cb8 | ||
|
|
4c656c0321 | ||
|
|
87c6ee2035 | ||
|
|
aab190423f | ||
|
|
05e3e6bda6 | ||
|
|
1f299ed73e | ||
|
|
4ba9792fbe | ||
|
|
f405511b6b | ||
|
|
8ad5eb3e59 | ||
|
|
3df8fa99f0 | ||
|
|
cca97341e9 | ||
|
|
13195d06fd | ||
|
|
9b6e86b55c | ||
|
|
65cf7527b0 | ||
|
|
f74d50439c | ||
|
|
28e7ca5a84 | ||
|
|
8f64da5bc7 | ||
|
|
25f537e730 | ||
|
|
6d8af3d9c0 | ||
|
|
e56a46882d | ||
|
|
0476ffecdb | ||
|
|
47a0400a72 | ||
|
|
de3417d557 | ||
|
|
83b546c1c5 | ||
|
|
04709dc1df | ||
|
|
f48171dcab | ||
|
|
7b8362b64c | ||
|
|
188538651a | ||
|
|
a9fc7e04e9 | ||
|
|
4812285512 | ||
|
|
ec1fa8e90a | ||
|
|
3a1b432234 | ||
|
|
98f853128a | ||
|
|
276d2bc866 | ||
|
|
0472e3a3e5 | ||
|
|
a0afa9f2e8 | ||
|
|
0116fa9b95 | ||
|
|
42f0eebf8f | ||
|
|
8194660d16 | ||
|
|
532a9ef9fe | ||
|
|
0be69f57ac | ||
|
|
97f748d58e | ||
|
|
3662a58ad8 | ||
|
|
f5bf6a0beb | ||
|
|
0fc0aa39b1 | ||
|
|
c8cf46f62b | ||
|
|
28a1960fda | ||
|
|
44bb79c90e | ||
|
|
2f01bb2e63 | ||
|
|
71708e349c | ||
|
|
6ad3d40216 | ||
|
|
d6d498bc8f | ||
|
|
6df7f6d6ec | ||
|
|
5365182c86 | ||
|
|
e5121b33e6 | ||
|
|
5fb4eacf5b | ||
|
|
c4c520c1a3 | ||
|
|
9eab9ad40d | ||
|
|
a2fef11016 | ||
|
|
088eb3da14 | ||
|
|
a0706b8780 | ||
|
|
0e1dfcf408 | ||
|
|
3ca9f5f389 | ||
|
|
5acd225f0f | ||
|
|
1708bb5cdf | ||
|
|
8127484081 | ||
|
|
103c75e78c | ||
|
|
d886dcc7c3 | ||
|
|
ea54d73911 | ||
|
|
1ef4cc9fc2 | ||
|
|
4785db4471 | ||
|
|
c8cbc55b59 | ||
|
|
8d501e1c24 | ||
|
|
132a5d424d | ||
|
|
4c5f20fde4 | ||
|
|
35a20fb197 | ||
|
|
63848e8fc2 | ||
|
|
16a7409ce1 | ||
|
|
c23955d0b5 | ||
|
|
18ef355d2a | ||
|
|
def1eb0b5f | ||
|
|
bdbc189a4f | ||
|
|
6efe9efab8 | ||
|
|
7b72dde222 | ||
|
|
5948a0b235 | ||
|
|
a6fc7ba07a | ||
|
|
a326adc863 | ||
|
|
ab31c633d0 | ||
|
|
48254a93f0 | ||
|
|
365c8c18d7 | ||
|
|
bbc0695a8f | ||
|
|
1d0f8f01f2 | ||
|
|
2253439940 | ||
|
|
c1838a60df | ||
|
|
8a6713d5c0 | ||
|
|
201efecafa | ||
|
|
79f061be93 | ||
|
|
4786c1c59f | ||
|
|
116cad88a0 | ||
|
|
7d1200c434 | ||
|
|
99a9707a34 | ||
|
|
787f2390fb | ||
|
|
a510ac4052 | ||
|
|
9f414baa99 | ||
|
|
086711e467 | ||
|
|
bf2d6dd0fa | ||
|
|
4c43226b99 | ||
|
|
dfe8aac10b | ||
|
|
983786c29f | ||
|
|
edb81425cf | ||
|
|
69478aea58 | ||
|
|
e9d9c0c42d | ||
|
|
b41adc2eee | ||
|
|
115d6e29df | ||
|
|
83781f3a70 | ||
|
|
5f1ec550ec | ||
|
|
95f8dccc14 | ||
|
|
0134ec7b04 | ||
|
|
590938fa33 | ||
|
|
46f5f21368 | ||
|
|
b6bf3800c7 | ||
|
|
6043d37b05 | ||
|
|
34919b0396 | ||
|
|
846613c244 | ||
|
|
c4c137dc08 | ||
|
|
130bb19a11 | ||
|
|
d0f14d7a3c | ||
|
|
795c2cf540 | ||
|
|
bcd02ce718 | ||
|
|
469efc923b | ||
|
|
dcd74f6922 | ||
|
|
4b7eaf6dae | ||
|
|
085f909f35 | ||
|
|
4f394b683d | ||
|
|
7096ebedbc | ||
|
|
0b0243a5e0 | ||
|
|
5c9f9b1685 | ||
|
|
bf74bb196d | ||
|
|
c6e14caa31 | ||
|
|
506602b257 | ||
|
|
a79ec0d408 | ||
|
|
140724be2e | ||
|
|
04af1f3c97 | ||
|
|
cb4f1daac1 | ||
|
|
96bab5697d | ||
|
|
abac3e7758 | ||
|
|
8557cb5305 | ||
|
|
735840aafd | ||
|
|
1c777888d5 | ||
|
|
625810cd8a | ||
|
|
cd63abd72e | ||
|
|
d911b776bf | ||
|
|
9e7d1b3ed8 | ||
|
|
53735f2026 | ||
|
|
a43b31400f | ||
|
|
e15f2ac8ab | ||
|
|
06e760081c | ||
|
|
fc8637c81a | ||
|
|
01eaf48471 | ||
|
|
238a075c6a | ||
|
|
1d130b4a89 | ||
|
|
95d935d917 | ||
|
|
c4db8d37c2 | ||
|
|
17e0154995 | ||
|
|
d60c9800c2 | ||
|
|
04d2542b81 | ||
|
|
9a25cb3ee7 | ||
|
|
1e22b8e567 | ||
|
|
d05dfb18a7 | ||
|
|
a5c6ddb8ac | ||
|
|
90bff709a4 | ||
|
|
052132ec37 | ||
|
|
d42361bac1 | ||
|
|
2df19bcbb4 | ||
|
|
0ef4251462 | ||
|
|
a4af81563a | ||
|
|
36cd63836e | ||
|
|
48f9959fcd | ||
|
|
9effa3d2ab | ||
|
|
0782222c6b | ||
|
|
f7784b6543 | ||
|
|
fabc9e5d1c | ||
|
|
06805d70fe | ||
|
|
123e317e52 | ||
|
|
68a9855506 | ||
|
|
688a3251a9 | ||
|
|
30c5cc1dc4 | ||
|
|
4ab1d5ca7f | ||
|
|
5a808a0f91 | ||
|
|
a6cc31944a | ||
|
|
41a9e5f710 | ||
|
|
31790e0bb7 | ||
|
|
4e0c8e218d | ||
|
|
4fe4c0c72a | ||
|
|
f171357e36 | ||
|
|
ef91cc992e | ||
|
|
04b92f2ad2 | ||
|
|
4f049112de | ||
|
|
86242b322c | ||
|
|
14af95001e | ||
|
|
2dd56f5bda | ||
|
|
c250c632f3 | ||
|
|
e3fb4f8799 | ||
|
|
b4f704d7f1 | ||
|
|
9ee2c6be57 | ||
|
|
7de8f71f58 | ||
|
|
b6a75093b7 | ||
|
|
d54dda40d3 | ||
|
|
a705c714ab | ||
|
|
0e48837eec | ||
|
|
5e5ba54c3e | ||
|
|
4403b139d5 | ||
|
|
103974cae4 | ||
|
|
aea37467d8 | ||
|
|
9d2ed7bc5f | ||
|
|
bfb11d249e | ||
|
|
f7dbda4ed3 | ||
|
|
7c9c6ea3df | ||
|
|
7d49c4de87 | ||
|
|
e49a36c9fd | ||
|
|
236e773438 | ||
|
|
e3144c3093 | ||
|
|
cbd8409611 | ||
|
|
a85b38850c | ||
|
|
e01f1ae8ed | ||
|
|
d60a4c5f87 | ||
|
|
9c1fe2c922 | ||
|
|
3131c0d0ac | ||
|
|
364e9cf3de | ||
|
|
5a84863873 | ||
|
|
006b2b18b0 | ||
|
|
fb262e38a7 | ||
|
|
f8151284ee | ||
|
|
5d8c91b687 | ||
|
|
0f23462607 | ||
|
|
ca50ea190f | ||
|
|
31192abd2c | ||
|
|
42c7f41d24 | ||
|
|
fade03e337 | ||
|
|
d511d90a2f | ||
|
|
54d6cafda9 | ||
|
|
a730cd1c51 | ||
|
|
cccd75fc42 | ||
|
|
dbc16a6c9b | ||
|
|
6ffad6043f | ||
|
|
4e398950a0 | ||
|
|
f5e51897e3 | ||
|
|
698ea36cc2 | ||
|
|
0cf9cdd3b1 | ||
|
|
42c8de5620 | ||
|
|
ba4f63bca3 | ||
|
|
4ed2ef6298 | ||
|
|
d28c2af7de | ||
|
|
1e8c32fbdb | ||
|
|
97bd87773c | ||
|
|
b560828173 | ||
|
|
0d7a43ab38 | ||
|
|
7865fbea7e | ||
|
|
5fcbf9091e | ||
|
|
fedd2b638c | ||
|
|
22f44e342f | ||
|
|
f7eb013935 | ||
|
|
1c28f893e7 | ||
|
|
195dfd752d | ||
|
|
cc788d3b62 | ||
|
|
e54021e7be | ||
|
|
918db39eba | ||
|
|
213fe2ecea | ||
|
|
6e70352d0b | ||
|
|
d2403cdadb | ||
|
|
9a4f5e0ba8 | ||
|
|
502e4d672a | ||
|
|
99205bf72e | ||
|
|
b3d673b0aa | ||
|
|
baf51eb430 | ||
|
|
d8fc50c351 | ||
|
|
ba5057181e | ||
|
|
4c4dee0bf1 | ||
|
|
589c3a2be3 | ||
|
|
991f182f89 | ||
|
|
f44b03f5a9 | ||
|
|
5fd50040d3 | ||
|
|
5302108556 | ||
|
|
592c62de75 | ||
|
|
27c1bc0271 | ||
|
|
3744a68daf | ||
|
|
c209b7bb5d | ||
|
|
f04493c1ab | ||
|
|
62edf14893 | ||
|
|
0d11e32523 | ||
|
|
5484b06df8 | ||
|
|
9f3116e4e3 | ||
|
|
f144d671ff | ||
|
|
b294635e17 | ||
|
|
36234cc0e8 | ||
|
|
5c87003196 | ||
|
|
cc5d36f8be | ||
|
|
1359a4f88a | ||
|
|
1e1362bdbc | ||
|
|
813106efd3 | ||
|
|
08d129c2ea | ||
|
|
8491e57258 | ||
|
|
d86fb098a8 | ||
|
|
e21f8e46d4 | ||
|
|
b3a6ec2804 | ||
|
|
0034938c04 | ||
|
|
d2587337e4 | ||
|
|
8b2045523d | ||
|
|
e7e10c24be | ||
|
|
f06852f48d | ||
|
|
2f9fff7130 | ||
|
|
0b788f64f7 | ||
|
|
facf1d42f7 | ||
|
|
e77aae703b | ||
|
|
76f52dd89b | ||
|
|
0e980f7229 | ||
|
|
2f5d550df6 | ||
|
|
3acf5f76fe | ||
|
|
4be8ae7f3d | ||
|
|
029dd43ecd | ||
|
|
cf8feb37e1 | ||
|
|
648531f4cc | ||
|
|
11505d5d06 | ||
|
|
1d04897b32 | ||
|
|
eddcc0fbbd | ||
|
|
bd80a77b78 | ||
|
|
edc30a31c1 | ||
|
|
f5636f325d | ||
|
|
426bf3803b | ||
|
|
739d3c72b7 | ||
|
|
60c38a0c47 | ||
|
|
9566fbd3cd | ||
|
|
e41ee1bcf8 | ||
|
|
bc14f225af | ||
|
|
07148c1503 | ||
|
|
fcb5eea951 | ||
|
|
7a4bf26733 | ||
|
|
2c2910d1dd | ||
|
|
1dae7a2fe4 | ||
|
|
e19ab329bd | ||
|
|
922cb90cb3 | ||
|
|
765295136a | ||
|
|
7579333ad3 | ||
|
|
543ea28b72 | ||
|
|
fefd6d60f6 | ||
|
|
a7b8b4bf55 | ||
|
|
7cafa194c1 | ||
|
|
b37f78dbbf | ||
|
|
e20cd42cc2 | ||
|
|
7b99f81f72 | ||
|
|
f1bbdc9e59 | ||
|
|
5219fb63a1 | ||
|
|
dcc379c3fa | ||
|
|
0fb8dc3418 | ||
|
|
f4e9d245d0 | ||
|
|
c75a2051ff | ||
|
|
691ec164b1 | ||
|
|
7b596c750d | ||
|
|
b346556caa | ||
|
|
93ec7068df | ||
|
|
5f65f993a0 | ||
|
|
a9441521a4 | ||
|
|
2dede67ae9 | ||
|
|
6bf17e7d47 | ||
|
|
a3240da707 | ||
|
|
a28fee1ff4 | ||
|
|
57e5af2f69 | ||
|
|
8d3a214475 | ||
|
|
58eedce6fe | ||
|
|
f91704a372 | ||
|
|
f53c68e040 | ||
|
|
99e55f84f0 | ||
|
|
798ea75f3d | ||
|
|
913e6a5709 | ||
|
|
34f6d5ab45 | ||
|
|
86c0194e9a | ||
|
|
dfb2b9b569 | ||
|
|
7fae380ab6 | ||
|
|
4ca8272efc | ||
|
|
5d4bbc393e | ||
|
|
984275ff05 | ||
|
|
e6f70f2ab7 | ||
|
|
f7de252f67 | ||
|
|
532b299c40 | ||
|
|
5dcc63dc74 | ||
|
|
6eb8acf319 | ||
|
|
862251ab36 | ||
|
|
95a9742571 | ||
|
|
81d0921782 | ||
|
|
0fa556da1d | ||
|
|
05bc238c9a | ||
|
|
782813ab49 | ||
|
|
cbca126d9d | ||
|
|
a9ffe8d210 | ||
|
|
c242abb42e | ||
|
|
2c722ffc4f | ||
|
|
484c47cef8 | ||
|
|
f38dc37152 | ||
|
|
6133f09056 | ||
|
|
2a959edeba | ||
|
|
8995d689b8 | ||
|
|
f08bec6c78 | ||
|
|
d920d91786 | ||
|
|
e35b3745eb | ||
|
|
e8252d9468 | ||
|
|
8fff6a1a06 | ||
|
|
0eff821928 | ||
|
|
9e605e0f79 | ||
|
|
8fb991110e | ||
|
|
b383ebee48 | ||
|
|
138313dcb9 | ||
|
|
f254958db9 | ||
|
|
09eff88679 | ||
|
|
24b356dba4 | ||
|
|
91bca5fcba | ||
|
|
96a469db36 | ||
|
|
9ab05e7037 | ||
|
|
52a99faf68 | ||
|
|
94cf1f8741 | ||
|
|
e2a8f9b790 | ||
|
|
409f5cc4fd | ||
|
|
3b5e4c44eb | ||
|
|
69a7ea63e2 | ||
|
|
aac379daeb | ||
|
|
1d3472b5c4 | ||
|
|
66a590b774 | ||
|
|
dc4472e9e9 | ||
|
|
6bfd428c2e | ||
|
|
f4623bd277 | ||
|
|
1f04d7aafd | ||
|
|
78efeec368 | ||
|
|
5b15a2fc0a | ||
|
|
68f1fa0340 | ||
|
|
9293f17707 | ||
|
|
a4b32e2328 | ||
|
|
d109ca30e2 | ||
|
|
3b4a651dd9 | ||
|
|
a4ac53e2e9 | ||
|
|
cf1b5a7685 | ||
|
|
0789eb8b07 | ||
|
|
7f674fdd35 | ||
|
|
500aa37e3c | ||
|
|
efe668d26d | ||
|
|
90db97b980 | ||
|
|
bc3d27fac6 | ||
|
|
55b9f1207d | ||
|
|
2bbb6001a8 | ||
|
|
af7b7664c5 | ||
|
|
c31362655c | ||
|
|
c6a956382f | ||
|
|
bab0bda174 | ||
|
|
bb52a8417c | ||
|
|
e3d7be23cb | ||
|
|
3d5545494e | ||
|
|
7f1a535b30 | ||
|
|
254234b0dc | ||
|
|
3566f37981 | ||
|
|
e6259eb6e1 | ||
|
|
eeb3d1eb42 | ||
|
|
79295f6434 | ||
|
|
285e4e2e52 | ||
|
|
5587b64d64 | ||
|
|
f0f2a5aa67 | ||
|
|
061317866b | ||
|
|
65ffc01583 | ||
|
|
f5f7a63a23 | ||
|
|
b6a7fd1cec | ||
|
|
6245f92e16 | ||
|
|
0abab2107c | ||
|
|
afc7116260 | ||
|
|
0b3d2b46e0 | ||
|
|
f786e07179 | ||
|
|
b2469bb34f | ||
|
|
b721bfcc84 | ||
|
|
f16ce09a7a | ||
|
|
bbe9df306b | ||
|
|
1bd7392531 | ||
|
|
2002dfca17 | ||
|
|
38c2ecbd0c | ||
|
|
ce97faa8d4 | ||
|
|
31ca4bff8c | ||
|
|
6f9033b2fd | ||
|
|
8864f81402 | ||
|
|
71ba1af647 | ||
|
|
b894a4c19a | ||
|
|
c3a44f25fd | ||
|
|
1f36e7997f | ||
|
|
33b5c26da5 | ||
|
|
e5a7e6619f | ||
|
|
d2e2c1c05f | ||
|
|
557b8b0ded | ||
|
|
37d4cf3afb | ||
|
|
b716db225f | ||
|
|
fbe093705d | ||
|
|
db278e9109 | ||
|
|
88798435f6 | ||
|
|
6220f0d8a5 | ||
|
|
30716b349b | ||
|
|
7a8c8233a2 | ||
|
|
7a2abcca33 | ||
|
|
9a40e5e651 | ||
|
|
893bcd0533 | ||
|
|
f1a911d305 | ||
|
|
8e8d9118e9 | ||
|
|
9c108873e9 | ||
|
|
6fe5d00e9b | ||
|
|
a85d5cfe92 | ||
|
|
6b257cc287 | ||
|
|
2952497a60 | ||
|
|
9018af4f1d | ||
|
|
5204e84703 | ||
|
|
55eaea0110 | ||
|
|
671a125c62 | ||
|
|
eb827cdbe6 | ||
|
|
5504dd435f | ||
|
|
608bb1b5b1 | ||
|
|
88b64034e8 | ||
|
|
694c6f3ec6 | ||
|
|
1d82f80e73 | ||
|
|
e21fa37254 | ||
|
|
1ef44721f5 | ||
|
|
528630a8d3 | ||
|
|
856a760d89 | ||
|
|
9179b6d9c4 | ||
|
|
4ce91a4f5d | ||
|
|
aff93d8f2b | ||
|
|
e4ab4024c5 | ||
|
|
612f23f6e0 | ||
|
|
1d543f83d4 | ||
|
|
88ca852dcf | ||
|
|
9fe2a7f81d | ||
|
|
a988011f0c | ||
|
|
c816870083 | ||
|
|
c5c46b7283 | ||
|
|
8bb8306f80 | ||
|
|
d91e8bfee5 | ||
|
|
2b3e5c8800 | ||
|
|
5b00a8ae33 | ||
|
|
5ee6e7f94b | ||
|
|
f90271dae5 | ||
|
|
e1423bd9d9 | ||
|
|
557714e7b7 | ||
|
|
3df62a200f | ||
|
|
a65ea639ed | ||
|
|
defed52caa | ||
|
|
035f7a5292 | ||
|
|
0ea3100896 | ||
|
|
2e0c2bd190 | ||
|
|
ceca76b344 | ||
|
|
a062769672 | ||
|
|
af6a794ae9 | ||
|
|
f4ac087c83 | ||
|
|
4898dd8e23 | ||
|
|
a090b6a9d2 | ||
|
|
023910472c | ||
|
|
25bf10b125 | ||
|
|
dfb0c09c51 | ||
|
|
41c8d6302a | ||
|
|
83547e3fed | ||
|
|
0d92ffc7ed | ||
|
|
3d0525bc51 | ||
|
|
808cd0f728 | ||
|
|
f5b3df697c | ||
|
|
29a36b5d1c | ||
|
|
ec131a7416 | ||
|
|
3da49ceb60 | ||
|
|
a19cef07bf | ||
|
|
c39e6d7006 | ||
|
|
60f6895919 | ||
|
|
bfa4812482 | ||
|
|
a083cdba18 | ||
|
|
438f484d68 | ||
|
|
f95d780fcf | ||
|
|
79fe092c81 | ||
|
|
027f6a7c12 | ||
|
|
76fe2af0af | ||
|
|
e490185533 | ||
|
|
deba4d2b81 | ||
|
|
a8cc29f062 | ||
|
|
c9e6a75ea8 | ||
|
|
7efa7ec03f | ||
|
|
9eef7a94ab | ||
|
|
244eceadec | ||
|
|
28fdbdd5d8 | ||
|
|
4584990cc3 | ||
|
|
4f3c932bb1 | ||
|
|
d7f2bceea2 | ||
|
|
18c1b2b477 | ||
|
|
174e3e720a | ||
|
|
fdaa279930 | ||
|
|
a4323a0308 | ||
|
|
ec4bed436c | ||
|
|
636c558fe6 | ||
|
|
29873f9c22 | ||
|
|
912ee20f3c | ||
|
|
9f10080243 | ||
|
|
b3c386663f | ||
|
|
f2d25ff777 | ||
|
|
dec9d959db | ||
|
|
2aafdb1400 | ||
|
|
c6b02cdc02 | ||
|
|
f0c825a9b3 | ||
|
|
79c035da11 | ||
|
|
9deafd771e | ||
|
|
cbc09f3a12 | ||
|
|
22c4d79cfb | ||
|
|
71b9a15c9c | ||
|
|
db328e1ce5 | ||
|
|
8b4c85d69a | ||
|
|
78a51d3675 | ||
|
|
a5bcf53146 | ||
|
|
9f97b4aefd | ||
|
|
62468199af | ||
|
|
c220315cb0 | ||
|
|
5a3233da37 | ||
|
|
80109071a2 | ||
|
|
c43bb670d2 | ||
|
|
14874d8e8a | ||
|
|
ff793f1cb5 | ||
|
|
500f6d7baf | ||
|
|
ee9a229c0e | ||
|
|
8140bdaa88 | ||
|
|
5979a18852 | ||
|
|
5efb803b60 | ||
|
|
fd4a8edae9 | ||
|
|
e9fdf06bf6 | ||
|
|
0a5b72e71e | ||
|
|
51168e8e10 | ||
|
|
cefdca3d22 | ||
|
|
cc5eee1890 | ||
|
|
d3864db5e1 | ||
|
|
ece8ae3adc | ||
|
|
e25829c759 | ||
|
|
92afd5f232 | ||
|
|
b934d2e504 | ||
|
|
8cf70e7e20 | ||
|
|
cdfd720c65 | ||
|
|
c0f791cf13 | ||
|
|
290cf79778 | ||
|
|
8af1481749 | ||
|
|
d8f404096c | ||
|
|
ea2f7617df | ||
|
|
b6c258bb12 | ||
|
|
804b49cefb | ||
|
|
305b0d8edb | ||
|
|
05996019e5 | ||
|
|
31a967e072 | ||
|
|
0b56ebf291 | ||
|
|
fcc87b3219 | ||
|
|
82fca0c72d | ||
|
|
51661b0a21 | ||
|
|
e8670fe591 | ||
|
|
bee1dfc4a6 | ||
|
|
83c8449aca | ||
|
|
9dba1bb3e5 | ||
|
|
76e3398d44 | ||
|
|
a7e12931fa | ||
|
|
ba04c64567 | ||
|
|
8c8352ecc6 | ||
|
|
64b670033d | ||
|
|
b9d102a5fb | ||
|
|
77076e02e8 | ||
|
|
c6a761a5ad | ||
|
|
6f3a90c48b | ||
|
|
8f160a8590 | ||
|
|
2278d5bfd8 | ||
|
|
66ac147c9e | ||
|
|
05e0d15ae4 | ||
|
|
8562f018ed | ||
|
|
0cbdcce3ea | ||
|
|
980be65193 | ||
|
|
3aaaea37e4 | ||
|
|
964c594c4c | ||
|
|
9430c4bf43 | ||
|
|
538757317b | ||
|
|
fe986f7c51 | ||
|
|
09105871d9 | ||
|
|
e84a6059f4 | ||
|
|
09f20873df | ||
|
|
2adc1e8ba9 | ||
|
|
4a6c18532b | ||
|
|
921f882680 | ||
|
|
f79e5add58 | ||
|
|
d98d06377e | ||
|
|
22fdd05314 | ||
|
|
8c15a4e0c6 | ||
|
|
9250b45e6e | ||
|
|
3a1de3d2a5 | ||
|
|
b59dd11304 | ||
|
|
b6222abb7c | ||
|
|
dcf8e4f5ef | ||
|
|
f195073ac3 | ||
|
|
452a9d6725 | ||
|
|
1d74ddc547 | ||
|
|
932b589a14 | ||
|
|
671e514785 | ||
|
|
1e2ebdb69c | ||
|
|
148751d927 | ||
|
|
efecdfaea0 | ||
|
|
ab9c84a6b6 | ||
|
|
8711bc0dbd | ||
|
|
0adebd1ec8 | ||
|
|
43c1e893c0 | ||
|
|
7ce63e653b | ||
|
|
e5129a8b98 | ||
|
|
a922c1a298 | ||
|
|
f4aa812d96 | ||
|
|
0c9e41e1fa | ||
|
|
1d6320a88f | ||
|
|
4696e799ed | ||
|
|
2e08a91c53 | ||
|
|
4fd35573ad | ||
|
|
caf2cdbf6f | ||
|
|
c0293a7c1c | ||
|
|
17405f5de1 | ||
|
|
bfefa10462 | ||
|
|
55a140045b | ||
|
|
897bd2c56e | ||
|
|
7321c5937f | ||
|
|
98700cab8b | ||
|
|
3d07635820 | ||
|
|
59b18a6ada | ||
|
|
ce525c1985 | ||
|
|
a0d163a3c4 | ||
|
|
faab971931 | ||
|
|
38eb16dfea | ||
|
|
4494456cad | ||
|
|
876dde1280 | ||
|
|
17ee904828 | ||
|
|
4f1b58cd35 | ||
|
|
edcd3afc3e |
1129
.all-contributorsrc
1129
.all-contributorsrc
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,13 @@
|
||||
.git
|
||||
.github
|
||||
.gitattributes
|
||||
.gitignore
|
||||
.dockerignore
|
||||
app/storage/logs/*
|
||||
app/storage/views/*
|
||||
vendor/*
|
||||
storage/framework/cache/*
|
||||
node_modules
|
||||
.vagrant
|
||||
.idea
|
||||
|
||||
|
||||
51
.env.example
51
.env.example
@@ -7,6 +7,13 @@ APP_KEY=ChangeMe
|
||||
APP_URL=null
|
||||
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
|
||||
@@ -25,6 +32,7 @@ DB_COLLATION=utf8mb4_unicode_ci
|
||||
# OPTIONAL: SSL DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_SSL=false
|
||||
DB_SSL_IS_PAAS=false
|
||||
DB_SSL_KEY_PATH=null
|
||||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
@@ -43,6 +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
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: IMAGE LIBRARY
|
||||
@@ -63,8 +72,12 @@ SECURE_COOKIES=false
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SECURITY HEADER SETTINGS
|
||||
# --------------------------------------------
|
||||
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
|
||||
ALLOW_IFRAMING=false
|
||||
REFERRER_POLICY=same-origin
|
||||
ENABLE_CSP=false
|
||||
CORS_ALLOWED_ORIGINS=null
|
||||
ENABLE_HSTS=false
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: CACHE SETTINGS
|
||||
@@ -72,21 +85,40 @@ ENABLE_CSP=false
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
CACHE_PREFIX=snipeit
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: REDIS SETTINGS
|
||||
# --------------------------------------------
|
||||
REDIS_HOST=null
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT-null
|
||||
REDIS_PORT=null
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: AWS S3 SETTINGS
|
||||
# OPTIONAL: MEMCACHED SETTINGS
|
||||
# --------------------------------------------
|
||||
AWS_SECRET=null
|
||||
AWS_KEY=null
|
||||
AWS_REGION=null
|
||||
AWS_BUCKET=null
|
||||
MEMCACHED_HOST=null
|
||||
MEMCACHED_PORT=null
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: PUBLIC S3 Settings
|
||||
# --------------------------------------------
|
||||
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
|
||||
@@ -100,7 +132,8 @@ LOGIN_LOCKOUT_DURATION=60
|
||||
APP_LOG=single
|
||||
APP_LOG_MAX_FILES=10
|
||||
APP_LOCKED=false
|
||||
FILESYSTEM_DISK=local
|
||||
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
|
||||
ALLOW_IFRAMING=false
|
||||
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
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_DEFAULT=sqlite_testing
|
||||
DB_CONNECTION=mysql
|
||||
DB_DEFAULT=mysql
|
||||
DB_HOST=localhost
|
||||
DB_DATABASE=snipeittests
|
||||
DB_USERNAME=snipeit
|
||||
|
||||
19
.env.unit-tests
Normal file
19
.env.unit-tests
Normal file
@@ -0,0 +1,19 @@
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_DEFAULT=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: LOGIN THROTTLING
|
||||
# (LOGIN_LOCKOUT_DURATIONin minutes)
|
||||
# --------------------------------------------
|
||||
LOGIN_MAX_ATTEMPTS=1000000
|
||||
LOGIN_LOCKOUT_DURATION=100000000
|
||||
|
||||
MAIL_DRIVER=log
|
||||
MAIL_FROM_ADDR=you@example.com
|
||||
MAIL_FROM_NAME=Snipe-IT
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
* text=auto
|
||||
* text=auto
|
||||
public/js/** binary
|
||||
public/css/** binary
|
||||
|
||||
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# You can add one username per supported platform and one custom link
|
||||
# patreon: # Replace with your Patreon username
|
||||
# open_collective: # Replace with your Open Collective username
|
||||
# ko_fi: # Replace with your Ko-fi username
|
||||
custom: https://snipeitapp.com/donate
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -28,8 +28,8 @@
|
||||
- What specific Snipe-IT page you're on, and what specific element you're interacting with to trigger the error
|
||||
- If a stacktrace is provided in the error, include that too.
|
||||
- Any errors that appear in your browser's error console.
|
||||
- Confirm whether the error is reproduceable on the demo: https://snipeitapp.com/demo.
|
||||
- Include any additional information you can find in `app/storage/logs` and your webserver's logs.
|
||||
- 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 what you've done so far in the installation, and if you got any error messages along the way.
|
||||
- Indicate whether or not you've manually edited any data directly in the database
|
||||
|
||||
|
||||
61
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
#### Please confirm you have done the following before posting your bug report:
|
||||
|
||||
- [ ] I have enabled debug mode
|
||||
- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Server (please complete the following information):**
|
||||
- Snipe-IT Version
|
||||
- OS: [e.g. Ubuntu, CentOS]
|
||||
- Web Server: [e.g. Apache, IIS]
|
||||
- PHP Version
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Error Messages**
|
||||
- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
|
||||
- If a stacktrace is provided in the error, include that too.
|
||||
- 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.
|
||||
|
||||
**Additional context**
|
||||
- Is this a fresh install or an upgrade?
|
||||
- What OS and web server you're running Snipe-IT on
|
||||
- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
|
||||
- Include what you've done so far in the installation, and if you got any error messages along the way.
|
||||
- Indicate whether or not you've manually edited any data directly in the database
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.
|
||||
23
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Server (please complete the following information):**
|
||||
- Snipe-IT Version
|
||||
- OS: [e.g. Ubuntu, CentOS]
|
||||
- Web Server: [e.g. Apache, IIS]
|
||||
- PHP Version
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
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! 🎉🎉🎉
|
||||
40
.github/pull_request_template.md
vendored
Normal file
40
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context, providing screenshots where practical. List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
# How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
||||
|
||||
- [ ] Test A
|
||||
- [ ] Test B
|
||||
|
||||
**Test Configuration**:
|
||||
* PHP version:
|
||||
* MySQL version
|
||||
* Webserver version
|
||||
* OS version
|
||||
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] I have read the Contributing documentation available here: https://snipe-it.readme.io/docs/contributing-overview
|
||||
- [ ] I have formatted this PR according to the project guidelines: https://snipe-it.readme.io/docs/contributing-overview#pull-request-guidelines
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
43
.github/stale.yml
vendored
Normal file
43
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- :woman_technologist: ready for dev
|
||||
- :moneybag: bounty
|
||||
- :hand: bug
|
||||
- "🔐 security"
|
||||
- "👩💻 ready for dev"
|
||||
- "💰 bounty"
|
||||
- "✋ bug"
|
||||
|
||||
exemptMilestones: true
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
only: issues
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
unmarkComment: >
|
||||
Okay, it looks like this issue or feature request might still be important. We'll re-open
|
||||
it for now. Thank you for letting us know!
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Is this still relevant? We haven't heard from anyone in a bit. If so,
|
||||
please comment with any updates or additional detail.
|
||||
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Don't
|
||||
take it personally, we just need to keep a handle on things. Thank you
|
||||
for your contributions!
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because it has not had
|
||||
recent activity. If you believe this is still an issue, please confirm that
|
||||
this issue is still happening in the most recent version of Snipe-IT and reply
|
||||
to this thread to re-open it.
|
||||
1
.github/travis-memory.ini
vendored
Normal file
1
.github/travis-memory.ini
vendored
Normal file
@@ -0,0 +1 @@
|
||||
memory_limit= 2048M
|
||||
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,3 +50,13 @@ 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
|
||||
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,4 +1,7 @@
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token:
|
||||
secure: "C/bUAEpwfZB82dkzI2Nxx3PW5w/BzbKkSyCkp6YjT046jD2/QKvz6ngCFlt3tAWV11TXWFI6D8DzkMmdWOrQl3SGlPZXRD8QOvCiz0HiGMDvlxjAaPaQecGaQZdx/H4m6xTUXRNUVaYmxlMgkkFCWhAp+HZDs0iyOEVamp0Jszg="
|
||||
hosts:
|
||||
- localhost
|
||||
sudo: false
|
||||
@@ -11,17 +14,21 @@ services:
|
||||
|
||||
# list any PHP version you want to test against
|
||||
php:
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.2
|
||||
- 7.3.0
|
||||
- 7.4
|
||||
|
||||
|
||||
# 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;'
|
||||
- cp .env.testing-ci .env
|
||||
- composer self-update
|
||||
- composer install -n --prefer-source
|
||||
- chmod -R 777 storage
|
||||
@@ -45,9 +52,12 @@ before_script:
|
||||
script:
|
||||
- ./vendor/bin/codecept run unit
|
||||
# - ./vendor/bin/codecept run acceptance --env=testing-ci
|
||||
- ./vendor/bin/codecept run functional --env=functional-travis
|
||||
#script: ./vendor/bin/codecept run
|
||||
- ./vendor/bin/codecept run api --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
|
||||
|
||||
86
Dockerfile
86
Dockerfile
@@ -1,33 +1,59 @@
|
||||
FROM ubuntu:xenial
|
||||
MAINTAINER Brady Wetherington <uberbrady@gmail.com>
|
||||
FROM ubuntu:bionic
|
||||
LABEL maintainer Brady Wetherington <uberbrady@gmail.com>
|
||||
|
||||
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.0 \
|
||||
php7.0-curl \
|
||||
php7.0-ldap \
|
||||
php7.0-mysql \
|
||||
php7.0-mcrypt \
|
||||
php7.0-gd \
|
||||
php7.0-xml \
|
||||
php7.0-mbstring \
|
||||
php7.0-zip \
|
||||
php7.0-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.0/apache2/php.ini
|
||||
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.0/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
|
||||
|
||||
@@ -38,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
|
||||
|
||||
@@ -64,16 +90,21 @@ RUN chown -R docker /var/www/html
|
||||
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"
|
||||
&& 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 docker "/var/lib/snipeit/keys/" \
|
||||
&& chmod +x /var/www/html/artisan \
|
||||
&& echo "Finished setting up application in /var/www/html"
|
||||
|
||||
############## 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 #################
|
||||
@@ -91,16 +122,11 @@ VOLUME ["/var/lib/snipeit"]
|
||||
|
||||
##### START SERVER
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
COPY docker/startup.sh docker/supervisord.conf /
|
||||
COPY docker/supervisor-exit-event-listener /usr/bin/supervisor-exit-event-listener
|
||||
RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
|
||||
|
||||
# Add Tini
|
||||
ENV TINI_VERSION v0.14.0
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
|
||||
RUN chmod +x /tini
|
||||
ENTRYPOINT ["/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
CMD ["/startup.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
80
Dockerfile.alpine
Normal file
80
Dockerfile.alpine
Normal file
@@ -0,0 +1,80 @@
|
||||
FROM alpine:3.8
|
||||
# Apache + PHP
|
||||
RUN apk add --update --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 \
|
||||
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
|
||||
80
README.md
80
README.md
@@ -1,16 +1,17 @@
|
||||
[](https://travis-ci.org/snipe/snipe-it) [](http://waffle.io/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/snipeyhead) [](https://zenhub.io) [](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://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)
|
||||
[](#contributors) [](https://www.codetriage.com/snipe/snipe-it)
|
||||
|
||||
|
||||
## 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.4](http://laravel.com).
|
||||
It is built on [Laravel 5.5](http://laravel.com).
|
||||
|
||||
Snipe-IT is actively developed and we're [releasing quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
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/).)
|
||||
|
||||
__This is web-based software__. This means there there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||
__This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||
|
||||
-----
|
||||
|
||||
@@ -50,6 +51,39 @@ Please see the [translations documentation](https://snipe-it.readme.io/docs/tran
|
||||
|
||||
-----
|
||||
|
||||
### Libraries, Modules & Related Projects
|
||||
|
||||
Since the release of the JSON REST API, several third-party developers have been developing modules and libraries to work with Snipe-IT.
|
||||
|
||||
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [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).
|
||||
|
||||
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.
|
||||
|
||||
-----
|
||||
|
||||
### Contributors
|
||||
|
||||
Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
|
||||
@@ -68,22 +102,28 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/857740?v=3" width="110px;"/><br /><sub>Gil Rutkowski</sub>](http://FlashingCursor.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [<img src="https://avatars3.githubusercontent.com/u/129360?v=3" width="110px;"/><br /><sub>Desmond Morris</sub>](http://www.desmondmorris.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [<img src="https://avatars2.githubusercontent.com/u/52936?v=3" width="110px;"/><br /><sub>Nick Peelman</sub>](http://peelman.us)<br />[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [<img src="https://avatars0.githubusercontent.com/u/53161?v=3" width="110px;"/><br /><sub>Abraham Vegh</sub>](https://abrahamvegh.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [<img src="https://avatars0.githubusercontent.com/u/2818680?v=3" width="110px;"/><br /><sub>Mohamed Rashid</sub>](https://github.com/rashivkp)<br />[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1509456?v=3" width="110px;"/><br /><sub>Kasey</sub>](http://hinchk.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [<img src="https://avatars2.githubusercontent.com/u/10522541?v=3" width="110px;"/><br /><sub>Brett</sub>](https://github.com/BrettFagerlund)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/37537300?v=4" width="110px;"/><br /><sub>lea-mink</sub>](https://github.com/lea-mink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [<img src="https://avatars0.githubusercontent.com/u/7140719?v=4" width="110px;"/><br /><sub>Hannah Tinkler</sub>](https://github.com/hannahtinkler)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/1086388?v=4" width="110px;"/><br /><sub>Doeke Zanstra</sub>](https://github.com/doekman)<br />[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [<img src="https://avatars1.githubusercontent.com/u/4325936?v=4" width="110px;"/><br /><sub>Djamon Staal</sub>](https://www.sdhd.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [<img src="https://avatars3.githubusercontent.com/u/12306859?v=4" width="110px;"/><br /><sub>Earl Ramirez</sub>](https://github.com/EarlRamirez)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [<img src="https://avatars2.githubusercontent.com/u/8671456?v=4" width="110px;"/><br /><sub>Richard Ray Thomas</sub>](https://github.com/RichardRay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [<img src="https://avatars3.githubusercontent.com/u/1852688?v=4" width="110px;"/><br /><sub>Ryan Kuba</sub>](https://www.taisun.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [<img src="https://avatars1.githubusercontent.com/u/6751928?v=4" width="110px;"/><br /><sub>Brian Monroe</sub>](https://github.com/ParadoxGuitarist)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [<img src="https://avatars1.githubusercontent.com/u/605167?v=4" width="110px;"/><br /><sub>plexorama</sub>](https://github.com/plexorama)<br />[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/1795149?v=4" width="110px;"/><br /><sub>Till Deeke</sub>](https://tilldeeke.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [<img src="https://avatars0.githubusercontent.com/u/12634129?v=4" width="110px;"/><br /><sub>5quirrel</sub>](https://github.com/5quirrel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [<img src="https://avatars1.githubusercontent.com/u/13071957?v=4" width="110px;"/><br /><sub>Jason</sub>](https://github.com/jasonlshelton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [<img src="https://avatars3.githubusercontent.com/u/7128321?v=4" width="110px;"/><br /><sub>Antti</sub>](https://github.com/chemfy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [<img src="https://avatars3.githubusercontent.com/u/10080364?v=4" width="110px;"/><br /><sub>DeusMaximus</sub>](https://github.com/DeusMaximus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [<img src="https://avatars2.githubusercontent.com/u/16384611?v=4" width="110px;"/><br /><sub>a-royal</sub>](https://github.com/A-ROYAL)<br />[🌍](#translation-A-ROYAL "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5358208?v=4" width="110px;"/><br /><sub>Alberto Aldrigo</sub>](https://github.com/albertoaldrigo)<br />[🌍](#translation-albertoaldrigo "Translation") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1412342?v=4" width="110px;"/><br /><sub>Alex Stanev</sub>](http://alex.stanev.org/blog)<br />[🌍](#translation-RealEnder "Translation") | [<img src="https://avatars0.githubusercontent.com/u/177295?v=4" width="110px;"/><br /><sub>Andreas Rehm</sub>](http://devel.itsolution2.de)<br />[🌍](#translation-sirrus "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5080535?v=4" width="110px;"/><br /><sub>Andreas Erhard</sub>](https://github.com/xelan)<br />[🌍](#translation-xelan "Translation") | [<img src="https://avatars2.githubusercontent.com/u/142350?v=4" width="110px;"/><br /><sub>Andrés Vanegas Jiménez</sub>](https://github.com/angeldeejay)<br />[🌍](#translation-angeldeejay "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3910403?v=4" width="110px;"/><br /><sub>Antonio Schiavon</sub>](https://github.com/aschiavon91)<br />[🌍](#translation-aschiavon91 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10464547?v=4" width="110px;"/><br /><sub>benunter</sub>](https://github.com/benunter)<br />[🌍](#translation-benunter "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5038647?v=4" width="110px;"/><br /><sub>Borys Żmuda</sub>](http://catweb24.pl)<br />[🌍](#translation-rudashi "Translation") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/5539359?v=4" width="110px;"/><br /><sub>chibacityblues</sub>](https://github.com/chibacityblues)<br />[🌍](#translation-chibacityblues "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1954830?v=4" width="110px;"/><br /><sub>Chien Wei Lin</sub>](https://github.com/cwlin0416)<br />[🌍](#translation-cwlin0416 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/11700533?v=4" width="110px;"/><br /><sub>Christian Schuster</sub>](https://github.com/Againstreality)<br />[🌍](#translation-Againstreality "Translation") | [<img src="https://avatars1.githubusercontent.com/u/4308704?v=4" width="110px;"/><br /><sub>Christian Stefanus</sub>](http://chriss.webhostid.com)<br />[🌍](#translation-kopi-item "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3009327?v=4" width="110px;"/><br /><sub>wxcafé</sub>](http://wxcafe.net)<br />[🌍](#translation-wxcafe "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35761525?v=4" width="110px;"/><br /><sub>dpyroc</sub>](https://github.com/dpyroc)<br />[🌍](#translation-dpyroc "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2153639?v=4" width="110px;"/><br /><sub>Daniel Friedlmaier</sub>](http://www.friedlmaier.net)<br />[🌍](#translation-da-friedl "Translation") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/2947640?v=4" width="110px;"/><br /><sub>Daniel Heene</sub>](https://github.com/danielheene)<br />[🌍](#translation-danielheene "Translation") | [<img src="https://avatars3.githubusercontent.com/u/319022?v=4" width="110px;"/><br /><sub>danielcb</sub>](https://github.com/danielcb)<br />[🌍](#translation-danielcb "Translation") | [<img src="https://avatars3.githubusercontent.com/u/15846537?v=4" width="110px;"/><br /><sub>Dominik Senti</sub>](https://github.com/dominiksenti)<br />[🌍](#translation-dominiksenti "Translation") | [<img src="https://avatars0.githubusercontent.com/u/25570954?v=4" width="110px;"/><br /><sub>Eric Gautheron</sub>](http://www.konectik.com)<br />[🌍](#translation-EpixFr "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5732623?v=4" width="110px;"/><br /><sub>Erlend Pilø</sub>](https://erlpil.com)<br />[🌍](#translation-Erlpil "Translation") | [<img src="https://avatars0.githubusercontent.com/u/541832?v=4" width="110px;"/><br /><sub>Fabio Rapposelli</sub>](http://fabio.technology)<br />[🌍](#translation-frapposelli "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3605240?v=4" width="110px;"/><br /><sub>Felipe Barros</sub>](https://github.com/fgbs)<br />[🌍](#translation-fgbs "Translation") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/257745?v=4" width="110px;"/><br /><sub>Fernando Possebon</sub>](https://github.com/possebon)<br />[🌍](#translation-possebon "Translation") | [<img src="https://avatars3.githubusercontent.com/u/2540832?v=4" width="110px;"/><br /><sub>gdraque</sub>](https://github.com/gdraque)<br />[🌍](#translation-gdraque "Translation") | [<img src="https://avatars0.githubusercontent.com/u/23440381?v=4" width="110px;"/><br /><sub>Georg Wallisch</sub>](https://github.com/georgwallisch)<br />[🌍](#translation-georgwallisch "Translation") | [<img src="https://avatars1.githubusercontent.com/u/9852832?v=4" width="110px;"/><br /><sub>Gerardo Robles</sub>](https://github.com/jgroblesr85)<br />[🌍](#translation-jgroblesr85 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/11082640?v=4" width="110px;"/><br /><sub>Gluek</sub>](https://t.me/Gluek)<br />[🌍](#translation-mrgluek "Translation") | [<img src="https://avatars0.githubusercontent.com/u/6847946?v=4" width="110px;"/><br /><sub>AdnanAbuShahad</sub>](https://github.com/AdnanAbuShahad)<br />[🌍](#translation-AdnanAbuShahad "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3580608?v=4" width="110px;"/><br /><sub>Hafidzi My</sub>](https://hafidzi.my)<br />[🌍](#translation-hafidzi "Translation") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/205521?v=4" width="110px;"/><br /><sub>Harim Park</sub>](https://github.com/fofwisdom)<br />[🌍](#translation-fofwisdom "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3333841?v=4" width="110px;"/><br /><sub>Henrik Kentsson</sub>](http://www.kentsson.se)<br />[🌍](#translation-Kentsson "Translation") | [<img src="https://avatars0.githubusercontent.com/u/36551034?v=4" width="110px;"/><br /><sub>Husnul Yaqien</sub>](https://github.com/husnulyaqien)<br />[🌍](#translation-husnulyaqien "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2372747?v=4" width="110px;"/><br /><sub>Ibrahim</sub>](http://abaalkhail.org)<br />[🌍](#translation-abaalkh "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1389334?v=4" width="110px;"/><br /><sub>igolman</sub>](https://github.com/igolman)<br />[🌍](#translation-igolman "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3257070?v=4" width="110px;"/><br /><sub>itangiang</sub>](https://github.com/itangiang)<br />[🌍](#translation-itangiang "Translation") | [<img src="https://avatars2.githubusercontent.com/u/14814254?v=4" width="110px;"/><br /><sub>jarby1211</sub>](https://github.com/jarby1211)<br />[🌍](#translation-jarby1211 "Translation") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/6719357?v=4" width="110px;"/><br /><sub>Jhonn Willker</sub>](http://jwillker.com)<br />[🌍](#translation-JohnWillker "Translation") | [<img src="https://avatars2.githubusercontent.com/u/10983635?v=4" width="110px;"/><br /><sub>Jose</sub>](https://github.com/joxelito94)<br />[🌍](#translation-joxelito94 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5206122?v=4" width="110px;"/><br /><sub>laopangzi</sub>](https://github.com/laopangzi)<br />[🌍](#translation-laopangzi "Translation") | [<img src="https://avatars2.githubusercontent.com/u/79707?v=4" width="110px;"/><br /><sub>Lars Strojny</sub>](http://usrportage.de)<br />[🌍](#translation-lstrojny "Translation") | [<img src="https://avatars0.githubusercontent.com/u/389801?v=4" width="110px;"/><br /><sub>MarcosBL</sub>](http://twitter.com/marcosbl)<br />[🌍](#translation-MarcosBL "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35664606?v=4" width="110px;"/><br /><sub>marie joy cajes</sub>](https://github.com/mariejoyacajes)<br />[🌍](#translation-mariejoyacajes "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3052816?v=4" width="110px;"/><br /><sub>Mark S. Johansen</sub>](http://www.markjohansen.dk)<br />[🌍](#translation-msjohansen "Translation") |
|
||||
| [<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") |
|
||||
<!-- 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.
|
||||
|
||||
-----
|
||||
|
||||
### Security
|
||||
|
||||
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
|
||||
|
||||
102
Vagrantfile
vendored
Normal file
102
Vagrantfile
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
|
||||
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.define "bionic" do |bionic|
|
||||
bionic.vm.box = "ubuntu/bionic64"
|
||||
bionic.vm.hostname = 'bionic'
|
||||
bionic.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
bionic.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
bionic.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "xenial" do |xenial|
|
||||
xenial.vm.box = "ubuntu/xenial64"
|
||||
xenial.vm.hostname = 'xenial'
|
||||
xenial.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
xenial.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
xenial.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "trusty" do |trusty|
|
||||
trusty.vm.box = "ubuntu/trusty32"
|
||||
trusty.vm.hostname = 'trusty'
|
||||
trusty.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
trusty.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
trusty.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "centos7" do |centos7|
|
||||
centos7.vm.box = "centos/7"
|
||||
centos7.vm.hostname = 'centos7'
|
||||
centos7.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
centos7.vm.provision :shell, :inline => "sudo yum -y update"
|
||||
centos7.vm.provision :shell, :inline => "yum install -y wget"
|
||||
centos7.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
centos7.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "centos6" do |centos6|
|
||||
centos6.vm.box = "centos/6"
|
||||
centos6.vm.hostname = 'centos6'
|
||||
centos6.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
centos6.vm.provision :shell, :inline => "sudo yum -y update"
|
||||
centos6.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
centos6.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "jessie" do |jessie|
|
||||
jessie.vm.box = "debian/jessie64"
|
||||
jessie.vm.hostname = 'debian8'
|
||||
jessie.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
jessie.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
jessie.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "stretch" do |stretch|
|
||||
stretch.vm.box = "debian/stretch64"
|
||||
stretch.vm.hostname = 'debian9'
|
||||
stretch.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
stretch.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
stretch.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "fedora27" do |fedora27|
|
||||
fedora27.vm.box = "fedora/27-cloud-base"
|
||||
fedora27.vm.hostname = 'fedora27'
|
||||
fedora27.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
fedora27.vm.provision :shell, :inline => "dnf -y install wget"
|
||||
fedora27.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
|
||||
fedora27.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
|
||||
end
|
||||
|
||||
config.vm.define "fedora26" do |fedora26|
|
||||
fedora26.vm.box = "fedora/26-cloud-base"
|
||||
fedora26.vm.hostname = 'fedora26'
|
||||
fedora26.vm.network "public_network", bridge: NETWORK_BRIDGE
|
||||
fedora26.vm.provision :shell, :inline => "dnf -y install wget"
|
||||
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
|
||||
95
app/Console/Commands/CheckinLicensesFromAllUsers.php
Normal file
95
app/Console/Commands/CheckinLicensesFromAllUsers.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use App\Models\License;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CheckinLicensesFromAllUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:checkin-from-all {--license_id=} {--notify}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Checks in licenses from all users';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$license_id = $this->option('license_id');
|
||||
$notify = $this->option('notify');
|
||||
|
||||
if (!$license_id) {
|
||||
$this->error('ERROR: License ID is required.');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$license = License::where('id','=',$license_id)->first()) {
|
||||
$this->error('Invalid license ID');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->info('Checking in ALL seats for '.$license->name);
|
||||
|
||||
|
||||
$licenseSeats = LicenseSeat::where('license_id', '=', $license_id)
|
||||
->whereNotNull('assigned_to')
|
||||
->with('user')
|
||||
->get();
|
||||
|
||||
$this->info(' There are ' .$licenseSeats->count(). ' seats checked out: ');
|
||||
|
||||
if (!$notify) {
|
||||
$this->info('No mail will be sent.');
|
||||
}
|
||||
|
||||
foreach ($licenseSeats as $seat) {
|
||||
$this->info($seat->user->username .' has a license seat for '.$license->name);
|
||||
$seat->assigned_to = null;
|
||||
|
||||
if ($seat->save()) {
|
||||
|
||||
// Override the email address so we don't notify on checkin
|
||||
if (!$notify) {
|
||||
$seat->user->email = null;
|
||||
}
|
||||
|
||||
// Log the checkin
|
||||
$seat->logCheckin($seat->user, 'Checked in via cli tool');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
112
app/Console/Commands/CheckoutLicenseToAllUsers.php
Normal file
112
app/Console/Commands/CheckoutLicenseToAllUsers.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use App\Models\License;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CheckoutLicenseToAllUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:checkout-to-all {--license_id=} {--notify}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$license_id = $this->option('license_id');
|
||||
$notify = $this->option('notify');
|
||||
|
||||
if (!$license_id) {
|
||||
$this->error('ERROR: License ID is required.');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$license = License::where('id','=',$license_id)->with('assignedusers')->first()) {
|
||||
$this->error('Invalid license ID');
|
||||
return false;
|
||||
}
|
||||
|
||||
$users = User::whereNull('deleted_at')->with('licenses')->get();
|
||||
|
||||
if ($users->count() > $license->getAvailSeatsCountAttribute()) {
|
||||
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
|
||||
}
|
||||
|
||||
$this->info('Checking out '.$users->count().' of '.$license->getAvailSeatsCountAttribute().' seats for '.$license->name);
|
||||
|
||||
if (!$notify) {
|
||||
$this->info('No mail will be sent.');
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
|
||||
// Check to make sure this user doesn't already have this license checked out
|
||||
// to them
|
||||
|
||||
if ($user->licenses->where('id', '=', $license_id)->count()) {
|
||||
$this->info($user->username .' already has this license checked out to them. Skipping... ');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// If the license is valid, check that there is an available seat
|
||||
if ($license->availCount()->count() < 1) {
|
||||
$this->error('ERROR: No available seats');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->info($license->availCount()->count().' seats left');
|
||||
// Get the seat ID
|
||||
$licenseSeat = $license->freeSeat();
|
||||
|
||||
|
||||
// Update the seat with checkout info,
|
||||
$licenseSeat->assigned_to = $user->id;
|
||||
if ($licenseSeat->save()) {
|
||||
|
||||
// Temporarily null the user's email address so we don't send mail if we're not supposed to
|
||||
if (!$notify) {
|
||||
$user->email = null;
|
||||
}
|
||||
|
||||
// Log the checkout
|
||||
$licenseSeat->logCheckout('Checked out via cli tool', $user);
|
||||
$this->info('License '.$license_id.' seat '.$licenseSeat->id.' checked out to '.$user->username);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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]++;
|
||||
|
||||
|
||||
160
app/Console/Commands/ImportLocations.php
Normal file
160
app/Console/Commands/ImportLocations.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use League\Csv\Reader;
|
||||
use App\Models\Location;
|
||||
|
||||
class ImportLocations extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:import-locations {filename}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import locations and their parents';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
|
||||
if (!ini_get("auto_detect_line_endings")) {
|
||||
ini_set("auto_detect_line_endings", '1');
|
||||
}
|
||||
|
||||
$filename = $this->argument('filename');
|
||||
$csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r');
|
||||
$this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename);
|
||||
$csv->setHeaderOffset(0); //because we don't want to insert the header
|
||||
$results = $csv->getRecords();
|
||||
|
||||
// Import parent location names first if they don't exist
|
||||
foreach ($results as $parent_index => $parent_row) {
|
||||
|
||||
if (array_key_exists('Parent Name', $parent_row)) {
|
||||
$parent_name = trim($parent_row['Parent Name']);
|
||||
if (array_key_exists('Name', $parent_row)) {
|
||||
$this->info('- Parent: ' . $parent_name . ' in row as: ' . trim($parent_row['Parent Name']));
|
||||
}
|
||||
|
||||
// Save parent location name
|
||||
// This creates a sort of name-stub that we'll update later on in this script
|
||||
$parent_location = Location::firstOrCreate(array('name' => $parent_name));
|
||||
if (array_key_exists('Name', $parent_row)) {
|
||||
$this->info('Parent for ' . $parent_row['Name'] . ' is ' . $parent_name . '. Attempting to save ' . $parent_name . '.');
|
||||
}
|
||||
|
||||
// Check if the record was updated or created.
|
||||
// This is mostly for clearer debugging.
|
||||
if ($parent_location->exists) {
|
||||
$this->info('- Parent location '.$parent_name.' already exists.');
|
||||
} else {
|
||||
$this->info('- Parent location '.$parent_name.' was created.');
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->info('- No Parent Name provided, so no parent location will be created.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->info('----- Parents Created.... backfilling additional details... --------');
|
||||
// Loop through ALL records and add/update them if there are additional fields
|
||||
// besides name
|
||||
foreach ($results as $index => $row) {
|
||||
|
||||
if (array_key_exists('Parent Name', $row)) {
|
||||
$parent_name = trim($row['Parent Name']);
|
||||
}
|
||||
|
||||
// Set the location attributes to save
|
||||
if (array_key_exists('Name', $row)) {
|
||||
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
|
||||
$location->name = trim($row['Name']);
|
||||
$this->info('Checking location: '.$location->name);
|
||||
} else {
|
||||
$this->error('Location name is required and is missing from at least one row in this dataset. Check your CSV for extra trailing rows and try again.');
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('Currency', $row)) {
|
||||
$location->currency = trim($row['Currency']);
|
||||
}
|
||||
if (array_key_exists('Address 1', $row)) {
|
||||
$location->address = trim($row['Address 1']);
|
||||
}
|
||||
if (array_key_exists('Address 2', $row)) {
|
||||
$location->address2 = trim($row['Address 2']);
|
||||
}
|
||||
if (array_key_exists('City', $row)) {
|
||||
$location->city = trim($row['City']);
|
||||
}
|
||||
if (array_key_exists('State', $row)) {
|
||||
$location->state = trim($row['State']);
|
||||
}
|
||||
if (array_key_exists('Zip', $row)) {
|
||||
$location->zip = trim($row['Zip']);
|
||||
}
|
||||
if (array_key_exists('Country', $row)) {
|
||||
$location->country = trim($row['Country']);
|
||||
}
|
||||
if (array_key_exists('OU', $row)) {
|
||||
$location->ldap_ou = trim($row['OU']);
|
||||
}
|
||||
|
||||
|
||||
// If a parent name is provided, we created it earlier in the script,
|
||||
// so let's grab that ID
|
||||
if ($parent_name) {
|
||||
$this->info('-- Searching for Parent Name: '.$parent_name);
|
||||
$parent = Location::where('name', '=', $parent_name)->first();
|
||||
$location->parent_id = $parent->id;
|
||||
$this->info('Parent: '.$parent_name.' - ID: '.$parent->id);
|
||||
}
|
||||
|
||||
// Make sure the more advanced (non-name) fields pass validation
|
||||
if (($location->isValid()) && ($location->save())) {
|
||||
|
||||
// Check if the record was updated or created.
|
||||
// This is mostly for clearer debugging.
|
||||
if ($location->exists) {
|
||||
$this->info('Location ' . $location->name . ' already exists. Updating...');
|
||||
} else {
|
||||
$this->info('- Location '.$location->name.' was created. ');
|
||||
}
|
||||
|
||||
// If there's a validation error, display that
|
||||
} else {
|
||||
$this->error('- Non-parent Location '.$location->name.' could not be created: '.$location->getErrors() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
504
app/Console/Commands/LdapSync.php
Executable file → Normal file
504
app/Console/Commands/LdapSync.php
Executable file → Normal file
@@ -1,14 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\User;
|
||||
use App\Models\Location;
|
||||
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 LdapSync extends Command
|
||||
{
|
||||
/**
|
||||
@@ -16,23 +26,79 @@ class LdapSync extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--summary} {--json_summary}';
|
||||
protected $signature = 'snipeit:ldap-sync
|
||||
{--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 sync';
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(LdapAd $ldap)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->ldap = $ldap;
|
||||
$this->settings = $this->ldap->ldapSettings;
|
||||
$this->summary = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,172 +108,274 @@ class LdapSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
ini_set('max_execution_time', 600); //600 seconds = 10 minutes
|
||||
ini_set('max_execution_time', '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)
|
||||
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag_field;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
|
||||
try {
|
||||
$ldapconn = Ldap::connectToLdap();
|
||||
Ldap::bindAdminToLdap($ldapconn);
|
||||
} catch (\Exception $e) {
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::error($e);
|
||||
return [];
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
$this->checkIfLdapIsEnabled();
|
||||
$this->checkLdapConnetion();
|
||||
$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());
|
||||
}
|
||||
|
||||
$summary = array();
|
||||
error_reporting($old_error_reporting); // re-enable deprecation warnings.
|
||||
return $this->getSummary();
|
||||
}
|
||||
|
||||
$results = Ldap::findLdapUsers();
|
||||
/**
|
||||
* 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']);
|
||||
|
||||
// Retrieve locations with a mapped OU, and sort them from the shallowest to deepest OU (see #3993)
|
||||
$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"]);
|
||||
}
|
||||
|
||||
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
|
||||
|
||||
if (sizeof($ldap_ou_locations) > 0) {
|
||||
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
|
||||
}
|
||||
|
||||
// Inject location information fields
|
||||
for ($i = 0; $i < $results["count"]; $i++) {
|
||||
$results[$i]["ldap_location_override"] = false;
|
||||
$results[$i]["location_id"] = 0;
|
||||
}
|
||||
|
||||
if ($this->option('location')!='') {
|
||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
||||
LOG::debug('Location name '.$this->option('location').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
} elseif ($this->option('location_id')!='') {
|
||||
$location = Location::where('id', '=', $this->option('location_id'))->first();
|
||||
LOG::debug('Location ID '.$this->option('location_id').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
} else {
|
||||
$location = NULL;
|
||||
}
|
||||
|
||||
if (!isset($location)) {
|
||||
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
||||
}
|
||||
|
||||
// Grab subsets based on location-specific DNs, and overwrite location for these users.
|
||||
foreach ($ldap_ou_locations as $ldap_loc) {
|
||||
$location_users = Ldap::findLdapUsers($ldap_loc["ldap_ou"]);
|
||||
$usernames = array();
|
||||
for ($i = 0; $i < $location_users["count"]; $i++) {
|
||||
$location_users[$i]["ldap_location_override"] = true;
|
||||
$location_users[$i]["location_id"] = $ldap_loc["id"];
|
||||
$usernames[] = $location_users[$i][$ldap_result_username][0];
|
||||
}
|
||||
|
||||
// Delete located users from the general group.
|
||||
foreach ($results as $key => $generic_entry) {
|
||||
if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
|
||||
unset($results[$key]);
|
||||
if ('ERROR' === $item['status']) {
|
||||
$this->error('ERROR: '.$item['note']);
|
||||
}
|
||||
}
|
||||
|
||||
$global_count = $results['count'];
|
||||
$results = array_merge($location_users, $results);
|
||||
$results['count'] = $global_count;
|
||||
}
|
||||
|
||||
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
|
||||
$pass = bcrypt($tmp_pass);
|
||||
|
||||
|
||||
for ($i = 0; $i < $results["count"]; $i++) {
|
||||
if (empty($ldap_result_active_flag) || $results[$i][$ldap_result_active_flag][0] == "TRUE") {
|
||||
|
||||
$item = array();
|
||||
$item["username"] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : "";
|
||||
$item["employee_number"] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : "";
|
||||
$item["lastname"] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : "";
|
||||
$item["firstname"] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : "";
|
||||
$item["email"] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : "" ;
|
||||
$item["ldap_location_override"] = isset($results[$i]["ldap_location_override"]) ? $results[$i]["ldap_location_override"]:"";
|
||||
$item["location_id"] = isset($results[$i]["location_id"]) ? $results[$i]["location_id"]:"";
|
||||
|
||||
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
|
||||
$enabled_accounts = [
|
||||
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224'
|
||||
];
|
||||
$item['activated'] = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
|
||||
} else {
|
||||
$item['activated'] = 0;
|
||||
}
|
||||
|
||||
// User exists
|
||||
$item["createorupdate"] = 'updated';
|
||||
if (!$user = User::where('username', $item["username"])->first()) {
|
||||
$user = new User;
|
||||
$user->password = $pass;
|
||||
$item["createorupdate"] = 'created';
|
||||
}
|
||||
|
||||
// Create the user if they don't exist.
|
||||
$user->first_name = e($item["firstname"]);
|
||||
$user->last_name = e($item["lastname"]);
|
||||
$user->username = e($item["username"]);
|
||||
$user->email = e($item["email"]);
|
||||
$user->employee_num = e($item["employee_number"]);
|
||||
$user->activated = $item['activated'];
|
||||
|
||||
if ($item['ldap_location_override'] == true) {
|
||||
$user->location_id = $item['location_id'];
|
||||
} else if ($location) {
|
||||
$user->location_id = e($location->id);
|
||||
}
|
||||
|
||||
$user->notes = 'Imported from LDAP';
|
||||
$user->ldap_import = 1;
|
||||
|
||||
$errors = '';
|
||||
|
||||
if ($user->save()) {
|
||||
$item["note"] = $item["createorupdate"];
|
||||
$item["status"]='success';
|
||||
} else {
|
||||
foreach ($user->getErrors()->getMessages() as $key => $err) {
|
||||
$errors .= $err[0];
|
||||
}
|
||||
$item["note"] = $errors;
|
||||
$item["status"]='error';
|
||||
}
|
||||
|
||||
array_push($summary, $item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($this->option('summary')) {
|
||||
for ($x = 0; $x < count($summary); $x++) {
|
||||
if ($summary[$x]['status']=='error') {
|
||||
$this->error('ERROR: '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].' was not imported: '.$summary[$x]['note']);
|
||||
} else {
|
||||
$this->info('User '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].' was '.strtoupper($summary[$x]['createorupdate']).'.');
|
||||
}
|
||||
}
|
||||
} else if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => false, "error_message" => "", "summary" => $summary ];
|
||||
});
|
||||
} elseif ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => false,
|
||||
'error_message' => '',
|
||||
'summary' => $this->summary->toArray(),
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
} else {
|
||||
return $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->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';
|
||||
}
|
||||
}
|
||||
|
||||
// $summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED'); // this seems, kinda, like, superfluous, relative to the $summary['note'] thing above, yeah?
|
||||
$this->summary->push($summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the users to update / create.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $page The page to get the result set
|
||||
*/
|
||||
private function processLdapUsers(int $page=0): void
|
||||
{
|
||||
try {
|
||||
$ldapUsers = $this->ldap->getLdapUsers($page);
|
||||
} 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 as $user) {
|
||||
$this->updateCreateUser($user);
|
||||
}
|
||||
|
||||
if ($ldapUsers->getCurrentPage() < $ldapUsers->getPages()-1) {
|
||||
$this->processLdapUsers($ldapUsers->getCurrentPage() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 checkLdapConnetion(): 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);
|
||||
}
|
||||
}
|
||||
|
||||
109
app/Console/Commands/MergeUsersByUsername.php
Normal file
109
app/Console/Commands/MergeUsersByUsername.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
||||
class MergeUsersByUsername extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:merge-users';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command allows you to merge the history of users. It looks for users without an email address as their username and merges them into the version that does have an email username.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Get the list of users who have an email address as their username
|
||||
$users = User::where('username', 'LIKE', '%@%')->whereNull('deleted_at')->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$parts = explode("@", $user->username);
|
||||
$bad_users = User::where('username', '=', $parts[0])->whereNull('deleted_at')->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations')->get();
|
||||
|
||||
foreach ($bad_users as $bad_user) {
|
||||
$this->info($bad_user->username.' ('.$bad_user->id.') will be merged into '.$user->username.' ('.$user->id.') ');
|
||||
|
||||
// Walk the list of assets
|
||||
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();
|
||||
}
|
||||
|
||||
// Walk the list of licenses
|
||||
foreach ($bad_user->licenses as $license) {
|
||||
$this->info( 'Updating license '.$license->name.' '.$license->id.' to user '.$user->id);
|
||||
$bad_user->licenses()->updateExistingPivot($license->id, ['assigned_to' => $user->id]);
|
||||
}
|
||||
|
||||
// Walk the list of consumables
|
||||
foreach ($bad_user->consumables as $consumable) {
|
||||
$this->info( 'Updating consumable '.$consumable->id.' to user '.$user->id);
|
||||
$bad_user->consumables()->updateExistingPivot($consumable->id, ['assigned_to' => $user->id]);
|
||||
}
|
||||
|
||||
// Walk the list of accessories
|
||||
foreach ($bad_user->accessories as $accessory) {
|
||||
$this->info( 'Updating accessory '.$accessory->id.' to user '.$user->id);
|
||||
$bad_user->accessories()->updateExistingPivot($accessory->id, ['assigned_to' => $user->id]);
|
||||
}
|
||||
|
||||
// Walk the list of logs
|
||||
foreach ($bad_user->userlog as $log) {
|
||||
$this->info( 'Updating action log record '.$log->id.' to user '.$user->id);
|
||||
$log->target_id = $user->id;
|
||||
$log->save();
|
||||
}
|
||||
|
||||
// Update any manager IDs
|
||||
$this->info( 'Updating managed user records to user '.$user->id);
|
||||
User::where('manager_id', '=', $bad_user->id)->update(['manager_id' => $user->id]);
|
||||
|
||||
|
||||
// Update location manager IDs
|
||||
foreach ($bad_user->managedLocations as $managedLocation) {
|
||||
$this->info( 'Updating managed location record '.$managedLocation->name.' to manager '.$user->id);
|
||||
$managedLocation->manager_id = $user->id;
|
||||
$managedLocation->save();
|
||||
}
|
||||
|
||||
// Mark the user as deleted
|
||||
$this->info( 'Marking the user as deleted');
|
||||
$bad_user->deleted_at = Carbon::now()->timestamp;
|
||||
$bad_user->save();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
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('storage/app/public/accessories'."/*.*");
|
||||
$public_uploads['assets'] = glob('storage/app/public/assets'."/*.*");
|
||||
$public_uploads['avatars'] = glob('storage/app/public/avatars'."/*.*");
|
||||
$public_uploads['categories'] = glob('storage/app/public/categories'."/*.*");
|
||||
$public_uploads['companies'] = glob('storage/app/public/companies'."/*.*");
|
||||
$public_uploads['components'] = glob('storage/app/public/components'."/*.*");
|
||||
$public_uploads['consumables'] = glob('storage/app/public/consumables'."/*.*");
|
||||
$public_uploads['departments'] = glob('storage/app/public/departments'."/*.*");
|
||||
$public_uploads['locations'] = glob('storage/app/public/locations'."/*.*");
|
||||
$public_uploads['manufacturers'] = glob('storage/app/public/manufacturers'."/*.*");
|
||||
$public_uploads['suppliers'] = glob('storage/app/public/suppliers'."/*.*");
|
||||
$public_uploads['assetmodels'] = glob('storage/app/public/models'."/*.*");
|
||||
|
||||
|
||||
// iterate files
|
||||
foreach($public_uploads as $public_type => $public_upload)
|
||||
{
|
||||
$type_count = 0;
|
||||
$this->info("\nThere 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($public_type.'/'.$filename, file_get_contents($public_upload[$i]));
|
||||
$new_url = Storage::disk('public')->url($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("\nThere are ".count($logos).' files that might be logos.');
|
||||
$type_count = 0;
|
||||
|
||||
for ($l = 0; $l < count($logos); $l++) {
|
||||
$type_count++;
|
||||
$filename = basename($logos[$l]);
|
||||
$new_url = Storage::disk('public')->url($logos[$l], file_get_contents($public_upload[$i]));
|
||||
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.$new_url);
|
||||
}
|
||||
|
||||
$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("\nThere are ".count($private_upload).' PRIVATE '.$private_type.' files.');
|
||||
// $this->info(print_r($private_upload, true));
|
||||
|
||||
$type_count = 0;
|
||||
for ($x = 0; $x < count($private_upload); $x++) {
|
||||
$type_count++;
|
||||
$filename = basename($private_upload[$x]);
|
||||
|
||||
try {
|
||||
Storage::disk('private_uploads')->put($private_type.'/'.$filename, file_get_contents($public_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');
|
||||
@@ -65,7 +43,7 @@ class ObjectImportCommand extends Command
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$filename = $this->argument('filename');
|
||||
$class = title_case($this->option('item-type'));
|
||||
@@ -74,6 +52,7 @@ class ObjectImportCommand extends Command
|
||||
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
|
||||
->setUserId($this->option('user_id'))
|
||||
->setUpdating($this->option('update'))
|
||||
->setShouldNotify($this->option('send-welcome'))
|
||||
->setUsernameFormat($this->option('username_format'));
|
||||
|
||||
$logFile = $this->option('logfile');
|
||||
@@ -172,6 +151,7 @@ class ObjectImportCommand extends Command
|
||||
array('web-importer', null, InputOption::VALUE_NONE, 'Internal: packages output for use with the web importer'),
|
||||
array('user_id', null, InputOption::VALUE_REQUIRED, 'ID of user creating items', 1),
|
||||
array('update', null, InputOption::VALUE_NONE, 'If a matching item is found, update item information'),
|
||||
array('send-welcome', null, InputOption::VALUE_NONE, 'Whether to send a welcome email to any new users that are created.'),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -79,14 +79,24 @@ class PaveIt extends Command
|
||||
DB::statement('delete from accessories_users');
|
||||
DB::statement('delete from asset_logs');
|
||||
DB::statement('delete from asset_maintenances');
|
||||
DB::statement('delete from login_attempts');
|
||||
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');
|
||||
@@ -103,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');
|
||||
@@ -113,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
|
||||
{
|
||||
@@ -33,7 +30,7 @@ class Purge extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also reqrite history for users associated with deleted items.';
|
||||
protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also rewrite history for users associated with deleted items.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
|
||||
126
app/Console/Commands/ReEncodeCustomFieldNames.php
Normal file
126
app/Console/Commands/ReEncodeCustomFieldNames.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ReEncodeCustomFieldNames extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:regenerate-fieldnames';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This utility will regenerate the column names for custom fields. It should typically only be needed when a PHP upgrade changed the behavior of the unicode conversion between versions.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* All three of these things must match for the custom fields system to work as expected:
|
||||
*
|
||||
* - what the system thinks the output of $field->convertUnicodeDbSlug() is
|
||||
* - the actual db_column name in the customfields table
|
||||
* - the physical column name that was created on the assets table
|
||||
*
|
||||
* For some people who upgraded their version of PHP, the unicode converter now behaves
|
||||
* differently in than it did when their custom fields were first created, specifically as it
|
||||
* relates to handling slashes, ampersands, etc. This can result in the field names no longer
|
||||
* matching up, as an older version of the PHP extension simply dropped slashes, etc, while the
|
||||
* newer version of the PHP extension will convert them to underscores.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if ($this->confirm('This will regenerate all of the custom field database fieldnames in your database. THIS WILL CHANGE YOUR SCHEMA AND SHOULD NOT BE DONE WITHOUT MAKING A BACKUP FIRST. Do you wish to continue?'))
|
||||
{
|
||||
|
||||
/** Get all of the custom fields */
|
||||
$fields = CustomField::get();
|
||||
|
||||
$asset_columns = \DB::getSchemaBuilder()->getColumnListing('assets');
|
||||
$custom_field_columns = array();
|
||||
|
||||
/** Loop through the columns on the assets table */
|
||||
foreach ($asset_columns as $asset_column) {
|
||||
|
||||
/** Add ones that start with _snipeit_ to an array for handling */
|
||||
if (strpos($asset_column, '_snipeit_') === 0) {
|
||||
|
||||
/**
|
||||
* Get the ID of the custom field based on the fieldname.
|
||||
* For example, in _snipeit_mac_address_1, we grab the 1 because we know
|
||||
* that's the ID of the custom field that created the column.
|
||||
* Then use that ID as the array key for use comparing the actual assets field name
|
||||
* and the db_column value from the custom fields table.
|
||||
*/
|
||||
$last_part = substr(strrchr($asset_column, "_snipeit_"), 1);
|
||||
$custom_field_columns[$last_part] = $asset_column;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fields as $field) {
|
||||
|
||||
$this->info($field->name .' ('.$field->id.') column should be '. $field->convertUnicodeDbSlug().'');
|
||||
|
||||
/** The assets table has the column it should have, all is well */
|
||||
if (\Schema::hasColumn('assets', $field->convertUnicodeDbSlug()))
|
||||
{
|
||||
$this->info('-- ✓ This field exists - all good');
|
||||
|
||||
/**
|
||||
* There is a mismatch between the fieldname on the assets table and
|
||||
* what $field->convertUnicodeDbSlug() is *now* expecting.
|
||||
*/
|
||||
} else {
|
||||
$this->warn('-- X Field mismatch: updating... ');
|
||||
|
||||
/** Make sure the custom_field_columns array has the ID */
|
||||
if (array_key_exists($field->id, $custom_field_columns)) {
|
||||
|
||||
/**
|
||||
* Update the asset schema to the corrected fieldname that will be recognized by the
|
||||
* system elsewhere that we use $field->convertUnicodeDbSlug()
|
||||
*/
|
||||
\Schema::table('assets', function($table) use ($custom_field_columns, $field) {
|
||||
$table->renameColumn($custom_field_columns[$field->id], $field->convertUnicodeDbSlug());
|
||||
});
|
||||
|
||||
$this->warn('-- ✓ Field updated from '.$custom_field_columns[$field->id].' to '.$field->convertUnicodeDbSlug());
|
||||
|
||||
} else {
|
||||
$this->warn('-- X WARNING: There is no field on the assets table ending in '.$field->id.'. This may require more in-depth investigation and may mean the schema was altered manually.');
|
||||
}
|
||||
}
|
||||
|
||||
/** Update the db_column property in the custom fields table, just in case it doesn't match the other
|
||||
* things.
|
||||
*/
|
||||
$field->db_column = $field->convertUnicodeDbSlug();
|
||||
$field->save();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
104
app/Console/Commands/RegenerateAssetTags.php
Normal file
104
app/Console/Commands/RegenerateAssetTags.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Artisan;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RegenerateAssetTags extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:regenerate-tags {--start=} {--output= : info|warn|error|all} ';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This utility will regenerate all asset tags. THIS IS DATA-DESTRUCTIVE AND SHOULD BE USED WITH CAUTION. ';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if ($this->confirm('This will regenerate all of the asset tags within your system. This action is data-destructive and should be used with caution. Do you wish to continue?'))
|
||||
{
|
||||
|
||||
$output['info'] = [];
|
||||
$output['warn'] = [];
|
||||
$output['error'] = [];
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$start_tag = ($this->option('start')) ? $this->option('start') : (($settings->next_auto_tag_base) ? Setting::getSettings()->next_auto_tag_base : 1) ;
|
||||
|
||||
$this->info('Starting at '.$start_tag);
|
||||
|
||||
$total_assets = Asset::orderBy('id','asc')->get();
|
||||
$bar = $this->output->createProgressBar(count($total_assets));
|
||||
|
||||
try {
|
||||
Artisan::call('backup:run');
|
||||
} catch (\Exception $e) {
|
||||
$output['error'][] = $e;
|
||||
}
|
||||
|
||||
foreach ($total_assets as $asset) {
|
||||
|
||||
$start_tag++;
|
||||
$output['info'][] = 'Asset tag:'.$asset->asset_tag;
|
||||
$asset->asset_tag = $settings->auto_increment_prefix.$settings->auto_increment_prefix.$start_tag;
|
||||
|
||||
if ($settings->zerofill_count > 0) {
|
||||
$asset->asset_tag = $settings->auto_increment_prefix.Asset::zerofill($start_tag, $settings->zerofill_count);
|
||||
}
|
||||
|
||||
$output['info'][] = 'New Asset tag:'.$asset->asset_tag;
|
||||
|
||||
// Use forceSave here to override model level validation
|
||||
$asset->forceSave();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->info("\n");
|
||||
|
||||
|
||||
if (($this->option('output')=='all') || ($this->option('output')=='info')) {
|
||||
foreach ($output['info'] as $key => $output_text) {
|
||||
$this->info($output_text);
|
||||
}
|
||||
}
|
||||
if (($this->option('output')=='all') || ($this->option('output')=='warn')) {
|
||||
foreach ($output['warn'] as $key => $output_text) {
|
||||
$this->warn($output_text);
|
||||
}
|
||||
}
|
||||
if (($this->option('output')=='all') || ($this->option('output')=='error')) {
|
||||
foreach ($output['error'] as $key => $output_text) {
|
||||
$this->error($output_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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,10 +51,11 @@ 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';
|
||||
$settings->email_format = 'filastname';
|
||||
$settings->username_format = 'filastname';
|
||||
@@ -62,6 +63,20 @@ class ResetDemoSettings extends Command
|
||||
$settings->time_display_format = 'g:iA';
|
||||
$settings->thumbnail_max_h = '30';
|
||||
$settings->locale = 'en';
|
||||
$settings->version_footer = 'on';
|
||||
$settings->support_footer = 'on';
|
||||
$settings->saml_enabled = '0';
|
||||
$settings->saml_sp_entitiyid = '0';
|
||||
$settings->saml_sp_acs_url = null;
|
||||
$settings->saml_sp_sls_url = null;
|
||||
$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()) {
|
||||
|
||||
117
app/Console/Commands/RestoreDeletedUsers.php
Normal file
117
app/Console/Commands/RestoreDeletedUsers.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RestoreDeletedUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:restore-users {--start_date=} {--end_date=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Restore users, and any associated assets and license checkouts.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$start_date = $this->option('start_date');
|
||||
$end_date = $this->option('end_date');
|
||||
$asset_totals = 0;
|
||||
$license_totals = 0;
|
||||
$user_count = 0;
|
||||
|
||||
|
||||
if (($start_date=='') || ($end_date=='')) {
|
||||
$this->info('ERROR: All fields are required.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$users = User::whereBetween('deleted_at', [$start_date, $end_date])->withTrashed()->get();
|
||||
$this->info('There are '.$users->count().' users deleted between '.$start_date.' and '.$end_date);
|
||||
$this->warn('Making a backup!');
|
||||
Artisan::call('backup:run');
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user_count++;
|
||||
$user_logs = Actionlog::where('target_id', $user->id)->where('target_type',User::class)
|
||||
->where('action_type','checkout')->with('item')->get();
|
||||
|
||||
$this->info($user_count.'. '.$user->username.' ('.$user->id.') was deleted at '.$user->deleted_at. ' and has '.$user_logs->count().' checkouts associated.');
|
||||
|
||||
foreach ($user_logs as $user_log) {
|
||||
$this->info(' * '.$user_log->item_type.': '.$user_log->item->name.' - item_id: '.$user_log->item_id);
|
||||
|
||||
if ($user_log->item_type==Asset::class) {
|
||||
$asset_totals++;
|
||||
|
||||
DB::table('assets')
|
||||
->where('id', $user_log->item_id)
|
||||
->update(['assigned_to' => $user->id, 'assigned_type'=> User::class]);
|
||||
|
||||
$this->info(' ** Asset '.$user_log->item->id.' ('.$user_log->item->asset_tag.') restored to user '.$user->id.'');
|
||||
|
||||
} elseif ($user_log->item_type==License::class) {
|
||||
$license_totals++;
|
||||
|
||||
$avail_seat = DB::table('license_seats')->where('license_id','=',$user_log->item->id)
|
||||
->whereNull('assigned_to')->whereNull('asset_id')->whereBetween('updated_at', [$start_date, $end_date])->first();
|
||||
if ($avail_seat) {
|
||||
$this->info(' ** Allocating seat '.$avail_seat->id.' for this License');
|
||||
|
||||
DB::table('license_seats')
|
||||
->where('id', $avail_seat->id)
|
||||
->update(['assigned_to' => $user->id]);
|
||||
|
||||
} else {
|
||||
$this->warn('ERROR: No available seats for '.$user_log->item->name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->warn('Restoring user '.$user->username.'!');
|
||||
$user->restore();
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->info($asset_totals.' assets affected');
|
||||
$this->info($license_totals.' licenses affected');
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
135
app/Console/Commands/RotateAppKey.php
Normal file
135
app/Console/Commands/RotateAppKey.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Artisan;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use \Illuminate\Encryption\Encrypter;
|
||||
|
||||
class RotateAppKey extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:rotate-key';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* 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 MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ")) {
|
||||
|
||||
|
||||
|
||||
// Get the existing app_key and ciphers
|
||||
// We put them in a variable since we clear the cache partway through here.
|
||||
$old_app_key = config('app.key');
|
||||
$cipher = config('app.cipher');
|
||||
|
||||
// Generate a new one
|
||||
Artisan::call('key:generate', ['--show' => true]);
|
||||
$new_app_key = Artisan::output();
|
||||
|
||||
// Clear the config cache
|
||||
Artisan::call('config:clear');
|
||||
|
||||
$this->warn('Your app cipher is: '.$cipher);
|
||||
$this->warn('Your old APP_KEY is: '.$old_app_key);
|
||||
$this->warn('Your new APP_KEY is: '.$new_app_key);
|
||||
|
||||
// Write the new app key to the .env file
|
||||
$this->writeNewEnvironmentFileWith($new_app_key);
|
||||
|
||||
// Manually create an old encrypter instance using the old app key
|
||||
// and also create a new encrypter instance so we can re-crypt the field
|
||||
// using the newly generated app key
|
||||
$oldEncrypter = new Encrypter(base64_decode(substr($old_app_key, 7)), $cipher);
|
||||
$newEncrypter = new Encrypter(base64_decode(substr($new_app_key, 7)), $cipher);
|
||||
|
||||
$fields = CustomField::where('field_encrypted', '1')->get();
|
||||
|
||||
|
||||
foreach ($fields as $field) {
|
||||
|
||||
$assets = Asset::whereNotNull($field->db_column)->get();
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
|
||||
$this->line('DECRYPTED: '. $field->db_column);
|
||||
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
|
||||
$this->line('ENCRYPTED: '.$field->db_column);
|
||||
$asset->save();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle the LDAP password if one is provided
|
||||
$setting = Setting::first();
|
||||
if ($setting->ldap_pword!='') {
|
||||
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
|
||||
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
|
||||
$setting->save();
|
||||
$this->warn('LDAP password has been re-encrypted.');
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$this->info('This operation has been canceled. No changes have been made.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a new environment file with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
protected function writeNewEnvironmentFileWith($key)
|
||||
{
|
||||
|
||||
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
|
||||
$this->keyReplacementPattern(),
|
||||
'APP_KEY='.$key,
|
||||
file_get_contents($this->laravel->environmentFilePath())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a regex pattern that will match env APP_KEY with any random key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function keyReplacementPattern()
|
||||
{
|
||||
$escaped = preg_quote('='.$this->laravel['config']['app.key'], '/');
|
||||
return "/^APP_KEY{$escaped}/m";
|
||||
}
|
||||
|
||||
}
|
||||
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,15 +2,15 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\ExpectedCheckinAdminNotification;
|
||||
use App\Notifications\ExpectedCheckinNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendExpectedCheckinAlerts extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
@@ -27,8 +27,6 @@ class SendExpectedCheckinAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -40,25 +38,27 @@ class SendExpectedCheckinAlerts extends Command
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
$whenNotify = Carbon::now()->addDays(7);
|
||||
$assets = Asset::with('assignedTo')->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->assignedTo && $asset->checkoutOutToUser()) {
|
||||
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
|
||||
//$this->info($asset);
|
||||
if ($asset->assigned && $asset->checkedOutToUser()) {
|
||||
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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 AlertRecipient($item);
|
||||
});
|
||||
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +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\ExpiringAssetsNotification;
|
||||
use App\Notifications\ExpiringLicenseNotification;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendExpirationAlerts extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
@@ -28,8 +28,6 @@ class SendExpirationAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -41,97 +39,37 @@ class SendExpirationAlerts extends Command
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$threshold = $settings->alert_interval;
|
||||
|
||||
// Expiring Assets
|
||||
$expiring_assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
|
||||
$this->info(count($expiring_assets).' expiring assets');
|
||||
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
|
||||
|
||||
$asset_data['count'] = count($expiring_assets);
|
||||
$asset_data['email_content'] ='';
|
||||
$now = date("Y-m-d");
|
||||
|
||||
|
||||
foreach ($expiring_assets as $asset) {
|
||||
|
||||
$expires = $asset->present()->warrantee_expires();
|
||||
$difference = round(abs(strtotime($expires) - strtotime($now))/86400);
|
||||
|
||||
if ($difference > 30) {
|
||||
$asset_data['email_content'] .= '<tr style="background-color: #fcffa3;">';
|
||||
} else {
|
||||
$asset_data['email_content'] .= '<tr style="background-color:#d9534f;">';
|
||||
}
|
||||
$asset_data['email_content'] .= '<td><a href="'.config('app.url').'/hardware/'.e($asset->id).'/view">';
|
||||
$asset_data['email_content'] .= $asset->present()->name().'</a></td><td>'.e($asset->asset_tag).'</td>';
|
||||
$asset_data['email_content'] .= '<td>'.e($asset->present()->warrantee_expires()).'</td>';
|
||||
$asset_data['email_content'] .= '<td>'.$difference.' '.trans('mail.days').'</td>';
|
||||
$asset_data['email_content'] .= '<td>'.($asset->supplier ? e($asset->supplier->name) : '').'</td>';
|
||||
$asset_data['email_content'] .= '<td>'.($asset->assignedTo ? e($asset->assignedTo->present()->name()) : '').'</td>';
|
||||
$asset_data['email_content'] .= '</tr>';
|
||||
}
|
||||
|
||||
// Expiring licenses
|
||||
$expiring_licenses = License::getExpiringLicenses(Setting::getSettings()->alert_interval);
|
||||
$this->info(count($expiring_licenses).' expiring licenses');
|
||||
|
||||
|
||||
$license_data['count'] = count($expiring_licenses);
|
||||
$license_data['email_content'] = '';
|
||||
|
||||
foreach ($expiring_licenses as $license) {
|
||||
$expires = $license->expiration_date;
|
||||
$difference = round(abs(strtotime($expires) - strtotime($now))/86400);
|
||||
|
||||
if ($difference > 30) {
|
||||
$license_data['email_content'] .= '<tr style="background-color: #fcffa3;">';
|
||||
} else {
|
||||
$license_data['email_content'] .= '<tr style="background-color:#d9534f;">';
|
||||
}
|
||||
$license_data['email_content'] .= '<td><a href="'.route('licenses.show', $license->id).'">';
|
||||
$license_data['email_content'] .= $license->name.'</a></td>';
|
||||
$license_data['email_content'] .= '<td>'.$license->expiration_date.'</td>';
|
||||
$license_data['email_content'] .= '<td>'.$difference.' days</td>';
|
||||
$license_data['email_content'] .= '</tr>';
|
||||
}
|
||||
|
||||
if ((Setting::getSettings()->alert_email!='') && (Setting::getSettings()->alerts_enabled==1)) {
|
||||
|
||||
|
||||
if (count($expiring_assets) > 0) {
|
||||
$this->info('Report sent to '.Setting::getSettings()->alert_email);
|
||||
\Mail::send('emails.expiring-assets-report', $asset_data, function ($m) {
|
||||
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Expiring_Assets_Report'));
|
||||
});
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
// Expiring Assets
|
||||
$assets = Asset::getExpiringWarrantee($threshold);
|
||||
if ($assets->count() > 0) {
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
|
||||
Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
|
||||
}
|
||||
|
||||
if (count($expiring_licenses) > 0) {
|
||||
$this->info('Report sent to '.Setting::getSettings()->alert_email);
|
||||
\Mail::send('emails.expiring-licenses-report', $license_data, function ($m) {
|
||||
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Expiring_Licenses_Report'));
|
||||
});
|
||||
|
||||
// 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 (Setting::getSettings()->alert_email=='') {
|
||||
echo "Could not send email. No alert email configured in settings. \n";
|
||||
} elseif (Setting::getSettings()->alerts_enabled!=1) {
|
||||
echo "Alerts are disabled in the settings. No mail will be sent. \n";
|
||||
if ($settings->alert_email == '') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif (1 != $settings->alerts_enabled) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +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
|
||||
{
|
||||
@@ -27,8 +27,6 @@ class SendInventoryAlerts extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -42,28 +40,26 @@ class SendInventoryAlerts extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ((Setting::getSettings()->alert_email!='') && (Setting::getSettings()->alerts_enabled==1)) {
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$data['data'] = Helper::checkLowInventory();
|
||||
$data['count'] = count($data['data']);
|
||||
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
|
||||
$items = Helper::checkLowInventory();
|
||||
|
||||
if (count($data['data']) > 0) {
|
||||
\Mail::send('emails.low-inventory', $data, function ($m) {
|
||||
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Low_Inventory_Report'));
|
||||
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 AlertRecipient($item);
|
||||
});
|
||||
|
||||
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (Setting::getSettings()->alert_email=='') {
|
||||
echo "Could not send email. No alert email configured in settings. \n";
|
||||
} elseif (Setting::getSettings()->alerts_enabled!=1) {
|
||||
echo "Alerts are disabled in the settings. No mail will be sent. \n";
|
||||
if ($settings->alert_email == '') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif (1 != $settings->alerts_enabled) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
91
app/Console/Commands/SendUpcomingAuditReport.php
Normal file
91
app/Console/Commands/SendUpcomingAuditReport.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\ExpiringAssetsNotification;
|
||||
use App\Models\Recipients;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Notifications\SendUpcomingAuditNotification;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SendUpcomingAuditReport extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:upcoming-audits';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send email/slack notifications for upcoming asset audits.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($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);
|
||||
});
|
||||
|
||||
|
||||
// Assets due for auditing
|
||||
|
||||
$assets = Asset::whereNotNull('next_audit_date')
|
||||
->DueOrOverdueForAudit($settings)
|
||||
->orderBy('last_audit_date', 'asc')->get();
|
||||
|
||||
if ($assets->count() > 0) {
|
||||
|
||||
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
|
||||
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
|
||||
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
|
||||
$this->info('Audit report sent to '.$settings->alert_email);
|
||||
} else {
|
||||
$this->info('No assets to be audited. No report sent.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
} elseif ($settings->alert_email=='') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif (!$settings->audit_warning_days) {
|
||||
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
|
||||
} elseif ($settings->alerts_enabled!=1) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
} else {
|
||||
$this->error('Something went wrong. :( ');
|
||||
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
|
||||
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
|
||||
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
76
app/Console/Commands/SyncAssetCounters.php
Normal file
76
app/Console/Commands/SyncAssetCounters.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncAssetCounters extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:counter-sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Syncs checkedout, checked in, and requested counters for assets';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$start = microtime(true);
|
||||
$assets = Asset::withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')
|
||||
->withTrashed()->get();
|
||||
|
||||
if ($assets) {
|
||||
if ($assets->count() > 0) {
|
||||
$bar = $this->output->createProgressBar($assets->count());
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$asset->checkin_counter = (int) $asset->checkins_count;
|
||||
$asset->checkout_counter = (int) $asset->checkouts_count;
|
||||
$asset->requests_counter = (int) $asset->user_requests_count;
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->save();
|
||||
$output['info'][] = 'Asset: ' . $asset->id . ' has ' . $asset->checkin_counter . ' checkins, ' . $asset->checkout_counter . ' checkouts, and ' . $asset->requests_counter . ' requests';
|
||||
$bar->advance();
|
||||
}
|
||||
$bar->finish();
|
||||
|
||||
foreach ($output['info'] as $key => $output_text) {
|
||||
$this->info($output_text);
|
||||
}
|
||||
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
$this->info('Sync executed in ' . $time_elapsed_secs . ' seconds');
|
||||
|
||||
} else {
|
||||
$this->info('No assets to sync');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -64,7 +62,7 @@ class SyncAssetLocations extends Command
|
||||
$output['info'][] = 'There are '.$assigned_user_assets->count().' assets checked out to users.';
|
||||
foreach ($assigned_user_assets as $assigned_user_asset) {
|
||||
if (($assigned_user_asset->assignedTo) && ($assigned_user_asset->assignedTo->userLoc)) {
|
||||
$new_location=$assigned_user_asset->assignedTo->userloc->id;
|
||||
$new_location = $assigned_user_asset->assignedTo->userLoc->id;
|
||||
$output['info'][] ='Setting User Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') to ' . $assigned_user_asset->assignedTo->userLoc->name . ' which is id: ' . $new_location;
|
||||
} else {
|
||||
$output['warn'][] ='Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') still has no location! ';
|
||||
@@ -109,7 +107,7 @@ class SyncAssetLocations extends Command
|
||||
$assigned_asset_asset->unsetEventDispatcher();
|
||||
$assigned_asset_asset->save();
|
||||
} else {
|
||||
$output['warn'][] ='Asset Assigned asset ' . $assigned_asset_asset->assetLoc()->id. ' ('.$assigned_asset_asset->asset_tag.') does not seem to have a valid location';
|
||||
$output['warn'][] ='Asset Assigned asset ' . $assigned_asset_asset->id. ' ('.$assigned_asset_asset->asset_tag.') does not seem to have a valid location';
|
||||
}
|
||||
|
||||
$bar->advance();
|
||||
|
||||
@@ -36,7 +36,7 @@ class SystemBackup extends Command
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->call('backup:run');
|
||||
|
||||
135
app/Console/Commands/Version.php
Normal file
135
app/Console/Commands/Version.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Version extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'version:update {--branch=master} {--type=patch}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$use_branch = $this->option('branch');
|
||||
$use_type = $this->option('type');
|
||||
$git_branch = trim(shell_exec('git rev-parse --abbrev-ref HEAD'));
|
||||
$build_version = trim(shell_exec('git rev-list --count '.$use_branch));
|
||||
$versionFile = 'config/version.php';
|
||||
$full_hash_version = str_replace("\n", '', shell_exec('git describe master --tags'));
|
||||
|
||||
$version = explode('-', $full_hash_version);
|
||||
$app_version = $current_app_version = $version[0];
|
||||
$hash_version = (array_key_exists('2', $version)) ? $version[2] : '';
|
||||
$prerelease_version = '';
|
||||
|
||||
$this->line('Branch is: '.$use_branch);
|
||||
$this->line('Type is: '.$use_type);
|
||||
$this->line('Current version is: '.$full_hash_version);
|
||||
|
||||
if (count($version)==3) {
|
||||
$this->line('This does not look like an alpha/beta release.');
|
||||
} else {
|
||||
if (array_key_exists('3',$version)) {
|
||||
$this->line('The current version looks like a beta release.');
|
||||
$prerelease_version = $version[1];
|
||||
$hash_version = $version[3];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$app_version_raw = explode('.', $app_version);
|
||||
|
||||
$maj = str_replace('v', '', $app_version_raw[0]);
|
||||
$min = $app_version_raw[1];
|
||||
$patch = '';
|
||||
|
||||
|
||||
|
||||
// This is a major release that might not have a third .0
|
||||
if (array_key_exists(2, $app_version_raw)) {
|
||||
$patch = $app_version_raw[2];
|
||||
}
|
||||
|
||||
if ($use_type=='major') {
|
||||
$app_version = "v".($maj + 1).".$min.$patch";
|
||||
} elseif ($use_type=='minor') {
|
||||
$app_version = "v"."$maj.".($min + 1).".$patch";
|
||||
} elseif ($use_type=='pre') {
|
||||
$pre_raw = str_replace('beta','', $prerelease_version);
|
||||
$pre_raw = str_replace('alpha','', $pre_raw);
|
||||
$pre_raw = str_ireplace('rc','', $pre_raw);
|
||||
$pre_raw = $pre_raw++;
|
||||
$this->line('Setting the pre-release to '. $prerelease_version.'-'.$pre_raw);
|
||||
$app_version = "v"."$maj.".($min + 1).".$patch";
|
||||
} elseif ($use_type=='patch') {
|
||||
$app_version = "v" . "$maj.$min." . ($patch + 1);
|
||||
// If nothing is passed, leave the version as it is, just increment the build
|
||||
} else {
|
||||
$app_version = "v" . "$maj.$min." . $patch;
|
||||
}
|
||||
|
||||
// Determine if this tag already exists, or if this prior to a release
|
||||
$this->line('Running: git rev-parse master '.$current_app_version);
|
||||
// $pre_release = trim(shell_exec('git rev-parse '.$use_branch.' '.$current_app_version.' 2>&1 1> /dev/null'));
|
||||
|
||||
if ($use_branch=='develop') {
|
||||
$app_version = $app_version.'-pre';
|
||||
}
|
||||
|
||||
$full_app_version = $app_version.' - build '.$build_version.'-'.$hash_version;
|
||||
|
||||
|
||||
$array = var_export(
|
||||
array(
|
||||
'app_version' => $app_version,
|
||||
'full_app_version' => $full_app_version,
|
||||
'build_version' => $build_version,
|
||||
'prerelease_version' => $prerelease_version,
|
||||
'hash_version' => $hash_version,
|
||||
'full_hash' => $full_hash_version,
|
||||
'branch' => $git_branch),
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Construct our file content
|
||||
$content = <<<CON
|
||||
<?php
|
||||
return $array;
|
||||
CON;
|
||||
|
||||
// And finally write the file and output the current version
|
||||
\File::put($versionFile, $content);
|
||||
$this->info('Setting NEW version: '. $full_app_version.' ('.$git_branch.')');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Versioning extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'versioning:update';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate and update app\'s version via git.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fire()
|
||||
{
|
||||
|
||||
$versionFile = 'config/version.php';
|
||||
$hash_version = str_replace("\n", '', shell_exec('git describe --tags'));
|
||||
|
||||
$version = explode('-', $hash_version);
|
||||
|
||||
$array = var_export(
|
||||
array(
|
||||
'app_version' => $version[0],
|
||||
'build_version' => $version[1],
|
||||
'hash_version' => $version[2],
|
||||
'full_hash' => $hash_version),
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
// Construct our file content
|
||||
$content = <<<CON
|
||||
<?php
|
||||
return $array;
|
||||
CON;
|
||||
|
||||
// And finally write the file and output the current version
|
||||
\File::put($versionFile, $content);
|
||||
$this->line('Setting version: '. config('version.app_version').' build '.config('version.build_version').' ('.config('version.hash_version').')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArguments()
|
||||
{
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return array(
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,33 +2,14 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\ImportLocations;
|
||||
use App\Console\Commands\ReEncodeCustomFieldNames;
|
||||
use App\Console\Commands\RestoreDeletedUsers;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
Commands\PaveIt::class,
|
||||
Commands\CreateAdmin::class,
|
||||
Commands\SendExpirationAlerts::class,
|
||||
Commands\SendInventoryAlerts::class,
|
||||
Commands\SendExpectedCheckinAlerts::class,
|
||||
Commands\ObjectImportCommand::class,
|
||||
Commands\Versioning::class,
|
||||
Commands\SystemBackup::class,
|
||||
Commands\DisableLDAP::class,
|
||||
Commands\Purge::class,
|
||||
Commands\LdapSync::class,
|
||||
Commands\FixDoubleEscape::class,
|
||||
Commands\RecryptFromMcrypt::class,
|
||||
Commands\ResetDemoSettings::class,
|
||||
Commands\SyncAssetLocations::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
@@ -38,16 +19,21 @@ 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();
|
||||
$schedule->command('snipeit:backup')->weekly();
|
||||
$schedule->command('backup:clean')->daily();
|
||||
$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');
|
||||
$this->load(__DIR__.'/Commands');
|
||||
}
|
||||
}
|
||||
|
||||
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,10 +3,20 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CheckoutNotAllowed extends Exception
|
||||
{
|
||||
|
||||
private $errorMessage;
|
||||
|
||||
function __construct($errorMessage = null)
|
||||
{
|
||||
$this->errorMessage = $errorMessage;
|
||||
|
||||
parent::__construct($errorMessage);
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return "A checkout is not allowed under these circumstances";
|
||||
return is_null($this->errorMessage) ? "A checkout is not allowed under these circumstances" : $this->errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Helpers\Helper;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Log;
|
||||
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
@@ -23,6 +24,7 @@ class Handler extends ExceptionHandler
|
||||
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
|
||||
\Illuminate\Session\TokenMismatchException::class,
|
||||
\Illuminate\Validation\ValidationException::class,
|
||||
\Intervention\Image\Exception\NotSupportedException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -66,10 +68,6 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
|
||||
}
|
||||
|
||||
if ($e instanceof \Illuminate\Validation\ValidationException) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $e->response['messages'], 400));
|
||||
}
|
||||
|
||||
if ($this->isHttpException($e)) {
|
||||
|
||||
$statusCode = $e->getStatusCode();
|
||||
@@ -84,11 +82,6 @@ class Handler extends ExceptionHandler
|
||||
|
||||
}
|
||||
}
|
||||
// Try to parse 500 Errors in a bit nicer way when debug is enabled.
|
||||
if (config('app.debug')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -117,4 +110,16 @@ class Handler extends ExceptionHandler
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a validation exception into a JSON response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Validation\ValidationException $exception
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
protected function invalidJson($request, ValidationException $exception)
|
||||
{
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors(), 400));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -127,99 +118,16 @@ class Helper
|
||||
$floatString = str_replace(",", "", $floatString);
|
||||
$floatString = str_replace($LocaleInfo["decimal_point"], ".", $floatString);
|
||||
// Strip Currency symbol
|
||||
$floatString = str_replace($LocaleInfo['currency_symbol'], '', $floatString);
|
||||
// If no currency symbol is set, default to $ because Murica
|
||||
$currencySymbol = $LocaleInfo['currency_symbol'];
|
||||
if (empty($currencySymbol)) {
|
||||
$currencySymbol = '$';
|
||||
}
|
||||
|
||||
$floatString = str_replace($currencySymbol, '', $floatString);
|
||||
return floatval($floatString);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of models in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function modelList()
|
||||
{
|
||||
$models = AssetModel::with('manufacturer')->get();
|
||||
$model_array[''] = trans('general.select_model');
|
||||
foreach ($models as $model) {
|
||||
$model_array[$model->id] = $model->present()->modelName();
|
||||
}
|
||||
return $model_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of companies in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function companyList()
|
||||
{
|
||||
$company_list = array('' => trans('general.select_company')) + DB::table('companies')
|
||||
->orderBy('name', 'asc')
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
return $company_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of categories in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function categoryList($category_type = null)
|
||||
{
|
||||
$categories = Category::orderBy('name', 'asc')
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('name', 'asc');
|
||||
if (!empty($category_type)) {
|
||||
$categories = $categories->where('category_type', '=', $category_type);
|
||||
}
|
||||
$category_list = array('' => trans('general.select_category')) + $categories->pluck('name', 'id')->toArray();
|
||||
return $category_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of categories in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function departmentList()
|
||||
{
|
||||
$departments = Department::orderBy('name', 'asc')
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('name', 'asc');
|
||||
|
||||
return array('' => trans('general.select_department')) + $departments->pluck('name', 'id')->toArray();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of suppliers in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function suppliersList()
|
||||
{
|
||||
$supplier_list = array('' => trans('general.select_supplier')) + Supplier::orderBy('name', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $supplier_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of status labels in an array to make a dropdown menu
|
||||
*
|
||||
@@ -229,42 +137,11 @@ class Helper
|
||||
*/
|
||||
public static function statusLabelList()
|
||||
{
|
||||
$statuslabel_list = array('' => trans('general.select_statuslabel')) + Statuslabel::orderBy('deployable', 'desc')
|
||||
$statuslabel_list = array('' => trans('general.select_statuslabel')) + Statuslabel::orderBy('default_label', 'desc')->orderBy('name','asc')->orderBy('deployable','desc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $statuslabel_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of locations in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function locationsList()
|
||||
{
|
||||
$location_list = array('' => trans('general.select_location')) + Location::orderBy('name', 'asc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $location_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of manufacturers in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function manufacturerList()
|
||||
{
|
||||
$manufacturer_list = array('' => trans('general.select_manufacturer')) +
|
||||
Manufacturer::orderBy('name', 'asc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $manufacturer_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of status label types in an array to make a dropdown menu
|
||||
*
|
||||
@@ -283,24 +160,6 @@ class Helper
|
||||
return $statuslabel_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of managers in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function managerList()
|
||||
{
|
||||
$manager_list = array('' => trans('general.select_user')) +
|
||||
User::where('deleted_at', '=', null)
|
||||
->orderBy('last_name', 'asc')
|
||||
->orderBy('first_name', 'asc')->get()
|
||||
->pluck('complete_name', 'id')->toArray();
|
||||
|
||||
return $manager_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of depreciations in an array to make a dropdown menu
|
||||
*
|
||||
@@ -324,58 +183,17 @@ class Helper
|
||||
*/
|
||||
public static function categoryTypeList()
|
||||
{
|
||||
$category_types = array('' => '','accessory' => 'Accessory', 'asset' => 'Asset', 'consumable' => 'Consumable','component' => 'Component');
|
||||
$category_types = array(
|
||||
'' => '',
|
||||
'accessory' => 'Accessory',
|
||||
'asset' => 'Asset',
|
||||
'consumable' => 'Consumable',
|
||||
'component' => 'Component',
|
||||
'license' => 'License'
|
||||
);
|
||||
return $category_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of users in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function usersList()
|
||||
{
|
||||
$users_list = array( '' => trans('general.select_user')) +
|
||||
Company::scopeCompanyables(User::where('deleted_at', '=', null))
|
||||
->where('show_in_list', '=', 1)
|
||||
->orderBy('last_name', 'asc')
|
||||
->orderBy('first_name', 'asc')->get()
|
||||
->pluck('complete_name', 'id')->toArray();
|
||||
|
||||
return $users_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of assets in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return Array
|
||||
*/
|
||||
public static function assetsList()
|
||||
{
|
||||
$assets_list = array('' => trans('general.select_asset')) + Asset::orderBy('name', 'asc')
|
||||
->whereNull('deleted_at')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $assets_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the detailed list of assets in an array to make a dropdown menu
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.5]
|
||||
* @return array
|
||||
*/
|
||||
public static function detailedAssetList()
|
||||
{
|
||||
$assets = array('' => trans('general.select_asset')) + Company::scopeCompanyables(Asset::with('assignedTo', 'model'), 'assets.company_id')->get()->pluck('detailed_name', 'id')->toArray();
|
||||
return $assets;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of custom fields in an array to make a dropdown menu
|
||||
*
|
||||
@@ -398,9 +216,10 @@ 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+["" => trans('admin/custom_fields/general.custom_format')];
|
||||
|
||||
return $stuff;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,9 +273,9 @@ class Helper
|
||||
*/
|
||||
public static function checkLowInventory()
|
||||
{
|
||||
$consumables = Consumable::with('users')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::with('users')->whereNotNull('min_amt')->get();
|
||||
$components = Component::with('assets')->whereNotNull('min_amt')->get();
|
||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::withCount('assets as assets_count')->whereNotNull('min_amt')->get();
|
||||
|
||||
$avail_consumables = 0;
|
||||
$items_array = array();
|
||||
@@ -466,7 +285,7 @@ class Helper
|
||||
$avail = $consumable->numRemaining();
|
||||
if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($consumable->qty > 0) {
|
||||
$percent = number_format((($consumable->numRemaining() / $consumable->qty) * 100), 0);
|
||||
$percent = number_format((($avail / $consumable->qty) * 100), 0);
|
||||
} else {
|
||||
$percent = 100;
|
||||
}
|
||||
@@ -475,7 +294,7 @@ class Helper
|
||||
$items_array[$all_count]['name'] = $consumable->name;
|
||||
$items_array[$all_count]['type'] = 'consumables';
|
||||
$items_array[$all_count]['percent'] = $percent;
|
||||
$items_array[$all_count]['remaining']=$consumable->numRemaining();
|
||||
$items_array[$all_count]['remaining'] = $avail;
|
||||
$items_array[$all_count]['min_amt']=$consumable->min_amt;
|
||||
$all_count++;
|
||||
}
|
||||
@@ -484,11 +303,11 @@ class Helper
|
||||
}
|
||||
|
||||
foreach ($accessories as $accessory) {
|
||||
$avail = $accessory->numRemaining();
|
||||
$avail = $accessory->qty - $accessory->users_count;
|
||||
if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
|
||||
if ($accessory->qty > 0) {
|
||||
$percent = number_format((($accessory->numRemaining() / $accessory->qty) * 100), 0);
|
||||
$percent = number_format((($avail / $accessory->qty) * 100), 0);
|
||||
} else {
|
||||
$percent = 100;
|
||||
}
|
||||
@@ -497,7 +316,7 @@ class Helper
|
||||
$items_array[$all_count]['name'] = $accessory->name;
|
||||
$items_array[$all_count]['type'] = 'accessories';
|
||||
$items_array[$all_count]['percent'] = $percent;
|
||||
$items_array[$all_count]['remaining']=$accessory->numRemaining();
|
||||
$items_array[$all_count]['remaining'] = $avail;
|
||||
$items_array[$all_count]['min_amt']=$accessory->min_amt;
|
||||
$all_count++;
|
||||
}
|
||||
@@ -505,10 +324,10 @@ class Helper
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$avail = $component->numRemaining();
|
||||
$avail = $component->qty - $component->assets_count;
|
||||
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($component->qty > 0) {
|
||||
$percent = number_format((($component->numRemaining() / $component->qty) * 100), 0);
|
||||
$percent = number_format((($avail / $component->qty) * 100), 0);
|
||||
} else {
|
||||
$percent = 100;
|
||||
}
|
||||
@@ -517,7 +336,7 @@ class Helper
|
||||
$items_array[$all_count]['name'] = $component->name;
|
||||
$items_array[$all_count]['type'] = 'components';
|
||||
$items_array[$all_count]['percent'] = $percent;
|
||||
$items_array[$all_count]['remaining']=$component->numRemaining();
|
||||
$items_array[$all_count]['remaining'] = $avail;
|
||||
$items_array[$all_count]['min_amt']=$component->min_amt;
|
||||
$all_count++;
|
||||
}
|
||||
@@ -684,7 +503,7 @@ class Helper
|
||||
|
||||
$array['status'] = $status;
|
||||
$array['messages'] = $messages;
|
||||
if (($messages) && (count($messages) > 0)) {
|
||||
if (($messages) && (is_array($messages)) && (count($messages) > 0)) {
|
||||
$array['messages'] = $messages;
|
||||
}
|
||||
($payload) ? $array['payload'] = $payload : $array['payload'] = null;
|
||||
@@ -725,5 +544,191 @@ class Helper
|
||||
}
|
||||
|
||||
|
||||
// Nicked from Drupal :)
|
||||
// Returns a file size limit in bytes based on the PHP upload_max_filesize
|
||||
// and post_max_size
|
||||
public static function file_upload_max_size() {
|
||||
static $max_size = -1;
|
||||
|
||||
if ($max_size < 0) {
|
||||
|
||||
// Start with post_max_size.
|
||||
$post_max_size = Helper::parse_size(ini_get('post_max_size'));
|
||||
if ($post_max_size > 0) {
|
||||
$max_size = $post_max_size;
|
||||
}
|
||||
|
||||
// If upload_max_size is less, then reduce. Except if upload_max_size is
|
||||
// zero, which indicates no limit.
|
||||
$upload_max = Helper::parse_size(ini_get('upload_max_filesize'));
|
||||
if ($upload_max > 0 && $upload_max < $max_size) {
|
||||
$max_size = $upload_max;
|
||||
}
|
||||
}
|
||||
|
||||
return $max_size;
|
||||
}
|
||||
|
||||
public static function file_upload_max_size_readable() {
|
||||
static $max_size = -1;
|
||||
|
||||
if ($max_size < 0) {
|
||||
// Start with post_max_size.
|
||||
$post_max_size = Helper::parse_size(ini_get('post_max_size'));
|
||||
if ($post_max_size > 0) {
|
||||
$max_size = ini_get('post_max_size');
|
||||
}
|
||||
|
||||
// If upload_max_size is less, then reduce. Except if upload_max_size is
|
||||
// zero, which indicates no limit.
|
||||
$upload_max = Helper::parse_size(ini_get('upload_max_filesize'));
|
||||
if ($upload_max > 0 && $upload_max < $max_size) {
|
||||
$max_size = ini_get('upload_max_filesize');
|
||||
}
|
||||
}
|
||||
return $max_size;
|
||||
}
|
||||
|
||||
public static function parse_size($size) {
|
||||
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
|
||||
$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
|
||||
if ($unit) {
|
||||
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
|
||||
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
||||
}
|
||||
else {
|
||||
return round($size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function filetype_icon($filename) {
|
||||
|
||||
$extension = substr(strrchr($filename,'.'),1);
|
||||
|
||||
$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";
|
||||
}
|
||||
|
||||
public static function show_file_inline($filename) {
|
||||
|
||||
$extension = substr(strrchr($filename,'.'),1);
|
||||
|
||||
if ($extension) {
|
||||
switch ($extension) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'gif':
|
||||
case 'png':
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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,88 @@
|
||||
<?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')
|
||||
]);
|
||||
|
||||
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,414 +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 Mail;
|
||||
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');
|
||||
|
||||
if ($request->hasFile('image')) {
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
$image = $request->file('image');
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = "accessory-".str_random(18).'.'.$ext;
|
||||
$path = public_path('/uploads/accessories');
|
||||
if ($image->getClientOriginalExtension()!='svg') {
|
||||
Image::make($image->getRealPath())->resize(null, 250, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path.'/'.$file_name);
|
||||
} else {
|
||||
$image->move($path, $file_name);
|
||||
}
|
||||
$accessory->image = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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');
|
||||
|
||||
if ($request->hasFile('image')) {
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
|
||||
$image = $request->file('image');
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = "accessory-".str_random(18).'.'.$ext;
|
||||
$path = public_path('/uploads/accessories');
|
||||
if ($image->getClientOriginalExtension()!='svg') {
|
||||
Image::make($image->getRealPath())->resize(null, 250, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path.'/'.$file_name);
|
||||
} else {
|
||||
$image->move($path, $file_name);
|
||||
}
|
||||
if (($accessory->image) && (file_exists($path.'/'.$accessory->image))) {
|
||||
unlink($path.'/'.$accessory->image);
|
||||
}
|
||||
|
||||
$accessory->image = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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', compact('id')));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
// Get the dropdown of users and then pass it to the checkout view
|
||||
return view('accessories/checkout', compact('accessory'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// TODO: Port this to new mail notifications
|
||||
|
||||
if ((($accessory->requireAcceptance()=='1') || ($accessory->getEula())) && ($user->email!='')) {
|
||||
|
||||
Mail::send('emails.accept-accessory', $data, function ($m) use ($user) {
|
||||
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Confirm_accessory_delivery'));
|
||||
});
|
||||
}
|
||||
|
||||
// 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.not_found'));
|
||||
}
|
||||
|
||||
$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['item_name'] = e($accessory->name);
|
||||
$data['checkin_date'] = e($logaction->created_at);
|
||||
$data['item_tag'] = '';
|
||||
$data['note'] = e($logaction->note);
|
||||
|
||||
if ((($accessory->checkin_email()=='1')) && ($user->email!='')) {
|
||||
|
||||
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
|
||||
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Confirm_Accessory_Checkin'));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
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,13 +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
|
||||
{
|
||||
@@ -24,30 +28,36 @@ class AccessoriesController extends Controller
|
||||
$this->authorize('view', Accessory::class);
|
||||
$allowed_columns = ['id','name','model_number','eol','notes','created_at','min_amt','company_id'];
|
||||
|
||||
$accessories = Accessory::whereNull('accessories.deleted_at')->with('category', 'company', 'manufacturer', 'users', 'location');
|
||||
$accessories = Accessory::with('category', 'company', 'manufacturer', 'users', 'location');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$accessories = $accessories->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->has('company_id')) {
|
||||
if ($request->filled('company_id')) {
|
||||
$accessories->where('company_id','=',$request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->has('category_id')) {
|
||||
if ($request->filled('category_id')) {
|
||||
$accessories->where('category_id','=',$request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->has('manufacturer_id')) {
|
||||
if ($request->filled('manufacturer_id')) {
|
||||
$accessories->where('manufacturer_id','=',$request->input('manufacturer_id'));
|
||||
}
|
||||
|
||||
if ($request->has('supplier_id')) {
|
||||
if ($request->filled('supplier_id')) {
|
||||
$accessories->where('supplier_id','=',$request->input('supplier_id'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->count() : $request->get('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');
|
||||
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
|
||||
@@ -132,18 +142,37 @@ 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->lastCheckoutArray = $accessory->lastCheckout->toArray();
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +187,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());
|
||||
|
||||
@@ -191,4 +220,121 @@ class AccessoriesController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success')));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 checkout(Request $request, $accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
|
||||
if ($accessory->numRemaining() > 0) {
|
||||
|
||||
if (!$user = User::find($request->input('assigned_to'))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->assigned_to = $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')
|
||||
]);
|
||||
|
||||
$accessory->logCheckout($request->input('note'), $user);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 checkin(Request $request, $accessoryUserId = null)
|
||||
{
|
||||
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
}
|
||||
|
||||
$accessory = Accessory::find($accessory_user->accessory_id);
|
||||
$this->authorize('checkin', $accessory);
|
||||
|
||||
$logaction = $accessory->logCheckin(User::find($accessoryUserId), $request->input('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'] = $user->first_name;
|
||||
$data['last_name'] = $user->last_name;
|
||||
$data['item_name'] = $accessory->name;
|
||||
$data['checkin_date'] = $logaction->created_at;
|
||||
$data['item_tag'] = '';
|
||||
$data['note'] = $logaction->note;
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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,23 +33,50 @@ 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'));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
if ($request->filled('asset_id')) {
|
||||
$maintenances->where('asset_id', '=', $request->input('asset_id'));
|
||||
}
|
||||
|
||||
$allowed_columns = ['id','title','asset_maintenance_time','asset_maintenance_type','cost','start_date','completion_date','notes','user_id'];
|
||||
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
// 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 = (($maintenances) && ($request->get('offset') > $maintenances->count())) ? $maintenances->count() : $request->get('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');
|
||||
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'title',
|
||||
'asset_maintenance_time',
|
||||
'asset_maintenance_type',
|
||||
'cost',
|
||||
'start_date',
|
||||
'completion_date',
|
||||
'notes',
|
||||
'asset_tag',
|
||||
'asset_name',
|
||||
'user_id'
|
||||
];
|
||||
$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':
|
||||
$maintenances = $maintenances->OrderAdmin($order);
|
||||
break;
|
||||
case 'asset_tag':
|
||||
$maintenances = $maintenances->OrderByTag($order);
|
||||
break;
|
||||
case 'asset_name':
|
||||
$maintenances = $maintenances->OrderByAssetName($order);
|
||||
break;
|
||||
default:
|
||||
$maintenances = $maintenances->orderBy($sort, $order);
|
||||
break;
|
||||
|
||||
@@ -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,23 +30,44 @@ 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'];
|
||||
$allowed_columns = ['id','image','name','model_number','eol','notes','created_at','manufacturer','requestable', 'assets_count'];
|
||||
|
||||
$assetmodels = AssetModel::select(['models.id','models.image','models.name','model_number','eol','models.notes','models.created_at','category_id','manufacturer_id','depreciation_id','fieldset_id', 'models.deleted_at'])
|
||||
$assetmodels = AssetModel::select([
|
||||
'models.id',
|
||||
'models.image',
|
||||
'models.name',
|
||||
'model_number',
|
||||
'eol',
|
||||
'requestable',
|
||||
'models.notes',
|
||||
'models.created_at',
|
||||
'category_id',
|
||||
'manufacturer_id',
|
||||
'depreciation_id',
|
||||
'fieldset_id',
|
||||
'models.deleted_at',
|
||||
'models.updated_at',
|
||||
])
|
||||
->with('category','depreciation', 'manufacturer','fieldset')
|
||||
->withCount('assets');
|
||||
->withCount('assets as assets_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
$assetmodels->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->has('status')) {
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$assetmodels->onlyTrashed();
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$assetmodels->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 = (($assetmodels) && ($request->get('offset') > $assetmodels->count())) ? $assetmodels->count() : $request->get('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');
|
||||
|
||||
$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') : 'models.created_at';
|
||||
|
||||
@@ -60,6 +81,7 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
|
||||
$total = $assetmodels->count();
|
||||
$assetmodels = $assetmodels->skip($offset)->take($limit)->get();
|
||||
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
|
||||
@@ -98,7 +120,7 @@ class AssetModelsController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', AssetModel::class);
|
||||
$assetmodel = AssetModel::withCount('assets')->findOrFail($id);
|
||||
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
|
||||
return (new AssetModelsTransformer)->transformAssetModel($assetmodel);
|
||||
}
|
||||
|
||||
@@ -129,13 +151,25 @@ class AssetModelsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', AssetModel::class);
|
||||
$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/assetmodels/message.update.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
|
||||
@@ -161,14 +195,14 @@ 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::error($e);
|
||||
\Log::info($e);
|
||||
}
|
||||
}
|
||||
|
||||
$assetmodel->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/assetmodels/message.delete.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.delete.success')));
|
||||
|
||||
}
|
||||
|
||||
@@ -188,18 +222,37 @@ class AssetModelsController extends Controller
|
||||
'models.name',
|
||||
'models.image',
|
||||
'models.model_number',
|
||||
])->with('manufacturer');
|
||||
'models.manufacturer_id',
|
||||
'models.category_id',
|
||||
])->with('manufacturer','category');
|
||||
|
||||
$settings = \App\Models\Setting::getSettings();
|
||||
|
||||
if ($request->has('search')) {
|
||||
$assetmodels = $assetmodels->where('models.name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('models.model_number', 'LIKE', '%'.$request->get('search').'%');
|
||||
if ($request->filled('search')) {
|
||||
$assetmodels = $assetmodels->SearchByManufacturerOrCat($request->input('search'));
|
||||
}
|
||||
$assetmodels = $assetmodels->orderby('models.name', 'asc')->orderby('models.model_number', 'asc')->paginate(50);
|
||||
|
||||
$assetmodels = $assetmodels->OrderCategory('ASC')->OrderManufacturer('ASC')->orderby('models.name', 'asc')->orderby('models.model_number', 'asc')->paginate(50);
|
||||
|
||||
foreach ($assetmodels as $assetmodel) {
|
||||
$assetmodel->use_text = $assetmodel->present()->modelName;
|
||||
$assetmodel->use_image = ($assetmodel->image) ? url('/').'/uploads/models/'.$assetmodel->image : null;
|
||||
|
||||
$assetmodel->use_text = '';
|
||||
|
||||
if ($settings->modellistCheckedValue('category')) {
|
||||
$assetmodel->use_text .= (($assetmodel->category) ? e($assetmodel->category->name).' - ' : '');
|
||||
}
|
||||
|
||||
if ($settings->modellistCheckedValue('manufacturer')) {
|
||||
$assetmodel->use_text .= (($assetmodel->manufacturer) ? e($assetmodel->manufacturer->name).' ' : '');
|
||||
}
|
||||
|
||||
$assetmodel->use_text .= e($assetmodel->name);
|
||||
|
||||
if (($settings->modellistCheckedValue('model_number')) && ($assetmodel->model_number!='')) {
|
||||
$assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')';
|
||||
}
|
||||
|
||||
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($assetmodels);
|
||||
|
||||
@@ -1,36 +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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -51,9 +46,11 @@ class AssetsController extends Controller
|
||||
* @since [v4.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function index(Request $request)
|
||||
public function index(Request $request, $audit = null)
|
||||
{
|
||||
|
||||
$this->authorize('index', Asset::class);
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
@@ -71,72 +68,106 @@ class AssetsController extends Controller
|
||||
'updated_at',
|
||||
'purchase_date',
|
||||
'purchase_cost',
|
||||
'last_audit_date',
|
||||
'next_audit_date',
|
||||
'warranty_months',
|
||||
'checkout_counter',
|
||||
'checkin_counter',
|
||||
'requests_counter',
|
||||
];
|
||||
|
||||
$filter = array();
|
||||
if ($request->has('filter')) {
|
||||
$filter = json_decode($request->input('filter'));
|
||||
}
|
||||
|
||||
if ($request->filled('filter')) {
|
||||
$filter = json_decode($request->input('filter'), true);
|
||||
}
|
||||
|
||||
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
|
||||
foreach ($all_custom_fields as $field) {
|
||||
$allowed_columns[]=$field->db_column_name();
|
||||
}
|
||||
|
||||
$assets = Company::scopeCompanyables(Asset::select('assets.*'))->with(
|
||||
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
|
||||
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
|
||||
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
|
||||
|
||||
|
||||
if (count($filter) > 0) {
|
||||
$assets->ByFilter($filter);
|
||||
} elseif ($request->has('search')) {
|
||||
$assets->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// These are used by the API to query against specific ID numbers
|
||||
if ($request->has('status_id')) {
|
||||
// These are used by the API to query against specific ID numbers.
|
||||
// They are also used by the individual searches on detail pages like
|
||||
// locations, etc.
|
||||
if ($request->filled('status_id')) {
|
||||
$assets->where('assets.status_id', '=', $request->input('status_id'));
|
||||
}
|
||||
|
||||
if ($request->has('model_id')) {
|
||||
if ($request->input('requestable')=='true') {
|
||||
$assets->where('assets.requestable', '=', '1');
|
||||
}
|
||||
|
||||
if ($request->filled('model_id')) {
|
||||
$assets->InModelList([$request->input('model_id')]);
|
||||
}
|
||||
|
||||
if ($request->has('category_id')) {
|
||||
if ($request->filled('category_id')) {
|
||||
$assets->InCategory($request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->has('location_id')) {
|
||||
if ($request->filled('location_id')) {
|
||||
$assets->where('assets.location_id', '=', $request->input('location_id'));
|
||||
}
|
||||
|
||||
if ($request->has('supplier_id')) {
|
||||
if ($request->filled('rtd_location_id')) {
|
||||
$assets->where('assets.rtd_location_id', '=', $request->input('rtd_location_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('supplier_id')) {
|
||||
$assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->has('company_id')) {
|
||||
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
|
||||
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
|
||||
->where('assets.assigned_type', '=', $request->input('assigned_type'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$assets->where('assets.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->has('manufacturer_id')) {
|
||||
if ($request->filled('manufacturer_id')) {
|
||||
$assets->ByManufacturer($request->input('manufacturer_id'));
|
||||
}
|
||||
|
||||
if ($request->has('depreciation_id')) {
|
||||
if ($request->filled('depreciation_id')) {
|
||||
$assets->ByDepreciationId($request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
$request->has('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
|
||||
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
|
||||
$offset = (($assets) && (request('offset') > $assets->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.
|
||||
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('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');
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
// This is used by the audit reporting routes
|
||||
if (Gate::allows('audit', Asset::class)) {
|
||||
|
||||
switch ($audit) {
|
||||
case 'due':
|
||||
$assets->DueOrOverdueForAudit($settings);
|
||||
break;
|
||||
case 'overdue':
|
||||
$assets->overdueForAudit($settings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This is used by the sidenav, mostly
|
||||
|
||||
@@ -145,7 +176,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) {
|
||||
@@ -157,12 +188,12 @@ class AssetsController extends Controller
|
||||
break;
|
||||
case 'RTD':
|
||||
$assets->whereNull('assets.assigned_to')
|
||||
->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.deployable','=',1)
|
||||
->where('status_alias.pending','=',0)
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.deployable','=',1)
|
||||
->where('status_alias.pending','=',0)
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
break;
|
||||
case 'Undeployable':
|
||||
$assets->Undeployable();
|
||||
@@ -178,11 +209,11 @@ class AssetsController extends Controller
|
||||
case 'Requestable':
|
||||
$assets->where('assets.requestable', '=', 1)
|
||||
->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.deployable','=',1)
|
||||
->where('status_alias.pending','=',0)
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.deployable','=',1)
|
||||
->where('status_alias.pending','=',0)
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
|
||||
break;
|
||||
case 'Deployed':
|
||||
@@ -190,11 +221,28 @@ class AssetsController extends Controller
|
||||
$assets->where('assets.assigned_to', '>', '0');
|
||||
break;
|
||||
default:
|
||||
// terrible workaround for complex-query Laravel bug in fulltext
|
||||
$assets->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
|
||||
if ((!$request->filled('status_id')) && ($settings->show_archived_in_list!='1')) {
|
||||
// terrible workaround for complex-query Laravel bug in fulltext
|
||||
$assets->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id")
|
||||
->where('status_alias.archived', '=', 0);
|
||||
});
|
||||
|
||||
// If there is a status ID, don't take show_archived_in_list into consideration
|
||||
} else {
|
||||
$assets->join('status_labels AS status_alias',function ($join) {
|
||||
$join->on('status_alias.id', "=", "assets.status_id");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ((!is_null($filter)) && (count($filter)) > 0) {
|
||||
$assets->ByFilter($filter);
|
||||
} elseif ($request->filled('search')) {
|
||||
$assets->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
@@ -206,8 +254,8 @@ class AssetsController extends Controller
|
||||
// This handles all of the pivot sorting (versus the assets.* fields
|
||||
// in the allowed_columns array)
|
||||
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
|
||||
|
||||
|
||||
|
||||
|
||||
switch ($sort_override) {
|
||||
case 'model':
|
||||
$assets->OrderModels($order);
|
||||
@@ -226,6 +274,8 @@ class AssetsController extends Controller
|
||||
break;
|
||||
case 'location':
|
||||
$assets->OrderLocation($order);
|
||||
case 'rtd_location':
|
||||
$assets->OrderRtdLocation($order);
|
||||
break;
|
||||
case 'status_label':
|
||||
$assets->OrderStatus($order);
|
||||
@@ -240,14 +290,54 @@ class AssetsController extends Controller
|
||||
$assets->orderBy($column_sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$total = $assets->count();
|
||||
$assets = $assets->skip($offset)->take($limit)->get();
|
||||
// dd($assets);
|
||||
return (new AssetsTransformer)->transformAssets($assets, $total);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns JSON with information about an asset (by tag) for detail view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $tag
|
||||
* @since [v4.2.1]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function showByTag($tag)
|
||||
{
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag',$tag)->first()) {
|
||||
$this->authorize('view', $asset);
|
||||
return (new AssetsTransformer)->transformAsset($asset);
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON with information about an asset (by serial) for detail view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param string $serial
|
||||
* @since [v4.2.1]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function showBySerial($serial)
|
||||
{
|
||||
$this->authorize('index', Asset::class);
|
||||
if ($assets = Asset::with('assetstatus')->with('assignedTo')
|
||||
->withTrashed()->where('serial',$serial)->get()) {
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns JSON with information about an asset for detail view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
@@ -257,12 +347,22 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->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());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -281,27 +381,37 @@ class AssetsController extends Controller
|
||||
'assets.name',
|
||||
'assets.asset_tag',
|
||||
'assets.model_id',
|
||||
'assets.assigned_to',
|
||||
'assets.assigned_type',
|
||||
'assets.status_id'
|
||||
])->with('model', 'assetstatus')->NotArchived());
|
||||
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets');
|
||||
|
||||
|
||||
if ($request->has('search')) {
|
||||
$assets = $assets->where('assets.name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('assets.asset_tag', 'LIKE', '%'.$request->get('search').'%')
|
||||
->join('models AS assets_models',function ($join) use ($request) {
|
||||
$join->on('assets_models.id', "=", "assets.model_id");
|
||||
})->orWhere('assets_models.name','LIKE','%'.$request->get('search').'%');
|
||||
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
|
||||
$assets = $assets->RTD();
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$assets = $assets->AssignedSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
$assets = $assets->paginate(50);
|
||||
|
||||
// Loop through and set some custom properties for the transformer to use.
|
||||
// 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 ($assets as $asset) {
|
||||
|
||||
|
||||
$asset->use_text = $asset->present()->fullName;
|
||||
|
||||
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
|
||||
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
|
||||
}
|
||||
|
||||
|
||||
if ($asset->assetstatus->getStatuslabelType()=='pending') {
|
||||
$asset->use_text = $asset->present()->fullName.' ('.$asset->assetstatus->getStatuslabelType().')';
|
||||
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
|
||||
}
|
||||
|
||||
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
|
||||
@@ -320,7 +430,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);
|
||||
@@ -334,7 +444,7 @@ class AssetsController extends Controller
|
||||
$asset->model_id = $request->get('model_id');
|
||||
$asset->order_number = $request->get('order_number');
|
||||
$asset->notes = $request->get('notes');
|
||||
$asset->asset_tag = $request->get('asset_tag');
|
||||
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
|
||||
$asset->user_id = Auth::id();
|
||||
$asset->archived = '0';
|
||||
$asset->physical = '1';
|
||||
@@ -347,13 +457,36 @@ class AssetsController extends Controller
|
||||
$asset->supplier_id = $request->get('supplier_id', 0);
|
||||
$asset->requestable = $request->get('requestable', 0);
|
||||
$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'));
|
||||
if ($model->fieldset) {
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug(), null));
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +502,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')));
|
||||
}
|
||||
|
||||
@@ -386,66 +524,71 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if ($asset = Asset::find($id)) {
|
||||
($request->has('model_id')) ?
|
||||
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : '';
|
||||
($request->has('name')) ?
|
||||
$asset->name = $request->get('name') : '';
|
||||
($request->has('serial')) ?
|
||||
$asset->serial = $request->get('serial') : '';
|
||||
($request->has('model_id')) ?
|
||||
$asset->model_id = $request->get('model_id') : '';
|
||||
($request->has('order_number')) ?
|
||||
$asset->order_number = $request->get('order_number') : '';
|
||||
($request->has('notes')) ?
|
||||
$asset->notes = $request->get('notes') : '';
|
||||
($request->has('asset_tag')) ?
|
||||
$asset->asset_tag = $request->get('asset_tag') : '';
|
||||
($request->has('archived')) ?
|
||||
$asset->archived = $request->get('archived') : '';
|
||||
($request->has('status_id')) ?
|
||||
$asset->status_id = $request->get('status_id') : '';
|
||||
($request->has('warranty_months')) ?
|
||||
$asset->warranty_months = $request->get('warranty_months') : '';
|
||||
($request->has('purchase_cost')) ?
|
||||
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost')) : '';
|
||||
($request->has('purchase_date')) ?
|
||||
$asset->purchase_date = $request->get('purchase_date') : '';
|
||||
($request->has('assigned_to')) ?
|
||||
$asset->assigned_to = $request->get('assigned_to') : '';
|
||||
($request->has('supplier_id')) ?
|
||||
$asset->supplier_id = $request->get('supplier_id') : '';
|
||||
($request->has('requestable')) ?
|
||||
$asset->requestable = $request->get('requestable') : '';
|
||||
($request->has('rtd_location_id')) ?
|
||||
$asset->rtd_location_id = $request->get('rtd_location_id') : '';
|
||||
($request->has('rtd_location_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->has('company_id')) ?
|
||||
($request->filled('company_id')) ?
|
||||
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
|
||||
|
||||
if ($request->has('model_id')) {
|
||||
if (($model = AssetModel::find($request->get('model_id'))) && (isset($model->fieldset))) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($request->has($field->convertUnicodeDbSlug())) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug()));
|
||||
($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))) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($request->has($field->convertUnicodeDbSlug())) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($asset->save()) {
|
||||
|
||||
if ($request->get('assigned_user')) {
|
||||
$target = User::find(request('assigned_user'));
|
||||
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
|
||||
$location = $target->location_id;
|
||||
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
|
||||
$location = $target->location_id;
|
||||
} elseif ($request->get('assigned_asset')) {
|
||||
$target = Asset::find(request('assigned_asset'));
|
||||
$location = $target->location_id;
|
||||
} elseif ($request->get('assigned_location')) {
|
||||
$target = Location::find(request('assigned_location'));
|
||||
|
||||
Asset::where('assigned_type', '\\App\\Models\\Asset')->where('assigned_to', $id)
|
||||
->update(['location_id' => $target->location_id]);
|
||||
|
||||
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
|
||||
$location = $target->id;
|
||||
}
|
||||
|
||||
@@ -453,6 +596,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);
|
||||
@@ -499,7 +646,7 @@ class AssetsController extends Controller
|
||||
* @since [v4.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkout(Request $request, $asset_id)
|
||||
public function checkout(AssetCheckoutRequest $request, $asset_id)
|
||||
{
|
||||
$this->authorize('checkout', Asset::class);
|
||||
$asset = Asset::findOrFail($asset_id);
|
||||
@@ -515,27 +662,35 @@ class AssetsController extends Controller
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
];
|
||||
if ($request->has('user_id')) {
|
||||
$target = User::find($request->input('user_id'));
|
||||
$location = $target->location_id;
|
||||
$error_payload['target_id'] = $request->input('user_id');
|
||||
$error_payload['target_type'] = User::class;
|
||||
// Don't let the user check an asset out to itself
|
||||
} elseif ($request->has('asset_id')) {
|
||||
$target = Asset::where('id','!=',$asset_id)->find($request->input('asset_id'));
|
||||
$location = $target->location_id;
|
||||
$error_payload['target_id'] = $request->input('asset_id');
|
||||
$error_payload['target_type'] = Asset::class;
|
||||
} elseif ($request->has('location_id')) {
|
||||
$target = Location::find($request->input('location_id'));
|
||||
$location = $target->id;
|
||||
$target = Location::find($request->input('location_id'));
|
||||
$error_payload['target_id'] = $request->input('location_id');
|
||||
$error_payload['target_type'] = Location::class;
|
||||
|
||||
|
||||
// This item is checked out to a location
|
||||
if (request('checkout_to_type')=='location') {
|
||||
$target = Location::find(request('assigned_location'));
|
||||
$asset->location_id = ($target) ? $target->id : '';
|
||||
$error_payload['target_id'] = $request->input('assigned_location');
|
||||
$error_payload['target_type'] = 'location';
|
||||
|
||||
} elseif (request('checkout_to_type')=='asset') {
|
||||
$target = Asset::where('id','!=',$asset_id)->find(request('assigned_asset'));
|
||||
$asset->location_id = $target->rtd_location_id;
|
||||
// Override with the asset's location_id if it has one
|
||||
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
|
||||
$error_payload['target_id'] = $request->input('assigned_asset');
|
||||
$error_payload['target_type'] = 'asset';
|
||||
|
||||
} elseif (request('checkout_to_type')=='user') {
|
||||
// Fetch the target and set the asset's new location_id
|
||||
$target = User::find(request('assigned_user'));
|
||||
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
|
||||
$error_payload['target_id'] = $request->input('assigned_user');
|
||||
$error_payload['target_type'] = 'user';
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!isset($target)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'No valid checkout target specified for asset '.e($asset->asset_tag).'.'));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
|
||||
}
|
||||
|
||||
|
||||
@@ -544,21 +699,23 @@ class AssetsController extends Controller
|
||||
$expected_checkin = request('expected_checkin', null);
|
||||
$note = request('note', null);
|
||||
$asset_name = request('name', null);
|
||||
|
||||
|
||||
// Set the location ID to the RTD location id if there is one
|
||||
if ($asset->rtd_location_id!='') {
|
||||
$asset->location_id = $target->rtd_location_id;
|
||||
}
|
||||
// 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.
|
||||
|
||||
$asset->location_id = $location;
|
||||
|
||||
|
||||
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
|
||||
// $asset->location_id = $target->rtd_location_id;
|
||||
// }
|
||||
|
||||
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $location)) {
|
||||
|
||||
|
||||
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')))->withErrors($asset->getErrors());
|
||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
|
||||
}
|
||||
|
||||
|
||||
@@ -587,42 +744,23 @@ class AssetsController extends Controller
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
$asset->name = e(Input::get('name'));
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$asset->name = $request->input('name');
|
||||
}
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
if ($request->has('location_id')) {
|
||||
if ($request->filled('location_id')) {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
if (Input::has('status_id')) {
|
||||
$asset->status_id = e(Input::get('status_id'));
|
||||
if ($request->has('status_id')) {
|
||||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
// 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['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;
|
||||
|
||||
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!config('app.lock_passwords'))) {
|
||||
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
|
||||
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.Confirm_Asset_Checkin'));
|
||||
});
|
||||
}
|
||||
|
||||
$asset->logCheckin($target, e($request->input('note')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
|
||||
}
|
||||
|
||||
@@ -653,17 +791,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) {
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
// We don't want to log this as a normal update, so let's bypass that
|
||||
$asset->unsetEventDispatcher();
|
||||
$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')));
|
||||
}
|
||||
}
|
||||
@@ -674,5 +830,51 @@ class AssetsController extends Controller
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns JSON listing of all requestable assets
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function requestable(Request $request)
|
||||
{
|
||||
$this->authorize('viewRequestable', Asset::class);
|
||||
|
||||
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
|
||||
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset','supplier')->where('assets.requestable', '=', '1');
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$assets->TextSearch($request->input('search'));
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'model':
|
||||
$assets->OrderModels($order);
|
||||
break;
|
||||
case 'model_number':
|
||||
$assets->OrderModelNumber($order);
|
||||
break;
|
||||
case 'category':
|
||||
$assets->OrderCategory($order);
|
||||
break;
|
||||
case 'manufacturer':
|
||||
$assets->OrderManufacturer($order);
|
||||
break;
|
||||
default:
|
||||
$assets->orderBy('assets.created_at', $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$total = $assets->count();
|
||||
$assets = $assets->skip($offset)->take($limit)->get();
|
||||
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -21,17 +22,22 @@ class CategoriesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Category::class);
|
||||
$allowed_columns = ['id', 'name','category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count', 'image'];
|
||||
$allowed_columns = ['id', 'name','category_type', 'category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count','licenses_count', 'image'];
|
||||
|
||||
$categories = Category::select(['id', 'created_at', 'updated_at', 'name','category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email','image'])
|
||||
->withCount('assets', 'accessories', 'consumables', 'components');
|
||||
->withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count','licenses as licenses_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$categories = $categories->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($categories) && ($request->get('offset') > $categories->count())) ? $categories->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
|
||||
$categories->orderBy($sort, $order);
|
||||
@@ -92,7 +98,7 @@ class CategoriesController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Category::class);
|
||||
$this->authorize('update', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
$category->fill($request->all());
|
||||
|
||||
@@ -116,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')));
|
||||
@@ -141,7 +143,6 @@ class CategoriesController extends Controller
|
||||
*/
|
||||
public function selectlist(Request $request, $category_type = 'asset')
|
||||
{
|
||||
$this->authorize('view', Categories::class);
|
||||
|
||||
$categories = Category::select([
|
||||
'id',
|
||||
@@ -149,7 +150,7 @@ class CategoriesController extends Controller
|
||||
'image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$categories = $categories->where('name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -159,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
|
||||
{
|
||||
@@ -22,19 +23,32 @@ class CompaniesController extends Controller
|
||||
{
|
||||
$this->authorize('view', Company::class);
|
||||
|
||||
$allowed_columns = ['id','name'];
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'name',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'users_count',
|
||||
'assets_count',
|
||||
'licenses_count',
|
||||
'accessories_count',
|
||||
'consumables_count',
|
||||
'components_count',
|
||||
];
|
||||
|
||||
$companies = Company::withCount('assets','licenses','accessories','consumables','components','users')
|
||||
->withCount('users')->withCount('users')->withCount('assets')
|
||||
->withCount('licenses')->withCount('accessories')
|
||||
->withCount('consumables')->withCount('components');
|
||||
$companies = Company::withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count','components as components_count','users as users_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$companies->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($companies) && ($request->get('offset') > $companies->count())) ? $companies->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$companies->orderBy($sort, $order);
|
||||
@@ -96,7 +110,7 @@ class CompaniesController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Company::class);
|
||||
$this->authorize('update', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
$company->fill($request->all());
|
||||
|
||||
@@ -119,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')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +163,7 @@ class CompaniesController extends Controller
|
||||
'companies.image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -170,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
|
||||
{
|
||||
@@ -24,15 +22,31 @@ class ComponentsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Component::class);
|
||||
$components = Company::scopeCompanyables(Component::select('components.*')->whereNull('components.deleted_at')
|
||||
$components = Company::scopeCompanyables(Component::select('components.*')
|
||||
->with('company', 'location', 'category'));
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$components = $components->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
if ($request->filled('company_id')) {
|
||||
$components->where('company_id','=',$request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('category_id')) {
|
||||
$components->where('category_id','=',$request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
$components->where('location_id','=',$request->input('location_id'));
|
||||
}
|
||||
|
||||
// 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 = (($components) && ($request->get('offset') > $components->count())) ? $components->count() : $request->get('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');
|
||||
|
||||
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location','image'];
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
@@ -89,13 +103,11 @@ class ComponentsController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Component::class);
|
||||
$component = Component::find($id);
|
||||
$component = Component::findOrFail($id);
|
||||
|
||||
if ($component) {
|
||||
return (new ComponentsTransformer)->transformComponent($component);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist')));
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +122,7 @@ class ComponentsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Component::class);
|
||||
$this->authorize('update', Component::class);
|
||||
$component = Component::findOrFail($id);
|
||||
$component->fill($request->all());
|
||||
|
||||
@@ -149,7 +161,7 @@ class ComponentsController extends Controller
|
||||
*/
|
||||
public function getAssets(Request $request, $id)
|
||||
{
|
||||
$this->authorize('index', Asset::class);
|
||||
$this->authorize('view', \App\Models\Asset::class);
|
||||
|
||||
$component = Component::findOrFail($id);
|
||||
$assets = $component->assets();
|
||||
|
||||
@@ -2,12 +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\Http\Transformers\ConsumablesTransformer;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ConsumablesController extends Controller
|
||||
{
|
||||
@@ -24,25 +26,33 @@ class ConsumablesController extends Controller
|
||||
$this->authorize('index', Consumable::class);
|
||||
$consumables = Company::scopeCompanyables(
|
||||
Consumable::select('consumables.*')
|
||||
->whereNull('consumables.deleted_at')
|
||||
->with('company', 'location', 'category', 'users', 'manufacturer')
|
||||
);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$consumables = $consumables->TextSearch(e($request->input('search')));
|
||||
}
|
||||
|
||||
if ($request->has('company_id')) {
|
||||
if ($request->filled('company_id')) {
|
||||
$consumables->where('company_id','=',$request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->has('manufacturer_id')) {
|
||||
if ($request->filled('category_id')) {
|
||||
$consumables->where('category_id','=',$request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('manufacturer_id')) {
|
||||
$consumables->where('manufacturer_id','=',$request->input('manufacturer_id'));
|
||||
}
|
||||
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
// 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 = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->count() : $request->get('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');
|
||||
|
||||
$allowed_columns = ['id','name','order_number','min_amt','purchase_date','purchase_cost','company','category','model_number', 'item_no', 'manufacturer','location','qty','image'];
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
@@ -121,7 +131,7 @@ class ConsumablesController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Consumable::class);
|
||||
$this->authorize('update', Consumable::class);
|
||||
$consumable = Consumable::findOrFail($id);
|
||||
$consumable->fill($request->all());
|
||||
|
||||
@@ -149,11 +159,11 @@ class ConsumablesController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.delete.success')));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 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
|
||||
@@ -162,7 +172,7 @@ class ConsumablesController extends Controller
|
||||
{
|
||||
$consumable = Consumable::with(array('consumableAssignments'=>
|
||||
function ($query) {
|
||||
$query->orderBy('created_at', 'DESC');
|
||||
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
|
||||
},
|
||||
'consumableAssignments.admin'=> function ($query) {
|
||||
},
|
||||
@@ -179,7 +189,7 @@ class ConsumablesController extends Controller
|
||||
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
||||
$rows[] = [
|
||||
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
|
||||
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
|
||||
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
|
||||
];
|
||||
}
|
||||
@@ -188,4 +198,79 @@ class ConsumablesController extends Controller
|
||||
$data = array('total' => $consumableCount, 'rows' => $rows);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout a consumable
|
||||
*
|
||||
* @author [A. Gutierrez] [<andres@baller.tv>]
|
||||
* @param int $id
|
||||
* @since [v4.9.5]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkout(Request $request, $id)
|
||||
{
|
||||
// Check if the consumable exists
|
||||
if (is_null($consumable = Consumable::find($id))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.does_not_exist')));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $consumable);
|
||||
|
||||
if ($consumable->qty > 0) {
|
||||
|
||||
// Check if the user exists
|
||||
$assigned_to = $request->input('assigned_to');
|
||||
if (is_null($user = User::find($assigned_to))) {
|
||||
// Return error message
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
|
||||
}
|
||||
|
||||
// Update the consumable data
|
||||
$consumable->assigned_to = e($assigned_to);
|
||||
|
||||
$consumable->users()->attach($consumable->id, [
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $user->id,
|
||||
'assigned_to' => $assigned_to
|
||||
]);
|
||||
|
||||
// Log checkout event
|
||||
$logaction = $consumable->logCheckout(e($request->input('note')), $user);
|
||||
$data['log_id'] = $logaction->id;
|
||||
$data['eula'] = $consumable->getEula();
|
||||
$data['first_name'] = $user->first_name;
|
||||
$data['item_name'] = $consumable->name;
|
||||
$data['checkout_date'] = $logaction->created_at;
|
||||
$data['note'] = $logaction->note;
|
||||
$data['require_acceptance'] = $consumable->requireAcceptance();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\CustomFieldsTransformer;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\CustomFieldset;
|
||||
use Illuminate\Http\Request;
|
||||
use Validator;
|
||||
|
||||
class CustomFieldsController extends Controller
|
||||
{
|
||||
@@ -21,15 +23,97 @@ class CustomFieldsController extends Controller
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', CustomFields::class);
|
||||
$this->authorize('index', CustomField::class);
|
||||
$fields = CustomField::get();
|
||||
|
||||
$total = count($fields);
|
||||
return (new CustomFieldsTransformer)->transformCustomFields($fields, $total);
|
||||
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the given field
|
||||
* @author [V. Cordes] [<volker@fdatek.de>]
|
||||
* @param int $id
|
||||
* @since [v4.1.10]
|
||||
* @return View
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', CustomField::class);
|
||||
if ($field = CustomField::find($id)) {
|
||||
return (new CustomFieldsTransformer)->transformCustomField($field);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/custom_fields/message.field.invalid')), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified field
|
||||
*
|
||||
* @author [V. Cordes] [<volker@fdatek.de>]
|
||||
* @since [v4.1.10]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($id);
|
||||
|
||||
/**
|
||||
* Updated values for the field,
|
||||
* without the "field_encrypted" flag, preventing the change of encryption status
|
||||
* @var array
|
||||
*/
|
||||
$data = $request->except(['field_encrypted']);
|
||||
|
||||
$validator = Validator::make($data, $field->validationRules());
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
|
||||
}
|
||||
|
||||
$field->fill($data);
|
||||
|
||||
if ($field->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $field, trans('admin/custom_fields/message.field.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $field->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created field.
|
||||
*
|
||||
* @author [V. Cordes] [<volker@fdatek.de>]
|
||||
* @since [v4.1.10]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', CustomField::class);
|
||||
$field = new CustomField;
|
||||
|
||||
$data = $request->all();
|
||||
$validator = Validator::make($data, $field->validationRules());
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
|
||||
}
|
||||
$field->fill($data);
|
||||
|
||||
if ($field->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $field, trans('admin/custom_fields/message.field.create.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $field->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
public function postReorder(Request $request, $id)
|
||||
{
|
||||
$fieldset = CustomFieldset::find($id);
|
||||
|
||||
$this->authorize('update', $fieldset);
|
||||
|
||||
$fields = array();
|
||||
$order_array = array();
|
||||
|
||||
@@ -47,6 +131,40 @@ class CustomFieldsController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function associate(Request $request, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
|
||||
$fieldset_id = $request->input('fieldset_id');
|
||||
foreach ($field->fieldset as $fieldset) {
|
||||
if ($fieldset->id == $fieldset_id) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
|
||||
}
|
||||
}
|
||||
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
$fieldset->fields()->attach($field->id, ["required" => ($request->input('required') == "on"), "order" => $request->input('order', $fieldset->fields->count())]);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
|
||||
}
|
||||
|
||||
public function disassociate(Request $request, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
|
||||
$fieldset_id = $request->input('fieldset_id');
|
||||
foreach ($field->fieldset as $fieldset) {
|
||||
if ($fieldset->id == $fieldset_id) {
|
||||
$fieldset->fields()->detach($field->id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
|
||||
}
|
||||
}
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a custom field.
|
||||
@@ -59,10 +177,12 @@ class CustomFieldsController extends Controller
|
||||
{
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
|
||||
$this->authorize('delete', $field);
|
||||
|
||||
if ($field->fieldset->count() >0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Field is in use.'));
|
||||
}
|
||||
|
||||
|
||||
$field->delete();
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/custom_fields/message.field.delete.success')));
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -43,10 +35,8 @@ class CustomFieldsetsController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', CustomFieldset::class);
|
||||
$fieldsets = CustomFieldset::withCount(['fields', 'models'])->get();
|
||||
|
||||
$total = count($fieldsets);
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $total);
|
||||
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +50,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('show', CustomFieldset::class);
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
|
||||
}
|
||||
@@ -81,7 +71,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', CustomFieldset::class);
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
$fieldset->fill($request->all());
|
||||
|
||||
@@ -126,7 +116,7 @@ class CustomFieldsetsController extends Controller
|
||||
{
|
||||
$this->authorize('delete', CustomFieldset::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
|
||||
|
||||
$modelsCount = $fieldset->models->count();
|
||||
$fieldsCount = $fieldset->fields->count();
|
||||
|
||||
@@ -141,7 +131,41 @@ class CustomFieldsetsController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Unspecified error'));
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON containing a list of fields belonging to a fieldset.
|
||||
*
|
||||
* @author [V. Cordes] [<volker@fdatek.de>]
|
||||
* @since [v4.1.10]
|
||||
* @param $fieldsetId
|
||||
* @return string JSON
|
||||
*/
|
||||
public function fields($id)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$set = CustomFieldset::findOrFail($id);
|
||||
$fields = $set->fields;
|
||||
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON containing a list of fields belonging to a fieldset with the
|
||||
* default values for a given model
|
||||
*
|
||||
* @param $modelId
|
||||
* @param $fieldsetId
|
||||
* @return string JSON
|
||||
*/
|
||||
public function fieldsWithDefaultValues($fieldsetId, $modelId)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
|
||||
$set = CustomFieldset::findOrFail($fieldsetId);
|
||||
|
||||
$fields = $set->fields;
|
||||
|
||||
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,48 +2,65 @@
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @author [Godfrey Martinez] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Department::class);
|
||||
$allowed_columns = ['id','name','image'];
|
||||
$allowed_columns = ['id','name','image','users_count'];
|
||||
|
||||
$departments = Department::select([
|
||||
'id',
|
||||
'name',
|
||||
'location_id',
|
||||
'company_id',
|
||||
'manager_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'image'
|
||||
])->with('users')->with('location')->with('manager')->with('company')->withCount('users');
|
||||
'departments.id',
|
||||
'departments.name',
|
||||
'departments.location_id',
|
||||
'departments.company_id',
|
||||
'departments.manager_id',
|
||||
'departments.created_at',
|
||||
'departments.updated_at',
|
||||
'departments.image'
|
||||
])->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$departments = $departments->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($departments) && ($request->get('offset') > $departments->count())) ? $departments->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$departments->orderBy($sort, $order);
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'location':
|
||||
$departments->OrderLocation($order);
|
||||
break;
|
||||
case 'manager':
|
||||
$departments->OrderManager($order);
|
||||
break;
|
||||
default:
|
||||
$departments->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
$total = $departments->count();
|
||||
$departments = $departments->skip($offset)->take($limit)->get();
|
||||
@@ -65,7 +82,7 @@ class DepartmentsController extends Controller
|
||||
$department = new Department;
|
||||
$department->fill($request->all());
|
||||
$department->user_id = Auth::user()->id;
|
||||
$department->manager_id = ($request->has('manager_id' ) ? $request->input('manager_id') : null);
|
||||
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
|
||||
|
||||
if ($department->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.create.success')));
|
||||
@@ -89,20 +106,44 @@ 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)
|
||||
{
|
||||
$department = Department::findOrFail($id);
|
||||
|
||||
$this->authorize('delete', $department);
|
||||
|
||||
if ($department->users->count() > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/departments/message.assoc_users')));
|
||||
}
|
||||
@@ -129,7 +170,7 @@ class DepartmentsController extends Controller
|
||||
'image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$departments = $departments->where('name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -139,7 +180,7 @@ 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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -24,12 +24,17 @@ class DepreciationsController extends Controller
|
||||
|
||||
$depreciations = Depreciation::select('id','name','months','user_id','created_at','updated_at');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$depreciations = $depreciations->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($depreciations) && ($request->get('offset') > $depreciations->count())) ? $depreciations->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$depreciations->orderBy($sort, $order);
|
||||
@@ -88,7 +93,7 @@ class DepreciationsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Depreciation::class);
|
||||
$this->authorize('update', Depreciation::class);
|
||||
$depreciation = Depreciation::findOrFail($id);
|
||||
$depreciation->fill($request->all());
|
||||
|
||||
@@ -110,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
|
||||
{
|
||||
@@ -20,16 +20,21 @@ class GroupsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Group::class);
|
||||
$allowed_columns = ['id','name','created_at'];
|
||||
$allowed_columns = ['id','name','created_at', 'users_count'];
|
||||
|
||||
$groups = Group::select('id','name','permissions','created_at','updated_at')->withCount('users');
|
||||
$groups = Group::select('id','name','permissions','created_at','updated_at')->withCount('users as users_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$groups = $groups->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($groups) && ($request->get('offset') > $groups->count())) ? $groups->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$groups->orderBy($sort, $order);
|
||||
@@ -88,7 +93,7 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Group::class);
|
||||
$this->authorize('update', Group::class);
|
||||
$group = Group::findOrFail($id);
|
||||
$group->fill($request->all());
|
||||
|
||||
|
||||
@@ -6,14 +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;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
@@ -24,7 +25,7 @@ class ImportController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
$this->authorize('import');
|
||||
$imports = Import::latest()->get();
|
||||
return (new ImportsTransformer)->transformImports($imports);
|
||||
|
||||
@@ -38,11 +39,9 @@ class ImportController extends Controller
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
//
|
||||
if (!Company::isCurrentUserAuthorized()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
|
||||
} elseif (!config('app.lock_passwords')) {
|
||||
$files = Input::file('files');
|
||||
$this->authorize('import');
|
||||
if (!config('app.lock_passwords')) {
|
||||
$files = Request::file('files');
|
||||
$path = config('app.private_uploads').'/imports';
|
||||
$results = [];
|
||||
$import = new Import;
|
||||
@@ -57,6 +56,35 @@ class ImportController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
|
||||
}
|
||||
|
||||
//TODO: is there a lighter way to do this?
|
||||
if (! ini_get("auto_detect_line_endings")) {
|
||||
ini_set("auto_detect_line_endings", '1');
|
||||
}
|
||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
|
||||
//duplicate headers check
|
||||
$duplicate_headers = [];
|
||||
|
||||
for($i = 0; $i<count($import->header_row); $i++) {
|
||||
$header = $import->header_row[$i];
|
||||
if(in_array($header, $import->header_row)) {
|
||||
$found_at = array_search($header, $import->header_row);
|
||||
if($i > $found_at) {
|
||||
//avoid reporting duplicates twice, e.g. "1 is same as 17! 17 is same as 1!!!"
|
||||
//as well as "1 is same as 1!!!" (which is always true)
|
||||
//has to be > because otherwise the first result of array_search will always be $i itself(!)
|
||||
array_push($duplicate_headers,"Duplicate header '$header' detected, first at column: ".($found_at+1).", repeats at column: ".($i+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(count($duplicate_headers) > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error',null, implode("; ",$duplicate_headers)), 500); //should this be '4xx'?
|
||||
}
|
||||
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
|
||||
$date = date('Y-m-d-his');
|
||||
$fixed_filename = str_slug($file->getClientOriginalName());
|
||||
try {
|
||||
@@ -71,11 +99,6 @@ class ImportController extends Controller
|
||||
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
|
||||
$import->file_path = $file_name;
|
||||
$import->filesize = filesize($path.'/'.$file_name);
|
||||
//TODO: is there a lighter way to do this?
|
||||
$reader = Reader::createFromPath("{$path}/{$file_name}");
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
$import->save();
|
||||
$results[] = $import;
|
||||
}
|
||||
@@ -89,14 +112,21 @@ 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('create', Asset::class);
|
||||
$this->authorize('import');
|
||||
|
||||
// Run a backup immediately before processing
|
||||
Artisan::call('backup:run');
|
||||
if ($request->has('run-backup')) {
|
||||
\Log::debug('Backup manually requested via importer');
|
||||
Artisan::call('backup:run');
|
||||
} else {
|
||||
\Log::debug('NO BACKUP requested via importer');
|
||||
}
|
||||
|
||||
$errors = $request->import(Import::find($import_id));
|
||||
$redirectTo = "hardware.index";
|
||||
switch ($request->get('import-type')) {
|
||||
@@ -132,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('create', Asset::class);
|
||||
$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);
|
||||
|
||||
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')));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\LicenseSeatsTransformer;
|
||||
use App\Http\Transformers\LicensesTransformer;
|
||||
use App\Models\License;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LicensesController extends Controller
|
||||
{
|
||||
@@ -21,58 +26,70 @@ class LicensesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', License::class);
|
||||
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier')->withCount('freeSeats'));
|
||||
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier','category')->withCount('freeSeats as free_seats_count'));
|
||||
|
||||
if ($request->has('search')) {
|
||||
$licenses = $licenses->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->has('company_id')) {
|
||||
if ($request->filled('company_id')) {
|
||||
$licenses->where('company_id','=',$request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->has('name')) {
|
||||
if ($request->filled('name')) {
|
||||
$licenses->where('licenses.name','=',$request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->has('product_key')) {
|
||||
if ($request->filled('product_key')) {
|
||||
$licenses->where('licenses.serial','=',$request->input('product_key'));
|
||||
}
|
||||
|
||||
if ($request->has('order_number')) {
|
||||
if ($request->filled('order_number')) {
|
||||
$licenses->where('order_number','=',$request->input('order_number'));
|
||||
}
|
||||
|
||||
if ($request->has('purchase_order')) {
|
||||
if ($request->filled('purchase_order')) {
|
||||
$licenses->where('purchase_order','=',$request->input('purchase_order'));
|
||||
}
|
||||
|
||||
if ($request->has('license_name')) {
|
||||
if ($request->filled('license_name')) {
|
||||
$licenses->where('license_name','=',$request->input('license_name'));
|
||||
}
|
||||
|
||||
if ($request->has('license_email')) {
|
||||
if ($request->filled('license_email')) {
|
||||
$licenses->where('license_email','=',$request->input('license_email'));
|
||||
}
|
||||
|
||||
if ($request->has('manufacturer_id')) {
|
||||
if ($request->filled('manufacturer_id')) {
|
||||
$licenses->where('manufacturer_id','=',$request->input('manufacturer_id'));
|
||||
}
|
||||
|
||||
if ($request->has('supplier_id')) {
|
||||
if ($request->filled('supplier_id')) {
|
||||
$licenses->where('supplier_id','=',$request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->has('depreciation_id')) {
|
||||
if ($request->filled('category_id')) {
|
||||
$licenses->where('category_id','=',$request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation_id')) {
|
||||
$licenses->where('depreciation_id','=',$request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->has('supplier_id')) {
|
||||
if ($request->filled('supplier_id')) {
|
||||
$licenses->where('supplier_id','=',$request->input('supplier_id'));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$licenses = $licenses->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 = (($licenses) && ($request->get('offset') > $licenses->count())) ? $licenses->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
|
||||
@@ -83,16 +100,20 @@ class LicensesController extends Controller
|
||||
case 'supplier':
|
||||
$licenses = $licenses->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
|
||||
break;
|
||||
case 'category':
|
||||
$licenses = $licenses->leftJoin('categories', 'licenses.category_id', '=', 'categories.id')->orderBy('categories.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','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'];
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$licenses = $licenses->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$total = $licenses->count();
|
||||
|
||||
@@ -115,6 +136,14 @@ class LicensesController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
$this->authorize('create', License::class);
|
||||
$license = new License;
|
||||
$license->fill($request->all());
|
||||
|
||||
if($license->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $license, trans('admin/licenses/message.create.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $license->getErrors()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,13 +155,10 @@ class LicensesController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$license = License::find($id);
|
||||
if (isset($license->id)) {
|
||||
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
|
||||
$this->authorize('view', $license);
|
||||
return (new LicensesTransformer)->transformLicense($license);
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
|
||||
$this->authorize('view', License::class);
|
||||
$license = License::withCount('freeSeats')->findOrFail($id);
|
||||
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
|
||||
return (new LicensesTransformer)->transformLicense($license);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +174,16 @@ class LicensesController extends Controller
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
$this->authorize('update', License::class);
|
||||
|
||||
$license = License::findOrFail($id);
|
||||
$license->fill($request->all());
|
||||
|
||||
if ($license->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $license, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
||||
return Helper::formatStandardApiResponse('error', null, $license->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,5 +197,91 @@ class LicensesController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
$license = License::findOrFail($id);
|
||||
$this->authorize('delete', $license);
|
||||
|
||||
if($license->assigned_seats_count == 0) {
|
||||
// Delete the license and the associated license seats
|
||||
DB::table('license_seats')
|
||||
->where('id', $license->id)
|
||||
->update(array('assigned_to' => null,'asset_id' => null));
|
||||
|
||||
$licenseSeats = $license->licenseseats();
|
||||
$licenseSeats->delete();
|
||||
$license->delete();
|
||||
|
||||
// Redirect to the licenses management page
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/licenses/message.delete.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.assoc_users')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get license seat listing
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $licenseId
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function seats(Request $request, $licenseId)
|
||||
{
|
||||
|
||||
if ($license = License::find($licenseId)) {
|
||||
|
||||
$this->authorize('view', $license);
|
||||
|
||||
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
|
||||
->where('license_seats.license_id', $licenseId);
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
if ($request->input('sort')=='department') {
|
||||
$seats->OrderDepartments($order);
|
||||
} else {
|
||||
$seats->orderBy('id', $order);
|
||||
}
|
||||
|
||||
$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) {
|
||||
return (new LicenseSeatsTransformer)->transformLicenseSeats($seats, $total);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use App\Helpers\Helper;
|
||||
use App\Models\Location;
|
||||
use App\Http\Transformers\LocationsTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class LocationsController extends Controller
|
||||
{
|
||||
@@ -22,11 +24,11 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$allowed_columns = [
|
||||
'id','name','address','address2','city','state','country','zip','created_at',
|
||||
'updated_at','parent_id', 'manager_id','image',
|
||||
'assigned_assets_count','users_count','assets_count'];
|
||||
'id','name','address','address2','city','state','country','zip','created_at',
|
||||
'updated_at','manager_id','image',
|
||||
'assigned_assets_count','users_count','assets_count','currency'];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'childLocations')->select([
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
'locations.address',
|
||||
@@ -41,19 +43,36 @@ class LocationsController extends Controller
|
||||
'locations.updated_at',
|
||||
'locations.image',
|
||||
'locations.currency'
|
||||
])->withCount('assignedAssets')
|
||||
->withCount('assets')
|
||||
->withCount('users');
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$locations = $locations->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
|
||||
|
||||
$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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$locations->orderBy($sort, $order);
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'parent':
|
||||
$locations->OrderParent($order);
|
||||
break;
|
||||
case 'manager':
|
||||
$locations->OrderManager($order);
|
||||
break;
|
||||
default:
|
||||
$locations->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$total = $locations->count();
|
||||
$locations = $locations->skip($offset)->take($limit)->get();
|
||||
@@ -92,7 +111,26 @@ class LocationsController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$location = Location::findOrFail($id);
|
||||
$location = Location::with('parent', 'manager', 'children')
|
||||
->select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
'locations.address',
|
||||
'locations.address2',
|
||||
'locations.city',
|
||||
'locations.state',
|
||||
'locations.zip',
|
||||
'locations.country',
|
||||
'locations.parent_id',
|
||||
'locations.manager_id',
|
||||
'locations.created_at',
|
||||
'locations.updated_at',
|
||||
'locations.image',
|
||||
'locations.currency'
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count')->findOrFail($id);
|
||||
return (new LocationsTransformer)->transformLocation($location);
|
||||
}
|
||||
|
||||
@@ -104,15 +142,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('edit', Location::class);
|
||||
$this->authorize('update', Location::class);
|
||||
$location = Location::findOrFail($id);
|
||||
|
||||
$location->fill($request->all());
|
||||
|
||||
if ($location->save()) {
|
||||
|
||||
if ($location->isValid()) {
|
||||
|
||||
$location->save();
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'success',
|
||||
@@ -137,6 +179,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')));
|
||||
@@ -145,6 +191,27 @@ class LocationsController extends Controller
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
* This is handled slightly differently as of ~4.7.8-pre, as
|
||||
* we have to do some recursive magic to get the hierarchy to display
|
||||
* properly when looking at the parent/child relationship in the
|
||||
* rich menus.
|
||||
*
|
||||
* This means we can't use the normal pagination that we use elsewhere
|
||||
* in our selectlists, since we have to get the full set before we can
|
||||
* determine which location is parent/child/grandchild, etc.
|
||||
*
|
||||
* This also means that hierarchy display gets a little funky when people
|
||||
* use the Select2 search functionality, but there's not much we can do about
|
||||
* that right now.
|
||||
*
|
||||
* As a result, instead of paginating as part of the query, we have to grab
|
||||
* the entire data set, and then invoke a paginator manually and pass that
|
||||
* through to the SelectListTransformer.
|
||||
*
|
||||
* Many thanks to @uberbrady for the help getting this working better.
|
||||
* Recursion still sucks, but I guess he doesn't have to get in the
|
||||
* sea... this time.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0.16]
|
||||
* @see \App\Http\Transformers\SelectlistTransformer
|
||||
@@ -156,25 +223,44 @@ class LocationsController extends Controller
|
||||
$locations = Location::select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
'locations.parent_id',
|
||||
'locations.image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
$locations = $locations->where('locations.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
$page = 1;
|
||||
if ($request->filled('page')) {
|
||||
$page = $request->input('page');
|
||||
}
|
||||
|
||||
$locations = $locations->orderBy('name', 'ASC')->paginate(50);
|
||||
if ($request->filled('search')) {
|
||||
$locations = $locations->where('locations.name', 'LIKE', '%'.$request->input('search').'%');
|
||||
}
|
||||
|
||||
$locations = $locations->orderBy('name', 'ASC')->get();
|
||||
|
||||
$locations_with_children = [];
|
||||
|
||||
// Loop through and set some custom properties for the transformer to use.
|
||||
// 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 ($locations as $location) {
|
||||
$location->use_text = $location->name;
|
||||
$location->use_image = ($location->image) ? url('/').'/uploads/locations/'.$location->image : null;
|
||||
if (!array_key_exists($location->parent_id, $locations_with_children)) {
|
||||
$locations_with_children[$location->parent_id] = [];
|
||||
}
|
||||
$locations_with_children[$location->parent_id][] = $location;
|
||||
}
|
||||
|
||||
return (new SelectlistTransformer)->transformSelectlist($locations);
|
||||
if ($request->filled('search')) {
|
||||
$locations_formatted = $locations;
|
||||
} else {
|
||||
$location_options = Location::indenter($locations_with_children);
|
||||
$locations_formatted = new Collection($location_options);
|
||||
|
||||
}
|
||||
|
||||
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
|
||||
|
||||
//return [];
|
||||
return (new SelectlistTransformer)->transformSelectlist($paginated_results);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -22,19 +22,28 @@ class ManufacturersController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Manufacturer::class);
|
||||
$allowed_columns = ['id','name','url','support_url','support_email','support_phone','created_at','updated_at','image'];
|
||||
$allowed_columns = ['id','name','url','support_url','support_email','support_phone','created_at','updated_at','image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
|
||||
|
||||
$manufacturers = Manufacturer::select(
|
||||
array('id','name','url','support_url','support_email','support_phone','created_at','updated_at','image')
|
||||
)->withCount('assets')->withCount('licenses')->withCount('consumables')->withCount('accessories');
|
||||
array('id','name','url','support_url','support_email','support_phone','created_at','updated_at','image', 'deleted_at')
|
||||
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count');
|
||||
|
||||
if ($request->input('deleted')=='true') {
|
||||
$manufacturers->onlyTrashed();
|
||||
}
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$manufacturers = $manufacturers->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
|
||||
// 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 = (($manufacturers) && ($request->get('offset') > $manufacturers->count())) ? $manufacturers->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$manufacturers->orderBy($sort, $order);
|
||||
@@ -77,7 +86,7 @@ class ManufacturersController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Manufacturer::class);
|
||||
$manufacturer = Manufacturer::findOrFail($id);
|
||||
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
|
||||
return (new ManufacturersTransformer)->transformManufacturer($manufacturer);
|
||||
}
|
||||
|
||||
@@ -93,7 +102,7 @@ class ManufacturersController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Manufacturer::class);
|
||||
$this->authorize('update', Manufacturer::class);
|
||||
$manufacturer = Manufacturer::findOrFail($id);
|
||||
$manufacturer->fill($request->all());
|
||||
|
||||
@@ -114,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')));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -139,7 +158,7 @@ class ManufacturersController extends Controller
|
||||
'image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$manufacturers = $manufacturers->where('name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -150,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
|
||||
}
|
||||
}
|
||||
48
app/Http/Controllers/Api/ProfileController.php
Normal file
48
app/Http/Controllers/Api/ProfileController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CheckoutRequest;
|
||||
use Auth;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of requested assets.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.3.0]
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function requestedAssets()
|
||||
{
|
||||
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
|
||||
|
||||
$results = [];
|
||||
$results['total'] = $checkoutRequests->count();
|
||||
|
||||
|
||||
foreach ($checkoutRequests as $checkoutRequest) {
|
||||
|
||||
// Make sure the asset and request still exist
|
||||
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
|
||||
$results['rows'][] = [
|
||||
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
|
||||
'name' => $checkoutRequest->itemRequested()->present()->name(),
|
||||
'type' => $checkoutRequest->itemType(),
|
||||
'qty' => $checkoutRequest->quantity,
|
||||
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
|
||||
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
|
||||
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -18,43 +18,49 @@ class ReportsController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('reports.view');
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'target','location');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
|
||||
}
|
||||
|
||||
if (($request->has('target_type')) && ($request->has('target_id'))) {
|
||||
if (($request->filled('target_type')) && ($request->filled('target_id'))) {
|
||||
$actionlogs = $actionlogs->where('target_id','=',$request->input('target_id'))
|
||||
->where('target_type','=',"App\\Models\\".ucwords($request->input('target_type')));
|
||||
|
||||
}
|
||||
|
||||
if (($request->has('item_type')) && ($request->has('item_id'))) {
|
||||
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
|
||||
$actionlogs = $actionlogs->where('item_id','=',$request->input('item_id'))
|
||||
->where('item_type','=',"App\\Models\\".ucwords($request->input('item_type')));
|
||||
}
|
||||
|
||||
if ($request->has('action_type')) {
|
||||
if ($request->filled('action_type')) {
|
||||
$actionlogs = $actionlogs->where('action_type','=',$request->input('action_type'))->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
if ($request->filled('uploads')) {
|
||||
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'created_at'
|
||||
'created_at',
|
||||
'target_id',
|
||||
'user_id',
|
||||
'action_type',
|
||||
'note'
|
||||
];
|
||||
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
$total = $actionlogs->count();
|
||||
$actionlogs = $actionlogs->orderBy($sort, $order);
|
||||
$actionlogs = $actionlogs->skip($offset)->take($limit)->get();
|
||||
return (new ActionlogsTransformer)->transformActionlogs($actionlogs, $total);
|
||||
|
||||
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
|
||||
|
||||
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,100 +2,128 @@
|
||||
|
||||
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\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;
|
||||
|
||||
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 cannot test.');
|
||||
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) {
|
||||
\Log::debug('Bind failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
$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' => 'Error logging into LDAP server, error: ' . $ex->getMessage() . ' - Verify your that your username and password are correct']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed');
|
||||
return response()->json(['message' => $e->getMessage()], 600);
|
||||
\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
|
||||
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();
|
||||
$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 slacktest(Request $request)
|
||||
{
|
||||
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
\Log::debug('LDAP is not enabled. Cannot test.');
|
||||
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
|
||||
}
|
||||
$slack = new Client([
|
||||
'base_url' => e($request->input('slack_endpoint')),
|
||||
'defaults' => [
|
||||
'exceptions' => false
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
$rules = array(
|
||||
'ldaptest_user' => 'required',
|
||||
'ldaptest_password' => 'required'
|
||||
);
|
||||
$payload = json_encode(
|
||||
[
|
||||
'channel' => e($request->input('slack_channel')),
|
||||
'text' => trans('general.slack_test_msg'),
|
||||
'username' => e($request->input('slack_botname')),
|
||||
'icon_emoji' => ':heart:'
|
||||
]);
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
if ($validator->fails()) {
|
||||
\Log::debug('LDAP Validation test failed.');
|
||||
$validation_errors = implode(' ',$validator->errors()->all());
|
||||
return response()->json(['message' => $validator->errors()->all()], 400);
|
||||
}
|
||||
|
||||
|
||||
\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);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('LDAP login failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Bind failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
$slack->post($request->input('slack_endpoint'),['body' => $payload]);
|
||||
return response()->json(['message' => 'Success'], 200);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed');
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the email configuration
|
||||
*
|
||||
@@ -107,11 +135,7 @@ class SettingsController extends Controller
|
||||
{
|
||||
if (!config('app.lock_passwords')) {
|
||||
try {
|
||||
Mail::send('emails.test', [], function ($m) {
|
||||
$m->to(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
|
||||
$m->subject(trans('mail.test_email'));
|
||||
});
|
||||
Notification::send(Setting::first(), new MailTest());
|
||||
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||
} catch (Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
@@ -122,5 +146,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
|
||||
{
|
||||
@@ -22,16 +22,21 @@ class StatuslabelsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
$allowed_columns = ['id','name','created_at', 'assets_count'];
|
||||
$allowed_columns = ['id','name','created_at', 'assets_count','color','default_label'];
|
||||
|
||||
$statuslabels = Statuslabel::withCount('assets');
|
||||
$statuslabels = Statuslabel::withCount('assets as assets_count');
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$statuslabels = $statuslabels->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($statuslabels) && ($request->get('offset') > $statuslabels->count())) ? $statuslabels->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$statuslabels->orderBy($sort, $order);
|
||||
@@ -55,8 +60,8 @@ class StatuslabelsController extends Controller
|
||||
$this->authorize('create', Statuslabel::class);
|
||||
$request->except('deployable', 'pending','archived');
|
||||
|
||||
if (!$request->has('type')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ["type" => ["Status label type is required."]]));
|
||||
if (!$request->filled('type')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ["type" => ["Status label type is required."]]),500);
|
||||
}
|
||||
|
||||
$statuslabel = new Statuslabel;
|
||||
@@ -101,12 +106,12 @@ class StatuslabelsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Statuslabel::class);
|
||||
$this->authorize('update', Statuslabel::class);
|
||||
$statuslabel = Statuslabel::findOrFail($id);
|
||||
|
||||
$request->except('deployable', 'pending','archived');
|
||||
|
||||
if (!$request->has('type')) {
|
||||
if (!$request->filled('type')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Status label type is required.'));
|
||||
}
|
||||
|
||||
@@ -160,8 +165,9 @@ class StatuslabelsController extends Controller
|
||||
|
||||
public function getAssetCountByStatuslabel()
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$statuslabels = Statuslabel::with('assets')->groupBy('id')->withCount('assets')->get();
|
||||
$statuslabels = Statuslabel::with('assets')->groupBy('id')->withCount('assets as assets_count')->get();
|
||||
|
||||
$labels=[];
|
||||
$points=[];
|
||||
@@ -203,11 +209,11 @@ class StatuslabelsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
$this->authorize('index', Asset::class);
|
||||
$assets = Asset::where('status_id','=',$id);
|
||||
$assets = Asset::where('status_id','=',$id)->with('assignedTo');
|
||||
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'name'
|
||||
'name',
|
||||
];
|
||||
|
||||
$offset = request('offset', 0);
|
||||
|
||||
@@ -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,19 +22,24 @@ 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')
|
||||
)->withCount('assets')->withCount('licenses')->withCount('accessories')->whereNull('deleted_at');
|
||||
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes')
|
||||
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count');
|
||||
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$suppliers = $suppliers->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
// 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 = (($suppliers) && ($request->get('offset') > $suppliers->count())) ? $suppliers->count() : $request->get('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');
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
$suppliers->orderBy($sort, $order);
|
||||
@@ -93,7 +98,7 @@ class SuppliersController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('edit', Supplier::class);
|
||||
$this->authorize('update', Supplier::class);
|
||||
$supplier = Supplier::findOrFail($id);
|
||||
$supplier->fill($request->all());
|
||||
|
||||
@@ -115,7 +120,7 @@ class SuppliersController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Supplier::class);
|
||||
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances','assets', 'licenses')->findOrFail($id);
|
||||
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count','assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
$this->authorize('delete', $supplier);
|
||||
|
||||
|
||||
@@ -153,7 +158,7 @@ class SuppliersController extends Controller
|
||||
'image',
|
||||
]);
|
||||
|
||||
if ($request->has('search')) {
|
||||
if ($request->filled('search')) {
|
||||
$suppliers = $suppliers->where('suppliers.name', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
|
||||
@@ -164,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,16 +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\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
|
||||
{
|
||||
@@ -28,64 +33,81 @@ class UsersController extends Controller
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$users = User::select([
|
||||
'users.id',
|
||||
'users.employee_num',
|
||||
'users.two_factor_enrolled',
|
||||
'users.jobtitle',
|
||||
'users.email',
|
||||
'users.phone',
|
||||
'users.activated',
|
||||
'users.address',
|
||||
'users.avatar',
|
||||
'users.city',
|
||||
'users.state',
|
||||
'users.country',
|
||||
'users.zip',
|
||||
'users.username',
|
||||
'users.location_id',
|
||||
'users.manager_id',
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
'users.created_at',
|
||||
'users.notes',
|
||||
'users.company_id',
|
||||
'users.last_login',
|
||||
'users.country',
|
||||
'users.created_at',
|
||||
'users.deleted_at',
|
||||
'users.department_id',
|
||||
'users.activated',
|
||||
'users.avatar',
|
||||
'users.email',
|
||||
'users.employee_num',
|
||||
'users.first_name',
|
||||
'users.id',
|
||||
'users.jobtitle',
|
||||
'users.last_login',
|
||||
'users.last_name',
|
||||
'users.location_id',
|
||||
'users.manager_id',
|
||||
'users.notes',
|
||||
'users.permissions',
|
||||
'users.phone',
|
||||
'users.state',
|
||||
'users.two_factor_enrolled',
|
||||
'users.two_factor_optin',
|
||||
'users.updated_at',
|
||||
'users.username',
|
||||
'users.zip',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department','assets','licenses','accessories','consumables')
|
||||
->withCount('assets','licenses','accessories','consumables');
|
||||
->withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count');
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
|
||||
if ($request->has('search')) {
|
||||
$users = $users->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
if (($request->has('deleted')) && ($request->input('deleted')=='true')) {
|
||||
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
|
||||
$users = $users->GetDeleted();
|
||||
}
|
||||
|
||||
if ($request->has('company_id')) {
|
||||
$users = $users->where('company_id', '=', $request->input('company_id'));
|
||||
if ($request->filled('company_id')) {
|
||||
$users = $users->where('users.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->has('location_id')) {
|
||||
$users = $users->where('location_id', '=', $request->input('location_id'));
|
||||
if ($request->filled('location_id')) {
|
||||
$users = $users->where('users.location_id', '=', $request->input('location_id'));
|
||||
}
|
||||
|
||||
if ($request->has('group_id')) {
|
||||
|
||||
if ($request->filled('email')) {
|
||||
$users = $users->where('users.email', '=', $request->input('email'));
|
||||
}
|
||||
|
||||
if ($request->filled('username')) {
|
||||
$users = $users->where('users.username', '=', $request->input('username'));
|
||||
}
|
||||
|
||||
if ($request->filled('group_id')) {
|
||||
$users = $users->ByGroup($request->get('group_id'));
|
||||
}
|
||||
|
||||
if ($request->has('department_id')) {
|
||||
if ($request->filled('department_id')) {
|
||||
$users = $users->where('users.department_id','=',$request->input('department_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$users = $users->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 20);
|
||||
$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.
|
||||
$offset = (($users) && ($request->get('offset') > $users->count())) ? $users->count() : $request->get('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');
|
||||
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'manager':
|
||||
@@ -97,6 +119,9 @@ class UsersController extends Controller
|
||||
case 'department':
|
||||
$users = $users->OrderDepartment($order);
|
||||
break;
|
||||
case 'company':
|
||||
$users = $users->OrderCompany($order);
|
||||
break;
|
||||
default:
|
||||
$allowed_columns =
|
||||
[
|
||||
@@ -104,13 +129,15 @@ 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'
|
||||
'country', 'zip', 'id'
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
$users = $users->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$total = $users->count();
|
||||
$users = $users->skip($offset)->take($limit)->get();
|
||||
return (new UsersTransformer)->transformUsers($users, $total);
|
||||
@@ -131,6 +158,7 @@ class UsersController extends Controller
|
||||
$users = User::select(
|
||||
[
|
||||
'users.id',
|
||||
'users.username',
|
||||
'users.employee_num',
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
@@ -138,13 +166,12 @@ class UsersController extends Controller
|
||||
'users.avatar',
|
||||
'users.email',
|
||||
]
|
||||
);
|
||||
)->where('show_in_list', '=', '1');
|
||||
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
if ($request->has('search')) {
|
||||
$users = $users->where('first_name', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('last_name', 'LIKE', '%'.$request->get('search').'%')
|
||||
if ($request->filled('search')) {
|
||||
$users = $users->SimpleNameSearch($request->get('search'))
|
||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||
}
|
||||
@@ -159,8 +186,12 @@ class UsersController extends Controller
|
||||
}
|
||||
$name_str .= e($user->first_name);
|
||||
|
||||
if ($user->username!='') {
|
||||
$name_str .= ' ('.e($user->username).')';
|
||||
}
|
||||
|
||||
if ($user->employee_num!='') {
|
||||
$name_str .= ' (#'.e($user->employee_num).')';
|
||||
$name_str .= ' - #'.e($user->employee_num);
|
||||
}
|
||||
|
||||
$user->use_text = $name_str;
|
||||
@@ -183,12 +214,33 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function store(SaveUserRequest $request)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('create', User::class);
|
||||
|
||||
$user = new User;
|
||||
$user->fill($request->all());
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
|
||||
if ($request->has('permissions')) {
|
||||
|
||||
$permissions_array = $request->input('permissions');
|
||||
|
||||
// Strip out the superuser permission if the API user isn't a superadmin
|
||||
if (!Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
}
|
||||
$user->permissions = $permissions_array;
|
||||
}
|
||||
|
||||
$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'));
|
||||
} else {
|
||||
$user->groups()->sync(array());
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.create')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
|
||||
@@ -204,7 +256,7 @@ class UsersController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$user = User::withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count')->findOrFail($id);
|
||||
return (new UsersTransformer)->transformUser($user);
|
||||
}
|
||||
|
||||
@@ -220,23 +272,67 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function update(SaveUserRequest $request, $id)
|
||||
{
|
||||
$this->authorize('edit', User::class);
|
||||
$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')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
||||
}
|
||||
|
||||
if ($request->has('password')) {
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
// We need to use has() instead of filled()
|
||||
// here because we need to overwrite permissions
|
||||
// if someone needs to null them out
|
||||
if ($request->has('permissions')) {
|
||||
|
||||
$permissions_array = $request->input('permissions');
|
||||
|
||||
// Strip out the superuser permission if the API user isn't a superadmin
|
||||
if (!Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
}
|
||||
$user->permissions = $permissions_array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
if ($user->save()) {
|
||||
|
||||
// Sync group memberships:
|
||||
// This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5
|
||||
// which changes the behavior of has vs filled.
|
||||
// The $request->has method will now return true even if the input value is an empty string or null.
|
||||
// A new $request->filled method has was added that provides the previous behavior of the has method.
|
||||
|
||||
// Check if the request has groups passed and has a value
|
||||
if ($request->filled('groups')) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
// The groups field has been passed but it is null, so we should blank it out
|
||||
} elseif ($request->has('groups')) {
|
||||
$user->groups()->sync(array());
|
||||
}
|
||||
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
|
||||
}
|
||||
|
||||
@@ -258,11 +354,32 @@ class UsersController extends Controller
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
|
||||
if ($user->assets()->count() > 0) {
|
||||
if (($user->assets) && ($user->assets->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
|
||||
}
|
||||
|
||||
if (($user->licenses) && ($user->licenses->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (($user->accessories) && ($user->accessories->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.'));
|
||||
}
|
||||
|
||||
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
|
||||
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')));
|
||||
@@ -279,7 +396,85 @@ class UsersController extends Controller
|
||||
public function assets($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$assets = Asset::where('assigned_to', '=', $id)->with('model')->get();
|
||||
$this->authorize('view', Asset::class);
|
||||
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON containing a list of accessories assigned to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.6.14]
|
||||
* @param $userId
|
||||
* @return string JSON
|
||||
*/
|
||||
public function accessories($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('view', Accessory::class);
|
||||
$accessories = $user->accessories;
|
||||
return (new AccessoriesTransformer)->transformAccessories($accessories, $accessories->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON containing a list of licenses assigned to a user.
|
||||
*
|
||||
* @author [N. Mathar] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @param $userId
|
||||
* @return string JSON
|
||||
*/
|
||||
public function licenses($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', License::class);
|
||||
$user = User::where('id', $id)->withTrashed()->first();
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
||||
* Reset the user's two-factor status
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $userId
|
||||
* @return string JSON
|
||||
*/
|
||||
public function postTwoFactorReset(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
if ($request->filled('id')) {
|
||||
try {
|
||||
$user = User::find($request->get('id'));
|
||||
$user->two_factor_secret = null;
|
||||
$user->two_factor_enrolled = 0;
|
||||
$user->save();
|
||||
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_error')], 500);
|
||||
}
|
||||
}
|
||||
return response()->json(['message' => 'No ID provided'], 500);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info on the current user.
|
||||
*
|
||||
* @author [Juan Font] [<juanfontalonso@gmail.com>]
|
||||
* @since [v4.4.2]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function getCurrentUserInfo(Request $request)
|
||||
{
|
||||
return (new UsersTransformer)->transformUser($request->user());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -63,85 +54,6 @@ class AssetMaintenancesController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the JSON response for asset maintenances listing view.
|
||||
*
|
||||
* @see AssetMaintenancesController::getIndex() method that generates view
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
* @since [v1.8]
|
||||
* @return String JSON
|
||||
*/
|
||||
public function getDatatable(Request $request)
|
||||
{
|
||||
$maintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company', 'admin');
|
||||
|
||||
if (Input::has('search')) {
|
||||
$maintenances = $maintenances->TextSearch(e($request->input('search')));
|
||||
}
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$allowed_columns = ['id','title','asset_maintenance_time','asset_maintenance_type','cost','start_date','completion_date','notes','user_id'];
|
||||
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
|
||||
switch ($sort) {
|
||||
case 'user_id':
|
||||
$maintenances = $maintenances->OrderAdmin($order);
|
||||
break;
|
||||
default:
|
||||
$maintenances = $maintenances->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
$maintenancesCount = $maintenances->count();
|
||||
$maintenances = $maintenances->skip($offset)->take($limit)->get();
|
||||
|
||||
$rows = array();
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
foreach ($maintenances as $maintenance) {
|
||||
$actions = '';
|
||||
if (Gate::allows('update', Asset::class)) {
|
||||
$actions .= Helper::generateDatatableButton('edit', route('maintenances.edit', $maintenance->id));
|
||||
$actions .= Helper::generateDatatableButton(
|
||||
'delete',
|
||||
route('maintenances.destroy', $maintenance->id),
|
||||
$enabled = true,
|
||||
trans('admin/asset_maintenances/message.delete.confirm'),
|
||||
$maintenance->title
|
||||
);
|
||||
}
|
||||
|
||||
if (($maintenance->cost) && (isset($maintenance->asset)) && ($maintenance->asset->location) && ($maintenance->asset->location->currency!='')) {
|
||||
$maintenance_cost = $maintenance->asset->location->currency.$maintenance->cost;
|
||||
} else {
|
||||
$maintenance_cost = $settings->default_currency.$maintenance->cost;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
'id' => $maintenance->id,
|
||||
'asset_name' => ($maintenance->asset) ? (string)link_to_route('maintenances.show', $maintenance->asset->present()->Name(), ['maintenance' => $maintenance->asset->id]) : 'Deleted Asset' ,
|
||||
'title' => $maintenance->title,
|
||||
'notes' => $maintenance->notes,
|
||||
'supplier' => ($maintenance->supplier) ? (string)link_to_route('suppliers.show', $maintenance->supplier->name, ['maintenance'=>$maintenance->supplier->id]) : 'Deleted Supplier',
|
||||
'cost' => $maintenance_cost,
|
||||
'asset_maintenance_type' => e($maintenance->asset_maintenance_type),
|
||||
'start_date' => $maintenance->start_date,
|
||||
'asset_maintenance_time' => $maintenance->asset_maintenance_time,
|
||||
'completion_date' => $maintenance->completion_date,
|
||||
'user_id' => ($maintenance->admin) ? (string)link_to_route('users.show', $maintenance->admin->present()->fullName(), ['user'=>$maintenance->admin->id]) : '',
|
||||
'actions' => $actions,
|
||||
'company' => ($maintenance->asset->company) ? $maintenance->asset->company->name : ''
|
||||
);
|
||||
}
|
||||
|
||||
$data = array('total' => $maintenancesCount, 'rows' => $rows);
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form view to create a new asset maintenance.
|
||||
@@ -192,7 +104,7 @@ class AssetMaintenancesController extends Controller
|
||||
$assetMaintenance->notes = e($request->input('notes'));
|
||||
$asset = Asset::find(e($request->input('asset_id')));
|
||||
|
||||
if (!Company::isCurrentUserHasAccess($asset)) {
|
||||
if ((!Company::isCurrentUserHasAccess($asset)) && ($asset!=null)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
@@ -241,6 +153,10 @@ class AssetMaintenancesController extends Controller
|
||||
// Redirect to the improvement management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} 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();
|
||||
}
|
||||
@@ -265,9 +181,7 @@ class AssetMaintenancesController extends Controller
|
||||
// Get Supplier List
|
||||
// Render the view
|
||||
return view('asset_maintenances/edit')
|
||||
->with('asset_list', Helper::detailedAssetList())
|
||||
->with('selectedAsset', null)
|
||||
->with('supplier_list', Helper::suppliersList())
|
||||
->with('assetMaintenanceType', $assetMaintenanceType)
|
||||
->with('item', $assetMaintenance);
|
||||
|
||||
|
||||
@@ -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,44 +23,50 @@ 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);
|
||||
return view('models/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$category_type = 'asset';
|
||||
return view('models/edit')->with('category_type',$category_type)
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', new AssetModel);
|
||||
$this->authorize('create', AssetModel::class);
|
||||
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)
|
||||
{
|
||||
|
||||
$this->authorize('create', AssetModel::class);
|
||||
// Create a new asset model
|
||||
$model = new AssetModel;
|
||||
|
||||
@@ -81,32 +79,20 @@ 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'));
|
||||
}
|
||||
|
||||
if (Input::file('image')) {
|
||||
|
||||
$image = Input::file('image');
|
||||
$file_name = str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
|
||||
$path = app('models_upload_path');
|
||||
|
||||
if ($image->getClientOriginalExtension()!='svg') {
|
||||
Image::make($image->getRealPath())->resize(500, null, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save($path.'/'.$file_name);
|
||||
} else {
|
||||
$image->move($path, $file_name);
|
||||
}
|
||||
$model->image = $file_name;
|
||||
|
||||
}
|
||||
$model = $request->handleImages($model);
|
||||
|
||||
// Was it created?
|
||||
if ($model->save()) {
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
|
||||
}
|
||||
|
||||
// Redirect to the new model page
|
||||
return redirect()->route("models.index")->with('success', trans('admin/models/message.create.success'));
|
||||
}
|
||||
@@ -114,54 +100,17 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores new Asset Model data created from the
|
||||
* modal form on the Asset Creation view.
|
||||
* Returns a view containing the asset model edit form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @param Request $request
|
||||
* @return String JSON
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function apiStore(Request $request)
|
||||
{
|
||||
//COPYPASTA!!!! FIXME
|
||||
$model = new AssetModel;
|
||||
|
||||
$settings=Input::all();
|
||||
$settings['eol']= null;
|
||||
|
||||
$model->name=$request->input('name');
|
||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||
$model->category_id = $request->input('category_id');
|
||||
$model->model_number = $request->input('model_number');
|
||||
$model->user_id = Auth::id();
|
||||
$model->notes = $request->input('notes');
|
||||
$model->eol= null;
|
||||
|
||||
if ($request->input('fieldset_id')=='') {
|
||||
$model->fieldset_id = null;
|
||||
} else {
|
||||
$model->fieldset_id = e($request->input('fieldset_id'));
|
||||
}
|
||||
|
||||
if ($model->save()) {
|
||||
return JsonResponse::create($model);
|
||||
} else {
|
||||
return JsonResponse::create(["error" => "Failed validation: ".print_r($model->getErrors()->all('<li>:message</li>'), true)], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view containing the asset model edit form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @return View
|
||||
*/
|
||||
public function edit($modelId = null)
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
if ($item = AssetModel::find($modelId)) {
|
||||
$category_type = 'asset';
|
||||
$view = View::make('models/edit', compact('item','category_type'));
|
||||
@@ -175,22 +124,27 @@ 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);
|
||||
// Check if the model exists
|
||||
if (is_null($model = AssetModel::find($modelId))) {
|
||||
// Redirect to the models management page
|
||||
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');
|
||||
@@ -200,40 +154,17 @@ class AssetModelsController extends Controller
|
||||
$model->notes = $request->input('notes');
|
||||
$model->requestable = $request->input('requestable', '0');
|
||||
|
||||
|
||||
|
||||
$this->removeCustomFieldsDefaultValues($model);
|
||||
|
||||
if ($request->input('custom_fieldset')=='') {
|
||||
$model->fieldset_id = null;
|
||||
} else {
|
||||
$model->fieldset_id = $request->input('custom_fieldset');
|
||||
}
|
||||
|
||||
$old_image = $model->image;
|
||||
|
||||
// Set the model's image property to null if the image is being deleted
|
||||
if ($request->input('image_delete') == 1) {
|
||||
$model->image = null;
|
||||
}
|
||||
|
||||
if ($request->file('image')) {
|
||||
$image = $request->file('image');
|
||||
$file_name = $model->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
|
||||
|
||||
if ($image->getClientOriginalExtension()!='svg') {
|
||||
Image::make($image->getRealPath())->resize(500, null, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->save(app('models_upload_path').$file_name);
|
||||
} else {
|
||||
$image->move(app('models_upload_path'), $file_name);
|
||||
}
|
||||
$model->image = $file_name;
|
||||
|
||||
}
|
||||
|
||||
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
|
||||
try {
|
||||
unlink(app('models_upload_path').$old_image);
|
||||
} catch (\Exception $e) {
|
||||
\Log::error($e);
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,16 +176,18 @@ 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);
|
||||
// Check if the model exists
|
||||
if (is_null($model = AssetModel::find($modelId))) {
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.not_found'));
|
||||
@@ -267,9 +200,9 @@ 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::error($e);
|
||||
\Log::info($e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,30 +215,23 @@ 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);
|
||||
// Get user information
|
||||
$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'));
|
||||
|
||||
@@ -313,25 +239,24 @@ 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);
|
||||
$model = AssetModel::withTrashed()->find($modelId);
|
||||
|
||||
if (isset($model->id)) {
|
||||
return view('models/view', compact('model'));
|
||||
}
|
||||
// Prepare the error message
|
||||
$error = trans('admin/models/message.does_not_exist', compact('id'));
|
||||
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('models.index')->with('error', $error);
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,14 +278,10 @@ class AssetModelsController extends Controller
|
||||
$model->id = null;
|
||||
|
||||
// Show the page
|
||||
$view = View::make('models/edit');
|
||||
$view->with('category_list', Helper::categoryList('asset'));
|
||||
$view->with('depreciation_list', Helper::depreciationList());
|
||||
$view->with('manufacturer_list', Helper::manufacturerList());
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
@@ -374,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));
|
||||
}
|
||||
|
||||
|
||||
@@ -390,23 +310,37 @@ class AssetModelsController extends Controller
|
||||
*/
|
||||
public function postBulkEdit(Request $request)
|
||||
{
|
||||
|
||||
$models_raw_array = Input::get('ids');
|
||||
|
||||
if (is_array($models_raw_array)) {
|
||||
$models = AssetModel::whereIn('id', $models_raw_array)->get();
|
||||
$nochange = ['NC' => 'No Change'];
|
||||
$fieldset_list = $nochange + Helper::customFieldsetList();
|
||||
$depreciation_list = $nochange + Helper::depreciationList();
|
||||
$category_list = $nochange + Helper::categoryList('asset');
|
||||
$manufacturer_list = $nochange + Helper::manufacturerList();
|
||||
$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
|
||||
} else {
|
||||
|
||||
$nochange = ['NC' => 'No Change'];
|
||||
$fieldset_list = $nochange + Helper::customFieldsetList();
|
||||
$depreciation_list = $nochange + Helper::depreciationList();
|
||||
|
||||
return view('models/bulk-edit', compact('models'))
|
||||
->with('fieldset_list', $fieldset_list)
|
||||
->with('depreciation_list', $depreciation_list);
|
||||
}
|
||||
|
||||
|
||||
return view('models/bulk-edit', compact('models'))
|
||||
->with('manufacturer_list', $manufacturer_list)
|
||||
->with('category_list', $category_list)
|
||||
->with('fieldset_list', $fieldset_list)
|
||||
->with('depreciation_list', $depreciation_list);
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
@@ -426,13 +360,14 @@ class AssetModelsController extends Controller
|
||||
public function postBulkEditSave(Request $request)
|
||||
{
|
||||
|
||||
$models_raw_array = Input::get('ids');
|
||||
$models_raw_array = $request->input('ids');
|
||||
$update_array = array();
|
||||
|
||||
if (($request->has('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
|
||||
|
||||
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
|
||||
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
|
||||
}
|
||||
if (($request->has('category_id') && ($request->input('category_id')!='NC'))) {
|
||||
if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) {
|
||||
$update_array['category_id'] = $request->input('category_id');
|
||||
}
|
||||
if ($request->input('fieldset_id')!='NC') {
|
||||
@@ -455,4 +390,91 @@ class AssetModelsController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
*/
|
||||
public function postBulkDelete(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) {
|
||||
\Log::debug($model->id);
|
||||
|
||||
if ($model->assets_count > 0) {
|
||||
$del_error_count++;
|
||||
} else {
|
||||
$model->delete();
|
||||
$del_count++;
|
||||
}
|
||||
}
|
||||
|
||||
\Log::debug($del_count);
|
||||
\Log::debug($del_error_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'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a fieldset is set, 'add default values' is ticked and if
|
||||
* any default values were entered into the form.
|
||||
*
|
||||
* @param array $input
|
||||
* @return boolean
|
||||
*/
|
||||
private function shouldAddDefaultValues(array $input)
|
||||
{
|
||||
return !empty($input['add_default_values'])
|
||||
&& !empty($input['default_values'])
|
||||
&& !empty($input['custom_fieldset']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default values to a model (as long as they are truthy)
|
||||
*
|
||||
* @param AssetModel $model
|
||||
* @param array $defaultValues
|
||||
* @return void
|
||||
*/
|
||||
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
|
||||
{
|
||||
foreach ($defaultValues as $customFieldId => $defaultValue) {
|
||||
if ($defaultValue) {
|
||||
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all default values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function removeCustomFieldsDefaultValues(AssetModel $model)
|
||||
{
|
||||
$model->defaultValues()->detach();
|
||||
}
|
||||
}
|
||||
|
||||
102
app/Http/Controllers/Assets/AssetCheckinController.php
Normal file
102
app/Http/Controllers/Assets/AssetCheckinController.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
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\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
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function create($assetId, $backto = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset = Asset::find($assetId))) {
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('checkin', $asset);
|
||||
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and process the form data to check an asset back into inventory.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param AssetCheckinRequest $request
|
||||
* @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)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset = Asset::find($assetId))) {
|
||||
// Redirect to the asset management page with error
|
||||
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;
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->assigned_type = null;
|
||||
$asset->accepted = null;
|
||||
$asset->name = $request->get('name');
|
||||
|
||||
if ($request->filled('status_id')) {
|
||||
$asset->status_id = e($request->get('status_id'));
|
||||
}
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
if ($request->filled('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()) {
|
||||
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'));
|
||||
}
|
||||
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'));
|
||||
}
|
||||
}
|
||||
91
app/Http/Controllers/Assets/AssetCheckoutController.php
Normal file
91
app/Http/Controllers/Assets/AssetCheckoutController.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AssetCheckoutController extends Controller
|
||||
{
|
||||
use CheckInOutRequest;
|
||||
/**
|
||||
* Returns a view that presents a form to check an asset out to a
|
||||
* user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function create($assetId)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset = Asset::find(e($assetId)))) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $asset);
|
||||
|
||||
if ($asset->availableForCheckout()) {
|
||||
return view('hardware/checkout', compact('asset'));
|
||||
}
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and process the form data to check out an asset to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param AssetCheckoutRequest $request
|
||||
* @param int $assetId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
*/
|
||||
public function store(AssetCheckoutRequest $request, $assetId)
|
||||
{
|
||||
try {
|
||||
// Check if the asset exists
|
||||
if (!$asset = Asset::find($assetId)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
} elseif (!$asset->availableForCheckout()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
|
||||
}
|
||||
$this->authorize('checkout', $asset);
|
||||
$admin = Auth::user();
|
||||
|
||||
$target = $this->determineCheckoutTarget($asset);
|
||||
|
||||
$asset = $this->updateAssetLocation($asset, $target);
|
||||
|
||||
$checkout_at = date("Y-m-d H:i:s");
|
||||
if (($request->filled('checkout_at')) && ($request->get('checkout_at')!= date("Y-m-d"))) {
|
||||
$checkout_at = $request->get('checkout_at');
|
||||
}
|
||||
|
||||
$expected_checkin = '';
|
||||
if ($request->filled('expected_checkin')) {
|
||||
$expected_checkin = $request->get('expected_checkin');
|
||||
}
|
||||
|
||||
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());
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
|
||||
} catch (CheckoutNotAllowed $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
133
app/Http/Controllers/Assets/AssetFilesController.php
Normal file
133
app/Http/Controllers/Assets/AssetFilesController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AssetFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Upload a file to the server.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $assetId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $assetId = null)
|
||||
{
|
||||
if (!$asset = Asset::find($assetId)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
|
||||
if (!Storage::exists('private_uploads/assets')) Storage::makeDirectory('private_uploads/assets', 775);
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$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'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and display the file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($assetId = null, $fileId = null, $download = true)
|
||||
{
|
||||
$asset = Asset::find($assetId);
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
if (!$log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/assets/'.$log->filename;
|
||||
\Log::debug('Checking for '.$file);
|
||||
|
||||
if ($log->action_type =='audit') {
|
||||
$file = 'private_uploads/audits/'.$log->filename;
|
||||
}
|
||||
|
||||
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(Storage::url($file))) {
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
return JsonResponse::create(["error" => "Failed validation: "], 500);
|
||||
}
|
||||
return Storage::download($file);
|
||||
}
|
||||
// Prepare the error message
|
||||
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
|
||||
|
||||
// Redirect to the hardware management page
|
||||
return redirect()->route('hardware.index')->with('error', $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$rel_path = 'storage/private_uploads/assets';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($asset->id)) {
|
||||
$this->authorize('update', $asset);
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
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'));
|
||||
}
|
||||
$log->delete();
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the hardware management page
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
}
|
||||
804
app/Http/Controllers/Assets/AssetsController.php
Executable file
804
app/Http/Controllers/Assets/AssetsController.php
Executable file
@@ -0,0 +1,804 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
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\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Input;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
use Paginator;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use Slack;
|
||||
use Str;
|
||||
use TCPDF;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* This class controls all actions related to assets for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
class AssetsController extends Controller
|
||||
{
|
||||
protected $qrCodeDimensions = array( 'height' => 3.5, 'width' => 3.5);
|
||||
protected $barCodeDimensions = array( 'height' => 2, 'width' => 22);
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that invokes the ajax tables which actually contains
|
||||
* the content for the assets listing, which is generated in getDatatable.
|
||||
*
|
||||
* @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);
|
||||
$company = Company::find($request->input('company_id'));
|
||||
return view('hardware/index')->with('company', $company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to create a new asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param Request $request
|
||||
* @return View
|
||||
* @internal param int $model_id
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
$view = View::make('hardware/edit')
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('item', new Asset)
|
||||
->with('statuslabel_types', Helper::statusTypeList());
|
||||
|
||||
if ($request->filled('model_id')) {
|
||||
$selected_model = AssetModel::find($request->input('model_id'));
|
||||
$view->with('selected_model', $selected_model);
|
||||
}
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and process new asset form data.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
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');
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$success = false;
|
||||
$serials = $request->input('serials');
|
||||
|
||||
for ($a = 1; $a <= count($asset_tags); $a++) {
|
||||
|
||||
$asset = new Asset();
|
||||
$asset->model()->associate(AssetModel::find($request->input('model_id')));
|
||||
$asset->name = $request->input('name');
|
||||
|
||||
// Check for a corresponding serial
|
||||
if (($serials) && (array_key_exists($a, $serials))) {
|
||||
$asset->serial = $serials[$a];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!empty($settings->audit_interval)) {
|
||||
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
}
|
||||
|
||||
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')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
// Redirect to the asset listing page
|
||||
return redirect()->route('hardware.index')
|
||||
->with('success', trans('admin/hardware/message.create.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to edit an existing asset.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function edit($assetId = null)
|
||||
{
|
||||
if (!$item = Asset::find($assetId)) {
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
//Handles company checks and permissions.
|
||||
$this->authorize($item);
|
||||
|
||||
return view('hardware/edit', compact('item'))
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('statuslabel_types', Helper::statusTypeList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that presents information about an asset for detail view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function show($assetId = null)
|
||||
{
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
$this->authorize('view', $asset);
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if (isset($asset)) {
|
||||
$audit_log = Actionlog::where('action_type', '=', 'audit')
|
||||
->where('item_id', '=', $assetId)
|
||||
->where('item_type', '=', Asset::class)
|
||||
->orderBy('created_at', 'DESC')->first();
|
||||
|
||||
if ($asset->location) {
|
||||
$use_currency = $asset->location->currency;
|
||||
} else {
|
||||
if ($settings->default_currency!='') {
|
||||
$use_currency = $settings->default_currency;
|
||||
} else {
|
||||
$use_currency = trans('general.currency');
|
||||
}
|
||||
}
|
||||
|
||||
$qr_code = (object) array(
|
||||
'display' => $settings->qr_code == '1',
|
||||
'url' => route('qr_code/hardware', $asset->id)
|
||||
);
|
||||
|
||||
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
|
||||
->with('use_currency', $use_currency)->with('audit_log', $audit_log);
|
||||
}
|
||||
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and process asset edit form.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
|
||||
public function update(ImageUploadRequest $request, $assetId = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (!$asset = Asset::find($assetId)) {
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
$this->authorize($asset);
|
||||
|
||||
$asset->status_id = $request->input('status_id', null);
|
||||
$asset->warranty_months = $request->input('warranty_months', null);
|
||||
$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');
|
||||
$asset->rtd_location_id = $request->input('rtd_location_id', null);
|
||||
|
||||
if ($asset->assigned_to=='') {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('image_delete')) {
|
||||
try {
|
||||
unlink(public_path().'/uploads/assets/'.$asset->image);
|
||||
$asset->image = '';
|
||||
} catch (\Exception $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 = $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 = $asset_tag[1];
|
||||
$asset->notes = $request->input('notes');
|
||||
$asset->physical = '1';
|
||||
|
||||
$asset = $request->handleImages($asset);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handlded through the AssetRequest form request
|
||||
// FIXME: No idea why this is returning a Builder error on db_column_name.
|
||||
// Need to investigate and fix. Using static method for now.
|
||||
$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')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($asset->save()) {
|
||||
return redirect()->route("hardware.show", $assetId)
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given asset (mark as deleted).
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function destroy($assetId)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset = Asset::find($assetId))) {
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('delete', $asset);
|
||||
|
||||
DB::table('assets')
|
||||
->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'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Searches the assets table by asset tag, and redirects if it finds one
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return Redirect
|
||||
*/
|
||||
public function getAssetByTag(Request $request)
|
||||
{
|
||||
$topsearch = ($request->get('topsearch')=="true");
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* Return a QR code for the asset
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return Response
|
||||
*/
|
||||
public function getQrCode($assetId = null)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if ($settings->qr_code == '1') {
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
if ($asset) {
|
||||
$size = Helper::barcodeDimensions($settings->barcode_type);
|
||||
$qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png';
|
||||
|
||||
if (isset($asset->id, $asset->asset_tag)) {
|
||||
if (file_exists($qr_file)) {
|
||||
$header = ['Content-type' => 'image/png'];
|
||||
return response()->file($qr_file, $header);
|
||||
} else {
|
||||
$barcode = new \Com\Tecnick\Barcode\Barcode();
|
||||
$barcode_obj = $barcode->getBarcodeObj($settings->barcode_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', array(-2, -2, -2, -2));
|
||||
file_put_contents($qr_file, $barcode_obj->getPngData());
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'That asset is invalid';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a 2D barcode for the asset
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return Response
|
||||
*/
|
||||
public function getBarCode($assetId = null)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$asset = Asset::find($assetId);
|
||||
$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);
|
||||
} else {
|
||||
// Calculate barcode width in pixel based on label width (inch)
|
||||
$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);
|
||||
|
||||
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.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getClone($assetId = null)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset_to_clone = Asset::find($assetId))) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('create', $asset_to_clone);
|
||||
|
||||
$asset = clone $asset_to_clone;
|
||||
$asset->id = null;
|
||||
$asset->asset_tag = '';
|
||||
$asset->serial = '';
|
||||
$asset->assigned_to = '';
|
||||
|
||||
return view('hardware/edit')
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('statuslabel_types', Helper::statusTypeList())
|
||||
->with('item', $asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return history import view
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getImportHistory()
|
||||
{
|
||||
$this->authorize('admin');
|
||||
return view('hardware/history');
|
||||
}
|
||||
|
||||
/**
|
||||
* Import history
|
||||
*
|
||||
* This needs a LOT of love. It's done very inelegantly right now, and there are
|
||||
* a ton of optimizations that could (and should) be done.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.3]
|
||||
* @return View
|
||||
*/
|
||||
public function postImportHistory(Request $request)
|
||||
{
|
||||
|
||||
if (!$request->hasFile('user_import_csv')) {
|
||||
return back()->with('error', 'No file provided. Please select a file for import and try again. ');
|
||||
}
|
||||
|
||||
if (!ini_get("auto_detect_line_endings")) {
|
||||
ini_set("auto_detect_line_endings", '1');
|
||||
}
|
||||
$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);
|
||||
$asset_tag = Helper::array_smart_fetch($row, "asset tag");
|
||||
if (!array_key_exists($asset_tag, $item)) {
|
||||
$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'];
|
||||
$user->orWhere('username', '=', $flastname['username']);
|
||||
$user_query .= ', or on username '.$flastname['username'];
|
||||
}
|
||||
if ($request->input('match_firstname')=='1') {
|
||||
$firstname = User::generateFormattedNameFromFullName('firstname', $item[$asset_tag][$batch_counter]['name']);
|
||||
$item[$asset_tag][$batch_counter]['username'][] = $firstname['username'];
|
||||
$user->orWhere('username', '=', $firstname['username']);
|
||||
$user_query .= ', or on username '.$firstname['username'];
|
||||
}
|
||||
if ($request->input('match_email')=='1') {
|
||||
if ($item[$asset_tag][$batch_counter]['email']=='') {
|
||||
$item[$asset_tag][$batch_counter]['username'][] = $user_email = User::generateEmailFromFullName($item[$asset_tag][$batch_counter]['name']);
|
||||
$user->orWhere('username', '=', $user_email);
|
||||
$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,
|
||||
'user_id' => Auth::user()->id,
|
||||
'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer',
|
||||
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
|
||||
'target_type' => User::class,
|
||||
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
|
||||
'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 {
|
||||
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset and user was matched but could not be saved.';
|
||||
}
|
||||
} else {
|
||||
$item[$asset_tag][$batch_counter]['checkedout_to'] = null;
|
||||
$status['error'][]['user'][Helper::array_smart_fetch($row, "name")]['msg'] = 'User does not exist so no checkin log was created.';
|
||||
}
|
||||
} else {
|
||||
$item[$asset_tag][$batch_counter]['asset_id'] = null;
|
||||
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset does not exist so no match was attempted.';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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(
|
||||
'item_id' => $asset_batch[$x]['asset_id'],
|
||||
'item_type' => Asset::class,
|
||||
'user_id' => Auth::user()->id,
|
||||
'note' => 'Checkin imported by ' . Auth::user()->present()->fullName() . ' from history importer',
|
||||
'target_id' => null,
|
||||
'created_at' => $checkin_date,
|
||||
'action_type' => 'checkin'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $assetId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getRestore($assetId = null)
|
||||
{
|
||||
// Get asset information
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
$this->authorize('delete', $asset);
|
||||
if (isset($asset->id)) {
|
||||
// Restore the asset
|
||||
Asset::withTrashed()->where('id', $assetId)->restore();
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Asset::class;
|
||||
$logaction->item_id = $asset->id;
|
||||
$logaction->created_at = date("Y-m-d H:i:s");
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restored');
|
||||
|
||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
||||
}
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
public function quickScan()
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths(12)->toDateString();
|
||||
return view('hardware/quickscan')->with('next_audit_date', $dt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function audit($id)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
$asset = Asset::findOrFail($id);
|
||||
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
|
||||
}
|
||||
|
||||
public function dueForAudit()
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
return view('hardware/audit-due');
|
||||
}
|
||||
|
||||
public function overdueForAudit()
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
return view('hardware/audit-overdue');
|
||||
}
|
||||
|
||||
|
||||
public function auditStore(Request $request, $id)
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
||||
$rules = array(
|
||||
'location_id' => 'exists:locations,id|nullable|numeric',
|
||||
'next_audit_date' => 'date|nullable'
|
||||
);
|
||||
|
||||
$validator = \Validator::make($request->all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||
}
|
||||
|
||||
$asset = Asset::findOrFail($id);
|
||||
|
||||
// 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');
|
||||
|
||||
// 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()) {
|
||||
$file_name = '';
|
||||
// Upload an image, if attached
|
||||
if ($request->hasFile('image')) {
|
||||
$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'), $file_name);
|
||||
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getRequestedIndex($user_id = null)
|
||||
{
|
||||
$requestedItems = CheckoutRequest::with('user', 'requestedItem')->whereNull('canceled_at')->with('user', 'requestedItem');
|
||||
|
||||
if ($user_id) {
|
||||
$requestedItems->where('user_id', $user_id)->get();
|
||||
}
|
||||
|
||||
$requestedItems = $requestedItems->orderBy('created_at', 'desc')->get();
|
||||
|
||||
return view('hardware/requested', compact('requestedItems'));
|
||||
}
|
||||
|
||||
}
|
||||
259
app/Http/Controllers/Assets/BulkAssetsController.php
Normal file
259
app/Http/Controllers/Assets/BulkAssetsController.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
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 Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BulkAssetsController extends Controller
|
||||
{
|
||||
use CheckInOutRequest;
|
||||
|
||||
/**
|
||||
* Display the bulk edit page.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return View
|
||||
* @internal param int $assetId
|
||||
* @since [v2.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
if (!$request->filled('ids')) {
|
||||
return redirect()->back()->with('error', 'No assets selected');
|
||||
}
|
||||
|
||||
$asset_ids = array_keys($request->input('ids'));
|
||||
|
||||
if ($request->filled('bulk_actions')) {
|
||||
switch($request->input('bulk_actions')) {
|
||||
case 'labels':
|
||||
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);
|
||||
$assets->each(function ($asset) {
|
||||
$this->authorize('delete', $asset);
|
||||
});
|
||||
return view('hardware/bulk-delete')->with('assets', $assets);
|
||||
case 'edit':
|
||||
return view('hardware/bulk')
|
||||
->with('assets', request('ids'))
|
||||
->with('statuslabel_list', Helper::statusLabelList());
|
||||
}
|
||||
}
|
||||
return redirect()->back()->with('error', 'No action selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save bulk edits
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @return Redirect
|
||||
* @internal param array $assets
|
||||
* @since [v2.0]
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
\Log::debug($request->input('ids'));
|
||||
|
||||
if(!$request->filled('ids') || count($request->input('ids')) <= 0) {
|
||||
return redirect()->route("hardware.index")->with('warning', trans('No assets selected, so nothing was updated.'));
|
||||
}
|
||||
|
||||
$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'))
|
||||
|| ($request->filled('warranty_months'))
|
||||
|| ($request->filled('rtd_location_id'))
|
||||
|| ($request->filled('requestable'))
|
||||
|| ($request->filled('company_id'))
|
||||
|| ($request->filled('status_id'))
|
||||
|| ($request->filled('model_id'))
|
||||
) {
|
||||
foreach ($assets as $assetId) {
|
||||
$this->update_array = [];
|
||||
|
||||
$this->conditionallyAddItem('purchase_date')
|
||||
->conditionallyAddItem('expected_checkin')
|
||||
->conditionallyAddItem('model_id')
|
||||
->conditionallyAddItem('order_number')
|
||||
->conditionallyAddItem('requestable')
|
||||
->conditionallyAddItem('status_id')
|
||||
->conditionallyAddItem('supplier_id')
|
||||
->conditionallyAddItem('warranty_months');
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
$this->update_array['purchase_cost'] = Helper::ParseFloat($request->input('purchase_cost'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$this->update_array['company_id'] = $request->input('company_id');
|
||||
if ($request->input('company_id')=="clear") {
|
||||
$this->update_array['company_id'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->filled('rtd_location_id')) {
|
||||
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
|
||||
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
|
||||
$this->update_array['location_id'] = $request->input('rtd_location_id');
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('assets')
|
||||
->where('id', $assetId)
|
||||
->update($this->update_array);
|
||||
} // endforeach
|
||||
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.update.success'));
|
||||
// no values given, nothing to update
|
||||
}
|
||||
return redirect()->route("hardware.index")->with('warning', trans('admin/hardware/message.update.nothing_updated'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Array to store update data per item
|
||||
* @var Array
|
||||
*/
|
||||
private $update_array;
|
||||
|
||||
/**
|
||||
* Adds parameter to update array for an item if it exists in request
|
||||
* @param String $field field name
|
||||
* @return BulkAssetsController Model for Chaining
|
||||
*/
|
||||
protected function conditionallyAddItem($field)
|
||||
{
|
||||
if(request()->filled($field)) {
|
||||
$this->update_array[$field] = request()->input($field);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$this->authorize('delete', Asset::class);
|
||||
|
||||
if ($request->filled('ids')) {
|
||||
$assets = Asset::find($request->get('ids'));
|
||||
foreach ($assets as $asset) {
|
||||
$update_array['deleted_at'] = date('Y-m-d H:i:s');
|
||||
$update_array['assigned_to'] = null;
|
||||
|
||||
DB::table('assets')
|
||||
->where('id', $asset->id)
|
||||
->update($update_array);
|
||||
} // endforeach
|
||||
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.delete.success'));
|
||||
// no values given, nothing to update
|
||||
}
|
||||
return redirect()->to("hardware")->with('info', trans('admin/hardware/message.delete.nothing_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Bulk Checkout Page
|
||||
* @return View View to checkout multiple assets
|
||||
*/
|
||||
public function showCheckout()
|
||||
{
|
||||
$this->authorize('checkout', Asset::class);
|
||||
// Filter out assets that are not deployable.
|
||||
|
||||
return view('hardware/bulk-checkout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Multiple Checkout Request
|
||||
* @return View
|
||||
*/
|
||||
public function storeCheckout(Request $request)
|
||||
{
|
||||
try {
|
||||
$admin = Auth::user();
|
||||
|
||||
$target = $this->determineCheckoutTarget();
|
||||
|
||||
if (!is_array($request->get('selected_assets'))) {
|
||||
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
}
|
||||
|
||||
$asset_ids = array_filter($request->get('selected_assets'));
|
||||
|
||||
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");
|
||||
if (($request->filled('checkout_at')) && ($request->get('checkout_at')!= date("Y-m-d"))) {
|
||||
$checkout_at = e($request->get('checkout_at'));
|
||||
}
|
||||
|
||||
$expected_checkin = '';
|
||||
|
||||
if ($request->filled('expected_checkin')) {
|
||||
$expected_checkin = e($request->get('expected_checkin'));
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) {
|
||||
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
$asset = Asset::findOrFail($asset_id);
|
||||
$this->authorize('checkout', $asset);
|
||||
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), null);
|
||||
|
||||
if ($target->location_id!='') {
|
||||
$asset->location_id = $target->location_id;
|
||||
$asset->unsetEventDispatcher();
|
||||
$asset->save();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
array_merge_recursive($errors, $asset->getErrors()->toArray());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!$errors) {
|
||||
// Redirect to the new asset page
|
||||
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to("hardware/bulk-checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->to("hardware/bulk-checkout")->with('error', $e->getErrors());
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user