Compare commits
1033 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23cbee9493 | |||
| 7c21158680 | |||
| 30f4af3ac2 | |||
| b1fa50cde2 | |||
| 236684ec92 | |||
| c45e777324 | |||
| 53499b1e29 | |||
| 04b6f023ae | |||
| 3c7d63c060 | |||
| 7cb1ca8754 | |||
| 52e33a8b0e | |||
| 0f5fbb6a04 | |||
| 6d5ace0458 | |||
| 6f14355cbc | |||
| eb81c290dc | |||
| 2f9e097854 | |||
| 97aeb1fcec | |||
| e6d259bac0 | |||
| 8887d40b86 | |||
| 3d8e0b707e | |||
| 71a7176a6e | |||
| def89bfa0c | |||
| f1cb7ee410 | |||
| 84c0f50266 | |||
| f51b312843 | |||
| 3d3a4b02fc | |||
| 276f534ded | |||
| 1d47d9e52b | |||
| 2106b64da6 | |||
| fa79a6c15f | |||
| 761da534f3 | |||
| b362951c95 | |||
| 25f69a7bd2 | |||
| f6a6478804 | |||
| 3282702c73 | |||
| 4e5c878b73 | |||
| 5711706367 | |||
| a930661150 | |||
| 7cb4740359 | |||
| 9f8f028484 | |||
| d0b9e956df | |||
| c1eee2cc72 | |||
| f32183d51b | |||
| 73f30c0a41 | |||
| c233d7fb1c | |||
| dea0fcefbd | |||
| 38678d4646 | |||
| 476a5cbc02 | |||
| bc4c6abe06 | |||
| 73f61ce032 | |||
| 12c7223bcd | |||
| de856d6045 | |||
| b7efb58733 | |||
| 28b0d8cf0f | |||
| 88ae8858e8 | |||
| 0bc0a116aa | |||
| e670ffe349 | |||
| a94f1c4a64 | |||
| b503b672ba | |||
| 72296eea5c | |||
| bc023a2566 | |||
| 2f7e22be1e | |||
| 9409965239 | |||
| f54c92ade2 | |||
| b1fb70cb7b | |||
| 9e08936cbc | |||
| cd385e0865 | |||
| 1e3281c76c | |||
| af4d62759f | |||
| 9452973845 | |||
| 8e23787888 | |||
| 3b16157d6b | |||
| 3ec3d38c9f | |||
| ae430959a4 | |||
| 85a85082b7 | |||
| 5acb493efd | |||
| 515b14a001 | |||
| 4eed2baa31 | |||
| 453f2c3b0e | |||
| bc78d341a0 | |||
| da579a302c | |||
| edf191b724 | |||
| 12d86bd6e2 | |||
| c4f11de90d | |||
| 8fb61cf5f8 | |||
| 4e83b06d71 | |||
| 23e613f903 | |||
| f1c82ca732 | |||
| 2543fa6b98 | |||
| 7c34652da0 | |||
| aa3f94973f | |||
| 1ec303931a | |||
| d359bcb88d | |||
| 28059c878a | |||
| b7bcfaccc9 | |||
| f4a3f896bd | |||
| 0303df4b25 | |||
| 9151868839 | |||
| 8c6aaec9ce | |||
| fb228d6ded | |||
| 1360ce90a5 | |||
| 3549afe157 | |||
| cfb780b20b | |||
| eb9f17122e | |||
| 2ef45afb2b | |||
| c8baf4c6ad | |||
| 0b20cf6d70 | |||
| ecc6bd8a24 | |||
| 3c9c89355e | |||
| 43479efed0 | |||
| 80279a95ae | |||
| ac055c01dc | |||
| 5ff72420a6 | |||
| 55b33c1042 | |||
| a499a92bb7 | |||
| e7ae7742b0 | |||
| 98f2e0271b | |||
| e721665fd8 | |||
| cd43fc50fb | |||
| 06803092c5 | |||
| 0312ecf09d | |||
| 0ed50f4e58 | |||
| 3f105cd3b2 | |||
| ab0c5bcd9d | |||
| 5ba01728d9 | |||
| ae0eb378e4 | |||
| ad8a2387f0 | |||
| 4fa01350c3 | |||
| d3161fad02 | |||
| 36eb51b52f | |||
| 8ff109e9da | |||
| 15da3b831d | |||
| 0c6441ebed | |||
| fbfc9d123c | |||
| cd942196cc | |||
| f1edbc9810 | |||
| 66e64cb136 | |||
| 1ef3eb94db | |||
| 25e4b7d59d | |||
| 9605dec22a | |||
| ca937667b4 | |||
| e9c61903f1 | |||
| 14b7319b63 | |||
| 9d641573b5 | |||
| 3605aa8f5a | |||
| 0817308822 | |||
| bdfac1a345 | |||
| f7a9be92ca | |||
| 56ba8a7e24 | |||
| cf62761d18 | |||
| d23e178c62 | |||
| 7353ecd388 | |||
| 18663c2599 | |||
| 5b8e0b3af1 | |||
| dab83091d2 | |||
| 4c0be7ec98 | |||
| 018f71a157 | |||
| 9f03f0f6f6 | |||
| da40c0ad48 | |||
| c70c8ce856 | |||
| fe49ac79d1 | |||
| 687a764fc1 | |||
| 81be84568e | |||
| 61fa0bda4d | |||
| 81d2a3acda | |||
| 931d81d825 | |||
| bcebc1e33b | |||
| ba0483b33e | |||
| 97b9f96030 | |||
| 891b5b2882 | |||
| e21660ae05 | |||
| 53392d6069 | |||
| 3469e49470 | |||
| e9224f094e | |||
| 25c10c3819 | |||
| b9419c7454 | |||
| b07c4caa37 | |||
| aa52bc6c6a | |||
| 5a7e35c0e8 | |||
| bae200edd7 | |||
| 2fd197c2db | |||
| 36484d50ef | |||
| 2b7c8cf82b | |||
| 0424308863 | |||
| 78580136f6 | |||
| 420c216973 | |||
| 9c6fa18454 | |||
| 70c766e8b3 | |||
| 0b2ce7be07 | |||
| 3d48dd19cb | |||
| 08bab4f101 | |||
| 95b49f8deb | |||
| 4f5d536e32 | |||
| 9dbc5070f8 | |||
| 055a2f827f | |||
| 8d3fe423e1 | |||
| 1377725a97 | |||
| 58ccfe9226 | |||
| 05ac9f4a68 | |||
| 0889cd61cf | |||
| 8168ca4074 | |||
| de399a9f8f | |||
| af150f0192 | |||
| c194c8a0c8 | |||
| a7b83fc531 | |||
| ef22da622d | |||
| 8443796cbe | |||
| ac996a771c | |||
| 942eaac2ee | |||
| ada2a2cd43 | |||
| 06ba9356ca | |||
| e034f7b674 | |||
| 4d14243f57 | |||
| 88a371082d | |||
| ee7e26ae8d | |||
| 9d93c72534 | |||
| 44b72e0f5f | |||
| d91d226652 | |||
| 091ef62cbe | |||
| 8316a4eb92 | |||
| b4518677bd | |||
| 23cf556a8b | |||
| c0f83a7927 | |||
| 5a1062f0f4 | |||
| c32676596c | |||
| f75917d34d | |||
| c55f2d5417 | |||
| e267f5491a | |||
| 571ce5b741 | |||
| 733b0750a2 | |||
| 9369165007 | |||
| f75d348dc0 | |||
| dcab1381e7 | |||
| 0924d2144b | |||
| 9cae4f1d1b | |||
| ca70988879 | |||
| 421c9baecc | |||
| 5c9e84f663 | |||
| fbb5f1445a | |||
| 07e0fec1de | |||
| c6cbf3712e | |||
| 0f777b3168 | |||
| 62ad3fad1c | |||
| eb1e287c15 | |||
| 3eab44f748 | |||
| 1885e73704 | |||
| 24910433a1 | |||
| 1899e4d1e8 | |||
| 481eb2c7f0 | |||
| 51d7f2b97b | |||
| 9b2eda2d70 | |||
| 41b75704e3 | |||
| 20220764bb | |||
| abd2349604 | |||
| ac4f91918e | |||
| 28f0b72bcd | |||
| ad7bc6f38a | |||
| 72feaee6c0 | |||
| 4ac3650d64 | |||
| 3877770160 | |||
| 3192a68b06 | |||
| 284b4d869f | |||
| 6b6a079440 | |||
| a0624fe179 | |||
| d598f75721 | |||
| d51eca20f0 | |||
| 9b5790b7e6 | |||
| a8a00f0a79 | |||
| cbd16174d8 | |||
| 448aefac28 | |||
| b23cd6d4f0 | |||
| b0ea1a31dc | |||
| 11a2f96b4f | |||
| 7361977cdc | |||
| 659703bd7d | |||
| 3aff97ace1 | |||
| 6e82de2f47 | |||
| ae505ef44d | |||
| e86e96e159 | |||
| 93428e1ed4 | |||
| 61e1650542 | |||
| ab54ac9408 | |||
| dcbd407698 | |||
| 4e7fea3468 | |||
| 3ef25c3a4d | |||
| 22596f4bb8 | |||
| 8fdca7f42b | |||
| 1f04cfdb44 | |||
| 04c130e596 | |||
| 7fa9214beb | |||
| ea63049b4b | |||
| e94b5ac435 | |||
| 9cf5f30c77 | |||
| e8f2e50ada | |||
| 3527902ba3 | |||
| f60ea9581d | |||
| 74fe3dc733 | |||
| 96ec96ff7f | |||
| 4fd1827576 | |||
| 0d4e9c183b | |||
| 1ff2d15c4a | |||
| 482a7b2a3a | |||
| 6f0f6c8042 | |||
| 4cc9b5a5a2 | |||
| 8720a58b51 | |||
| 22710afee3 | |||
| 968f8fb554 | |||
| 6fde72a693 | |||
| afb7fa2e81 | |||
| c9ef49ec65 | |||
| 36ab794adc | |||
| 2ad9c39090 | |||
| c59fd7b8da | |||
| 44e598b0dc | |||
| ffd38292cf | |||
| 660c1429b2 | |||
| 8ad5fb34d3 | |||
| a5fd9ebbb1 | |||
| 49d2aef831 | |||
| 74f5980af7 | |||
| 1a47e1000d | |||
| ab60fa08df | |||
| 8a312f69a6 | |||
| 581cae94ab | |||
| 6e85c2e5cb | |||
| 30d0125cbd | |||
| e2dec5d5d7 | |||
| af34fc207c | |||
| cca531f8ca | |||
| 45f17fea8a | |||
| 3ac84eab58 | |||
| 15af4ea56e | |||
| d649c65508 | |||
| 0965ac9592 | |||
| bfadc69d73 | |||
| 41be3f7208 | |||
| 906a36dc1a | |||
| aba8449dc6 | |||
| 861283b874 | |||
| 2994c3613a | |||
| ba7d6dde3f | |||
| 4b9a91f55b | |||
| 94bbf2bf2f | |||
| e5cc6ec972 | |||
| e97c526050 | |||
| 7b45328fcc | |||
| 40bc13a946 | |||
| 368bd5da3f | |||
| 1763bbfbd3 | |||
| 7b44afc43c | |||
| 13b6c281df | |||
| 5feea17f8d | |||
| 60d1578a01 | |||
| 07585809b3 | |||
| 98a111796b | |||
| 7b3be076b4 | |||
| 2c68ec927a | |||
| a0ba33ed7c | |||
| 70bb4cd746 | |||
| 91e824c5b0 | |||
| 491cdd4de6 | |||
| 3454cdd2a0 | |||
| db53a00d3f | |||
| 8324b94022 | |||
| a33ca39237 | |||
| 98bcd82108 | |||
| d2c486bb1e | |||
| 8c579d693a | |||
| 814992eb7d | |||
| 267997f0a6 | |||
| 0172fe6403 | |||
| 509bbbc685 | |||
| 8cee5060af | |||
| a7de97f060 | |||
| c1074000f9 | |||
| c7d9baad8e | |||
| c77d763f05 | |||
| ef099aa644 | |||
| acd666fdf5 | |||
| 8de7e9a2ab | |||
| dd0260c3be | |||
| 512348cc5a | |||
| fb3d7bf82e | |||
| e3293151a2 | |||
| 3ec77724d8 | |||
| 024a014298 | |||
| c1e5d03340 | |||
| fb2fe61daf | |||
| 0d9eccb7eb | |||
| f9956cc5df | |||
| f7cceaedd5 | |||
| e7c47408a7 | |||
| 637604dfaf | |||
| f66df4d468 | |||
| b6adfa294c | |||
| f374f231d4 | |||
| d4e60a46c1 | |||
| a834ac6ec2 | |||
| 92e6ea96b2 | |||
| de449fd1c2 | |||
| 77a1fcb1fc | |||
| 0cf0598936 | |||
| 80d91c0c51 | |||
| c39bf7b7e6 | |||
| 9db0b038a5 | |||
| 4f4c4ed5be | |||
| 51f7298720 | |||
| bc4032c2cd | |||
| c055c91739 | |||
| ea8f12579d | |||
| 88db2d9e18 | |||
| 5f93d7b3be | |||
| 1875ed55cc | |||
| 9f0ecba9b9 | |||
| 9acfceba29 | |||
| 92bf8b4436 | |||
| b52c00d49b | |||
| 20fcf7a1e9 | |||
| 095dd3ecf2 | |||
| 5a1b8bc4b6 | |||
| eadb36cb26 | |||
| 6eac133caf | |||
| cc92d342ea | |||
| 02caa13222 | |||
| 975b4dee2f | |||
| 65f0a3f535 | |||
| 9ae7c66b77 | |||
| 77ab4ac1b5 | |||
| b0e66ae445 | |||
| 5fe8db541e | |||
| 87bed9b294 | |||
| da1c1dbdd9 | |||
| 56b6904564 | |||
| 10e64deafe | |||
| ac72b69ece | |||
| 8e18ce74b2 | |||
| 254ce72415 | |||
| 1b657ee995 | |||
| 71facb1850 | |||
| 8d8c502546 | |||
| 0a9bd07f20 | |||
| 688577c1ed | |||
| 8027039754 | |||
| 031b7998c9 | |||
| 13053b85ef | |||
| e3b13143bf | |||
| e150ab58e9 | |||
| ff080a2d8f | |||
| a63d00bb5c | |||
| 96b40a5edd | |||
| 8596c54d6a | |||
| e07532dd1b | |||
| 04a467bdc7 | |||
| 06d60bb1d1 | |||
| 9c6c01bab1 | |||
| 4133ac6ef0 | |||
| 4834c33861 | |||
| 904b5c2886 | |||
| e79a819724 | |||
| 0fdbd410b6 | |||
| af7ccf3beb | |||
| 3e48f436af | |||
| 5e488bd81f | |||
| 53ff28d2b0 | |||
| 4a4636bd03 | |||
| 744d0d299e | |||
| b035003546 | |||
| 842dafefec | |||
| 375963f92d | |||
| a51b446a10 | |||
| 371aba9912 | |||
| 8b30992a11 | |||
| 33de45d8fb | |||
| d7d0056b73 | |||
| 7d2fcef807 | |||
| 1981b79557 | |||
| 0c7d4a709a | |||
| 6c8d226b21 | |||
| 211bbeda5b | |||
| 7057cb0104 | |||
| 658edb64d6 | |||
| dc06f14505 | |||
| f446314ce6 | |||
| 443b1df5e1 | |||
| 3c22e0d203 | |||
| d636ad8e09 | |||
| adfdbf3d44 | |||
| 8b03abde8d | |||
| 67320dc024 | |||
| c5530104ff | |||
| 78097df7ff | |||
| 072e6029b6 | |||
| 5926441984 | |||
| a1fe2a8d58 | |||
| 25cc46e416 | |||
| 458d61fafe | |||
| 32010d5387 | |||
| 7313bca403 | |||
| b7c7e6c623 | |||
| 0b4d243bf0 | |||
| 377c0e7075 | |||
| 0c4c4fc352 | |||
| 9d627936e6 | |||
| 29a32c4726 | |||
| e72c4f989f | |||
| 0d656e3963 | |||
| 50ce471dc6 | |||
| 8113838eee | |||
| a92bb80055 | |||
| e9121c15d8 | |||
| 43bd00da86 | |||
| d124c89ced | |||
| b2112e6792 | |||
| 250db10249 | |||
| ff5d64fcbf | |||
| ec01aadbe4 | |||
| f0a9de72b1 | |||
| 145c191a65 | |||
| 5cb5ef249b | |||
| ea38cef48c | |||
| 4808185b5c | |||
| b3d65b5703 | |||
| 0f38714e21 | |||
| 7a099bc268 | |||
| 6f1f6c75e6 | |||
| 40d5c37aea | |||
| 58499c2322 | |||
| 6428dc5cd2 | |||
| 5f5ff04636 | |||
| f21824a8e2 | |||
| 0ccd7e09de | |||
| 27039325f3 | |||
| 58c54cc3dc | |||
| 48349071ed | |||
| bb5ac900ec | |||
| 5efe45226d | |||
| a0183ff56f | |||
| bb091760af | |||
| 78d13871eb | |||
| fc5cf3e291 | |||
| 3182f251e1 | |||
| d6ba8655bb | |||
| 8114d01799 | |||
| 309229619b | |||
| 75844c5942 | |||
| 78d5c37267 | |||
| 397e89cbed | |||
| 2518e2f0ee | |||
| e833052e8e | |||
| 43e370f35a | |||
| 8d3c9e67a0 | |||
| 3988c0e3f0 | |||
| 723454b04d | |||
| 30c03435e4 | |||
| 5043533fde | |||
| d458973649 | |||
| 9c86b4bf88 | |||
| 3a6c6d8466 | |||
| d62167e364 | |||
| 9297d8af1d | |||
| bae9792f01 | |||
| b98af0f50f | |||
| 75e5cde39b | |||
| f853188bc7 | |||
| 61813a6f2c | |||
| 7a0c9776b4 | |||
| ee4f355e49 | |||
| bfc8c18675 | |||
| f1488767f8 | |||
| 2d5755c1dd | |||
| acc2e12f12 | |||
| eb2793bfa7 | |||
| 1a05843183 | |||
| ef17fd41f7 | |||
| 7bdbbbee50 | |||
| 2e800e6a0d | |||
| dae53a1128 | |||
| 2781bd02b5 | |||
| 7772ef22db | |||
| 2137c4e897 | |||
| 4a0e51bf34 | |||
| 9f581a7677 | |||
| be8869978d | |||
| 1ae044b91e | |||
| e550fb1a52 | |||
| fc65bb020f | |||
| a4dfdcd90d | |||
| 16d2e34148 | |||
| 567eefab1a | |||
| 1ef20ec622 | |||
| afab167e8c | |||
| af14ee0d47 | |||
| 0a5ca6eb25 | |||
| 6e9f24c08f | |||
| 07ac4087a3 | |||
| 1650c9f878 | |||
| 123963c14c | |||
| 1b45170ca3 | |||
| 9a0b677dac | |||
| b44f27dafa | |||
| 92d46edca3 | |||
| 471cf117ab | |||
| bf78bd4b8b | |||
| a0a0d7e344 | |||
| a9361571d6 | |||
| 1a13be9b5d | |||
| 0ed84c83c8 | |||
| 9670ca3b8a | |||
| d4476cb10b | |||
| 45c616639c | |||
| eedd646c28 | |||
| acbd4deb1b | |||
| 4e547a3639 | |||
| b910db0617 | |||
| bb382f3d2a | |||
| 25cb32ca6a | |||
| 6a13a7e096 | |||
| e366caf3d1 | |||
| cfa301f5ae | |||
| f72aa2415f | |||
| 7969a66552 | |||
| 4edba064d5 | |||
| f3a3c59b7b | |||
| aa54c23f98 | |||
| df1e2687d6 | |||
| 4f07e77bf9 | |||
| a10f570350 | |||
| ac94aa8e46 | |||
| 7ebf125dae | |||
| 9a361d573f | |||
| 1a423a252b | |||
| 7591f3f092 | |||
| 2cace3c73a | |||
| 97afafdd48 | |||
| 656efc5f92 | |||
| 46d055cf74 | |||
| 423e7439ee | |||
| 2262ef818e | |||
| 02459aad26 | |||
| 2451bb9a2b | |||
| cd9d2d0cec | |||
| fc636ea888 | |||
| e471aa8639 | |||
| 180f36d145 | |||
| f8fd87b896 | |||
| 130c8ea1b0 | |||
| 5af6330398 | |||
| daaf8713d8 | |||
| 5b02d9ed06 | |||
| b04cf20735 | |||
| 6531657ee0 | |||
| e28e7e37b8 | |||
| 467f59e193 | |||
| be9e6fe847 | |||
| b0e13611f7 | |||
| f3887aef33 | |||
| 7b3f891edd | |||
| b590f29f33 | |||
| 1debdc47cf | |||
| bb7662a214 | |||
| 53bc15900b | |||
| 90fe7af863 | |||
| 67ad24af08 | |||
| 67e9b7795a | |||
| 52332bc9ed | |||
| dc27d3bec9 | |||
| a711e608c9 | |||
| 2f7c04362e | |||
| 9b6fd7e19a | |||
| 9680b02bce | |||
| e7de7d1716 | |||
| 112f147596 | |||
| 413487de80 | |||
| 1158fa9ea8 | |||
| a8e8112b34 | |||
| 3df9260ca8 | |||
| 14ba3af086 | |||
| 71c8050883 | |||
| df5b01492c | |||
| 9dbb355e8d | |||
| 0f3778f07b | |||
| f515bd2dc8 | |||
| f3075facb4 | |||
| ed95adb45c | |||
| 95d4f7c62e | |||
| c90ed9f25f | |||
| 7f664a7971 | |||
| 14c6879f06 | |||
| d8d12d4590 | |||
| be3388d647 | |||
| 242836719d | |||
| 393c32558b | |||
| 94e723a88f | |||
| 14d8fb66aa | |||
| ae73d4cc7c | |||
| bf08e73f8f | |||
| 4a3f56acf2 | |||
| e33a4c2ef2 | |||
| 52bd7d0d68 | |||
| baad3b9d58 | |||
| 131edb611e | |||
| 518395bbc7 | |||
| 34b4499178 | |||
| 8f900fb4e1 | |||
| 9355689dd4 | |||
| 0909feaa6b | |||
| 7f18180105 | |||
| bee694e605 | |||
| 08525a3c20 | |||
| d70b36750c | |||
| 8c85d7bc97 | |||
| cb225cb1ce | |||
| 7e7ae3bb95 | |||
| 838579e9a8 | |||
| a99896618d | |||
| 24cb13d52b | |||
| cc7513e202 | |||
| c0b6d5aa2c | |||
| 5788038b49 | |||
| fbf0815b16 | |||
| e2c227f02b | |||
| be0f0fc421 | |||
| a03075b6ea | |||
| 3b3f1a817e | |||
| 601f7a6994 | |||
| 75d19d815d | |||
| d167d2a10f | |||
| fce4f0dc0e | |||
| 657039882c | |||
| cf99d42413 | |||
| f483eafae9 | |||
| 77bf28bcb6 | |||
| 6c2d06efb9 | |||
| d0081188c7 | |||
| ce2362459c | |||
| 4e568bec8a | |||
| 18c37c97b8 | |||
| 82e5faa869 | |||
| dec7122ac7 | |||
| a75fc8af7e | |||
| 86d2c2b153 | |||
| 9fc4565bc1 | |||
| f0cc418965 | |||
| 0bc3ca5c42 | |||
| 62ab867051 | |||
| 9fd3541520 | |||
| 3e559044b2 | |||
| 75a631b91f | |||
| 3a8786fdb7 | |||
| f2a89161b3 | |||
| 1c25057e42 | |||
| 590630e4e0 | |||
| 487dedba25 | |||
| f4c346a57c | |||
| 35365882ac | |||
| ea254ccc04 | |||
| 06d5b5f4b1 | |||
| 2536b02ace | |||
| 6b8abb1511 | |||
| 9c9f5be6fe | |||
| d8daec2e0a | |||
| c9b81d65f1 | |||
| 7f05029089 | |||
| 23c50ea9a5 | |||
| 2e5e8f363b | |||
| e63183649a | |||
| ef86c0273a | |||
| dd8d90aa39 | |||
| 670a46e85c | |||
| daf6c72005 | |||
| 1a4579b770 | |||
| 398c77bfdc | |||
| d45d322b54 | |||
| b0897a1fc9 | |||
| d00b469001 | |||
| c5a6cec194 | |||
| e2b1494511 | |||
| df76e6eacf | |||
| 541ae919d9 | |||
| 7976401aa2 | |||
| 7e10abe605 | |||
| afdf93ca63 | |||
| 321367b974 | |||
| 366f3aacef | |||
| 4b9ec9218d | |||
| cae66753fb | |||
| 5ac9efa9a3 | |||
| ded635207f | |||
| f0f37df76e | |||
| f5702532f0 | |||
| 2f02eee69b | |||
| 89c234b1c2 | |||
| c24052cb2d | |||
| a7a61a3620 | |||
| 135fdae209 | |||
| 5c30de517d | |||
| a7dc6162fa | |||
| 18778d3723 | |||
| 5ff1b5fd50 | |||
| 1c1f3dc42c | |||
| d67afc3bd0 | |||
| a5b857c753 | |||
| b00db3cc56 | |||
| 57720cb978 | |||
| 172e8d463f | |||
| 284dbb7553 | |||
| 1156eea8af | |||
| 17ee332715 | |||
| 2f258a3e3d | |||
| a31bca1798 | |||
| 791f77f641 | |||
| 1b6df232aa | |||
| 386272a618 | |||
| 7f8fc7add9 | |||
| 6bc525bc25 | |||
| de048e1009 | |||
| 68150d11b7 | |||
| f4f400ed87 | |||
| a49ccf0863 | |||
| 300879847f | |||
| d4c53945d9 | |||
| 21875100b6 | |||
| 87980643ea | |||
| 675f42401c | |||
| 3a5c09c424 | |||
| 3b462ffadc | |||
| d60af478ad | |||
| cabef8ff12 | |||
| 8a27ef30d5 | |||
| a111482217 | |||
| a758e825ed | |||
| af66f83a3d | |||
| b3605fa141 | |||
| 6f713985fb | |||
| 677e5a8cf1 | |||
| 872600a7a7 | |||
| 3a879bda4a | |||
| a6852cf4d2 | |||
| 61c601dbdf | |||
| 74bc06cc49 | |||
| 5fe1078013 | |||
| a011b07d99 | |||
| b392ed269b | |||
| 6059e9e119 | |||
| e6d792bdf7 | |||
| f16a4b6aef | |||
| d74b4f55fb | |||
| 693043e645 | |||
| e935a34946 | |||
| 4052e360c1 | |||
| cc6a2f2d49 | |||
| 07bc2fd742 | |||
| a57a6486e7 | |||
| a33276cb3d | |||
| bfec0059c5 | |||
| aea9dd1de5 | |||
| 954b54f914 | |||
| 56a15731ef | |||
| 766e59acde | |||
| f1a63f25e7 | |||
| 88dfdb7538 | |||
| 9072f7c6c9 | |||
| bff34063cd | |||
| 1e685ca835 | |||
| b55630aafa | |||
| c3b644797e | |||
| 1806dacb9d | |||
| abb7f23ca5 | |||
| b448c89655 | |||
| 8857391da7 | |||
| 50c008ead5 | |||
| 3e8837dd6e | |||
| ea1ff1e8bb | |||
| a7b155108d | |||
| 44c0c1bf0e | |||
| 7cd35a80b6 | |||
| e8973f08b5 | |||
| dd9c9520fb | |||
| aec033d8d2 | |||
| 5496b62b33 | |||
| e393e2eb4b | |||
| 10781a6e4b | |||
| 5a86004081 | |||
| 91ade1f33e | |||
| eaecf8137a | |||
| 92bb8fac32 | |||
| d4a7811078 | |||
| cb8dbcc172 | |||
| 495e68d23c | |||
| 20a0c4e3b5 | |||
| 4d773829f8 | |||
| bcb9804be8 | |||
| 5e59fa2598 | |||
| 6ea722d41e | |||
| 66346684a9 | |||
| 9731a31cb6 | |||
| ef3e016667 | |||
| 93e4d23143 | |||
| 0153a37cd7 | |||
| cce5846018 | |||
| a070f7cd5e | |||
| 4c11041477 | |||
| 166f526302 | |||
| f8718ffc1e | |||
| 31e4d3b725 | |||
| a766572a2f | |||
| 6a8824a467 | |||
| 0c3972d7b7 | |||
| 4a0eb2b3f1 | |||
| b3559ac74e | |||
| 4252bd2348 | |||
| 728338bfa8 | |||
| df8834fd88 | |||
| c8bc0eff11 | |||
| 8b6e869215 | |||
| 063893c109 | |||
| 44f4f20187 | |||
| 792b18f845 | |||
| 0671e478cd | |||
| d2fc27e21d | |||
| 740f27198f | |||
| 064f4b3fc6 | |||
| 5458676ead | |||
| 8337628323 | |||
| 7faaa4ce24 | |||
| 9583a72016 | |||
| e0102ddbf0 | |||
| 1030ad9a27 | |||
| ab8dcdcc40 | |||
| 773997a492 | |||
| 8333c80b7a | |||
| 96644ab6b9 | |||
| f977c53ecb | |||
| 8c1843b351 | |||
| c9e86ac194 | |||
| f7449921e9 | |||
| 5f79534f4a | |||
| 3bcd5d94d6 | |||
| 01348187c8 | |||
| b26a4ad333 | |||
| 89f45d3d05 | |||
| e83a062eda | |||
| 1ea667e709 | |||
| 5326ebd136 | |||
| 4db9892f8c | |||
| 880828379e | |||
| 9b54077409 | |||
| 9f478b51e2 | |||
| 7c77e03c5a | |||
| 9d3aed42b7 | |||
| 3dedd51b84 | |||
| 711c713ab8 | |||
| 5bc5e9f108 | |||
| 9770692d07 | |||
| 133da6569b | |||
| 985e683896 | |||
| c3698053ea | |||
| dba06a3a9e | |||
| 07b1062fb2 | |||
| be0933a708 | |||
| d31f185cce | |||
| 5901182885 | |||
| f033aeda83 | |||
| 53f9e2bc7a | |||
| 514db05770 | |||
| 7ca617f077 | |||
| 8f5ac5fb55 | |||
| 54f828743e | |||
| f519fb747f | |||
| 36f714e414 | |||
| 773c773773 | |||
| 3071a83ae0 | |||
| 1c4864a3cc | |||
| 50c92d4730 | |||
| f8a0bf6a4b | |||
| 910fc08406 | |||
| eebc56d54b | |||
| b5ddc2c85b | |||
| 4e03ebe284 | |||
| 7577fc61e4 | |||
| d302675056 | |||
| 5316f41eba | |||
| fa6c463d46 | |||
| 23a441e7c8 | |||
| ac993184ee | |||
| 1dbc1f4aa2 | |||
| fe65de1207 | |||
| 089704c4f9 | |||
| bb933e5214 | |||
| eb34cf7917 | |||
| 4909cf2a9e | |||
| 37f9cca5ec | |||
| 72192257f2 | |||
| e0050bc844 | |||
| d2d0842737 | |||
| fe3b9f9e86 | |||
| 0ea6671bfa | |||
| 9209675d45 | |||
| 3e44f39f4d | |||
| 1f6ab340a6 | |||
| f208869aff | |||
| c031686825 | |||
| 791c39d977 | |||
| aac432b00c | |||
| 2b5b3273e2 | |||
| 4f57eebf24 | |||
| 0c9a1a8922 | |||
| fa2a3e4b03 | |||
| 94a337fc9e | |||
| 8d9cdf9c15 | |||
| fb8d52d599 | |||
| 070c36ffe6 | |||
| 5db1c50816 | |||
| 43e97ea6ea | |||
| 8fdedf9441 | |||
| b67ed3eac2 | |||
| 116ce931ce | |||
| 15d0fb4feb | |||
| 132b164a74 | |||
| bdbe2c3ac6 | |||
| 340c59969c | |||
| 157944b774 | |||
| 8ebe64e630 | |||
| c07a244bab | |||
| 8a032ee040 | |||
| a835401cb2 | |||
| 2cd7c7a357 | |||
| dafe353050 | |||
| b5378eff64 | |||
| 570dd09dcd | |||
| 0df9dd8320 | |||
| 0d49fc3a2e | |||
| 43d92bec5b |
@@ -2621,6 +2621,168 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "denzfarid",
|
||||
"name": "denzfarid",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1294403?v=4",
|
||||
"profile": "https://github.com/denzfarid",
|
||||
"contributions": []
|
||||
},
|
||||
{
|
||||
"login": "ntbutler-nbcs",
|
||||
"name": "ntbutler-nbcs",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/94018771?v=4",
|
||||
"profile": "https://github.com/ntbutler-nbcs",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "naveensrinivasan",
|
||||
"name": "Naveen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/172697?v=4",
|
||||
"profile": "https://naveensrinivasan.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mikeroq",
|
||||
"name": "Mike Roquemore",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55674383?v=4",
|
||||
"profile": "https://github.com/mikeroq",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "reederda",
|
||||
"name": "Daniel Reeder",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7991086?v=4",
|
||||
"profile": "https://github.com/reederda",
|
||||
"contributions": [
|
||||
"translation",
|
||||
"translation",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vickyjaura183",
|
||||
"name": "vickyjaura183",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/109422491?v=4",
|
||||
"profile": "https://github.com/vickyjaura183",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "julian-piehl",
|
||||
"name": "Peace",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32363424?v=4",
|
||||
"profile": "https://github.com/julian-piehl",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kylegordon",
|
||||
"name": "Kyle Gordon",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/231528?v=4",
|
||||
"profile": "https://github.com/kylegordon",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sunflowerbofh",
|
||||
"name": "Katharina Drexel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53009155?v=4",
|
||||
"profile": "http://www.bfh.ch",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dsferruzza",
|
||||
"name": "David Sferruzza",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1931963?v=4",
|
||||
"profile": "https://david.sferruzza.fr/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rnelsonee",
|
||||
"name": "Rick Nelson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19511639?v=4",
|
||||
"profile": "https://github.com/rnelsonee",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "BasO12",
|
||||
"name": "BasO12",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/94169344?v=4",
|
||||
"profile": "https://github.com/BasO12",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Vautia",
|
||||
"name": "Vautia",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/111710123?v=4",
|
||||
"profile": "https://github.com/Vautia",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chartjes",
|
||||
"name": "Chris Hartjes",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28321?v=4",
|
||||
"profile": "http://www.littlehart.net/atthekeyboard",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "geo-chen",
|
||||
"name": "geo-chen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2404584?v=4",
|
||||
"profile": "https://github.com/geo-chen",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nh314",
|
||||
"name": "Phan Nguyen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6006620?v=4",
|
||||
"profile": "https://github.com/nh314",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "StarlessNights",
|
||||
"name": "Iisakki Jaakkola",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/115993812?v=4",
|
||||
"profile": "https://github.com/StarlessNights",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "eltociear",
|
||||
"name": "Ikko Ashimine",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
|
||||
"profile": "https://bandism.net/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+2
-2
@@ -155,8 +155,8 @@ RESET_PASSWORD_LINK_EXPIRES=900
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=stderr
|
||||
APP_LOG_MAX_FILES=10
|
||||
LOG_CHANNEL=stderr
|
||||
LOG_MAX_DAYS=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
GOOGLE_MAPS_API=
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=snipeit-local
|
||||
DB_USERNAME=snipeit-local
|
||||
DB_PASSWORD=snipeit-local
|
||||
@@ -93,7 +94,7 @@ DISABLE_NOSAML_LOCAL_LOGIN=true
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
LOG_CHANNEL=single
|
||||
LOG_LEVEL=debug
|
||||
LOG_CHANNEL=stack
|
||||
LOG_SLACK_WEBHOOK_URL=null
|
||||
|
||||
+14
-5
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
@@ -70,11 +71,13 @@ IMAGE_LIB=gd
|
||||
MAIL_BACKUP_NOTIFICATION_DRIVER=null
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
|
||||
BACKUP_ENV=true
|
||||
|
||||
ALLOW_BACKUP_DELETE=false
|
||||
ALLOW_DATA_PURGE=false
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
# --------------------------------------------
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=12000
|
||||
EXPIRE_ON_CLOSE=false
|
||||
ENCRYPT=false
|
||||
@@ -97,7 +100,6 @@ ENABLE_HSTS=false
|
||||
# OPTIONAL: CACHE SETTINGS
|
||||
# --------------------------------------------
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
CACHE_PREFIX=snipeit
|
||||
|
||||
@@ -146,13 +148,19 @@ AWS_DEFAULT_REGION=null
|
||||
# --------------------------------------------
|
||||
LOGIN_MAX_ATTEMPTS=5
|
||||
LOGIN_LOCKOUT_DURATION=60
|
||||
RESET_PASSWORD_LINK_EXPIRES=900
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: FORGOTTEN PASSWORD SETTINGS
|
||||
# --------------------------------------------
|
||||
RESET_PASSWORD_LINK_EXPIRES=15
|
||||
PASSWORD_CONFIRM_TIMEOUT=10800
|
||||
PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
APP_LOG_MAX_FILES=10
|
||||
LOG_CHANNEL=single
|
||||
LOG_MAX_DAYS=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
APP_FORCE_TLS=false
|
||||
@@ -165,3 +173,4 @@ IMPORT_MEMORY_LIMIT=500M
|
||||
REPORT_TIME_LIMIT=12000
|
||||
REQUIRE_SAML=false
|
||||
API_THROTTLE_PER_MINUTE=120
|
||||
CSV_ESCAPE_FORMULAS=true
|
||||
|
||||
+3
-2
@@ -14,6 +14,7 @@ FILESYSTEM_DISK=local
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=testing.sqlite
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
@@ -70,5 +71,5 @@ SECURE_COOKIES=false
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: APP LOG FORMAT
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
APP_LOG_LEVEL=debug
|
||||
LOG_CHANNEL=single
|
||||
LOG_LEVEL=debug
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ FILESYSTEM_DISK=local
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE='sqlite_testing'
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=null
|
||||
@@ -34,4 +35,4 @@ IMAGE_LIB=gd
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: APP LOG FORMAT
|
||||
# --------------------------------------------
|
||||
APP_LOG=single
|
||||
LOG_CHANNEL=single
|
||||
|
||||
@@ -4,6 +4,7 @@ APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=mysql
|
||||
DB_DEFAULT=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=snipeittests
|
||||
DB_USERNAME=snipeit
|
||||
DB_PASSWORD=snipe
|
||||
|
||||
@@ -4,6 +4,7 @@ APP_URL=http://snipe-it.localapp
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_DEFAULT=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -15,8 +15,8 @@
|
||||
# *.js @octocat @github/js
|
||||
|
||||
|
||||
app/Importer/* @dmeltzer
|
||||
app/Http/Controllers/CustomFields* @uberbrady
|
||||
app/Http/Controllers/Api/CustomFields* @uberbrady
|
||||
resources/views/custom_fields/* @uberbrady
|
||||
docker/* @uberbrady
|
||||
app/Providers/SamlServiceProvider.php @uberbrady
|
||||
|
||||
+17
-3
@@ -1,4 +1,18 @@
|
||||
frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*"]
|
||||
backend: ["/app", "*.php"]
|
||||
legal: ["LICENSE*", "NOTICES*"]
|
||||
frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*", "*livewire*"]
|
||||
skins: ["*.js", "*.css", "*.scss", "*.less"]
|
||||
css: ["*.css","*.scss", "*.less"]
|
||||
backend: ["/app/*", "*.php"]
|
||||
backups: ["*backup*"]
|
||||
restore: ["*restore*"]
|
||||
saml: ["*saml*"]
|
||||
scim: ["*scim*"]
|
||||
custom fields: ["*fields*", "*fieldsets*"]
|
||||
dependencies: ["composer.json"]
|
||||
consumables: ["*consumables*"]
|
||||
api: ["/app/Http/Controllers/api/*"]
|
||||
notifications: ["/app/Notifications/*"]
|
||||
importer: ["/app/Importer/*"]
|
||||
cli / artisan: ["/app/Console/*"]
|
||||
LDAP: ["*LDAP*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
|
||||
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
|
||||
config: .github
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -26,14 +26,14 @@ jobs:
|
||||
language: [ 'javascript' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -17,18 +17,26 @@ on:
|
||||
schedule:
|
||||
- cron: '36 23 * * 3'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codacy-security-scan:
|
||||
# Ensure schedule job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
if: (github.repository == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule'))
|
||||
name: Codacy Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout the repository to the GitHub Actions runner
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||
- name: Run Codacy Analysis CLI
|
||||
uses: codacy/codacy-analysis-cli-action@1.1.0
|
||||
uses: codacy/codacy-analysis-cli-action@v4.2.0
|
||||
with:
|
||||
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
||||
# You can also omit the token and run the tools that support default configurations
|
||||
@@ -44,6 +52,6 @@ jobs:
|
||||
|
||||
# Upload the SARIF file generated in the previous step
|
||||
- name: Upload SARIF results file
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -15,6 +15,9 @@ on:
|
||||
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
@@ -38,17 +41,17 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
@@ -60,7 +63,7 @@ jobs:
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: snipe/snipe-it
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
@@ -69,7 +72,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.alpine
|
||||
|
||||
@@ -15,6 +15,9 @@ on:
|
||||
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
@@ -38,17 +41,17 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
@@ -60,7 +63,7 @@ jobs:
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: snipe/snipe-it
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
@@ -69,7 +72,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
+3
-1
@@ -23,6 +23,8 @@ php7.4-xml \
|
||||
php7.4-mbstring \
|
||||
php7.4-zip \
|
||||
php7.4-bcmath \
|
||||
php7.4-redis \
|
||||
php-memcached \
|
||||
patch \
|
||||
curl \
|
||||
wget \
|
||||
@@ -138,4 +140,4 @@ RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
|
||||
CMD ["/startup.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
EXPOSE 443
|
||||
|
||||
+3
-1
@@ -27,6 +27,8 @@ RUN apk add --no-cache \
|
||||
php7-xmlwriter \
|
||||
php7-xmlreader \
|
||||
php7-sodium \
|
||||
php7-redis \
|
||||
php7-pecl-memcached \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
@@ -84,4 +86,4 @@ ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 80
|
||||
|
||||
@@ -75,14 +75,14 @@ RUN set -eux; \
|
||||
rm snipeit.tar.gz; \
|
||||
# Install composer php dependencies
|
||||
if [ "$ENVIRONMENT" = "production" ]; then \
|
||||
echo "production enviroment detected!"; \
|
||||
echo "production environment detected!"; \
|
||||
composer update \
|
||||
--no-cache \
|
||||
--no-dev \
|
||||
--optimize-autoloader \
|
||||
--working-dir=/var/www/html; \
|
||||
else \
|
||||
echo "development enviroment detected!"; \
|
||||
echo "development environment detected!"; \
|
||||
apk add --no-cache \
|
||||
${DEV_PACKAGES}; \
|
||||
composer update \
|
||||
@@ -100,4 +100,4 @@ COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
|
||||
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
 [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
@@ -59,14 +59,16 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@RodneyLeeBrands](https://github.com/RodneyLeeBrands) - Python script to synchronize information between Mosyle and Snipe-IT
|
||||
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
|
||||
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
-----
|
||||
|
||||
@@ -77,6 +79,8 @@ Please see the documentation on [contributing and developing for Snipe-IT](https
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||
|
||||
-----
|
||||
|
||||
### Security
|
||||
@@ -131,7 +135,10 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Using the Test Suite
|
||||
|
||||
This document is targeted at developers looking to make modifications to
|
||||
this application's code base and want to run the existing test suite.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Follow the instructions for installing the application locally,
|
||||
making sure to have also run the [database migrations](link to db migrations).
|
||||
|
||||
|
||||
## Unit Tests
|
||||
|
||||
The application will use values in the `.env.testing` file located
|
||||
in the root directory to override the
|
||||
default settings and/or other values that exist in your `.env` files.
|
||||
|
||||
Make sure to modify the section in `.env.testing` that has the
|
||||
database settings. In the example below, it is connecting to the
|
||||
[MariaDB](link-to-maria-db) server that is used if you install the
|
||||
application using [Docker](https://docker.com).
|
||||
|
||||
```dotenv
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_DATABASE=snipeit
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=changeme1234
|
||||
```
|
||||
|
||||
To run the entire unit test suite, use the following command from your terminal:
|
||||
|
||||
`php artisan test --env=testing`
|
||||
|
||||
To run individual test files, you can pass the path to the test that
|
||||
you want to run.
|
||||
|
||||
`php artisan test --env=testing tests/Unit/AccessoryTest.php`
|
||||
|
||||
## Browser Tests
|
||||
|
||||
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them.
|
||||
When troubleshooting any problems, make sure that your `.env` file is configured
|
||||
correctly to run the existing application.
|
||||
|
||||
### Test Setup
|
||||
|
||||
Your application needs to be configued and up and running in order for the browser
|
||||
tests to actually run. When running the tests locally, you can start the application
|
||||
using the following command:
|
||||
|
||||
`php artisan serve`
|
||||
|
||||
|
||||
To run the test suite use the following command from another terminal tab or window:
|
||||
|
||||
`php artisan dusk`
|
||||
|
||||
To run individual test files, you can pass the path to the test that you want to run.
|
||||
|
||||
`php artisan dusk tests/Browser/LoginTest.php`
|
||||
@@ -118,7 +118,7 @@
|
||||
"description": "The duration (in seconds) that the user should be blocked from attempting to authenticate again.",
|
||||
"value": "60"
|
||||
},
|
||||
"APP_LOG": {
|
||||
"LOG_CHANNEL": {
|
||||
"description": "Driver to send logs to. (errorlog for stderr)",
|
||||
"value": "errorlog"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use Laravel\Passport\TokenRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use DB;
|
||||
|
||||
class GeneratePersonalAccessToken extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:make-api-key
|
||||
{--user_id= : The ID of the user to create the token for.}
|
||||
{--name= : The name of the new API token}
|
||||
{--key-only : Only return the value of the API key}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This console command allows you to generate Personal API tokens to be used with the Snipe-IT JSON REST API on behalf of a user.';
|
||||
|
||||
|
||||
/**
|
||||
* The token repository implementation.
|
||||
*
|
||||
* @var \Laravel\Passport\TokenRepository
|
||||
*/
|
||||
protected $tokenRepository;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
|
||||
{
|
||||
$this->validation = $validation;
|
||||
$this->tokenRepository = $tokenRepository;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$accessTokenName = $this->option('name');
|
||||
if ($accessTokenName=='') {
|
||||
$accessTokenName = 'CLI Auth Token';
|
||||
}
|
||||
|
||||
if ($this->option('user_id')=='') {
|
||||
return $this->error('ERROR: user_id cannot be blank.');
|
||||
}
|
||||
|
||||
if ($user = User::find($this->option('user_id'))) {
|
||||
|
||||
$createAccessToken = $user->createToken($accessTokenName)->accessToken;
|
||||
|
||||
if ($this->option('key-only')) {
|
||||
$this->info($createAccessToken);
|
||||
|
||||
} else {
|
||||
|
||||
$this->warn('Your API Token has been created. Be sure to copy this token now, as it will not be accessible again.');
|
||||
|
||||
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) {
|
||||
$this->info('API Token ID: '.$token->id);
|
||||
}
|
||||
|
||||
$this->info('API Token User: '.$user->present()->fullName.' ('.$user->username.')');
|
||||
$this->info('API Token Name: '.$accessTokenName);
|
||||
$this->info('API Token: '.$createAccessToken);
|
||||
}
|
||||
} else {
|
||||
return $this->error('ERROR: Invalid user. API key was not created.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class LdapSync extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--summary} {--json_summary}';
|
||||
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -49,7 +49,7 @@ class LdapSync extends Command
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag_field;
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
$ldap_result_phone = Setting::getSettings()->ldap_phone_field;
|
||||
@@ -80,7 +80,11 @@ class LdapSync extends Command
|
||||
} else {
|
||||
$search_base = null;
|
||||
}
|
||||
$results = Ldap::findLdapUsers($search_base);
|
||||
if ($this->option('filter') != '') {
|
||||
$results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
|
||||
} else {
|
||||
$results = Ldap::findLdapUsers($search_base);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
|
||||
@@ -109,7 +113,7 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
/* Process locations with explicitly defined OUs, if doing a full import. */
|
||||
if ($this->option('base_dn') == '') {
|
||||
if ($this->option('base_dn') == '' && $this->option('filter') == '') {
|
||||
// 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 = [];
|
||||
@@ -171,8 +175,9 @@ class LdapSync extends Command
|
||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
|
||||
$pass = bcrypt($tmp_pass);
|
||||
|
||||
$manager_cache = [];
|
||||
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
if (empty($ldap_result_active_flag) || $results[$i][$ldap_result_active_flag][0] == 'TRUE') {
|
||||
$item = [];
|
||||
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : '';
|
||||
$item['employee_number'] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : '';
|
||||
@@ -199,7 +204,7 @@ class LdapSync extends Command
|
||||
// Creating a new user.
|
||||
$user = new User;
|
||||
$user->password = $pass;
|
||||
$user->activated = 0;
|
||||
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
|
||||
$item['createorupdate'] = 'created';
|
||||
}
|
||||
|
||||
@@ -214,19 +219,61 @@ class LdapSync extends Command
|
||||
$user->department_id = $department->id;
|
||||
|
||||
if($item['manager'] != null) {
|
||||
//Captures only the Canonical Name
|
||||
$item['manager'] = ltrim($item['manager'], "CN=");
|
||||
$item['manager'] = substr($item['manager'],0, strpos($item['manager'], ','));
|
||||
$ldap_manager = User::where('username', $item['manager'])->first();
|
||||
if ( $ldap_manager && isset($ldap_manager->id) ) {
|
||||
$user->manager_id = $ldap_manager->id;
|
||||
// Check Cache first
|
||||
if (isset($manager_cache[$item['manager']])) {
|
||||
// found in cache; use that and avoid extra lookups
|
||||
$user->manager_id = $manager_cache[$item['manager']];
|
||||
} else {
|
||||
// Get the LDAP Manager
|
||||
try {
|
||||
$ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
|
||||
// Hail-mary for Okta manager 'shortnames' - will only work if
|
||||
// Okta configuration is using full email-address-style usernames
|
||||
$ldap_manager = [
|
||||
"count" => 1,
|
||||
0 => [
|
||||
$ldap_result_username => [$item['manager']]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($ldap_manager["count"] > 0) {
|
||||
|
||||
// Get the Manager's username
|
||||
// PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
|
||||
$ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0];
|
||||
|
||||
// Get User from Manager username.
|
||||
$ldap_manager = User::where('username', $ldapManagerUsername)->first();
|
||||
|
||||
if ($ldap_manager && isset($ldap_manager->id)) {
|
||||
// Link user to manager id.
|
||||
$user->manager_id = $ldap_manager->id;
|
||||
}
|
||||
}
|
||||
$manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sync activated state for Active Directory.
|
||||
if (array_key_exists('useraccountcontrol', $results[$i])) {
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
|
||||
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
|
||||
// (Specifically, we don't handle a value of '0.0' correctly)
|
||||
$raw_value = @$results[$i][$ldap_result_active_flag][0];
|
||||
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$boolean_cast = (bool)$raw_value;
|
||||
|
||||
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
|
||||
|
||||
} elseif (array_key_exists('useraccountcontrol', $results[$i]) ) {
|
||||
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
|
||||
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
|
||||
|
||||
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
some users may have been dependent upon the previous behavior, and this
|
||||
could cause additional access to be available to users they don't want
|
||||
to allow to log in.
|
||||
@@ -256,12 +303,11 @@ class LdapSync extends Command
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
];
|
||||
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// If we're not using AD, and there isn't an activated flag set, activate all users
|
||||
elseif (empty($ldap_result_active_flag)) {
|
||||
$user->activated = 1;
|
||||
}
|
||||
} /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
|
||||
already-existing accounts will be however the administrator has set them */
|
||||
|
||||
|
||||
if ($item['ldap_location_override'] == true) {
|
||||
$user->location_id = $item['location_id'];
|
||||
@@ -289,7 +335,6 @@ class LdapSync extends Command
|
||||
}
|
||||
|
||||
array_push($summary, $item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('summary')) {
|
||||
|
||||
@@ -27,6 +27,19 @@ function ip_in_range( $ip, $range ) {
|
||||
}
|
||||
// NOTE - this function was shamelessly stolen from this gist: https://gist.github.com/tott/7684443
|
||||
|
||||
/**
|
||||
* Ensure LDAP filters are parentheses-wrapped
|
||||
*/
|
||||
function parenthesized_filter($filter)
|
||||
{
|
||||
if(substr($filter,0,1) == "(" ) {
|
||||
return $filter;
|
||||
} else {
|
||||
return "(".$filter.")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LdapTroubleshooter extends Command
|
||||
{
|
||||
/**
|
||||
@@ -70,6 +83,47 @@ class LdapTroubleshooter extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the results from ldap_get_entries into something useful
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
public function ldap_results_cleaner ($array) {
|
||||
$cleaned = [];
|
||||
for($i = 0; $i < $array['count']; $i++) {
|
||||
$row = $array[$i];
|
||||
$clean_row = [];
|
||||
foreach($row AS $key => $val ) {
|
||||
$this->debugout("Key is: ".$key);
|
||||
if($key == "count" || is_int($key) || $key == "dn") {
|
||||
$this->debugout(" and we're gonna skip it\n");
|
||||
continue;
|
||||
}
|
||||
$this->debugout(" And that seems fine.\n");
|
||||
if(array_key_exists('count',$val)) {
|
||||
if($val['count'] == 1) {
|
||||
$clean_row[$key] = $val[0];
|
||||
} else {
|
||||
unset($val['count']); //these counts are annoying
|
||||
$elements = [];
|
||||
foreach($val as $entry) {
|
||||
if(isset($ldap_constants[$entry])) {
|
||||
$elements[] = $ldap_constants[$entry];
|
||||
} else {
|
||||
$elements[] = $entry;
|
||||
}
|
||||
}
|
||||
$clean_row[$key] = $elements;
|
||||
}
|
||||
} else {
|
||||
$clean_row[$key] = $val;
|
||||
}
|
||||
}
|
||||
$cleaned[$i] = $clean_row;
|
||||
}
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -102,16 +156,12 @@ class LdapTroubleshooter extends Command
|
||||
$output[] = "LDAPTLS_KEY=storage/ldap_client_tls.key";
|
||||
}
|
||||
$output[] = "ldapsearch";
|
||||
$output[] = $settings->ldap_server;
|
||||
$output[] = "-H ".$settings->ldap_server;
|
||||
$output[] = "-x";
|
||||
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
|
||||
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
|
||||
$output[] = "-w ".escapeshellarg(\Crypt::Decrypt($settings->ldap_pword));
|
||||
if(substr($settings->ldap_filter,0,1) == "(" ) {
|
||||
$output[] = escapeshellarg($settings->ldap_filter);
|
||||
} else {
|
||||
$output[] = escapeshellarg("(".$settings->ldap_filter.")");
|
||||
}
|
||||
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
|
||||
if($settings->ldap_tls) {
|
||||
$this->line("# adding STARTTLS option");
|
||||
$output[] = "-Z";
|
||||
@@ -290,45 +340,8 @@ class LdapTroubleshooter extends Command
|
||||
}
|
||||
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
|
||||
|
||||
// recursive function that 'cleans' the returned array from ldap_get_entries which are formatted awfully
|
||||
$cleaner = function ($array) {
|
||||
$cleaned = [];
|
||||
for($i = 0; $i < $array['count']; $i++) {
|
||||
$row = $array[$i];
|
||||
$clean_row = [];
|
||||
foreach($row AS $key => $val ) {
|
||||
$this->debugout("Key is: ".$key);
|
||||
if($key == "count" || is_int($key) || $key == "dn") {
|
||||
$this->debugout(" and we're gonna skip it\n");
|
||||
continue;
|
||||
}
|
||||
$this->debugout(" And that seems fine.\n");
|
||||
if(array_key_exists('count',$val)) {
|
||||
if($val['count'] == 1) {
|
||||
$clean_row[$key] = $val[0];
|
||||
} else {
|
||||
unset($val['count']); //these counts are annoying
|
||||
$elements = [];
|
||||
foreach($val as $entry) {
|
||||
if(isset($ldap_constants[$entry])) {
|
||||
$elements[] = $ldap_constants[$entry];
|
||||
} else {
|
||||
$elements[] = $entry;
|
||||
}
|
||||
}
|
||||
$clean_row[$key] = $elements;
|
||||
}
|
||||
} else {
|
||||
$clean_row[$key] = $val;
|
||||
}
|
||||
}
|
||||
$cleaned[$i] = $clean_row;
|
||||
}
|
||||
return $cleaned;
|
||||
};
|
||||
|
||||
foreach($ldap_urls AS $ldap_url) {
|
||||
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword))) {
|
||||
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword),$settings)) {
|
||||
$this->info("Success getting informational bind!");
|
||||
} else {
|
||||
$this->error("Unable to get information from bind.");
|
||||
@@ -422,9 +435,9 @@ class LdapTroubleshooter extends Command
|
||||
});
|
||||
}
|
||||
|
||||
public function test_informational_bind($ldap_url, $check_cert, $start_tls, $username, $password)
|
||||
public function test_informational_bind($ldap_url, $check_cert, $start_tls, $username, $password,$settings)
|
||||
{
|
||||
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert, $start_tls, $username, $password) {
|
||||
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert, $start_tls, $username, $password, $settings) {
|
||||
try { // TODO - copypasta'ed from test_authed_bind
|
||||
$conn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
|
||||
$bind_results = ldap_bind($conn, $username, $password);
|
||||
@@ -435,12 +448,13 @@ class LdapTroubleshooter extends Command
|
||||
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
|
||||
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
|
||||
$results = ldap_get_entries($conn, $result);
|
||||
$cleaned_results = $cleaner($results);
|
||||
$cleaned_results = $this->ldap_results_cleaner($results);
|
||||
$this->line(print_r($cleaned_results,true));
|
||||
//okay, great - now how do we display those results? I have no idea.
|
||||
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
|
||||
$this->comment("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
|
||||
$search_results = ldap_search($conn, $settings->base_dn, $settings->filter);
|
||||
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
|
||||
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
|
||||
$this->info("Printing first 10 results: ");
|
||||
for($i=0;$i<10;$i++) {
|
||||
$this->info($search_results[$i]);
|
||||
|
||||
@@ -71,22 +71,31 @@ class ReEncodeCustomFieldNames extends Command
|
||||
*/
|
||||
$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().'');
|
||||
$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');
|
||||
if ($field->db_column == $field->convertUnicodeDbSlug() && \Schema::hasColumn('assets', $field->convertUnicodeDbSlug())) {
|
||||
$this->info('-- ✓ This field exists on the assets table and the value for db_column matches in the custom_fields table.');
|
||||
|
||||
/**
|
||||
* 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... ');
|
||||
|
||||
if ($field->db_column != $field->convertUnicodeDbSlug()) {
|
||||
$this->error('-- ✘ Field mismatch: '.$field->name.' value should be '.$field->convertUnicodeDbSlug().' but is '.$field->db_column.' in the custom_fields table');
|
||||
|
||||
} else {
|
||||
$this->error('-- ✘ Field mismatch: '.$field->name.' column should be '.$field->convertUnicodeDbSlug().' but is '.$custom_field_columns[$field->id].' on the assets table.');
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Make sure the custom_field_columns array has the ID */
|
||||
if (array_key_exists($field->id, $custom_field_columns)) {
|
||||
@@ -95,13 +104,19 @@ class ReEncodeCustomFieldNames extends Command
|
||||
* Update the asset schema to the corrected fieldname that will be recognized by the
|
||||
* system elsewhere that we use $field->convertUnicodeDbSlug()
|
||||
*/
|
||||
$this->info('-- ✓ Updating field from '.$field->db_column.' to '.$field->convertUnicodeDbSlug().' in the assets table');
|
||||
\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());
|
||||
$this->info('-- ✓ Updating field from '.$field->db_column.' to '.$field->convertUnicodeDbSlug().' in the custom fields table');
|
||||
|
||||
$field->db_column = $field->convertUnicodeDbSlug();
|
||||
$field->save();
|
||||
|
||||
|
||||
} 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.');
|
||||
$this->warn('-- ✘ 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.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command('snipeit:backup')->weekly();
|
||||
$schedule->command('backup:clean')->daily();
|
||||
$schedule->command('snipeit:upcoming-audits')->daily();
|
||||
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+48
-16
@@ -22,12 +22,13 @@ class Helper
|
||||
* @since [v2.0]
|
||||
* @return string
|
||||
*/
|
||||
public static function parseEscapedMarkedown($str)
|
||||
public static function parseEscapedMarkedown($str = null)
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
$Parsedown->setSafeMode(true);
|
||||
|
||||
if ($str) {
|
||||
return $Parsedown->text(e($str));
|
||||
return $Parsedown->text($str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +624,7 @@ class Helper
|
||||
{
|
||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::withCount('assets as assets_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::whereNotNull('min_amt')->get();
|
||||
|
||||
$avail_consumables = 0;
|
||||
$items_array = [];
|
||||
@@ -668,7 +669,7 @@ class Helper
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$avail = $component->qty - $component->assets_count;
|
||||
$avail = $component->numRemaining();
|
||||
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($component->qty > 0) {
|
||||
$percent = number_format((($avail / $component->qty) * 100), 0);
|
||||
@@ -841,6 +842,16 @@ class Helper
|
||||
return preg_replace('/\s+/u', '_', trim($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array (or null) of the the raw and formatted date object for easy use in
|
||||
* the API and the bootstrap table listings.
|
||||
*
|
||||
* @param $date
|
||||
* @param $type
|
||||
* @param $array
|
||||
* @return array|string|null
|
||||
*/
|
||||
|
||||
public static function getFormattedDateObject($date, $type = 'datetime', $array = true)
|
||||
{
|
||||
if ($date == '') {
|
||||
@@ -848,21 +859,42 @@ class Helper
|
||||
}
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
$tmp_date = new \Carbon($date);
|
||||
|
||||
if ($type == 'datetime') {
|
||||
$dt['datetime'] = $tmp_date->format('Y-m-d H:i:s');
|
||||
$dt['formatted'] = $tmp_date->format($settings->date_display_format.' '.$settings->time_display_format);
|
||||
} else {
|
||||
$dt['date'] = $tmp_date->format('Y-m-d');
|
||||
$dt['formatted'] = $tmp_date->format($settings->date_display_format);
|
||||
/**
|
||||
* Wrap this in a try/catch so that if Carbon crashes, for example if the $date value
|
||||
* isn't actually valid, we don't crash out completely.
|
||||
*
|
||||
* While this *shouldn't* typically happen since we validate dates before entering them
|
||||
* into the database (and we use date/datetime fields for native fields in the system),
|
||||
* it is a possible scenario that a custom field could be created as an "ANY" field, data gets
|
||||
* added, and then the custom field format gets edited later. If someone put bad data in the
|
||||
* database before then - or if they manually edited the field's value - it will crash.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
try {
|
||||
$tmp_date = new \Carbon($date);
|
||||
|
||||
if ($type == 'datetime') {
|
||||
$dt['datetime'] = $tmp_date->format('Y-m-d H:i:s');
|
||||
$dt['formatted'] = $tmp_date->format($settings->date_display_format.' '.$settings->time_display_format);
|
||||
} else {
|
||||
$dt['date'] = $tmp_date->format('Y-m-d');
|
||||
$dt['formatted'] = $tmp_date->format($settings->date_display_format);
|
||||
}
|
||||
|
||||
if ($array == 'true') {
|
||||
return $dt;
|
||||
}
|
||||
|
||||
return $dt['formatted'];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning($e);
|
||||
return $date.' (Invalid '.$type.' value.)';
|
||||
}
|
||||
|
||||
if ($array == 'true') {
|
||||
return $dt;
|
||||
}
|
||||
|
||||
return $dt['formatted'];
|
||||
}
|
||||
|
||||
// Nicked from Drupal :)
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Accessory;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Redirect;
|
||||
|
||||
/** This controller handles all actions related to Accessories for
|
||||
@@ -130,6 +131,17 @@ class AccessoriesController extends Controller
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$min = $accessory->numCheckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
// Update the accessory data
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Accessory;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Accessory\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class AccessoriesFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a accessory.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $accessoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $accessoryId = null)
|
||||
{
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('accessories.files', $accessory);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
if (! Storage::exists('private_uploads/accessories')) {
|
||||
Storage::makeDirectory('private_uploads/accessories', 775);
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\Log::debug('This is an SVG');
|
||||
\Log::debug($file_name);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
Storage::put('private_uploads/accessories/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$accessory->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('general.file_upload_success'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded'));
|
||||
}
|
||||
// Prepare the error message
|
||||
return redirect()->route('accessories.index')
|
||||
->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected accessory file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $accessoryId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($accessoryId = null, $fileId = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
// the asset is valid
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('update', $accessory);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('accessories/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('accessories/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the selected file to be viewed.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.4]
|
||||
* @param int $accessoryId
|
||||
* @param int $fileId
|
||||
* @return \Symfony\Accessory\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($accessoryId = null, $fileId = null, $download = true)
|
||||
{
|
||||
|
||||
\Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||
$accessory = Accessory::find($accessoryId);
|
||||
|
||||
|
||||
|
||||
// the accessory is valid
|
||||
if (isset($accessory->id)) {
|
||||
$this->authorize('view', $accessory);
|
||||
$this->authorize('accessories.files', $accessory);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/accessories/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class AccessoryCheckoutController extends Controller
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
if (! $user = User::find($request->input('assigned_to'))) {
|
||||
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
|
||||
}
|
||||
|
||||
// Update the accessory data
|
||||
|
||||
@@ -12,9 +12,15 @@ use App\Models\Asset;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contracts\Acceptable;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\License;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -23,6 +29,7 @@ use Illuminate\Support\Str;
|
||||
use App\Http\Controllers\SettingsController;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Carbon\Carbon;
|
||||
use phpDocumentor\Reflection\Types\Compound;
|
||||
|
||||
class AcceptanceController extends Controller
|
||||
{
|
||||
@@ -106,12 +113,7 @@ class AcceptanceController extends Controller
|
||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for the eula-pdfs directory
|
||||
*/
|
||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||
}
|
||||
|
||||
|
||||
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
||||
$display_model = '';
|
||||
@@ -122,36 +124,99 @@ class AcceptanceController extends Controller
|
||||
|
||||
if ($request->input('asset_acceptance') == 'accepted') {
|
||||
|
||||
// The item was accepted, check for a signature
|
||||
if ($request->filled('signature_output')) {
|
||||
$sig_filename = 'siglog-'.Str::uuid().'-'.date('Y-m-d-his').'.png';
|
||||
$data_uri = $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);
|
||||
/**
|
||||
* Check for the eula-pdfs directory
|
||||
*/
|
||||
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
||||
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->require_accept_signature == '1') {
|
||||
|
||||
// Check if the signature directory exists, if not create it
|
||||
if (!Storage::exists('private_uploads/signatures')) {
|
||||
Storage::makeDirectory('private_uploads/signatures', 775);
|
||||
}
|
||||
|
||||
// The item was accepted, check for a signature
|
||||
if ($request->filled('signature_output')) {
|
||||
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
|
||||
$data_uri = $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);
|
||||
|
||||
// No image data is present, kick them back.
|
||||
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
|
||||
} else {
|
||||
return redirect()->back()->with('error', trans('general.shitty_browser'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// this is horrible
|
||||
if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
||||
$pdf_view_route ='account.accept.accept-asset-eula';
|
||||
$asset_model = AssetModel::find($item->model_id);
|
||||
$display_model = $asset_model->name;
|
||||
$assigned_to = User::find($item->assigned_to)->present()->fullName;
|
||||
switch($acceptance->checkoutable_type){
|
||||
case 'App\Models\Asset':
|
||||
$pdf_view_route ='account.accept.accept-asset-eula';
|
||||
$asset_model = AssetModel::find($item->model_id);
|
||||
$display_model = $asset_model->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
} elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
||||
$accessory = Accessory::find($item->id);
|
||||
$display_model = $accessory->name;
|
||||
$assigned_to = User::find($item->assignedTo);
|
||||
case 'App\Models\Accessory':
|
||||
$pdf_view_route ='account.accept.accept-accessory-eula';
|
||||
$accessory = Accessory::find($item->id);
|
||||
$display_model = $accessory->name;
|
||||
$assigned_to = User::find($item->assignedTo);
|
||||
break;
|
||||
|
||||
case 'App\Models\LicenseSeat':
|
||||
$pdf_view_route ='account.accept.accept-license-eula';
|
||||
$license = License::find($item->license_id);
|
||||
$display_model = $license->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Component':
|
||||
$pdf_view_route ='account.accept.accept-component-eula';
|
||||
$component = Component::find($item->id);
|
||||
$display_model = $component->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Consumable':
|
||||
$pdf_view_route ='account.accept.accept-consumable-eula';
|
||||
$consumable = Consumable::find($item->id);
|
||||
$display_model = $consumable->name;
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
}
|
||||
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
||||
// $pdf_view_route ='account.accept.accept-asset-eula';
|
||||
// $asset_model = AssetModel::find($item->model_id);
|
||||
// $display_model = $asset_model->name;
|
||||
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
|
||||
//
|
||||
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
||||
// $pdf_view_route ='account.accept.accept-accessory-eula';
|
||||
// $accessory = Accessory::find($item->id);
|
||||
// $display_model = $accessory->name;
|
||||
// $assigned_to = User::find($item->assignedTo);
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* Gather the data for the PDF. We fire this whether there is a signature required or not,
|
||||
* since we want the moment-in-time proof of what the EULA was when they accepted it.
|
||||
*/
|
||||
$branding_settings = SettingsController::getPDFBranding();
|
||||
|
||||
if (is_null($branding_settings->logo)){
|
||||
$path_logo = "";
|
||||
} else {
|
||||
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'item_tag' => $item->asset_tag,
|
||||
'item_model' => $display_model,
|
||||
@@ -162,7 +227,7 @@ class AcceptanceController extends Controller
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
||||
'logo' => public_path() . '/uploads/' . $branding_settings->logo,
|
||||
'logo' => $path_logo,
|
||||
'date_settings' => $branding_settings->date_display_format,
|
||||
];
|
||||
|
||||
@@ -173,12 +238,49 @@ class AcceptanceController extends Controller
|
||||
}
|
||||
|
||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename);
|
||||
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
||||
event(new CheckoutAccepted($acceptance));
|
||||
|
||||
$return_msg = trans('admin/users/message.accepted');
|
||||
|
||||
} else {
|
||||
// Format the data to send the declined notification
|
||||
$branding_settings = SettingsController::getPDFBranding();
|
||||
|
||||
// This is the most horriblest
|
||||
switch($acceptance->checkoutable_type){
|
||||
case 'App\Models\Asset':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Accessory':
|
||||
$assigned_to = User::find($item->assignedTo);
|
||||
break;
|
||||
|
||||
case 'App\Models\LicenseSeat':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Component':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
|
||||
case 'App\Models\Consumable':
|
||||
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
||||
break;
|
||||
}
|
||||
$data = [
|
||||
'item_tag' => $item->asset_tag,
|
||||
'item_model' => $display_model,
|
||||
'item_serial' => $item->serial,
|
||||
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'date_settings' => $branding_settings->date_display_format,
|
||||
];
|
||||
|
||||
$acceptance->decline($sig_filename);
|
||||
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
||||
event(new CheckoutDeclined($acceptance));
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ class AccessoriesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Accessory::class);
|
||||
$allowed_columns = ['id', 'name', 'model_number', 'eol', 'notes', 'created_at', 'min_amt', 'company_id'];
|
||||
|
||||
|
||||
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
|
||||
// Relations will be handled in query scopes a little further down.
|
||||
$allowed_columns =
|
||||
|
||||
@@ -35,7 +35,8 @@ class AssetMaintenancesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$maintenances = AssetMaintenance::with('asset', 'asset.model', 'asset.location', 'supplier', 'asset.company', 'admin');
|
||||
|
||||
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'supplier', 'asset.company', 'admin');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$maintenances = $maintenances->TextSearch($request->input('search'));
|
||||
@@ -45,6 +46,15 @@ class AssetMaintenancesController extends Controller
|
||||
$maintenances->where('asset_id', '=', $request->input('asset_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('supplier_id')) {
|
||||
$maintenances->where('supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_maintenance_type')) {
|
||||
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
@@ -64,6 +74,7 @@ class AssetMaintenancesController extends Controller
|
||||
'asset_tag',
|
||||
'asset_name',
|
||||
'user_id',
|
||||
'supplier'
|
||||
];
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
@@ -72,6 +83,9 @@ class AssetMaintenancesController extends Controller
|
||||
case 'user_id':
|
||||
$maintenances = $maintenances->OrderAdmin($order);
|
||||
break;
|
||||
case 'supplier':
|
||||
$maintenances = $maintenances->OrderBySupplier($order);
|
||||
break;
|
||||
case 'asset_tag':
|
||||
$maintenances = $maintenances->OrderByTag($order);
|
||||
break;
|
||||
|
||||
@@ -70,6 +70,10 @@ class AssetModelsController extends Controller
|
||||
$assetmodels->onlyTrashed();
|
||||
}
|
||||
|
||||
if ($request->filled('category_id')) {
|
||||
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$assetmodels->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
@@ -544,11 +544,11 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
|
||||
// Set the field value based on what was sent in the request
|
||||
$field_val = $request->input($field->convertUnicodeDbSlug(), null);
|
||||
$field_val = $request->input($field->db_column, null);
|
||||
|
||||
// If input value is null, use custom field's default value
|
||||
if ($field_val == null) {
|
||||
\Log::debug('Field value for '.$field->convertUnicodeDbSlug().' is null');
|
||||
\Log::debug('Field value for '.$field->db_column.' is null');
|
||||
$field_val = $field->defaultValue($request->get('model_id'));
|
||||
\Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
|
||||
}
|
||||
@@ -563,13 +563,13 @@ class AssetsController extends Controller
|
||||
if (($field_val == null) && ($request->has('model_id') != '')) {
|
||||
$field_val = \Crypt::encrypt($field->defaultValue($request->get('model_id')));
|
||||
} else {
|
||||
$field_val = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
$field_val = \Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $field_val;
|
||||
$asset->{$field->db_column} = $field_val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,13 +634,13 @@ class AssetsController extends Controller
|
||||
// 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 ($request->has($field->db_column)) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,30 +714,53 @@ class AssetsController extends Controller
|
||||
* @since [v5.1.18]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function restore($assetId = null)
|
||||
public function restore(Request $request, $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');
|
||||
if ($asset->deleted_at=='') {
|
||||
$message = 'Asset was not deleted. No data was changed.';
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.restore.success')));
|
||||
} else {
|
||||
|
||||
$message = trans('admin/hardware/message.restore.success');
|
||||
// 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 response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset, $request), $message));
|
||||
|
||||
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checkout an asset by its tag.
|
||||
*
|
||||
* @author [N. Butler]
|
||||
* @param string $tag
|
||||
* @since [v6.0.5]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
|
||||
{
|
||||
if ($asset = Asset::where('asset_tag', $tag)->first()) {
|
||||
return $this->checkout($request, $asset->id);
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout an asset
|
||||
@@ -849,7 +872,7 @@ class AssetsController extends Controller
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
|
||||
if ($request->filled('name')) {
|
||||
if ($request->has('name')) {
|
||||
$asset->name = $request->input('name');
|
||||
}
|
||||
|
||||
@@ -862,11 +885,9 @@ class AssetsController extends Controller
|
||||
if ($request->has('status_id')) {
|
||||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
||||
|
||||
$checkin_at = null;
|
||||
if ($request->filled('checkin_at')) {
|
||||
$checkin_at = $request->input('checkin_at');
|
||||
}
|
||||
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
@@ -889,7 +910,7 @@ class AssetsController extends Controller
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first();
|
||||
|
||||
if($asset) {
|
||||
if ($asset) {
|
||||
return $this->checkin($request, $asset->id);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,28 @@ class CategoriesController extends Controller
|
||||
$categories = $categories->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$categories->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('category_type')) {
|
||||
$categories->where('category_type', '=', $request->input('category_type'));
|
||||
}
|
||||
|
||||
if ($request->filled('use_default_eula')) {
|
||||
$categories->where('use_default_eula', '=', $request->input('use_default_eula'));
|
||||
}
|
||||
|
||||
if ($request->filled('require_acceptance')) {
|
||||
$categories->where('require_acceptance', '=', $request->input('require_acceptance'));
|
||||
}
|
||||
|
||||
if ($request->filled('checkin_email')) {
|
||||
$categories->where('checkin_email', '=', $request->input('checkin_email'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -43,6 +43,11 @@ class CompaniesController extends Controller
|
||||
$companies->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$companies->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -51,6 +51,10 @@ class ComponentsController extends Controller
|
||||
$components = $components->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$components->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$components->where('company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
@@ -242,7 +246,8 @@ class ComponentsController extends Controller
|
||||
'created_at' => \Carbon::now(),
|
||||
'assigned_qty' => $request->get('assigned_qty', 1),
|
||||
'user_id' => \Auth::id(),
|
||||
'asset_id' => $request->get('assigned_to')
|
||||
'asset_id' => $request->get('assigned_to'),
|
||||
'note' => $request->get('note'),
|
||||
]);
|
||||
|
||||
$component->logCheckout($request->input('note'), $asset);
|
||||
|
||||
@@ -55,6 +55,10 @@ class ConsumablesController extends Controller
|
||||
$consumables = $consumables->TextSearch(e($request->input('search')));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$consumables->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$consumables->where('company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
@@ -224,9 +228,11 @@ class ConsumablesController extends Controller
|
||||
|
||||
foreach ($consumable->consumableAssignments as $consumable_assignment) {
|
||||
$rows[] = [
|
||||
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
|
||||
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
|
||||
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
|
||||
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
|
||||
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
|
||||
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -269,6 +275,7 @@ class ConsumablesController extends Controller
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $user->id,
|
||||
'assigned_to' => $assigned_to,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
// Log checkout event
|
||||
|
||||
@@ -42,6 +42,22 @@ class DepartmentsController extends Controller
|
||||
$departments = $departments->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$departments->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$departments->where('company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('manager_id')) {
|
||||
$departments->where('manager_id', '=', $request->input('manager_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
$departments->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 = (($departments) && ($request->get('offset') > $departments->count())) ? $departments->count() : $request->get('offset', 0);
|
||||
|
||||
@@ -28,6 +28,10 @@ class GroupsController extends Controller
|
||||
$groups = $groups->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$groups->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -39,7 +39,7 @@ class LicenseSeatsController extends Controller
|
||||
}
|
||||
|
||||
$total = $seats->count();
|
||||
$offset = (($seats) && (request('offset') > $total)) ? 0 : request('offset', 0);
|
||||
$offset = (($seats) && (request('offset') >= $total)) ? 0 : request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$seats = $seats->skip($offset)->take($limit)->get();
|
||||
@@ -116,16 +116,20 @@ class LicenseSeatsController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
||||
// the logging functions expect only one "target". if both asset and user are present in the request,
|
||||
// we simply let assets take precedence over users...
|
||||
if ($licenseSeat->isDirty('assigned_to')) {
|
||||
$target = $is_checkin ? $oldUser : User::find($licenseSeat->assigned_to);
|
||||
}
|
||||
if ($licenseSeat->isDirty('asset_id')) {
|
||||
$target = $is_checkin ? $oldAsset : Asset::find($licenseSeat->asset_id);
|
||||
}
|
||||
|
||||
if (is_null($target)){
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Target not found'));
|
||||
}
|
||||
|
||||
if ($licenseSeat->save()) {
|
||||
// the logging functions expect only one "target". if both asset and user are present in the request,
|
||||
// we simply let assets take precedence over users...
|
||||
$changes = $licenseSeat->getChanges();
|
||||
if (array_key_exists('assigned_to', $changes)) {
|
||||
$target = $is_checkin ? $oldUser : User::find($changes['assigned_to']);
|
||||
}
|
||||
if (array_key_exists('asset_id', $changes)) {
|
||||
$target = $is_checkin ? $oldAsset : Asset::find($changes['asset_id']);
|
||||
}
|
||||
|
||||
if ($is_checkin) {
|
||||
$licenseSeat->logCheckin($target, $request->input('note'));
|
||||
|
||||
@@ -73,9 +73,6 @@ class LicensesController extends Controller
|
||||
$licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('supplier_id')) {
|
||||
$licenses->where('supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
|
||||
$licenses->where('maintained','=',1);
|
||||
|
||||
@@ -27,7 +27,7 @@ class LocationsController extends Controller
|
||||
$allowed_columns = [
|
||||
'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
|
||||
'updated_at', 'manager_id', 'image',
|
||||
'assigned_assets_count', 'users_count', 'assets_count', 'currency', 'ldap_ou', ];
|
||||
'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
'locations.id',
|
||||
@@ -47,12 +47,37 @@ class LocationsController extends Controller
|
||||
'locations.currency',
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$locations = $locations->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$locations->where('locations.name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('address')) {
|
||||
$locations->where('locations.address', '=', $request->input('address'));
|
||||
}
|
||||
|
||||
if ($request->filled('address2')) {
|
||||
$locations->where('locations.address2', '=', $request->input('address2'));
|
||||
}
|
||||
|
||||
if ($request->filled('city')) {
|
||||
$locations->where('locations.city', '=', $request->input('city'));
|
||||
}
|
||||
|
||||
if ($request->filled('zip')) {
|
||||
$locations->where('locations.zip', '=', $request->input('zip'));
|
||||
}
|
||||
|
||||
if ($request->filled('country')) {
|
||||
$locations->where('locations.country', '=', $request->input('country'));
|
||||
}
|
||||
|
||||
$offset = (($locations) && (request('offset') > $locations->count())) ? $locations->count() : request('offset', 0);
|
||||
|
||||
// Check to make sure the limit is not higher than the max allowed
|
||||
@@ -133,7 +158,9 @@ class LocationsController extends Controller
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('users as users_count')->findOrFail($id);
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('users as users_count')
|
||||
->findOrFail($id);
|
||||
|
||||
return (new LocationsTransformer)->transformLocation($location);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,26 @@ class ManufacturersController extends Controller
|
||||
$manufacturers = $manufacturers->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$manufacturers->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$manufacturers->where('url', '=', $request->input('url'));
|
||||
}
|
||||
|
||||
if ($request->filled('support_url')) {
|
||||
$manufacturers->where('support_url', '=', $request->input('support_url'));
|
||||
}
|
||||
|
||||
if ($request->filled('support_phone')) {
|
||||
$manufacturers->where('support_phone', '=', $request->input('support_phone'));
|
||||
}
|
||||
|
||||
if ($request->filled('support_email')) {
|
||||
$manufacturers->where('support_email', '=', $request->input('support_email'));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -5,10 +5,37 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CheckoutRequest;
|
||||
use Auth;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Passport\TokenRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use Gate;
|
||||
use DB;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* The token repository implementation.
|
||||
*
|
||||
* @var \Laravel\Passport\TokenRepository
|
||||
*/
|
||||
protected $tokenRepository;
|
||||
|
||||
/**
|
||||
* Create a controller instance.
|
||||
*
|
||||
* @param \Laravel\Passport\TokenRepository $tokenRepository
|
||||
* @param \Illuminate\Contracts\Validation\Factory $validation
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
|
||||
{
|
||||
$this->validation = $validation;
|
||||
$this->tokenRepository = $tokenRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of requested assets.
|
||||
*
|
||||
@@ -42,4 +69,90 @@ class ProfileController extends Controller
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an API token
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.5]
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function createApiToken(Request $request) {
|
||||
|
||||
if (!Gate::allows('self.api')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$accessTokenName = $request->input('name', 'Auth Token');
|
||||
|
||||
if ($accessToken = Auth::user()->createToken($accessTokenName)->accessToken) {
|
||||
|
||||
// Get the ID so we can return that with the payload
|
||||
$token = DB::table('oauth_access_tokens')->where('user_id', '=', Auth::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
|
||||
$accessTokenData['id'] = $token->id;
|
||||
$accessTokenData['token'] = $accessToken;
|
||||
$accessTokenData['name'] = $accessTokenName;
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, 'Personal access token '.$accessTokenName.' created successfully'));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an API token
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.5]
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function deleteApiToken($tokenId) {
|
||||
|
||||
if (!Gate::allows('self.api')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$token = $this->tokenRepository->findForUser(
|
||||
$tokenId, Auth::user()->getAuthIdentifier()
|
||||
);
|
||||
|
||||
if (is_null($token)) {
|
||||
return new Response('', 404);
|
||||
}
|
||||
|
||||
$token->revoke();
|
||||
|
||||
return new Response('', Response::HTTP_NO_CONTENT);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show user's API tokens
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.5]
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showApiTokens(Request $request) {
|
||||
|
||||
if (!Gate::allows('self.api')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$tokens = $this->tokenRepository->forUser(Auth::user()->getAuthIdentifier());
|
||||
$token_values = $tokens->load('client')->filter(function ($token) {
|
||||
return $token->client->personal_access_client && ! $token->revoked;
|
||||
})->values();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $token_values, null));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class ReportsController extends Controller
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'target', 'location');
|
||||
$actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Transformers\DatatablesTransformer;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Ldap;
|
||||
@@ -265,4 +268,52 @@ class SettingsController extends Controller
|
||||
|
||||
return (new LoginAttemptsTransformer)->transformLoginAttempts($login_attempt_results, $total);
|
||||
}
|
||||
|
||||
|
||||
public function listBackups() {
|
||||
$settings = Setting::getSettings();
|
||||
$path = 'app/backups';
|
||||
$backup_files = Storage::files($path);
|
||||
$files_raw = [];
|
||||
$count = 0;
|
||||
|
||||
if (count($backup_files) > 0) {
|
||||
|
||||
for ($f = 0; $f < count($backup_files); $f++) {
|
||||
|
||||
// Skip dotfiles like .gitignore and .DS_STORE
|
||||
if ((substr(basename($backup_files[$f]), 0, 1) != '.')) {
|
||||
$file_timestamp = Storage::lastModified($backup_files[$f]);
|
||||
|
||||
$files_raw[] = [
|
||||
'filename' => basename($backup_files[$f]),
|
||||
'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])),
|
||||
'modified_value' => $file_timestamp,
|
||||
'modified_display' => date($settings->date_display_format.' '.$settings->time_display_format, $file_timestamp),
|
||||
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$files = array_reverse($files_raw);
|
||||
return (new DatatablesTransformer)->transformDatatables($files, $count);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function downloadBackup($file) {
|
||||
|
||||
$path = 'app/backups';
|
||||
if (Storage::exists($path.'/'.$file)) {
|
||||
$headers = ['ContentType' => 'application/zip'];
|
||||
return Storage::download($path.'/'.$file, $file, $headers);
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'File not found'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use App\Http\Transformers\StatuslabelsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Statuslabel;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Transformers\PieChartTransformer;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class StatuslabelsController extends Controller
|
||||
{
|
||||
@@ -30,6 +32,10 @@ class StatuslabelsController extends Controller
|
||||
$statuslabels = $statuslabels->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$statuslabels->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
|
||||
// if a status_type is passed, filter by that
|
||||
if ($request->filled('status_type')) {
|
||||
@@ -184,43 +190,54 @@ class StatuslabelsController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetCountByStatuslabel()
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$statuslabels = Statuslabel::withCount('assets')->get();
|
||||
|
||||
$labels = [];
|
||||
$points = [];
|
||||
$default_color_count = 0;
|
||||
$colors_array = [];
|
||||
|
||||
foreach ($statuslabels as $statuslabel) {
|
||||
if ($statuslabel->assets_count > 0) {
|
||||
$labels[] = $statuslabel->name.' ('.number_format($statuslabel->assets_count).')';
|
||||
$points[] = $statuslabel->assets_count;
|
||||
|
||||
if ($statuslabel->color != '') {
|
||||
$colors_array[] = $statuslabel->color;
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
}
|
||||
$default_color_count++;
|
||||
$total[$statuslabel->name]['label'] = $statuslabel->name;
|
||||
$total[$statuslabel->name]['count'] = $statuslabel->assets_count;
|
||||
|
||||
if ($statuslabel->color != '') {
|
||||
$total[$statuslabel->name]['color'] = $statuslabel->color;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'labels' => $labels,
|
||||
'datasets' => [[
|
||||
'data' => $points,
|
||||
'backgroundColor' => $colors_array,
|
||||
'hoverBackgroundColor' => $colors_array,
|
||||
]],
|
||||
];
|
||||
return (new PieChartTransformer())->transformPieChartDate($total);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a count of assets by meta status type for pie chart
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.11]
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetCountByMetaStatus()
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$total['rtd']['label'] = trans('general.ready_to_deploy');
|
||||
$total['rtd']['count'] = Asset::RTD()->count();
|
||||
|
||||
$total['deployed']['label'] = trans('general.deployed');
|
||||
$total['deployed']['count'] = Asset::Deployed()->count();
|
||||
|
||||
$total['archived']['label'] = trans('general.archived');
|
||||
$total['archived']['count'] = Asset::Archived()->count();
|
||||
|
||||
$total['pending']['label'] = trans('general.pending');
|
||||
$total['pending']['count'] = Asset::Pending()->count();
|
||||
|
||||
$total['undeployable']['label'] = trans('general.undeployable');
|
||||
$total['undeployable']['count'] = Asset::Undeployable()->count();
|
||||
|
||||
return (new PieChartTransformer())->transformPieChartDate($total);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,46 @@ class SuppliersController extends Controller
|
||||
$suppliers = $suppliers->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$suppliers->where('name', '=', $request->input('name'));
|
||||
}
|
||||
|
||||
if ($request->filled('address')) {
|
||||
$suppliers->where('address', '=', $request->input('address'));
|
||||
}
|
||||
|
||||
if ($request->filled('address2')) {
|
||||
$suppliers->where('address2', '=', $request->input('address2'));
|
||||
}
|
||||
|
||||
if ($request->filled('city')) {
|
||||
$suppliers->where('city', '=', $request->input('city'));
|
||||
}
|
||||
|
||||
if ($request->filled('zip')) {
|
||||
$suppliers->where('zip', '=', $request->input('zip'));
|
||||
}
|
||||
|
||||
if ($request->filled('country')) {
|
||||
$suppliers->where('country', '=', $request->input('country'));
|
||||
}
|
||||
|
||||
if ($request->filled('fax')) {
|
||||
$suppliers->where('fax', '=', $request->input('fax'));
|
||||
}
|
||||
|
||||
if ($request->filled('email')) {
|
||||
$suppliers->where('email', '=', $request->input('email'));
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$suppliers->where('url', '=', $request->input('url'));
|
||||
}
|
||||
|
||||
if ($request->filled('notes')) {
|
||||
$suppliers->where('notes', '=', $request->input('notes'));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -36,6 +36,7 @@ class UsersController extends Controller
|
||||
|
||||
$users = User::select([
|
||||
'users.activated',
|
||||
'users.created_by',
|
||||
'users.address',
|
||||
'users.avatar',
|
||||
'users.city',
|
||||
@@ -65,8 +66,10 @@ class UsersController extends Controller
|
||||
'users.zip',
|
||||
'users.remote',
|
||||
'users.ldap_import',
|
||||
'users.start_date',
|
||||
'users.end_date',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables')
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
|
||||
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
@@ -89,6 +92,10 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.location_id', '=', $request->input('location_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('created_by')) {
|
||||
$users = $users->where('users.created_by', '=', $request->input('created_by'));
|
||||
}
|
||||
|
||||
if ($request->filled('email')) {
|
||||
$users = $users->where('users.email', '=', $request->input('email'));
|
||||
}
|
||||
@@ -141,6 +148,15 @@ class UsersController extends Controller
|
||||
$users = $users->where('remote', '=', $request->input('remote'));
|
||||
}
|
||||
|
||||
if ($request->filled('start_date')) {
|
||||
$users = $users->where('users.start_date', '=', $request->input('start_date'));
|
||||
}
|
||||
|
||||
if ($request->filled('end_date')) {
|
||||
$users = $users->where('users.end_date', '=', $request->input('end_date'));
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('assets_count')) {
|
||||
$users->has('assets', '=', $request->input('assets_count'));
|
||||
}
|
||||
@@ -182,6 +198,9 @@ class UsersController extends Controller
|
||||
case 'department':
|
||||
$users = $users->OrderDepartment($order);
|
||||
break;
|
||||
case 'created_by':
|
||||
$users = $users->OrderByCreatedBy($order);
|
||||
break;
|
||||
case 'company':
|
||||
$users = $users->OrderCompany($order);
|
||||
break;
|
||||
@@ -192,7 +211,7 @@ class UsersController extends Controller
|
||||
'assets', 'accessories', 'consumables', 'licenses', 'groups', 'activated', 'created_at',
|
||||
'two_factor_enrolled', 'two_factor_optin', 'last_login', 'assets_count', 'licenses_count',
|
||||
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
|
||||
'country', 'zip', 'id', 'ldap_import', 'remote',
|
||||
'country', 'zip', 'id', 'ldap_import', 'remote', 'start_date', 'end_date',
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -519,10 +538,14 @@ class UsersController extends Controller
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', License::class);
|
||||
$user = User::where('id', $id)->withTrashed()->first();
|
||||
$licenses = $user->licenses()->get();
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
|
||||
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\AssetModel;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Storage;
|
||||
@@ -90,7 +91,9 @@ class AssetModelsController extends Controller
|
||||
// Was it created?
|
||||
if ($model->save()) {
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
|
||||
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
|
||||
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to the new model page
|
||||
@@ -163,7 +166,9 @@ class AssetModelsController extends Controller
|
||||
$model->fieldset_id = $request->input('custom_fieldset');
|
||||
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
|
||||
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
|
||||
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +456,32 @@ class AssetModelsController extends Controller
|
||||
*/
|
||||
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
|
||||
{
|
||||
$data = array();
|
||||
foreach ($defaultValues as $customFieldId => $defaultValue) {
|
||||
$customField = \App\Models\CustomField::find($customFieldId);
|
||||
|
||||
$data[$customField->db_column] = $defaultValue;
|
||||
}
|
||||
|
||||
$fieldsets = $model->fieldset->validation_rules();
|
||||
$rules = array();
|
||||
|
||||
foreach ($fieldsets as $fieldset => $validation){
|
||||
// If the field is marked as required, eliminate the rule so it doesn't interfere with the default values
|
||||
// (we are at model level, the rule still applies when creating a new asset using this model)
|
||||
$index = array_search('required', $validation);
|
||||
if ($index !== false){
|
||||
$validation[$index] = 'nullable';
|
||||
}
|
||||
$rules[$fieldset] = $validation;
|
||||
}
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
if($validator->fails()){
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($defaultValues as $customFieldId => $defaultValue) {
|
||||
if(is_array($defaultValue)){
|
||||
$model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]);
|
||||
@@ -458,6 +489,7 @@ class AssetModelsController extends Controller
|
||||
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class AssetModelsFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Upload a file to the server.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $modelId = null)
|
||||
{
|
||||
if (! $model = AssetModel::find($modelId)) {
|
||||
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$this->authorize('update', $model);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
if (! Storage::exists('private_uploads/assetmodels')) {
|
||||
Storage::makeDirectory('private_uploads/assetmodels', 775);
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'model-'.$model->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension=='svg') {
|
||||
\Log::debug('This is an SVG');
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
Storage::put('private_uploads/assetmodels/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
} else {
|
||||
Storage::put('private_uploads/assetmodels/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
|
||||
$model->logUpload($file_name, e($request->get('notes')));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('general.file_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 $modelId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($modelId = null, $fileId = null, $download = true)
|
||||
{
|
||||
$model = AssetModel::find($modelId);
|
||||
// the asset is valid
|
||||
if (isset($model->id)) {
|
||||
$this->authorize('view', $model);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that model/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/assetmodels/'.$log->filename;
|
||||
\Log::debug('Checking for '.$file);
|
||||
|
||||
|
||||
if (! Storage::exists($file)) {
|
||||
return response('File '.$file.' not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
if ($download != 'true') {
|
||||
if ($contents = file_get_contents(Storage::url($file))) {
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($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 $modelId
|
||||
* @param int $fileId
|
||||
* @since [v1.0]
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($modelId = null, $fileId = null)
|
||||
{
|
||||
$model = AssetModel::find($modelId);
|
||||
$this->authorize('update', $model);
|
||||
$rel_path = 'private_uploads/assetmodels';
|
||||
|
||||
// the asset is valid
|
||||
if (isset($model->id)) {
|
||||
$this->authorize('update', $model);
|
||||
$log = Actionlog::find($fileId);
|
||||
if ($log) {
|
||||
if (Storage::exists($rel_path.'/'.$log->filename)) {
|
||||
Storage::delete($rel_path.'/'.$log->filename);
|
||||
}
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Input;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use League\Csv\Reader;
|
||||
@@ -167,17 +168,17 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if (is_array($request->input($field->convertUnicodeDbSlug()))) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(implode(', ', $request->input($field->convertUnicodeDbSlug())));
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->convertUnicodeDbSlug()))) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,18 +202,30 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
$success = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
// Redirect to the asset listing page
|
||||
$minutes = 518400;
|
||||
// dd( $_POST['options']);
|
||||
// Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes));
|
||||
return redirect()->route('hardware.index')
|
||||
->with('success', trans('admin/hardware/message.create.success'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
public function getOptionCookie(Request $request){
|
||||
$value = $request->cookie('optional_info');
|
||||
echo $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to edit an existing asset.
|
||||
*
|
||||
@@ -343,17 +356,17 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
if (is_array($request->input($field->convertUnicodeDbSlug()))) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(implode(', ', $request->input($field->convertUnicodeDbSlug())));
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->convertUnicodeDbSlug()))) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
|
||||
class BulkAssetsController extends Controller
|
||||
{
|
||||
@@ -32,17 +33,12 @@ class BulkAssetsController extends Controller
|
||||
|
||||
if (! $request->filled('ids')) {
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||
|
||||
}
|
||||
|
||||
// Figure out where we need to send the user after the update is complete, and store that in the session
|
||||
$bulk_back_url = request()->headers->get('referer');
|
||||
session(['bulk_back_url' => $bulk_back_url]);
|
||||
|
||||
\Log::debug('Back to url: '.$bulk_back_url);
|
||||
|
||||
|
||||
|
||||
$asset_ids = array_values(array_unique($request->input('ids')));
|
||||
|
||||
if ($request->filled('bulk_actions')) {
|
||||
@@ -106,6 +102,8 @@ class BulkAssetsController extends Controller
|
||||
|| ($request->filled('company_id'))
|
||||
|| ($request->filled('status_id'))
|
||||
|| ($request->filled('model_id'))
|
||||
|| ($request->filled('null_purchase_date'))
|
||||
|| ($request->filled('null_expected_checkin_date'))
|
||||
) {
|
||||
foreach ($assets as $assetId) {
|
||||
|
||||
@@ -120,6 +118,14 @@ class BulkAssetsController extends Controller
|
||||
->conditionallyAddItem('supplier_id')
|
||||
->conditionallyAddItem('warranty_months');
|
||||
|
||||
if ($request->input('null_purchase_date')=='1') {
|
||||
$this->update_array['purchase_date'] = null;
|
||||
}
|
||||
|
||||
if ($request->input('null_expected_checkin_date')=='1') {
|
||||
$this->update_array['expected_checkin'] = null;
|
||||
}
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
$this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
|
||||
}
|
||||
@@ -243,7 +249,7 @@ class BulkAssetsController extends Controller
|
||||
* Process Multiple Checkout Request
|
||||
* @return View
|
||||
*/
|
||||
public function storeCheckout(Request $request)
|
||||
public function storeCheckout(AssetCheckoutRequest $request)
|
||||
{
|
||||
|
||||
$this->authorize('checkout', Asset::class);
|
||||
@@ -254,7 +260,7 @@ class BulkAssetsController extends Controller
|
||||
$target = $this->determineCheckoutTarget();
|
||||
|
||||
if (! is_array($request->get('selected_assets'))) {
|
||||
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
||||
}
|
||||
|
||||
$asset_ids = array_filter($request->get('selected_assets'));
|
||||
@@ -301,9 +307,9 @@ class BulkAssetsController extends Controller
|
||||
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to('hardware/bulk-checkout')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->to('hardware/bulk-checkout')->with('error', $e->getErrors());
|
||||
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class LoginController extends Controller
|
||||
Auth::login($user);
|
||||
} else {
|
||||
$username = $saml->getUsername();
|
||||
\Log::warning("SAML user '$username' could not be found in database.");
|
||||
\Log::debug("SAML user '$username' could not be found in database.");
|
||||
$request->session()->flash('error', trans('auth/message.signin.error'));
|
||||
$saml->clearData();
|
||||
}
|
||||
@@ -127,7 +127,7 @@ class LoginController extends Controller
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning('There was an error authenticating the SAML user: '.$e->getMessage());
|
||||
\Log::debug('There was an error authenticating the SAML user: '.$e->getMessage());
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -136,13 +136,12 @@ class LoginController extends Controller
|
||||
|
||||
// Better logging
|
||||
if (!$saml->isEnabled()) {
|
||||
\Log::warning("SAML page requested, but SAML does not seem to enabled.");
|
||||
\Log::debug("SAML page requested, but SAML does not seem to enabled.");
|
||||
} else {
|
||||
\Log::warning("SAML page requested, but samlData seems empty.");
|
||||
\Log::debug("SAML page requested, but samlData seems empty.");
|
||||
}
|
||||
}
|
||||
|
||||
\Log::warning("Something else went wrong while trying to login as SAML user");
|
||||
|
||||
|
||||
}
|
||||
@@ -474,6 +473,11 @@ class LoginController extends Controller
|
||||
}
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
|
||||
if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){
|
||||
$request->session()->remove('password_hash_'.Auth::getDefaultDriver());
|
||||
}
|
||||
|
||||
Auth::logout();
|
||||
|
||||
if (! empty($sloRedirectUrl)) {
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\SaveUserRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
@@ -63,6 +61,14 @@ class ResetPasswordController extends Controller
|
||||
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
|
||||
$credentials = $request->only('email', 'token');
|
||||
|
||||
if (is_null($this->broker()->getUser($credentials))) {
|
||||
\Log::debug('Password reset form FAILED - this token is not valid.');
|
||||
return redirect()->route('password.request')->with('error', trans('passwords.token'));
|
||||
}
|
||||
|
||||
return view('auth.passwords.reset')->with(
|
||||
[
|
||||
'token' => $token,
|
||||
@@ -73,38 +79,53 @@ class ResetPasswordController extends Controller
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
|
||||
$broker = $this->broker();
|
||||
|
||||
$messages = [
|
||||
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
|
||||
];
|
||||
|
||||
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
|
||||
|
||||
// Check to see if the user even exists
|
||||
$user = User::where('username', '=', $request->input('username'))->first();
|
||||
\Log::debug('Checking if '.$request->input('username').' exists');
|
||||
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing
|
||||
if ($user = User::where('username', '=', $request->input('username'))->where('activated', '1')->whereNotNull('email')->first()) {
|
||||
\Log::debug($user->username.' exists');
|
||||
|
||||
|
||||
// handle the password validation rules set by the admin settings
|
||||
if (strpos(Setting::passwordComplexityRulesSaving('store'), 'disallow_same_pwd_as_user_fields') !== false) {
|
||||
$request->validate(
|
||||
[
|
||||
'password' => 'required|notIn:["'.$user->email.'","'.$user->username.'","'.$user->first_name.'","'.$user->last_name.'"',
|
||||
], $messages);
|
||||
}
|
||||
|
||||
|
||||
// set the response
|
||||
$response = $broker->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
});
|
||||
|
||||
// Check if the password reset above actually worked
|
||||
if ($response == \Password::PASSWORD_RESET) {
|
||||
\Log::debug('Password reset for '.$user->username.' worked');
|
||||
return redirect()->guest('login')->with('success', trans('passwords.reset'));
|
||||
}
|
||||
|
||||
\Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
|
||||
return redirect()->back()->withInput($request->only('email'))->with('error', trans('passwords.token'));
|
||||
|
||||
$broker = $this->broker();
|
||||
if (strpos(Setting::passwordComplexityRulesSaving('store'), 'disallow_same_pwd_as_user_fields') !== false) {
|
||||
$request->validate(
|
||||
[
|
||||
'password' => 'required|notIn:["'.$user->email.'","'.$user->username.'","'.$user->first_name.'","'.$user->last_name.'"',
|
||||
], $messages);
|
||||
}
|
||||
|
||||
$response = $broker->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
return $response == \Password::PASSWORD_RESET
|
||||
? $this->sendResetResponse($request, $response)
|
||||
: $this->sendResetFailedResponse($request, $response);
|
||||
\Log::debug('Password reset for '.$request->input('username').' FAILED - user does not exist or does not have an email address - but make it look like it succeeded');
|
||||
return redirect()->guest('login')->with('success', trans('passwords.reset'));
|
||||
|
||||
}
|
||||
|
||||
protected function sendResetFailedResponse(Request $request, $response)
|
||||
{
|
||||
return redirect()->back()
|
||||
->withInput(['username'=> $request->input('username')])
|
||||
->withErrors(['username' => trans($response), 'password' => trans($response)]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -142,6 +142,6 @@ class SamlController extends Controller
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
return redirect()->route('logout')->with(['saml_logout' => true,'saml_slo_redirect_url' => $sloUrl]);
|
||||
return redirect()->route('logout.get')->with(['saml_logout' => true,'saml_slo_redirect_url' => $sloUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ class ComponentCheckoutController extends Controller
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'assigned_qty' => $request->input('assigned_qty'),
|
||||
'asset_id' => $asset_id,
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
|
||||
|
||||
@@ -129,7 +129,7 @@ class ComponentsController extends Controller
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
|
||||
}
|
||||
$min = $component->numCHeckedOut();
|
||||
$min = $component->numCheckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
'qty' => "required|numeric|min:$min",
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Component;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class ComponentsFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a component.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $componentId = null)
|
||||
{
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('components.show', ['component'=>$componentId])->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
$component = Component::find($componentId);
|
||||
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('update', $component);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
if (! Storage::exists('private_uploads/components')) {
|
||||
Storage::makeDirectory('private_uploads/components', 775);
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\Log::debug('This is an SVG');
|
||||
\Log::debug($file_name);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
Storage::put('private_uploads/components/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$component->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('components.show', $component->id)->with('success', trans('general.file_upload_success'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('components.show', $component->id)->with('error', trans('general.no_files_uploaded'));
|
||||
}
|
||||
// Prepare the error message
|
||||
return redirect()->route('components.index')
|
||||
->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected component file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $componentId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($componentId = null, $fileId = null)
|
||||
{
|
||||
$component = Component::find($componentId);
|
||||
|
||||
// the asset is valid
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('update', $component);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('components/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('components/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the selected file to be viewed.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.4]
|
||||
* @param int $componentId
|
||||
* @param int $fileId
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($componentId = null, $fileId = null, $download = true)
|
||||
{
|
||||
\Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||
$component = Component::find($componentId);
|
||||
|
||||
// the component is valid
|
||||
if (isset($component->id)) {
|
||||
$this->authorize('view', $component);
|
||||
$this->authorize('components.files', $component);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/components/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ConsumableCheckoutController extends Controller
|
||||
// Check if the user exists
|
||||
if (is_null($user = User::find($assigned_to))) {
|
||||
// Redirect to the consumable management page with error
|
||||
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
|
||||
return redirect()->route('consumables.checkout.show', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'))->withInput();
|
||||
}
|
||||
|
||||
// Update the consumable data
|
||||
@@ -66,6 +66,7 @@ class ConsumableCheckoutController extends Controller
|
||||
'consumable_id' => $consumable->id,
|
||||
'user_id' => $admin_user->id,
|
||||
'assigned_to' => e($request->input('assigned_to')),
|
||||
'note' => $request->input('note'),
|
||||
]);
|
||||
|
||||
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Company;
|
||||
use App\Models\Consumable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Consumables for
|
||||
@@ -128,6 +129,17 @@ class ConsumablesController extends Controller
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$min = $consumable->numCheckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$this->authorize($consumable);
|
||||
|
||||
$consumable->name = $request->input('name');
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Consumables;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Consumable;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Consumable\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class ConsumablesFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a consumable.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param int $consumableId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $consumableId = null)
|
||||
{
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
$consumable = Consumable::find($consumableId);
|
||||
|
||||
if (isset($consumable->id)) {
|
||||
$this->authorize('update', $consumable);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
if (! Storage::exists('private_uploads/consumables')) {
|
||||
Storage::makeDirectory('private_uploads/consumables', 775);
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
|
||||
|
||||
// Check for SVG and sanitize it
|
||||
if ($extension == 'svg') {
|
||||
\Log::debug('This is an SVG');
|
||||
\Log::debug($file_name);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($file->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
Storage::put('private_uploads/consumables/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
//Log the upload to the log
|
||||
$consumable->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('consumables.show', $consumable->id)->with('success', trans('general.file_upload_success'));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('consumables.show', $consumable->id)->with('error', trans('general.no_files_uploaded'));
|
||||
}
|
||||
// Prepare the error message
|
||||
return redirect()->route('consumables.index')
|
||||
->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected consumable file.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $consumableId
|
||||
* @param int $fileId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function destroy($consumableId = null, $fileId = null)
|
||||
{
|
||||
$consumable = Consumable::find($consumableId);
|
||||
|
||||
// the asset is valid
|
||||
if (isset($consumable->id)) {
|
||||
$this->authorize('update', $consumable);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('consumables/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('consumables/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the selected file to be viewed.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.4]
|
||||
* @param int $consumableId
|
||||
* @param int $fileId
|
||||
* @return \Symfony\Consumable\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($consumableId = null, $fileId = null, $download = true)
|
||||
{
|
||||
$consumable = Consumable::find($consumableId);
|
||||
|
||||
// the consumable is valid
|
||||
if (isset($consumable->id)) {
|
||||
$this->authorize('view', $consumable);
|
||||
$this->authorize('consumables.files', $consumable);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
$file = 'private_uploads/consumables/'.$log->filename;
|
||||
|
||||
if (Storage::missing($file)) {
|
||||
\Log::debug('FILE DOES NOT EXISTS for '.$file);
|
||||
\Log::debug('URL should be '.Storage::url($file));
|
||||
|
||||
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
|
||||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,10 @@ class CustomFieldsController extends Controller
|
||||
$field->format = e($request->get('format'));
|
||||
}
|
||||
|
||||
if($field->element == 'checkbox' || $field->element == 'radio'){
|
||||
$field->format = 'ANY';
|
||||
}
|
||||
|
||||
if ($field->save()) {
|
||||
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success'));
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ class DepartmentsController extends Controller
|
||||
$department->fill($request->all());
|
||||
$department->user_id = Auth::user()->id;
|
||||
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
|
||||
|
||||
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
|
||||
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
|
||||
$department = $request->handleImages($department);
|
||||
|
||||
if ($department->save()) {
|
||||
@@ -167,6 +168,8 @@ class DepartmentsController extends Controller
|
||||
|
||||
$department->fill($request->all());
|
||||
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
|
||||
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
|
||||
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
|
||||
|
||||
$department = $request->handleImages($department);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
|
||||
$licenseSeat->user_id = Auth::id();
|
||||
|
||||
|
||||
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
|
||||
if ($this->$checkoutMethod($licenseSeat)) {
|
||||
@@ -76,14 +77,14 @@ class LicenseCheckoutController extends Controller
|
||||
|
||||
if (! $licenseSeat) {
|
||||
if ($seatId) {
|
||||
return redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.');
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
|
||||
}
|
||||
|
||||
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
|
||||
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
|
||||
}
|
||||
|
||||
if (! $licenseSeat->license->is($license)) {
|
||||
return redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.');
|
||||
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
|
||||
}
|
||||
|
||||
return $licenseSeat;
|
||||
|
||||
@@ -135,6 +135,7 @@ class LicenseFilesController extends Controller
|
||||
// the license is valid
|
||||
if (isset($license->id)) {
|
||||
$this->authorize('view', $license);
|
||||
$this->authorize('licenses.files', $license);
|
||||
|
||||
if (! $log = Actionlog::find($fileId)) {
|
||||
return response('No matching record for that asset/file', 500)
|
||||
@@ -171,6 +172,6 @@ class LicenseFilesController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('license.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,23 +211,35 @@ class LocationsController extends Controller
|
||||
|
||||
public function print_assigned($id)
|
||||
{
|
||||
$location = Location::where('id', $id)->first();
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
||||
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function print_all_assigned($id)
|
||||
{
|
||||
$location = Location::where('id', $id)->first();
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
}
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
|
||||
|
||||
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Auth;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@@ -133,7 +136,7 @@ class ProfileController extends Controller
|
||||
public function password()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
|
||||
return view('account/change-password', compact('user'));
|
||||
}
|
||||
|
||||
@@ -161,7 +164,7 @@ class ProfileController extends Controller
|
||||
$validator = \Validator::make($request->all(), $rules);
|
||||
$validator->after(function ($validator) use ($request, $user) {
|
||||
if (! Hash::check($request->input('current_password'), $user->password)) {
|
||||
$validator->errors()->add('current_password', trans('validation.hashed_pass'));
|
||||
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
|
||||
}
|
||||
|
||||
// This checks to make sure that the user's password isn't the same as their username,
|
||||
@@ -186,6 +189,9 @@ class ProfileController extends Controller
|
||||
if (! $validator->fails()) {
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->save();
|
||||
|
||||
// Log the user out of other devices
|
||||
Auth::logoutOtherDevices($request->input('password'));
|
||||
return redirect()->route('account.password.index')->with('success', 'Password updated!');
|
||||
|
||||
}
|
||||
@@ -213,4 +219,47 @@ class ProfileController extends Controller
|
||||
$request->session()->put('menu_state', 'closed');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print inventory
|
||||
*
|
||||
* @author A. Gianotto
|
||||
* @since [v6.0.12]
|
||||
* @return Illuminate\View\View
|
||||
*/
|
||||
public function printInventory()
|
||||
{
|
||||
$show_user = Auth::user();
|
||||
|
||||
return view('users/print')
|
||||
->with('assets', Auth::user()->assets)
|
||||
->with('licenses', $show_user->licenses()->get())
|
||||
->with('accessories', $show_user->accessories()->get())
|
||||
->with('consumables', $show_user->consumables()->get())
|
||||
->with('show_user', $show_user)
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emails user a list of assigned assets
|
||||
*
|
||||
* @author A. Gianotto
|
||||
* @since [v6.0.12]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function emailAssetList()
|
||||
{
|
||||
|
||||
if (!$user = User::find(Auth::user()->id)) {
|
||||
return redirect()->back()
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
}
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ use Illuminate\Support\Facades\View;
|
||||
use Input;
|
||||
use League\Csv\Reader;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use League\Csv\EscapeFormula;
|
||||
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Reports for
|
||||
@@ -411,6 +413,7 @@ class ReportsController extends Controller
|
||||
$customfields = CustomField::get();
|
||||
$response = new StreamedResponse(function () use ($customfields, $request) {
|
||||
\Log::debug('Starting streamed response');
|
||||
\Log::debug('CSV escaping is set to: '.config('app.escape_formulas'));
|
||||
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
@@ -422,6 +425,9 @@ class ReportsController extends Controller
|
||||
|
||||
$header = [];
|
||||
|
||||
if ($request->filled('id')) {
|
||||
$header[] = trans('general.id');
|
||||
}
|
||||
|
||||
if ($request->filled('company')) {
|
||||
$header[] = trans('general.company');
|
||||
@@ -552,6 +558,10 @@ class ReportsController extends Controller
|
||||
$header[] = trans('general.updated_at');
|
||||
}
|
||||
|
||||
if ($request->filled('deleted_at')) {
|
||||
$header[] = trans('general.deleted');
|
||||
}
|
||||
|
||||
if ($request->filled('last_audit_date')) {
|
||||
$header[] = trans('general.last_audit');
|
||||
}
|
||||
@@ -564,6 +574,10 @@ class ReportsController extends Controller
|
||||
$header[] = trans('general.notes');
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$header[] = trans('admin/manufacturers/table.url');
|
||||
}
|
||||
|
||||
|
||||
foreach ($customfields as $customfield) {
|
||||
if ($request->input($customfield->db_column_name()) == '1') {
|
||||
@@ -578,7 +592,7 @@ class ReportsController extends Controller
|
||||
\Log::debug('Added headers: '.$executionTime);
|
||||
|
||||
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
|
||||
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc', 'assignedTo',
|
||||
'location', 'assetstatus', 'company', 'defaultLoc', 'assignedTo',
|
||||
'model.category', 'model.manufacturer', 'supplier');
|
||||
|
||||
if ($request->filled('by_location_id')) {
|
||||
@@ -586,7 +600,6 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('by_rtd_location_id')) {
|
||||
\Log::debug('RTD location should match: '.$request->input('by_rtd_location_id'));
|
||||
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
|
||||
}
|
||||
|
||||
@@ -607,7 +620,6 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('by_dept_id')) {
|
||||
\Log::debug('Only users in dept '.$request->input('by_dept_id'));
|
||||
$assets->CheckedOutToTargetInDepartment($request->input('by_dept_id'));
|
||||
}
|
||||
|
||||
@@ -642,15 +654,32 @@ class ReportsController extends Controller
|
||||
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
||||
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
|
||||
}
|
||||
if ($request->filled('exclude_archived')) {
|
||||
$assets->notArchived();
|
||||
}
|
||||
if ($request->input('deleted_assets') == '1') {
|
||||
$assets->withTrashed();
|
||||
}
|
||||
if ($request->input('deleted_assets') == '0') {
|
||||
$assets->onlyTrashed();
|
||||
}
|
||||
|
||||
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
\Log::debug('Walking results: '.$executionTime);
|
||||
$count = 0;
|
||||
|
||||
$formatter = new EscapeFormula("`");
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$count++;
|
||||
$row = [];
|
||||
|
||||
|
||||
if ($request->filled('id')) {
|
||||
$row[] = ($asset->id) ? $asset->id : '';
|
||||
}
|
||||
|
||||
if ($request->filled('company')) {
|
||||
$row[] = ($asset->company) ? $asset->company->name : '';
|
||||
}
|
||||
@@ -734,7 +763,7 @@ class ReportsController extends Controller
|
||||
if ($request->filled('username')) {
|
||||
// Only works if we're checked out to a user, not anything else.
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = ($asset->assignedto) ? $asset->assignedto->username : '';
|
||||
$row[] = ($asset->assignedTo) ? $asset->assignedTo->username : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -743,7 +772,7 @@ class ReportsController extends Controller
|
||||
if ($request->filled('employee_num')) {
|
||||
// Only works if we're checked out to a user, not anything else.
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = ($asset->assignedto) ? $asset->assignedto->employee_num : '';
|
||||
$row[] = ($asset->assignedTo) ? $asset->assignedTo->employee_num : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -751,7 +780,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($request->filled('manager')) {
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = (($asset->assignedto) && ($asset->assignedto->manager)) ? $asset->assignedto->manager->present()->fullName : '';
|
||||
$row[] = (($asset->assignedTo) && ($asset->assignedTo->manager)) ? $asset->assignedTo->manager->present()->fullName : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -759,7 +788,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($request->filled('department')) {
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = (($asset->assignedto) && ($asset->assignedto->department)) ? $asset->assignedto->department->name : '';
|
||||
$row[] = (($asset->assignedTo) && ($asset->assignedTo->department)) ? $asset->assignedTo->department->name : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -767,7 +796,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($request->filled('title')) {
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = ($asset->assignedto) ? $asset->assignedto->jobtitle : '';
|
||||
$row[] = ($asset->assignedTo) ? $asset->assignedTo->jobtitle : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
@@ -779,7 +808,7 @@ class ReportsController extends Controller
|
||||
|
||||
if ($request->filled('warranty')) {
|
||||
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
|
||||
$row[] = $asset->present()->warrantee_expires();
|
||||
$row[] = $asset->present()->warranty_expires();
|
||||
}
|
||||
|
||||
if ($request->filled('depreciation')) {
|
||||
@@ -806,6 +835,10 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->updated_at) ? $asset->updated_at : '';
|
||||
}
|
||||
|
||||
if ($request->filled('deleted_at')) {
|
||||
$row[] = ($asset->deleted_at) ? $asset->deleted_at : '';
|
||||
}
|
||||
|
||||
if ($request->filled('last_audit_date')) {
|
||||
$row[] = ($asset->last_audit_date) ? $asset->last_audit_date : '';
|
||||
}
|
||||
@@ -818,13 +851,27 @@ class ReportsController extends Controller
|
||||
$row[] = ($asset->notes) ? $asset->notes : '';
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$row[] = config('app.url').'/hardware/'.$asset->id ;
|
||||
}
|
||||
|
||||
foreach ($customfields as $customfield) {
|
||||
$column_name = $customfield->db_column_name();
|
||||
if ($request->filled($customfield->db_column_name())) {
|
||||
$row[] = $asset->$column_name;
|
||||
}
|
||||
}
|
||||
fputcsv($handle, $row);
|
||||
|
||||
|
||||
// CSV_ESCAPE_FORMULAS is set to false in the .env
|
||||
if (config('app.escape_formulas') === false) {
|
||||
fputcsv($handle, $row);
|
||||
|
||||
// CSV_ESCAPE_FORMULAS is set to true or is not set in the .env
|
||||
} else {
|
||||
fputcsv($handle, $formatter->escapeRecord($row));
|
||||
}
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
\Log::debug('-- Record '.$count.' Asset ID:'.$asset->id.' in '.$executionTime);
|
||||
}
|
||||
@@ -975,7 +1022,11 @@ class ReportsController extends Controller
|
||||
}
|
||||
$assetItem = $acceptance->checkoutable;
|
||||
|
||||
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
|
||||
if (is_null($acceptance->created_at)){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||
} else {
|
||||
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
|
||||
}
|
||||
|
||||
if(!$assetItem->assignedTo->locale){
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
@@ -1032,9 +1083,9 @@ class ReportsController extends Controller
|
||||
* Get all assets with pending checkout acceptances
|
||||
*/
|
||||
if($showDeleted) {
|
||||
$acceptances = CheckoutAcceptance::pending()->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
} else {
|
||||
$acceptances = CheckoutAcceptance::pending()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
|
||||
}
|
||||
|
||||
$assetsForReport = $acceptances
|
||||
@@ -1148,18 +1199,4 @@ class ReportsController extends Controller
|
||||
$this->getModelsInCategoriesThatRequireAcceptance($this->getCategoriesThatRequireAcceptance())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* getAssetsNotAcceptedYet
|
||||
*
|
||||
* @return array
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
protected function getAssetsNotAcceptedYet()
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
|
||||
return Asset::unaccepted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use Response;
|
||||
use App\Http\Requests\SlackSettingsRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Settings for
|
||||
@@ -348,6 +349,7 @@ class SettingsController extends Controller
|
||||
$setting->privacy_policy_link = $request->input('privacy_policy_link');
|
||||
|
||||
$setting->depreciation_method = $request->input('depreciation_method');
|
||||
$setting->dash_chart_type = $request->input('dash_chart_type');
|
||||
|
||||
if ($request->input('per_page') != '') {
|
||||
$setting->per_page = $request->input('per_page');
|
||||
@@ -910,7 +912,24 @@ class SettingsController extends Controller
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
return view('settings.ldap', compact('setting'));
|
||||
/**
|
||||
* This validator is only temporary (famous last words.) - @snipe
|
||||
*/
|
||||
$messages = [
|
||||
'ldap_username_field.not_in' => '<code>sAMAccountName</code> (mixed case) will likely not work. You should use <code>samaccountname</code> (lowercase) instead. ',
|
||||
'ldap_auth_filter_query.not_in' => '<code>uid=samaccountname</code> is probably not a valid auth filter. You probably want <code>uid=</code> ',
|
||||
'ldap_filter.regex' => 'This value should probably not be wrapped in parentheses.',
|
||||
];
|
||||
|
||||
$validator = Validator::make($setting->toArray(), [
|
||||
'ldap_username_field' => 'not_in:sAMAccountName',
|
||||
'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1',
|
||||
'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1',
|
||||
], $messages);
|
||||
|
||||
|
||||
|
||||
return view('settings.ldap', compact('setting'))->withErrors($validator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -942,7 +961,7 @@ class SettingsController extends Controller
|
||||
$setting->ldap_lname_field = $request->input('ldap_lname_field');
|
||||
$setting->ldap_fname_field = $request->input('ldap_fname_field');
|
||||
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
|
||||
$setting->ldap_version = $request->input('ldap_version');
|
||||
$setting->ldap_version = $request->input('ldap_version', 3);
|
||||
$setting->ldap_active_flag = $request->input('ldap_active_flag');
|
||||
$setting->ldap_emp_num = $request->input('ldap_emp_num');
|
||||
$setting->ldap_email = $request->input('ldap_email');
|
||||
@@ -1147,23 +1166,31 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function deleteFile($filename = null)
|
||||
{
|
||||
if (! config('app.lock_passwords')) {
|
||||
$path = 'app/backups';
|
||||
if (config('app.allow_backup_delete')=='true') {
|
||||
|
||||
if (Storage::exists($path.'/'.$filename)) {
|
||||
try {
|
||||
Storage::delete($path.'/'.$filename);
|
||||
if (!config('app.lock_passwords')) {
|
||||
$path = 'app/backups';
|
||||
|
||||
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
if (Storage::exists($path . '/' . $filename)) {
|
||||
|
||||
try {
|
||||
Storage::delete($path . '/' . $filename);
|
||||
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
|
||||
}
|
||||
} else {
|
||||
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
// Hell to the no
|
||||
\Log::warning('User ID '.Auth::user()->id.' is attempting to delete backup file '.$filename.' and is not authorized to.');
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('general.backup_delete_not_allowed'));
|
||||
}
|
||||
|
||||
|
||||
@@ -1198,9 +1225,10 @@ class SettingsController extends Controller
|
||||
Storage::putFileAs('app/backups', $request->file('file'), $upload_filename);
|
||||
|
||||
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
|
||||
}
|
||||
|
||||
return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1251,34 +1279,30 @@ class SettingsController extends Controller
|
||||
// If it's greater than 300, it probably worked
|
||||
$output = Artisan::output();
|
||||
|
||||
if (strlen($output) > 300) {
|
||||
$find_user = DB::table('users')->where('first_name', $user->first_name)->where('last_name', $user->last_name)->exists();
|
||||
/* Run migrations */
|
||||
\Log::debug('Migrating database...');
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
$migrate_output = Artisan::output();
|
||||
\Log::debug($migrate_output);
|
||||
|
||||
if (!$find_user){
|
||||
\Log::warning('Attempting to restore user: ' . $user->first_name . ' ' . $user->last_name);
|
||||
$new_user = $user->replicate();
|
||||
$new_user->push();
|
||||
}
|
||||
|
||||
|
||||
\Log::debug('Logging all users out..');
|
||||
Artisan::call('snipeit:global-logout', ['--force' => true]);
|
||||
|
||||
/* run migrations */
|
||||
\Log::debug('Migrating database...');
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
$migrate_output = Artisan::output();
|
||||
\Log::debug($migrate_output);
|
||||
|
||||
DB::table('users')->update(['remember_token' => null]);
|
||||
\Auth::logout();
|
||||
|
||||
return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
|
||||
$find_user = DB::table('users')->where('username', $user->username)->exists();
|
||||
|
||||
if (!$find_user){
|
||||
\Log::warning('Attempting to restore user: ' . $user->username);
|
||||
$new_user = $user->replicate();
|
||||
$new_user->push();
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', $output);
|
||||
|
||||
\Log::debug('User: ' . $user->username .' already exists.');
|
||||
}
|
||||
|
||||
\Log::debug('Logging all users out..');
|
||||
Artisan::call('snipeit:global-logout', ['--force' => true]);
|
||||
|
||||
DB::table('users')->update(['remember_token' => null]);
|
||||
\Auth::logout();
|
||||
|
||||
return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
|
||||
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
|
||||
}
|
||||
@@ -1298,9 +1322,15 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function getPurge()
|
||||
{
|
||||
\Log::warning('User ID '.Auth::user()->id.' is attempting a PURGE');
|
||||
|
||||
return view('settings.purge-form');
|
||||
\Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE');
|
||||
|
||||
if (config('app.allow_purge')=='true') {
|
||||
return view('settings.purge-form');
|
||||
}
|
||||
|
||||
return redirect()->route('settings.index')->with('error', trans('general.purge_not_allowed'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1314,22 +1344,40 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function postPurge(Request $request)
|
||||
{
|
||||
if (! config('app.lock_passwords')) {
|
||||
if ('DELETE' == $request->input('confirm_purge')) {
|
||||
\Log::warning('User ID '.Auth::user()->id.' initiated a PURGE!');
|
||||
// Run a backup immediately before processing
|
||||
Artisan::call('backup:run');
|
||||
Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]);
|
||||
$output = Artisan::output();
|
||||
\Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE');
|
||||
|
||||
return view('settings/purge')
|
||||
->with('output', $output)->with('success', trans('admin/settings/message.purge.success'));
|
||||
if (config('app.allow_purge')=='true') {
|
||||
\Log::debug('Purging is not allowed via the .env');
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
if ($request->input('confirm_purge')=='DELETE') {
|
||||
|
||||
\Log::warning('User ID ' . Auth::user()->id . ' initiated a PURGE!');
|
||||
// Run a backup immediately before processing
|
||||
Artisan::call('backup:run');
|
||||
Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]);
|
||||
$output = Artisan::output();
|
||||
|
||||
return redirect()->route('settings.index')
|
||||
->with('output', $output)->with('success', trans('admin/settings/message.purge.success'));
|
||||
|
||||
} else {
|
||||
return redirect()->route('settings.purge.index')
|
||||
->with('error', trans('admin/settings/message.purge.validation_failed'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', trans('admin/settings/message.purge.validation_failed'));
|
||||
return redirect()->route('settings.index')
|
||||
->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
\Log::error('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting to purge deleted data and is not authorized to.');
|
||||
|
||||
|
||||
// Nope.
|
||||
return redirect()->route('settings.index')
|
||||
->with('error', trans('general.purge_not_allowed'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,10 +5,13 @@ namespace App\Http\Controllers\Users;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\License;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Group;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\ConsumableAssignment;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -162,13 +165,11 @@ class BulkUsersController extends Controller
|
||||
if ((! $request->filled('ids')) || (count($request->input('ids')) == 0)) {
|
||||
return redirect()->back()->with('error', 'No users selected');
|
||||
}
|
||||
if ((! $request->filled('status_id')) || ($request->input('status_id') == '')) {
|
||||
return redirect()->route('users.index')->with('error', 'No status selected');
|
||||
}
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('users.index')->with('error', 'Bulk delete is not enabled in this installation');
|
||||
}
|
||||
|
||||
$user_raw_array = request('ids');
|
||||
|
||||
if (($key = array_search(Auth::id(), $user_raw_array)) !== false) {
|
||||
@@ -179,27 +180,48 @@ class BulkUsersController extends Controller
|
||||
$assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get();
|
||||
$accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
$consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get();
|
||||
|
||||
if ((($assets->count() > 0) && ((!$request->filled('status_id')) || ($request->input('status_id') == '')))) {
|
||||
return redirect()->route('users.index')->with('error', 'No status selected');
|
||||
}
|
||||
|
||||
|
||||
$this->logItemCheckinAndDelete($assets, Asset::class);
|
||||
$this->logItemCheckinAndDelete($accessories, Accessory::class);
|
||||
$this->logItemCheckinAndDelete($licenses, LicenseSeat::class);
|
||||
$this->logItemCheckinAndDelete($licenses, License::class);
|
||||
$this->logItemCheckinAndDelete($consumables, Consumable::class);
|
||||
|
||||
|
||||
Asset::whereIn('id', $assets->pluck('id'))->update([
|
||||
'status_id' => e(request('status_id')),
|
||||
'assigned_to' => null,
|
||||
'assigned_type' => null,
|
||||
'expected_checkin' => null,
|
||||
]);
|
||||
|
||||
|
||||
LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]);
|
||||
ConsumableAssignment::whereIn('id', $consumables->pluck('id'))->delete();
|
||||
|
||||
|
||||
foreach ($users as $user) {
|
||||
|
||||
$user->consumables()->sync([]);
|
||||
$user->accessories()->sync([]);
|
||||
$user->delete();
|
||||
if ($request->input('delete_user')=='1') {
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('success', 'Your selected users have been deleted and their assets have been updated.');
|
||||
$msg = trans('general.bulk_checkin_success');
|
||||
if ($request->input('delete_user')=='1') {
|
||||
$msg = trans('general.bulk_checkin_delete_success');
|
||||
}
|
||||
|
||||
|
||||
return redirect()->route('users.index')->with('success', $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,14 +232,20 @@ class BulkUsersController extends Controller
|
||||
protected function logItemCheckinAndDelete($items, $itemType)
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$item_id = $item->id;
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $item->id;
|
||||
|
||||
if ($itemType == License::class){
|
||||
$item_id = $item->license_id;
|
||||
}
|
||||
|
||||
$logAction->item_id = $item_id;
|
||||
// We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays.
|
||||
$logAction->item_type = $itemType;
|
||||
$logAction->target_id = $item->assigned_to;
|
||||
$logAction->target_type = User::class;
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->note = 'Bulk checkin items and delete user';
|
||||
$logAction->note = 'Bulk checkin items';
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use Redirect;
|
||||
use Str;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use View;
|
||||
use App\Notifications\CurrentInventory;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Users for
|
||||
@@ -116,6 +117,10 @@ class UsersController extends Controller
|
||||
$user->country = $request->input('country', null);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->created_by = Auth::user()->id;
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
$permissions_array = $request->input('permission');
|
||||
@@ -266,6 +271,9 @@ class UsersController extends Controller
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
@@ -590,7 +598,7 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP form processing.
|
||||
* Print inventory
|
||||
*
|
||||
* @author Aladin Alaily
|
||||
* @since [v1.8]
|
||||
@@ -612,6 +620,30 @@ class UsersController extends Controller
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emails user a list of assigned assets
|
||||
*
|
||||
* @author [G. Martinez] [<godmartinz@gmail.com>]
|
||||
* @since [v6.0.5]
|
||||
* @param \App\Http\Controllers\Users\UsersController $id
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function emailAssetList($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
if (!$user = User::find($id)) {
|
||||
return redirect()->back()
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
}
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send individual password reset email
|
||||
*
|
||||
@@ -636,4 +668,4 @@ class UsersController extends Controller
|
||||
|
||||
return redirect()->back()->with('error', 'User is not activated, is LDAP synced, or does not have an email address ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ class ViewAssetsController extends Controller
|
||||
$settings->notify(new RequestAssetCancelation($data));
|
||||
|
||||
return redirect()->route('requestable-assets')
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.cancel'));
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
|
||||
}
|
||||
|
||||
$logaction->logaction('requested');
|
||||
@@ -180,123 +180,4 @@ class ViewAssetsController extends Controller
|
||||
{
|
||||
return view('account/requested');
|
||||
}
|
||||
|
||||
// Get the acceptance screen
|
||||
public function getAcceptAsset($logID = null)
|
||||
{
|
||||
$findlog = Actionlog::where('id', $logID)->first();
|
||||
|
||||
if (! $findlog) {
|
||||
return redirect()->to('account/view-assets')->with('error', 'No matching record.');
|
||||
}
|
||||
|
||||
if ($findlog->accepted_id != '') {
|
||||
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
// TODO - Fix this for non-assets
|
||||
if (($findlog->item_type == Asset::class) && ($user->id != $findlog->item->assigned_to)) {
|
||||
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
||||
}
|
||||
|
||||
$item = $findlog->item;
|
||||
|
||||
// Check if the asset exists
|
||||
if (is_null($item)) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->to('account')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
} elseif (! Company::isCurrentUserHasAccess($item)) {
|
||||
return redirect()->route('requestable-assets')->with('error', trans('general.insufficient_permissions'));
|
||||
} else {
|
||||
return view('account/accept-asset', compact('item'))->with('findlog', $findlog)->with('item', $item);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the acceptance
|
||||
public function postAcceptAsset(Request $request, $logID = null)
|
||||
{
|
||||
|
||||
// Check if the asset exists
|
||||
if (is_null($findlog = Actionlog::where('id', $logID)->first())) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->to('account/view-assets')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if ($findlog->accepted_id != '') {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
||||
}
|
||||
|
||||
if ($request->missing('asset_acceptance')) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
if (($findlog->item_type == Asset::class) && ($user->id != $findlog->item->assigned_to)) {
|
||||
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
||||
}
|
||||
|
||||
if ($request->filled('signature_output')) {
|
||||
$path = config('app.private_uploads').'/signatures';
|
||||
$sig_filename = 'siglog-'.$findlog->id.'-'.date('Y-m-d-his').'.png';
|
||||
$data_uri = e($request->get('signature_output'));
|
||||
$encoded_image = explode(',', $data_uri);
|
||||
$decoded_image = base64_decode($encoded_image[1]);
|
||||
|
||||
Storage::putFileAs($path, $decoded_image, $sig_filename);
|
||||
//file_put_contents($path.'/'.$sig_filename, $decoded_image);
|
||||
}
|
||||
|
||||
$logaction = new Actionlog();
|
||||
|
||||
if ($request->input('asset_acceptance') == 'accepted') {
|
||||
$logaction_msg = 'accepted';
|
||||
$accepted = 'accepted';
|
||||
$return_msg = trans('admin/users/message.accepted');
|
||||
} else {
|
||||
$logaction_msg = 'declined';
|
||||
$accepted = 'rejected';
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
}
|
||||
$logaction->item_id = $findlog->item_id;
|
||||
$logaction->item_type = $findlog->item_type;
|
||||
|
||||
// Asset
|
||||
if (($findlog->item_id != '') && ($findlog->item_type == Asset::class)) {
|
||||
if ($request->input('asset_acceptance') != 'accepted') {
|
||||
DB::table('assets')
|
||||
->where('id', $findlog->item_id)
|
||||
->update(['assigned_to' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
$logaction->target_id = $findlog->target_id;
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->note = e($request->input('note'));
|
||||
$logaction->updated_at = date('Y-m-d H:i:s');
|
||||
|
||||
if (isset($sig_filename)) {
|
||||
$logaction->accept_signature = $sig_filename;
|
||||
}
|
||||
$log = $logaction->logaction($logaction_msg);
|
||||
|
||||
$update_checkout = DB::table('action_logs')
|
||||
->where('id', $findlog->id)
|
||||
->update(['accepted_id' => $logaction->id]);
|
||||
|
||||
if (($findlog->item_id != '') && ($findlog->item_type == Asset::class)) {
|
||||
$affected_asset = $logaction->item;
|
||||
$affected_asset->accepted = $accepted;
|
||||
$affected_asset->save();
|
||||
}
|
||||
|
||||
if ($update_checkout) {
|
||||
return redirect()->to('account/view-assets')->with('success', $return_msg);
|
||||
} else {
|
||||
return redirect()->to('account/view-assets')->with('error', 'Something went wrong ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\CheckForTwoFactor::class,
|
||||
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
|
||||
\App\Http\Middleware\AssetCountForSidebar::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
||||
@@ -68,8 +68,6 @@ class AccessoriesTransformer
|
||||
$array = [];
|
||||
|
||||
foreach ($accessory_users as $user) {
|
||||
\Log::debug(print_r($user->pivot, true));
|
||||
\Log::debug(print_r($user->pivot, true));
|
||||
$array[] = [
|
||||
|
||||
'assigned_pivot_id' => $user->pivot->id,
|
||||
|
||||
@@ -55,25 +55,35 @@ class ActionlogsTransformer
|
||||
}
|
||||
}
|
||||
|
||||
$file_url = '';
|
||||
if($actionlog->filename!='') {
|
||||
if ($actionlog->action_type == 'accepted') {
|
||||
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
|
||||
} else {
|
||||
if ($actionlog->itemType() == 'asset') {
|
||||
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'license') {
|
||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'user') {
|
||||
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$array = [
|
||||
$array = [
|
||||
'id' => (int) $actionlog->id,
|
||||
'icon' => $icon,
|
||||
'file' => ($actionlog->filename!='')
|
||||
?
|
||||
[
|
||||
'url' => ($actionlog->present()->actionType()=='accepted')
|
||||
?
|
||||
route('log.storedeula.download', ['filename' => $actionlog->filename])
|
||||
:
|
||||
route('show/assetfile', ['assetId' => $actionlog->id, 'fileId' => $actionlog->id]),
|
||||
'url' => $file_url,
|
||||
'filename' => $actionlog->filename,
|
||||
'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
|
||||
] : null,
|
||||
|
||||
'item' => ($actionlog->item) ? [
|
||||
'id' => (int) $actionlog->item->id,
|
||||
'name' => ($actionlog->itemType()=='user') ? $actionlog->filename : e($actionlog->item->getDisplayNameAttribute()),
|
||||
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
|
||||
'type' => e($actionlog->itemType()),
|
||||
] : null,
|
||||
'location' => ($actionlog->location) ? [
|
||||
@@ -85,11 +95,11 @@ class ActionlogsTransformer
|
||||
'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(null, $actionlog->item), 'date'): null,
|
||||
'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item),
|
||||
'action_type' => $actionlog->present()->actionType(),
|
||||
'admin' => ($actionlog->user) ? [
|
||||
'id' => (int) $actionlog->user->id,
|
||||
'name' => e($actionlog->user->getFullNameAttribute()),
|
||||
'first_name'=> e($actionlog->user->first_name),
|
||||
'last_name'=> e($actionlog->user->last_name)
|
||||
'admin' => ($actionlog->admin) ? [
|
||||
'id' => (int) $actionlog->admin->id,
|
||||
'name' => e($actionlog->admin->getFullNameAttribute()),
|
||||
'first_name'=> e($actionlog->admin->first_name),
|
||||
'last_name'=> e($actionlog->admin->last_name)
|
||||
] : null,
|
||||
'target' => ($actionlog->target) ? [
|
||||
'id' => (int) $actionlog->target->id,
|
||||
@@ -104,6 +114,7 @@ class ActionlogsTransformer
|
||||
];
|
||||
//\Log::info("Clean Meta is: ".print_r($clean_meta,true));
|
||||
|
||||
//dd($array);
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,25 +94,40 @@ class AssetsTransformer
|
||||
$fields_array = [];
|
||||
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if ($field->isFieldDecryptable($asset->{$field->convertUnicodeDbSlug()})) {
|
||||
$decrypted = Helper::gracefulDecrypt($field, $asset->{$field->convertUnicodeDbSlug()});
|
||||
if ($field->isFieldDecryptable($asset->{$field->db_column})) {
|
||||
$decrypted = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
|
||||
$value = (Gate::allows('superadmin')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
|
||||
|
||||
if ($field->format == 'DATE'){
|
||||
if (Gate::allows('superadmin')){
|
||||
$value = Helper::getFormattedDateObject($value, 'date', false);
|
||||
} else {
|
||||
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
|
||||
}
|
||||
}
|
||||
|
||||
$fields_array[$field->name] = [
|
||||
'field' => e($field->convertUnicodeDbSlug()),
|
||||
'field' => e($field->db_column),
|
||||
'value' => e($value),
|
||||
'field_format' => $field->format,
|
||||
'element' => $field->element,
|
||||
];
|
||||
|
||||
} else {
|
||||
$value = $asset->{$field->db_column};
|
||||
|
||||
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
|
||||
$value = Helper::getFormattedDateObject($value, 'date', false);
|
||||
}
|
||||
|
||||
$fields_array[$field->name] = [
|
||||
'field' => e($field->convertUnicodeDbSlug()),
|
||||
'value' => e($asset->{$field->convertUnicodeDbSlug()}),
|
||||
'field' => e($field->db_column),
|
||||
'value' => e($value),
|
||||
'field_format' => $field->format,
|
||||
'element' => $field->element,
|
||||
];
|
||||
|
||||
|
||||
}
|
||||
|
||||
$array['custom_fields'] = $fields_array;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,7 @@ class ComponentsAssetsTransformer
|
||||
'created_at' => $asset->created_at->format('Y-m-d'),
|
||||
'qty' => $asset->components()->count(),
|
||||
'user_can_checkout' => $asset->availableForCheckout(),
|
||||
'note' => e($asset->note),
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
@@ -39,7 +40,7 @@ class ComponentsAssetsTransformer
|
||||
|
||||
if ($asset->model->fieldset) {
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
$fields_array = [$field->name => $asset->{$field->convertUnicodeDbSlug()}];
|
||||
$fields_array = [$field->name => $asset->{$field->db_column}];
|
||||
$array += $fields_array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ class ComponentsTransformer
|
||||
'id' => (int) $asset->id,
|
||||
'name' => e($asset->model->present()->name).' '.e($asset->present()->name),
|
||||
'qty' => $asset->pivot->assigned_qty,
|
||||
'note' => $asset->pivot->note,
|
||||
'type' => 'asset',
|
||||
'created_at' => Helper::getFormattedDateObject($asset->pivot->created_at, 'datetime'),
|
||||
'available_actions' => ['checkin' => true],
|
||||
|
||||
@@ -46,7 +46,7 @@ class CustomFieldsTransformer
|
||||
'field_values' => ($field->field_values) ? e($field->field_values) : null,
|
||||
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
|
||||
'type' => e($field->element),
|
||||
'required' => $field->pivot ? $field->pivot->required : false,
|
||||
'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false,
|
||||
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
|
||||
];
|
||||
|
||||
@@ -63,10 +63,16 @@ class DepreciationReportTransformer
|
||||
*/
|
||||
if (($asset->model) && ($asset->model->depreciation)) {
|
||||
$depreciated_value = Helper::formatCurrencyOutput($asset->getDepreciatedValue());
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0));
|
||||
if($asset->model->eol==0 || $asset->model->eol==null ){
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months);
|
||||
}
|
||||
else {
|
||||
$monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0));
|
||||
}
|
||||
$diff = Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
|
||||
}
|
||||
|
||||
|
||||
if ($asset->assigned) {
|
||||
$checkout_target = $asset->assigned->name;
|
||||
if ($asset->checkedOutToUser()) {
|
||||
|
||||
@@ -3,20 +3,21 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Depreciable;
|
||||
use App\Models\Depreciation;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class DepreciationsTransformer
|
||||
{
|
||||
public function transformDepreciations(Collection $depreciations)
|
||||
public function transformDepreciations(Collection $depreciations, $total)
|
||||
{
|
||||
$array = [];
|
||||
foreach ($depreciations as $depreciation) {
|
||||
$array[] = self::transformDepreciation($depreciation);
|
||||
}
|
||||
|
||||
return (new DatatablesTransformer)->transformDatatables($array);
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
}
|
||||
|
||||
public function transformDepreciation(Depreciation $depreciation)
|
||||
@@ -27,8 +28,7 @@ class DepreciationsTransformer
|
||||
'months' => $depreciation->months.' '.trans('general.months'),
|
||||
'depreciation_min' => $depreciation->depreciation_min,
|
||||
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime'),
|
||||
'depreciation_min' =>($depreciation->depreciation_min),
|
||||
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
@@ -40,4 +40,4 @@ class DepreciationsTransformer
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ class LocationsTransformer
|
||||
'zip' => ($location->zip) ? e($location->zip) : null,
|
||||
'assigned_assets_count' => (int) $location->assigned_assets_count,
|
||||
'assets_count' => (int) $location->assets_count,
|
||||
'rtd_assets_count' => (int) $location->rtd_assets_count,
|
||||
'users_count' => (int) $location->users_count,
|
||||
'currency' => ($location->currency) ? e($location->currency) : null,
|
||||
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
|
||||
use App\Helpers\Helper;
|
||||
/**
|
||||
* Class PieChartTransformer
|
||||
*
|
||||
* This handles the standardized formatting of the API response we need to provide for
|
||||
* the pie charts
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @since [v6.0.11]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
class PieChartTransformer
|
||||
{
|
||||
public function transformPieChartDate($totals)
|
||||
{
|
||||
|
||||
$labels = [];
|
||||
$counts = [];
|
||||
$default_color_count = 0;
|
||||
$colors_array = [];
|
||||
|
||||
foreach ($totals as $total) {
|
||||
|
||||
if ($total['count'] > 0) {
|
||||
|
||||
$labels[] = $total['label']." (".$total['count'].")";
|
||||
$counts[] = $total['count'];
|
||||
|
||||
if (isset($total['color'])) {
|
||||
$colors_array[] = $total['color'];
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
$default_color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results = [
|
||||
'labels' => $labels,
|
||||
'datasets' => [[
|
||||
'data' => $counts,
|
||||
'backgroundColor' => $colors_array,
|
||||
'hoverBackgroundColor' => $colors_array,
|
||||
]],
|
||||
];
|
||||
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,10 @@ class UsersTransformer
|
||||
'username' => e($user->username),
|
||||
'remote' => ($user->remote == '1') ? true : false,
|
||||
'locale' => ($user->locale) ? e($user->locale) : null,
|
||||
'employee_num' => e($user->employee_num),
|
||||
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
|
||||
'manager' => ($user->manager) ? [
|
||||
'id' => (int) $user->manager->id,
|
||||
'name'=> e($user->manager->username),
|
||||
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
|
||||
] : null,
|
||||
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
|
||||
'phone' => ($user->phone) ? e($user->phone) : null,
|
||||
@@ -43,7 +43,7 @@ class UsersTransformer
|
||||
'state' => ($user->state) ? e($user->state) : null,
|
||||
'country' => ($user->country) ? e($user->country) : null,
|
||||
'zip' => ($user->zip) ? e($user->zip) : null,
|
||||
'email' => e($user->email),
|
||||
'email' => ($user->email) ? e($user->email) : null,
|
||||
'department' => ($user->department) ? [
|
||||
'id' => (int) $user->department->id,
|
||||
'name'=> e($user->department->name),
|
||||
@@ -63,15 +63,21 @@ class UsersTransformer
|
||||
'accessories_count' => (int) $user->accessories_count,
|
||||
'consumables_count' => (int) $user->consumables_count,
|
||||
'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null,
|
||||
'created_by' => ($user->createdBy) ? [
|
||||
'id' => (int) $user->createdBy->id,
|
||||
'name'=> e($user->createdBy->present()->fullName),
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
|
||||
'start_date' => Helper::getFormattedDateObject($user->start_date, 'date'),
|
||||
'end_date' => Helper::getFormattedDateObject($user->end_date, 'date'),
|
||||
'last_login' => Helper::getFormattedDateObject($user->last_login, 'datetime'),
|
||||
'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null,
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
|
||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0) && ($user->consumables_count == 0)),
|
||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
|
||||
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
|
||||
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
|
||||
];
|
||||
|
||||
@@ -43,6 +43,7 @@ class AccessoryImporter extends ItemImporter
|
||||
$this->log('No Matching Accessory, Creating a new one');
|
||||
$accessory = new Accessory();
|
||||
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
|
||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
||||
$accessory->fill($this->sanitizeItemForStoring($accessory));
|
||||
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
|
||||
@@ -55,6 +55,11 @@ class AssetImporter extends ItemImporter
|
||||
{
|
||||
$editingAsset = false;
|
||||
$asset_tag = $this->findCsvMatch($row, 'asset_tag');
|
||||
|
||||
if(empty($asset_tag)){
|
||||
$asset_tag = Asset::autoincrement_asset();
|
||||
}
|
||||
|
||||
$asset = Asset::where(['asset_tag'=> $asset_tag])->first();
|
||||
if ($asset) {
|
||||
if (! $this->updating) {
|
||||
|
||||
@@ -43,6 +43,7 @@ class ConsumableImporter extends ItemImporter
|
||||
$consumable = new Consumable();
|
||||
$this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
|
||||
$this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
|
||||
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
|
||||
$consumable->fill($this->sanitizeItemForStoring($consumable));
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
$consumable->unsetEventDispatcher();
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class Importer
|
||||
'reassignable' => 'reassignable',
|
||||
'requestable' => 'requestable',
|
||||
'seats' => 'seats',
|
||||
'serial_number' => 'serial number',
|
||||
'serial' => 'serial number',
|
||||
'status' => 'status',
|
||||
'supplier' => 'supplier',
|
||||
'termination_date' => 'termination date',
|
||||
|
||||
@@ -90,7 +90,7 @@ class ItemImporter extends Importer
|
||||
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
||||
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
||||
$this->item['user_id'] = $this->user_id;
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial number');
|
||||
// NO need to call this method if we're running the user import.
|
||||
// TODO: Merge these methods.
|
||||
$this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class');
|
||||
@@ -112,7 +112,7 @@ class ItemImporter extends Importer
|
||||
return $this->createOrFetchUser($row);
|
||||
}
|
||||
|
||||
if (strtolower($this->item['checkout_class']) === 'location') {
|
||||
if (strtolower($this->item['checkout_class']) === 'location' && $this->findCsvMatch($row, 'checkout_location') != null ) {
|
||||
return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location')));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Importer;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\Department;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
@@ -79,6 +80,12 @@ class UserImporter extends ItemImporter
|
||||
$this->log('Updating User');
|
||||
$user->update($this->sanitizeItemForUpdating($user));
|
||||
$user->save();
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $user->location_id]);
|
||||
|
||||
// \Log::debug('UserImporter.php Updated User ' . print_r($user, true));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ use App\Notifications\CheckoutConsumableNotification;
|
||||
use App\Notifications\CheckoutLicenseNotification;
|
||||
use App\Notifications\CheckoutLicenseSeatNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
use Log;
|
||||
|
||||
class CheckoutableListener
|
||||
{
|
||||
@@ -30,7 +32,6 @@ class CheckoutableListener
|
||||
public function onCheckedOut($event)
|
||||
{
|
||||
|
||||
\Log::debug('onCheckedOut in the Checkoutable listener fired');
|
||||
|
||||
/**
|
||||
* When the item wasn't checked out to a user, we can't send notifications
|
||||
@@ -44,16 +45,20 @@ class CheckoutableListener
|
||||
*/
|
||||
$acceptance = $this->getCheckoutAcceptance($event);
|
||||
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
try {
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckoutNotification($event, $acceptance)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkout notification: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,17 +89,21 @@ class CheckoutableListener
|
||||
}
|
||||
}
|
||||
|
||||
// Use default locale
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
try {
|
||||
// Use default locale
|
||||
if (! $event->checkedOutTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$this->getNotifiables($event),
|
||||
$this->getCheckinNotification($event)
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkin notification: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class LogListener
|
||||
public function onCheckoutAccepted(CheckoutAccepted $event)
|
||||
{
|
||||
|
||||
\Log::error('event passed to the onCheckoutAccepted listener:');
|
||||
\Log::debug('event passed to the onCheckoutAccepted listener:');
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item()->associate($event->acceptance->checkoutable);
|
||||
$logaction->target()->associate($event->acceptance->assignedTo);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
@@ -100,6 +101,23 @@ class Accessory extends SnipeModel
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessories -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> supplier relationship
|
||||
*
|
||||
@@ -299,15 +317,29 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return $Parsedown->text(e($this->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within an accessory are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
$checkedout = $this->users->count();
|
||||
|
||||
return $checkedout;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,9 @@ class Actionlog extends SnipeModel
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'company' => ['name'],
|
||||
'user' => ['first_name','last_name','username'],
|
||||
'admin' => ['first_name','last_name','username', 'email'],
|
||||
'user' => ['first_name','last_name','username', 'email'],
|
||||
'assets' => ['asset_tag','name'],
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -95,6 +97,19 @@ class Actionlog extends SnipeModel
|
||||
return $this->hasMany(\App\Models\Company::class, 'id', 'company_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> asset relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item type relationship
|
||||
*
|
||||
@@ -154,6 +169,19 @@ class Actionlog extends SnipeModel
|
||||
return $this->target();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> admin user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> user relationship
|
||||
*
|
||||
@@ -163,8 +191,8 @@ class Actionlog extends SnipeModel
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id')
|
||||
->withTrashed();
|
||||
return $this->belongsTo(User::class, 'target_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+33
-8
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Events\AssetCheckedOut;
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Traits\UniqueSerialTrait;
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Acceptable;
|
||||
@@ -694,15 +695,15 @@ class Asset extends Depreciable
|
||||
public static function getExpiringWarrantee($days = 30)
|
||||
{
|
||||
$days = (is_null($days)) ? 30 : $days;
|
||||
|
||||
|
||||
return self::where('archived', '=', '0')
|
||||
->whereNotNull('warranty_months')
|
||||
->whereNotNull('purchase_date')
|
||||
->whereNull('deleted_at')
|
||||
->whereRaw(\DB::raw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
|
||||
->whereRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
|
||||
. $days
|
||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()'))
|
||||
->orderBy('purchase_date', 'ASC')
|
||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()')
|
||||
->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)')
|
||||
->get();
|
||||
}
|
||||
|
||||
@@ -875,13 +876,12 @@ class Asset extends Depreciable
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
$Parsedown = new \Parsedown();
|
||||
|
||||
|
||||
if (($this->model) && ($this->model->category)) {
|
||||
if ($this->model->category->eula_text) {
|
||||
return $Parsedown->text(e($this->model->category->eula_text));
|
||||
return Helper::parseEscapedMarkedown($this->model->category->eula_text);
|
||||
} elseif ($this->model->category->use_default_eula == '1') {
|
||||
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1147,6 +1147,31 @@ class Asset extends Depreciable
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope for Archived assets counting
|
||||
*
|
||||
* This is primarily used for the tab counters so that IF the admin
|
||||
* has chosen to not display archived assets in their regular lists
|
||||
* and views, it will return the correct number.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
|
||||
public function scopeAssetsForShow($query)
|
||||
{
|
||||
|
||||
if (Setting::getSettings()->show_archived_in_list!=1) {
|
||||
return $query->whereHas('assetstatus', function ($query) {
|
||||
$query->where('archived', '=', 0);
|
||||
});
|
||||
} else {
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for Archived assets
|
||||
*
|
||||
|
||||
@@ -74,10 +74,11 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'),
|
||||
trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'),
|
||||
trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
|
||||
'PAT test' => 'PAT test',
|
||||
trans('admin/asset_maintenances/general.pat_test') => trans('admin/asset_maintenances/general.pat_test'),
|
||||
trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'),
|
||||
'Software Support' => trans('admin/asset_maintenances/general.software_support'),
|
||||
'Hardware Support' => trans('admin/asset_maintenances/general.hardware_support'),
|
||||
trans('admin/asset_maintenances/general.software_support') => trans('admin/asset_maintenances/general.software_support'),
|
||||
trans('admin/asset_maintenances/general.hardware_support') => trans('admin/asset_maintenances/general.hardware_support'),
|
||||
trans('admin/asset_maintenances/general.configuration_change') => trans('admin/asset_maintenances/general.configuration_change'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -162,6 +163,20 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* Query builder scope to order on a supplier
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderBySupplier($query, $order)
|
||||
{
|
||||
return $query->leftJoin('suppliers as suppliers_maintenances', 'asset_maintenances.supplier_id', '=', 'suppliers_maintenances.id')
|
||||
->orderBy('suppliers_maintenances.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on admin user
|
||||
*
|
||||
|
||||
@@ -20,7 +20,7 @@ class AssetModel extends SnipeModel
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
protected $presenter = \App\Presenters\AssetModelPresenter::class;
|
||||
use Requestable, Presentable;
|
||||
use Loggable, Requestable, Presentable;
|
||||
|
||||
protected $table = 'models';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
@@ -181,6 +181,23 @@ class AssetModel extends SnipeModel
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uploads for this model
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany('\App\Models\Actionlog', 'item_id')
|
||||
->where('item_type', '=', AssetModel::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user