Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4913e56086 | |||
| 4d00bb98d1 | |||
| db2baae758 | |||
| fdecdf4b15 | |||
| 5c1d4aff23 | |||
| bf0edcb92e | |||
| 39fa8ef3c0 | |||
| 9e8a369bc8 | |||
| 3f3fe8935f | |||
| 82387630c7 | |||
| 16c5033514 | |||
| 46ffaee1e4 | |||
| d936f92a7d | |||
| b424ddf42d | |||
| b3d5a893fc | |||
| 69ea6eebae | |||
| 0892c34125 | |||
| 52e14d501f | |||
| 4d093160fc | |||
| 0bcca99573 | |||
| 63516c4a4f | |||
| 19fb79ffff | |||
| 02835de13d | |||
| 55b004d53d | |||
| c1b72a8ce6 | |||
| fa0bea87e6 | |||
| d146426dd8 | |||
| 1e1782c232 | |||
| dab5874fd7 | |||
| 4850227c04 | |||
| eb9a654cc3 | |||
| 4224bc0c43 | |||
| f893b23129 | |||
| 8c65880504 | |||
| 53cadf80fa | |||
| 8b3bfc6bc9 | |||
| 19cff25300 | |||
| 848e1fe1f6 | |||
| fe147adec3 | |||
| 103809b65f | |||
| a398496dd4 | |||
| 839db8ef44 | |||
| bfd0530597 | |||
| 494ec5cd9c | |||
| fc61a4b88d | |||
| 50d8b02f8b | |||
| 860764a436 | |||
| 52d6a8990a | |||
| 87de67e4a9 | |||
| 8356b57fb4 | |||
| 3f04afee5c | |||
| 2117f61e8c | |||
| d40604b574 | |||
| 76129e9011 | |||
| 9c8411c7ff | |||
| c661d732b3 | |||
| fbe9daace6 | |||
| 651001bf6e | |||
| 9167f8a3ba | |||
| 6dc9ccffcd | |||
| 4b4e3badb7 | |||
| fe4dd23d39 | |||
| de6d71cc3b | |||
| 9e44052709 | |||
| 2d112f227a | |||
| bd8737d986 | |||
| 4b6d236eb7 | |||
| bf058bd5c6 | |||
| dfaf01e8aa | |||
| 2888dd6fd8 | |||
| 2e92ee8eee | |||
| 14b6a75507 | |||
| 2484a9db2c | |||
| bfc30794c5 | |||
| 27bc7a847b | |||
| 2a71877bec | |||
| 30bd920497 | |||
| 1d5b48b88d | |||
| 4295bad12f | |||
| 3a2eeaea7a | |||
| 12418ae91b | |||
| 783a24eb68 | |||
| 2439758ef3 | |||
| 685f1cbfb8 | |||
| e2d1e6b0c5 | |||
| 56cb9a0f4e | |||
| c8c81a360c | |||
| bdd43b7134 | |||
| c50d5a678a | |||
| 96b3af7cbc | |||
| 9e7bbc968d | |||
| 5fc6771543 | |||
| 155f5f35cd | |||
| 7ad6caf30a | |||
| ac8aab0043 | |||
| 14ddf36d46 | |||
| 25f1167c9d | |||
| 420225c2d5 | |||
| 45f5eaac5b | |||
| 5229dd65ce | |||
| cfe39afc11 | |||
| 2aa3ce15bd | |||
| c9873da732 | |||
| 8dd71f99cc | |||
| ab45975883 | |||
| 7f38ca239e | |||
| c3b982e759 | |||
| e330e80c46 | |||
| f0836d4d3a | |||
| 1289920217 | |||
| a6a58094c9 | |||
| 424cbd3248 | |||
| e59b9627c7 | |||
| f080c0cdcd | |||
| 29bda2ef7d | |||
| 1d64692fd6 | |||
| 6f195cb8ec | |||
| 4c02a63acc | |||
| 0fc9fc7516 | |||
| 4450351b75 | |||
| 9bb15aaf1b | |||
| 65dd729e19 | |||
| c21142605d | |||
| 86e274faa3 | |||
| 5e8c129c7f | |||
| ab3b5ca4ef | |||
| 60a5afd752 | |||
| 9d0ea857fe | |||
| f763aea4fc | |||
| e16c04250e | |||
| ad99aa460b | |||
| e47f64f62d | |||
| c6d9da1571 | |||
| ab561d1ce8 | |||
| f39ba0136c | |||
| df60045bfe | |||
| 5e122f780f | |||
| eefe377159 | |||
| 20920c262d | |||
| 870612be1c | |||
| 266424ff0e | |||
| a8d48b758e | |||
| 67a8e0b5c6 | |||
| 7b7d424962 | |||
| a4e959818a | |||
| c3a71cc182 | |||
| da03cfdbe5 | |||
| 2b137d76fa | |||
| 81b8c111ca | |||
| fce98b9ca4 | |||
| c0dcae16f7 | |||
| c604f08749 | |||
| da21424416 | |||
| b5c83721ad | |||
| 9b21fca1a0 | |||
| af94b07771 | |||
| 0d23d28a65 | |||
| 710370ac24 | |||
| ed0a441e4d | |||
| 24e87cc0bb | |||
| 460693c153 | |||
| f54a94bd4c | |||
| a19b86add0 | |||
| 570944a48b | |||
| 5829b02323 | |||
| a2bca0e358 | |||
| 133fdd7a37 | |||
| 0849262243 | |||
| 17095feb33 | |||
| f42ae46338 | |||
| e2679852ce | |||
| 989dab6259 | |||
| adacdc038d | |||
| f0aefaac42 | |||
| 1aeaa0094a | |||
| 6d3fa12f37 | |||
| 052f1eedd0 | |||
| 858da800be | |||
| 954cac3af7 | |||
| d0f171ebc6 | |||
| 90cf612ac7 | |||
| 802b5863ab | |||
| 4baa949a99 | |||
| 3bed04a6d3 | |||
| d548b800d5 | |||
| f7f199d929 | |||
| 5519fddb78 | |||
| a9ed748fb2 | |||
| dcba2bfd25 | |||
| 72c91ead8b | |||
| 774d0aa90c | |||
| 2eea67ce34 | |||
| db1c44f921 | |||
| e11287ec25 | |||
| 4f10adfb76 | |||
| f22803c59f | |||
| a72d4e5dc1 | |||
| df5cacf8a2 | |||
| bb2c73348d | |||
| 4327653d70 | |||
| 5f7f4c9b91 | |||
| d44202e55b | |||
| df95447ea4 | |||
| 01852c7188 | |||
| 79c5697042 | |||
| 658ba092e2 | |||
| b221d99e7b | |||
| b7af049589 | |||
| c2d863da99 | |||
| 6f9ba6ede4 | |||
| df4e6a0023 | |||
| d60fa65f85 | |||
| 8081a82fe4 | |||
| f42e5d5292 | |||
| 5a3f5b03d0 | |||
| a450530c74 | |||
| 2b4886f37f | |||
| b45de3e17f | |||
| b2eea3e5a5 | |||
| f411c0fdd8 | |||
| 74d8431d01 | |||
| 3ea0cd75a5 | |||
| 5f6c746d25 | |||
| 1f2d30ebf4 | |||
| 6dd6b45829 | |||
| e5800a2dac | |||
| 0d3d172108 | |||
| 86677b5f13 | |||
| 3f5b94f8ad | |||
| cf8cb8521b | |||
| ccd00caa70 | |||
| 57010b4c23 | |||
| 545a185614 | |||
| 1572e339f9 | |||
| 7782e5cc93 | |||
| 1b8c7ff4ec | |||
| 3bb81d1e4d | |||
| 756c44fcba | |||
| befa4428d0 | |||
| 60c678680a | |||
| 8bc9688d71 | |||
| aa8af2220c | |||
| b124b9af4d | |||
| ae403da8c1 | |||
| b5b8777c94 | |||
| 850f85ff59 | |||
| 84cc88831d | |||
| f4f9b165a7 | |||
| 3222d4c8df | |||
| 3173ead2e7 | |||
| 040f826c52 | |||
| 89d733d442 | |||
| df49e8350f | |||
| 3ced85080a | |||
| 0611ab9b4c | |||
| f450cafe3e | |||
| 1f29eb1875 | |||
| a4e5ae0938 | |||
| 77dacfcc30 | |||
| 0d9b6eaf71 | |||
| 7060ffaf34 | |||
| 02a37e2f89 | |||
| 9c1b1bc2b5 | |||
| b34156ca25 | |||
| 61bdb57b5d | |||
| 9df84e235c | |||
| 566ba4783e | |||
| b41b4b1732 | |||
| ccf9457c45 | |||
| 53aabdab66 | |||
| 8ff85c952e | |||
| afe1cb8234 | |||
| bd42505799 | |||
| 6c735c97c6 | |||
| 31a57cdf14 | |||
| 7ebbef25e7 | |||
| f836342194 | |||
| 5cf1a6c300 | |||
| bd506820b7 | |||
| 5815607924 | |||
| 9b40c9788f | |||
| 67b5e9093e | |||
| 57d1c036ec | |||
| 71722b753d | |||
| a2625c889a | |||
| c98b9da612 | |||
| 675717ff82 | |||
| 83ef7c6fe8 | |||
| 5f4c964309 | |||
| 66ba96d531 | |||
| d67b2da064 | |||
| e45fd4088f | |||
| 7e4a0eedf0 | |||
| 3f812f696d | |||
| e9e6f925bf | |||
| 828b84084d | |||
| a6a0bfacc2 | |||
| c4b7e77498 | |||
| 91c7180bfd | |||
| 7c39f516b9 | |||
| ce1ddcfcee | |||
| 945e8b402f | |||
| 5ed2bd0fb7 | |||
| bc908b854d | |||
| 2067b1138a | |||
| 1ffbdee156 | |||
| bd2812cac1 | |||
| f2a5eac256 | |||
| b4e647dbd1 | |||
| de18e449a6 | |||
| dce19e0bea | |||
| 1f586d3102 | |||
| e5d01170d2 | |||
| 0f11963127 | |||
| d8378f2a10 | |||
| a0a5480c97 | |||
| 3391108551 | |||
| 52551dad0f | |||
| 321414f6e3 | |||
| 7787fe42c8 | |||
| 3b66912742 | |||
| 278a25c63b | |||
| 0d124bb5a1 | |||
| 417caae589 | |||
| 3d306aacc5 | |||
| ea4ecaea03 | |||
| 6c90f9e395 | |||
| ddf81ba135 | |||
| 8ebb41caa6 | |||
| faf48b1684 | |||
| b8232d205b | |||
| 62745923cf | |||
| 090466123f | |||
| 38a3e36cd6 | |||
| e8dc634a40 | |||
| 0d0984a400 | |||
| 8640cad033 | |||
| dc29717623 | |||
| 814914924f | |||
| c7083488b2 | |||
| 4b4d383509 | |||
| 3680e04817 | |||
| 21e23baa37 | |||
| fcd130ae15 | |||
| d1dffb84dc | |||
| 541350916d | |||
| 002fd4ce30 | |||
| 7b4ecb275f | |||
| 2df5d3a8ff | |||
| 7070bad53b | |||
| da62d6af26 | |||
| d0d4d14787 | |||
| 09d69b214b | |||
| 0e2aaebda4 | |||
| ec2c58163f | |||
| a28bee86ba | |||
| fb64892971 | |||
| 95ff692b14 | |||
| 8003615b1f | |||
| 948dc3c974 | |||
| 1afb724606 | |||
| 451281d833 | |||
| 485f11c945 | |||
| dbc79655b0 | |||
| 02f6aa6161 | |||
| 07c5264b41 | |||
| 7c178a6a78 | |||
| f56d53d7c1 | |||
| 4cd4a936d8 | |||
| 7bae4c39c6 | |||
| d55d95bbea | |||
| 5e48d56561 | |||
| 6a5098fb0c | |||
| 4e835e1772 | |||
| cce8cb4f5e | |||
| 8aed26aab1 | |||
| 01d5d4c2c8 | |||
| 592385cb07 | |||
| 85123b3e66 | |||
| 0fcf223960 | |||
| bf0bd06f20 | |||
| f07b0b6bd7 | |||
| 717b26c834 | |||
| 8bb8eab69b | |||
| 693d1c9452 | |||
| b049bb1d5c | |||
| c5c6b3bbc6 | |||
| 1df56a7cab | |||
| 3f5cc2507d | |||
| 5a85424295 | |||
| a53a8cca74 | |||
| 3fdee881f9 | |||
| a289dfaf88 | |||
| 8abd359b5b | |||
| 9359809b4f | |||
| cde5502f94 | |||
| 04e0a9d4a5 | |||
| 28ec0b8ebb | |||
| 37dfecf098 | |||
| 9d8ce872a4 | |||
| 07880dfe50 | |||
| 7eed9f8542 | |||
| a2e70dd6b2 | |||
| b9aeded957 | |||
| 027361f079 | |||
| ff10d1540f | |||
| 0f63fa23e0 | |||
| 22ef569e5e | |||
| f3663f0983 | |||
| 112ddaf55b | |||
| bda6fdf09c | |||
| a62198f33f | |||
| 4b0bfc52b4 | |||
| 78868813b1 | |||
| e8be178ac7 | |||
| 695428cd44 | |||
| 9ae779e442 | |||
| db92febdc7 | |||
| 5cf10cec34 | |||
| 7939c691f7 | |||
| ea2d54b0f7 | |||
| c66267a0f9 | |||
| f1d2f24534 | |||
| d83974e07f | |||
| f270f30728 | |||
| 55c237913c | |||
| 806671df7c | |||
| d64ee42ec3 | |||
| fe08f39900 | |||
| 5123ab57c9 | |||
| 313b327cd7 | |||
| 855d922a3e | |||
| cbe07ad23b | |||
| b3aba4ad99 | |||
| 21ad7f549d | |||
| 4b13fa45c5 | |||
| 485d40c752 | |||
| 8346ae8235 | |||
| 109c0f202a | |||
| 4231058aa1 | |||
| 6156d67e4a | |||
| 58bf036f52 | |||
| ff7d25c0f2 | |||
| 5f0b7f328c | |||
| 6559581bad | |||
| d78262f52b | |||
| bd566324ed | |||
| b65bf5082d | |||
| 939e4cba3e | |||
| 015a8763a0 | |||
| 02862d80eb | |||
| 7b0a3fc6d3 | |||
| b40b31b7b1 | |||
| 149d3d13d1 | |||
| 0f64d66cd8 | |||
| a85e2f7aad | |||
| 4f883b1264 | |||
| feb78d00cf | |||
| c9d54baa10 | |||
| 940f54dab1 | |||
| d83827a44e | |||
| 3a0a13d06d | |||
| 23f8e35716 | |||
| a251e61d73 | |||
| af06b1cd06 | |||
| 7769a93a10 | |||
| 95a6c7058f | |||
| 6c3a668400 | |||
| ad0f873ece | |||
| 3008a4ed7a | |||
| 95ef3a336b | |||
| 9a5c1b8126 | |||
| e926db76a0 | |||
| 9419c7fdeb | |||
| 9dbb4abe7e | |||
| 19e0fb7955 | |||
| 5b9b21a7d1 | |||
| 93e69ab0c6 | |||
| bc5c559413 | |||
| 9b2fcbff08 | |||
| ad2674d20b | |||
| 10ac2c830f | |||
| df3053eafb | |||
| aaae952acb | |||
| eb0657c953 | |||
| b1cd44341b | |||
| 3da47cdacd | |||
| 658dda916c | |||
| 7c3d8b896b | |||
| 1ce9df7998 | |||
| a0cbf66c81 | |||
| a4941031cb | |||
| 9fd6aea325 | |||
| a52181c995 | |||
| ee75df0f0a | |||
| 92eff653f1 | |||
| 1e66985a78 | |||
| a059a42799 | |||
| b5603fbfe9 | |||
| a0423a9cc3 | |||
| 4ab7112988 | |||
| 767cf96010 | |||
| 4ffb7790df | |||
| 3ccc55a78f | |||
| d0d29e03db | |||
| 92b7c4b5ec | |||
| dff7c43aed | |||
| 9efbcbbd5e | |||
| 31fa0a7044 | |||
| fcb2bf7fea | |||
| 0bd27d61b0 | |||
| 369ecfa490 | |||
| 551354b1bb | |||
| 03b7891edc | |||
| 8978dff054 | |||
| d20844fefa | |||
| cd579a04dd | |||
| 15b8140bff | |||
| 9a93ad2e06 | |||
| bd4d3aa52b | |||
| bf32ab177f | |||
| 2ea883aa15 | |||
| 43cc296582 | |||
| 4c1aadd74e | |||
| 7d3719bf70 | |||
| c08164d864 | |||
| b156aa74a5 | |||
| 87ba2cb407 | |||
| 5084e5d3ef | |||
| a5516e3511 | |||
| 0e460baf82 | |||
| ef52777ffb | |||
| b67ceab849 | |||
| 69022bb8b6 | |||
| 29d729171c | |||
| 9475871edb | |||
| b69364d5ff | |||
| 10dad8e6e6 | |||
| a184b4e67c | |||
| 8cd0a90ecd | |||
| 4a37632ef3 | |||
| 3271d020e9 | |||
| e494a2670f | |||
| d3a0a337b9 | |||
| 3951ee746d | |||
| 84e4257e75 | |||
| c401c88702 | |||
| 550f9e2afa | |||
| b55a19cebb | |||
| 4caadcfa19 | |||
| dba837b1d2 | |||
| 714fc63050 | |||
| b6fa6cba22 | |||
| 2df026bcb5 | |||
| 6ee24a7527 | |||
| e2dcee1959 | |||
| 4fbea9512f | |||
| b7850ab839 | |||
| c0215baca5 | |||
| c62758c5b5 | |||
| 14358651e4 | |||
| f04aeb9f2b | |||
| dc902e7a5a | |||
| 4fc66e19bb | |||
| e12d2b2a42 | |||
| cb78451d6c | |||
| 7979bc63ae | |||
| e8ad8a7448 | |||
| eb61f5aa9e | |||
| 3351998efd | |||
| 20dbacd22f | |||
| 0f3be4fdf8 | |||
| 3ae8adfbf9 | |||
| aa2632fe46 | |||
| 14c86d447b | |||
| bee016e0be | |||
| 54552fc95c | |||
| ffa7d25fc0 | |||
| a37d3b00d0 | |||
| 04891c7c61 | |||
| d67ff54f4b | |||
| ccec190985 | |||
| 26728a85ad | |||
| c6d85a1b0b | |||
| 71610fb20f | |||
| cb0f9024b1 | |||
| 1797480128 | |||
| 972b198248 | |||
| 9010b7acd0 | |||
| b57b68571e | |||
| 1ca9420baa | |||
| 0383938536 | |||
| 50b841d54d | |||
| 8f71460fa1 | |||
| d5324bce6a | |||
| 660f3ccba1 | |||
| bc8db3deab | |||
| b2c8fbf349 | |||
| c3f21d9292 | |||
| 9b146ae1d2 | |||
| a32c679519 | |||
| 1e602793b2 | |||
| a6a65b7523 | |||
| 04c1d9cbff | |||
| a6dfd67cd7 | |||
| 7d178da61c | |||
| 30f9acfcf3 | |||
| 1e351b4d63 | |||
| cb5b691ec1 | |||
| b47b401245 | |||
| 119e79e248 | |||
| d7254053b6 | |||
| 8f8edd4126 | |||
| 10bb844087 | |||
| ba3baabb50 | |||
| c76fbe4edb | |||
| cae2de4fc9 | |||
| 7c346d977a | |||
| 9847934de9 | |||
| db73f80058 | |||
| 4dd479dad7 | |||
| d01e1e8eeb | |||
| e3ef737ac4 | |||
| 69317fb403 | |||
| 1b80c8938a | |||
| dc77c01fd3 | |||
| 83474d6e59 | |||
| 529310c93a | |||
| 70f26f33a5 | |||
| 414bc10c40 | |||
| 250b0a7afb | |||
| e5355db672 | |||
| aef45a90b2 | |||
| 9221641bba | |||
| 6bca1e3b22 | |||
| 00cea3eb3c | |||
| 19b52a2f24 | |||
| 6237f6192c | |||
| cc58d4c3ad | |||
| 6a78706a3e | |||
| 060c59bf9d | |||
| 6fd3c494be | |||
| 8adfa8dd83 | |||
| 6c9001df09 | |||
| d6159d8cb5 | |||
| 81b9753b79 | |||
| d8b1eec91b | |||
| b871813cfd | |||
| 6f7dce53cc | |||
| 13d2af2155 | |||
| bf3794822c | |||
| 1fa8fba5fe | |||
| 3d7697da6f | |||
| 42cf17d3bf | |||
| 783e37f06d | |||
| 9a4cda0bf6 | |||
| b6279af1d8 | |||
| bc0a7542ac | |||
| 232fad0145 | |||
| 5cbcac28b1 | |||
| aec59f2da6 | |||
| 905df5ec25 | |||
| bacfdc5049 | |||
| 3cc72021b6 | |||
| 115e0fc119 | |||
| 4354e126b1 | |||
| 02f39472f9 | |||
| d7aed2edc9 | |||
| af513946a2 | |||
| 7bfd02054b | |||
| 1ceb703129 | |||
| fb28882f65 | |||
| d9c61fdb02 | |||
| 72c118a70f | |||
| b136e9e29d | |||
| 50c910461a | |||
| 88a84e9350 | |||
| 43bb8ae0a8 | |||
| 638071dadd | |||
| 8a4c90ade8 | |||
| 1dfa1da0ee | |||
| 71ebade641 | |||
| b86b05c4fc | |||
| 35a70988cb | |||
| 56ba26eb45 | |||
| adb8be9345 | |||
| abc9ee22c6 | |||
| e5ee9760c0 | |||
| 7868a8c174 | |||
| 03df4cec45 | |||
| 25241542d2 | |||
| 57a75e68b9 | |||
| ad1846fed6 | |||
| dcf2168454 | |||
| 9ab56fe9ca | |||
| f708b8b299 | |||
| c5e8d1c276 | |||
| 4093327b7f | |||
| 391b832613 | |||
| b653d19579 | |||
| 4a57cfaf3e | |||
| 31a75bd252 | |||
| e2c3386c33 | |||
| 0506f3bef9 | |||
| a866d6b16e | |||
| a76a69d085 | |||
| 852b0b3f11 | |||
| 307b39bd38 | |||
| d55358652b | |||
| 955f75f733 | |||
| 280e778749 | |||
| 60ba898167 | |||
| 2c9d5b9ea3 | |||
| eb6e2636b5 | |||
| 4c1964d509 | |||
| 7547277352 | |||
| 3e00bc49fd | |||
| 99e0b65de7 | |||
| 70ef904951 | |||
| fcf023e3d2 | |||
| 8c882ddead | |||
| 7d136f9970 | |||
| f4c1460c2b | |||
| bb2e1de0a8 | |||
| c81bc1d2ee | |||
| 7154d23759 | |||
| df23fd0dee | |||
| adfb8895df | |||
| c8e12ddb5c | |||
| 5b181ecea7 | |||
| 728aaaab20 | |||
| 095a7d9b34 | |||
| cf53f2778f | |||
| 65e20282b6 | |||
| 405c5b5ad0 | |||
| 6f0fe16b87 | |||
| 111daffc17 | |||
| b8a478f558 | |||
| 9f7084d077 | |||
| 0840cd3df3 | |||
| cefdaf9a9b | |||
| 13335b19e9 | |||
| 6e471a27e7 | |||
| 513ea67e7d | |||
| 3868e711f4 | |||
| f33f712de7 | |||
| c12e1f6d6c | |||
| 479abd5231 | |||
| a60a24a4a8 | |||
| 55b3050ca8 | |||
| 2c996a8508 | |||
| 84f8eee869 | |||
| 590c19dbd7 | |||
| fa47707974 | |||
| ca62481083 | |||
| 1c3306046c | |||
| f4fc845375 | |||
| a7af987322 | |||
| c4eaae923a | |||
| 849ba02516 | |||
| 9dcd14a712 | |||
| 3412b4dc5a | |||
| a3b96aff1f | |||
| 9bb191f29f | |||
| 4a43ccfa92 | |||
| bcfa913450 | |||
| 43d8474caa | |||
| b73e8642d3 | |||
| cfe2277a64 | |||
| e776c2cffa | |||
| ca59bc3c9c | |||
| 575362f4dc | |||
| f0139c5e41 | |||
| 5f8ac66036 | |||
| a49e66c689 | |||
| 1630e4bc2f | |||
| bf674a0f4d | |||
| b170755c3d | |||
| b25612bbac | |||
| a43183ff96 | |||
| 5aa34695a1 | |||
| 3b36372a66 | |||
| e763693829 | |||
| e08d60ed18 | |||
| 9e6e2de71e | |||
| b49935701b | |||
| 76cc5995d9 | |||
| c0fbf106ed | |||
| 7b4020c5e9 | |||
| 383e3829e5 | |||
| 91356af838 | |||
| 4c967a43a7 | |||
| deca842eb0 | |||
| 44366746dd | |||
| 3806cec10e | |||
| a800fa07f9 | |||
| 32c360f032 | |||
| 25f1445afc | |||
| 0042359e5f | |||
| cf2e2bfbc1 | |||
| 60f9a8be9a | |||
| 8316db9702 | |||
| 1a480435de | |||
| ebc373744a | |||
| 650aa25659 | |||
| 212dd06948 | |||
| b6eea2e170 | |||
| 688017bd8a | |||
| 58a9c27342 | |||
| f52b00256d | |||
| 2d48d96b3c | |||
| abd13a1140 | |||
| 5882d71f9b | |||
| dc92bbf61e | |||
| e6fdeb0e8a | |||
| 3b948c7b7e | |||
| d965fe759a | |||
| 5e4a3379a9 | |||
| 9dc428b720 | |||
| 8a5b469ff8 | |||
| ed06f32a7a | |||
| a876703f8f | |||
| 01bb8d8c9a | |||
| f452204d62 | |||
| 51ae485f20 | |||
| 0eabb147b2 | |||
| 2e0e39ccc8 | |||
| 63e733f0d6 | |||
| 2406d2cfdb | |||
| e074ca0bf9 | |||
| 13c37e708f | |||
| 3f76d65b95 | |||
| 87bce0c097 | |||
| 6d41c8cf67 | |||
| 22385a8e35 | |||
| 77fa213ccd | |||
| d0a82adc3f | |||
| de2aa903c5 | |||
| 42ec2548c9 | |||
| a2cba67f4e | |||
| 7d45cfff2c | |||
| f1ab8253f0 | |||
| e5d3df7d24 | |||
| 987676df08 | |||
| f16f62f76c | |||
| dfa33f651a | |||
| 30c2927987 | |||
| e8159d97fa | |||
| 1248260df3 | |||
| 4cb804cf03 | |||
| 2b0dd8851c | |||
| 2deba17d91 | |||
| 3574ef5bb9 | |||
| abf13f1619 | |||
| 4a7df470f0 | |||
| c9101f4d97 | |||
| 6e9a46e582 | |||
| 1d3124f89f | |||
| ada1a593a4 | |||
| 41e0275c95 | |||
| c9d46856a3 | |||
| 6d65f6646f | |||
| d556f0d275 |
@@ -3018,6 +3018,87 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "koiakoia",
|
||||
"name": "koiakoia",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/60405354?v=4",
|
||||
"profile": "https://github.com/koiakoia",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mustafa-online",
|
||||
"name": "Mustafa Online",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5323832?v=4",
|
||||
"profile": "https://github.com/mustafa-online",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "franceslui",
|
||||
"name": "franceslui",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/104601439?v=4",
|
||||
"profile": "https://github.com/franceslui",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Q4kK",
|
||||
"name": "Q4kK",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/125313163?v=4",
|
||||
"profile": "https://github.com/Q4kK",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "squintfox",
|
||||
"name": "squintfox",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55590532?v=4",
|
||||
"profile": "https://github.com/squintfox",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jeffclay",
|
||||
"name": "Jeff Clay",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1380084?v=4",
|
||||
"profile": "https://github.com/jeffclay",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PP-JN-RL",
|
||||
"name": "Phil J R",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/52716446?v=4",
|
||||
"profile": "https://github.com/PP-JN-RL",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chandanchowdhury",
|
||||
"name": "i_virus",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1496725?v=4",
|
||||
"profile": "https://www.corelight.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "gitgrimbo",
|
||||
"name": "Paul Grime",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1020541?v=4",
|
||||
"profile": "https://github.com/gitgrimbo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ DB_SSL_KEY_PATH=null
|
||||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
|
||||
@@ -36,6 +36,7 @@ DB_SSL_KEY_PATH=null
|
||||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
|
||||
@@ -42,6 +42,7 @@ DB_SSL_KEY_PATH=null
|
||||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
@@ -86,6 +87,7 @@ COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=15
|
||||
BS_TABLE_STORAGE=cookieStorage
|
||||
BS_TABLE_DEEPLINK=true
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SECURITY HEADER SETTINGS
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||
- name: Run Codacy Analysis CLI
|
||||
uses: codacy/codacy-analysis-cli-action@v4.3.0
|
||||
uses: codacy/codacy-analysis-cli-action@v4.4.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
|
||||
|
||||
@@ -432,6 +432,17 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bilias"><img src="https://avatars.githubusercontent.com/u/47315739?v=4?s=110" width="110px;" alt="bilias"/><br /><sub><b>bilias</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bilias" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coach1988"><img src="https://avatars.githubusercontent.com/u/2565989?v=4?s=110" width="110px;" alt="coach1988"/><br /><sub><b>coach1988</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=coach1988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mauro-miatello"><img src="https://avatars.githubusercontent.com/u/11910225?v=4?s=110" width="110px;" alt="MrM"/><br /><sub><b>MrM</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mauro-miatello" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/koiakoia"><img src="https://avatars.githubusercontent.com/u/60405354?v=4?s=110" width="110px;" alt="koiakoia"/><br /><sub><b>koiakoia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koiakoia" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mustafa-online"><img src="https://avatars.githubusercontent.com/u/5323832?v=4?s=110" width="110px;" alt="Mustafa Online"/><br /><sub><b>Mustafa Online</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mustafa-online" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franceslui"><img src="https://avatars.githubusercontent.com/u/104601439?v=4?s=110" width="110px;" alt="franceslui"/><br /><sub><b>franceslui</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=franceslui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Q4kK"><img src="https://avatars.githubusercontent.com/u/125313163?v=4?s=110" width="110px;" alt="Q4kK"/><br /><sub><b>Q4kK</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Q4kK" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/squintfox"><img src="https://avatars.githubusercontent.com/u/55590532?v=4?s=110" width="110px;" alt="squintfox"/><br /><sub><b>squintfox</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=squintfox" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeffclay"><img src="https://avatars.githubusercontent.com/u/1380084?v=4?s=110" width="110px;" alt="Jeff Clay"/><br /><sub><b>Jeff Clay</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jeffclay" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PP-JN-RL"><img src="https://avatars.githubusercontent.com/u/52716446?v=4?s=110" width="110px;" alt="Phil J R"/><br /><sub><b>Phil J R</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PP-JN-RL" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.corelight.com/"><img src="https://avatars.githubusercontent.com/u/1496725?v=4?s=110" width="110px;" alt="i_virus"/><br /><sub><b>i_virus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chandanchowdhury" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gitgrimbo"><img src="https://avatars.githubusercontent.com/u/1020541?v=4?s=110" width="110px;" alt="Paul Grime"/><br /><sub><b>Paul Grime</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gitgrimbo" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.18.5
|
||||
FROM alpine:3.18.6
|
||||
# Apache + PHP
|
||||
RUN apk add --no-cache \
|
||||
apache2 \
|
||||
@@ -29,6 +29,7 @@ RUN apk add --no-cache \
|
||||
php81-sodium \
|
||||
php81-redis \
|
||||
php81-pecl-memcached \
|
||||
php81-exif \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||

|
||||
|
||||
[](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) [](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk)
|
||||
[](#contributing) [](https://discord.gg/yZFtShAcKk)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
@@ -11,7 +11,8 @@ It is built on [Laravel 8](http://laravel.com).
|
||||
|
||||
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
|
||||
__This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||
> [!TIP]
|
||||
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
|
||||
|
||||
-----
|
||||
|
||||
@@ -21,7 +22,7 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
|
||||
|
||||
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
|
||||
|
||||
[](https://heroku.com/deploy)
|
||||
<!-- [](https://heroku.com/deploy) -->
|
||||
|
||||
-----
|
||||
### User's Manual
|
||||
@@ -32,8 +33,9 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
|
||||
|
||||
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
|
||||
|
||||
**PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
|
||||
>
|
||||
-----
|
||||
|
||||
### Upgrading
|
||||
@@ -57,6 +59,9 @@ Please see the [translations documentation](https://snipe-it.readme.io/docs/tran
|
||||
|
||||
Since the release of the JSON REST API, several third-party developers have been developing modules and libraries to work with Snipe-IT.
|
||||
|
||||
> [!NOTE]
|
||||
> 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. :)
|
||||
|
||||
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
@@ -73,8 +78,6 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
|
||||
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
|
||||
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
-----
|
||||
|
||||
@@ -92,4 +95,5 @@ The ERD is available [online here](https://drawsql.app/templates/snipe-it).
|
||||
|
||||
### Security
|
||||
|
||||
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
|
||||
> [!IMPORTANT]
|
||||
> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
|
||||
|
||||
+15
-2
@@ -45,8 +45,21 @@ DB_PASSWORD={}
|
||||
|
||||
Now you are ready to run the entire test suite from your terminal:
|
||||
|
||||
`php artisan test`
|
||||
```shell
|
||||
php artisan test
|
||||
````
|
||||
|
||||
To run individual test files, you can pass the path to the test that you want to run:
|
||||
|
||||
`php artisan test tests/Unit/AccessoryTest.php`
|
||||
```shell
|
||||
php artisan test tests/Unit/AccessoryTest.php
|
||||
```
|
||||
|
||||
Some tests, like ones concerning LDAP, are marked with the `@group` annotation. Those groups can be run, or excluded, using the `--group` or `--exclude-group` flags:
|
||||
|
||||
```shell
|
||||
php artisan test --group=ldap
|
||||
|
||||
php artisan test --exclude-group=ldap
|
||||
```
|
||||
This can be helpful if a set of tests are failing because you don't have an extension, like LDAP, installed.
|
||||
|
||||
@@ -390,7 +390,7 @@ class LdapSync extends Command
|
||||
$user->location_id = $location->id;
|
||||
}
|
||||
}
|
||||
|
||||
$location = null;
|
||||
$user->ldap_import = 1;
|
||||
|
||||
$errors = '';
|
||||
|
||||
@@ -5,6 +5,151 @@ namespace App\Console\Commands;
|
||||
use Illuminate\Console\Command;
|
||||
use ZipArchive;
|
||||
|
||||
class SQLStreamer {
|
||||
private $input;
|
||||
private $output;
|
||||
// embed the prefix here?
|
||||
public ?string $prefix;
|
||||
|
||||
private bool $reading_beginning_of_line = true;
|
||||
|
||||
public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
|
||||
|
||||
public array $tablenames = [];
|
||||
private bool $should_guess = false;
|
||||
private bool $statement_is_permitted = false;
|
||||
|
||||
public function __construct($input, $output, string $prefix = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function parse_sql(string $line): string {
|
||||
// take into account the 'start of line or not' setting as an instance variable?
|
||||
// 'continuation' lines for a permitted statement are PERMITTED.
|
||||
if($this->statement_is_permitted && $line[0] === ' ') {
|
||||
return $line;
|
||||
}
|
||||
|
||||
$table_regex = '`?([a-zA-Z0-9_]+)`?';
|
||||
$allowed_statements = [
|
||||
"/^(DROP TABLE (?:IF EXISTS )?)`$table_regex(.*)$/" => false,
|
||||
"/^(CREATE TABLE )$table_regex(.*)$/" => true, //sets up 'continuation'
|
||||
"/^(LOCK TABLES )$table_regex(.*)$/" => false,
|
||||
"/^(INSERT INTO )$table_regex(.*)$/" => false,
|
||||
"/^UNLOCK TABLES/" => false,
|
||||
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
|
||||
"/^\\)[a-zA-Z0-9_= ]*;$/" => false
|
||||
// ^^^^^^ that bit should *exit* the 'perimitted' black
|
||||
];
|
||||
|
||||
foreach($allowed_statements as $statement => $statechange) {
|
||||
// $this->info("Checking regex: $statement...\n");
|
||||
$matches = [];
|
||||
if (preg_match($statement,$line,$matches)) {
|
||||
$this->statement_is_permitted = $statechange;
|
||||
// matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement
|
||||
// (with of course 0 being "the whole match")
|
||||
if (@$matches[2]) {
|
||||
// print "Found a tablename! It's: ".$matches[2]."\n";
|
||||
if ($this->should_guess) {
|
||||
@$this->tablenames[$matches[2]] += 1;
|
||||
continue; //oh? FIXME
|
||||
} else {
|
||||
$cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/','',$matches[2]);
|
||||
$line = preg_replace($statement,'$1`'.$cleaned_tablename.'`$3' , $line);
|
||||
}
|
||||
} else {
|
||||
// no explicit tablename in this one, leave the line alone
|
||||
}
|
||||
//how do we *replace* the tablename?
|
||||
// print "RETURNING LINE: $line";
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
// all that is not allowed is denied.
|
||||
return "";
|
||||
}
|
||||
|
||||
//this is used in exactly *TWO* places, and in both cases should return a prefix I think?
|
||||
// first - if you do the --sanitize-only one (which is mostly for testing/development)
|
||||
// next - when you run *without* a guessed prefix, this is run first to figure out the prefix
|
||||
// I think we have to *duplicate* the call to be able to run it again?
|
||||
public static function guess_prefix($input):string
|
||||
{
|
||||
$parser = new self($input, null);
|
||||
$parser->should_guess = true;
|
||||
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
|
||||
|
||||
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
|
||||
//can't use 'users' because the 'accessories_users' table?
|
||||
// can't use 'assets' because 'ver1_components_assets'
|
||||
foreach($check_tables as $check_table => $_ignore) {
|
||||
foreach ($parser->tablenames as $tablename => $_count) {
|
||||
// print "Comparing $tablename to $check_table\n";
|
||||
if (str_ends_with($tablename,$check_table)) {
|
||||
// print "Found one!\n";
|
||||
$check_tables[$check_table] = substr($tablename,0,-strlen($check_table));
|
||||
}
|
||||
}
|
||||
}
|
||||
$guessed_prefix = null;
|
||||
foreach ($check_tables as $clean_table => $prefix_guess) {
|
||||
if(is_null($prefix_guess)) {
|
||||
print("Couldn't find table $clean_table\n");
|
||||
die();
|
||||
}
|
||||
if(is_null($guessed_prefix)) {
|
||||
$guessed_prefix = $prefix_guess;
|
||||
} else {
|
||||
if ($guessed_prefix != $prefix_guess) {
|
||||
print("Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n");
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $guessed_prefix;
|
||||
|
||||
}
|
||||
|
||||
public function line_aware_piping(): int
|
||||
{
|
||||
$bytes_read = 0;
|
||||
if (! $this->input) {
|
||||
throw new \Exception("No Input available for line_aware_piping");
|
||||
}
|
||||
|
||||
while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) {
|
||||
$bytes_read += strlen($buffer);
|
||||
if ($this->reading_beginning_of_line) {
|
||||
// \Log::debug("Buffer is: '$buffer'");
|
||||
$cleaned_buffer = $this->parse_sql($buffer);
|
||||
if ($this->output) {
|
||||
$bytes_written = fwrite($this->output, $cleaned_buffer);
|
||||
|
||||
if ($bytes_written === false) {
|
||||
throw new \Exception("Unable to write to pipe");
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we got a newline at the end of this, then the _next_ read is the beginning of a line
|
||||
if($buffer[strlen($buffer)-1] === "\n") {
|
||||
$this->reading_beginning_of_line = true;
|
||||
} else {
|
||||
$this->reading_beginning_of_line = false;
|
||||
}
|
||||
|
||||
}
|
||||
return $bytes_read;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class RestoreFromBackup extends Command
|
||||
{
|
||||
/**
|
||||
@@ -12,10 +157,13 @@ class RestoreFromBackup extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
// FIXME - , stripping prefixes and nonstandard SQL statements. Without --prefix, guess and return the correct prefix to strip
|
||||
protected $signature = 'snipeit:restore
|
||||
{--force : Skip the danger prompt; assuming you enter "y"}
|
||||
{filename : The zip file to be migrated}
|
||||
{--no-progress : Don\'t show a progress bar}';
|
||||
{--no-progress : Don\'t show a progress bar}
|
||||
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
|
||||
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -34,8 +182,6 @@ class RestoreFromBackup extends Command
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -55,7 +201,7 @@ class RestoreFromBackup extends Command
|
||||
return $this->error('Missing required filename');
|
||||
}
|
||||
|
||||
if (! $this->option('force') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
|
||||
if (! $this->option('force') && ! $this->option('sanitize-guess-prefix') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
|
||||
return $this->error('Data loss not confirmed');
|
||||
}
|
||||
|
||||
@@ -158,11 +304,11 @@ class RestoreFromBackup extends Command
|
||||
}
|
||||
|
||||
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
|
||||
$last_pos = strrpos($raw_path, $dir.'/');
|
||||
$last_pos = strrpos($raw_path, $dir . '/');
|
||||
if ($last_pos !== false) {
|
||||
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
|
||||
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
|
||||
$interesting_files[$raw_path] = ['dest' =>$dir, 'index' => $i];
|
||||
$interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
|
||||
continue 2;
|
||||
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
|
||||
// we don't care about that; we just want files with the appropriate prefix
|
||||
@@ -171,7 +317,7 @@ class RestoreFromBackup extends Command
|
||||
}
|
||||
}
|
||||
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
|
||||
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', ];
|
||||
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
|
||||
foreach (array_merge($private_files, $public_files) as $file) {
|
||||
$has_wildcard = (strpos($file, '*') !== false);
|
||||
if ($has_wildcard) {
|
||||
@@ -180,8 +326,8 @@ class RestoreFromBackup extends Command
|
||||
$last_pos = strrpos($raw_path, $file); // no trailing slash!
|
||||
if ($last_pos !== false) {
|
||||
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
|
||||
if (! in_array($extension, $good_extensions)) {
|
||||
$this->warn('Potentially unsafe file '.$raw_path.' is being skipped');
|
||||
if (!in_array($extension, $good_extensions)) {
|
||||
$this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
|
||||
$boring_files[] = $raw_path;
|
||||
continue 2;
|
||||
}
|
||||
@@ -196,7 +342,6 @@ class RestoreFromBackup extends Command
|
||||
}
|
||||
$boring_files[] = $raw_path; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
|
||||
} // end of pre-processing the ZIP file for-loop
|
||||
|
||||
// print_r($interesting_files);exit(-1);
|
||||
|
||||
if (count($sqlfiles) != 1) {
|
||||
@@ -208,6 +353,17 @@ class RestoreFromBackup extends Command
|
||||
//older Snipe-IT installs don't have the db-dumps subdirectory component
|
||||
}
|
||||
|
||||
$sql_stat = $za->statIndex($sqlfile_indices[0]);
|
||||
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
|
||||
$sql_contents = $za->getStream($sql_stat['name']); // maybe copy *THIS* thing?
|
||||
|
||||
// OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think?
|
||||
if ($this->option('sanitize-guess-prefix')) {
|
||||
$prefix = SQLStreamer::guess_prefix($sql_contents);
|
||||
$this->line($prefix);
|
||||
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
|
||||
}
|
||||
|
||||
//how to invoke the restore?
|
||||
$pipes = [];
|
||||
|
||||
@@ -228,6 +384,7 @@ class RestoreFromBackup extends Command
|
||||
return $this->error('Unable to invoke mysql via CLI');
|
||||
}
|
||||
|
||||
// I'm not sure about these?
|
||||
stream_set_blocking($pipes[1], false); // use non-blocking reads for stdout
|
||||
stream_set_blocking($pipes[2], false); // use non-blocking reads for stderr
|
||||
|
||||
@@ -238,9 +395,9 @@ class RestoreFromBackup extends Command
|
||||
|
||||
//$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
|
||||
|
||||
$sql_stat = $za->statIndex($sqlfile_indices[0]);
|
||||
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
|
||||
$sql_contents = $za->getStream($sql_stat['name']);
|
||||
// FIXME - this feels like it wants to go somewhere else?
|
||||
// and it doesn't seem 'right' - if you can't get a stream to the .sql file,
|
||||
// why do we care what's happening with pipes and stdout and stderr?!
|
||||
if ($sql_contents === false) {
|
||||
$stdout = fgets($pipes[1]);
|
||||
$this->info($stdout);
|
||||
@@ -249,20 +406,27 @@ class RestoreFromBackup extends Command
|
||||
|
||||
return false;
|
||||
}
|
||||
$bytes_read = 0;
|
||||
|
||||
try {
|
||||
while (($buffer = fgets($sql_contents, self::$buffer_size)) !== false) {
|
||||
$bytes_read += strlen($buffer);
|
||||
// \Log::debug("Buffer is: '$buffer'");
|
||||
if ( $this->option('sanitize-with-prefix') === null) {
|
||||
// "Legacy" direct-piping
|
||||
$bytes_read = 0;
|
||||
while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) {
|
||||
$bytes_read += strlen($buffer);
|
||||
// \Log::debug("Buffer is: '$buffer'");
|
||||
$bytes_written = fwrite($pipes[0], $buffer);
|
||||
|
||||
if ($bytes_written === false) {
|
||||
throw new Exception("Unable to write to pipe");
|
||||
if ($bytes_written === false) {
|
||||
throw new Exception("Unable to write to pipe");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sql_importer = new SQLStreamer($sql_contents, $pipes[0], $this->option('sanitize-with-prefix'));
|
||||
$bytes_read = $sql_importer->line_aware_piping();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error("Error during restore!!!! ".$e->getMessage());
|
||||
// FIXME - put these back and/or put them in the right places?!
|
||||
$err_out = fgets($pipes[1]);
|
||||
$err_err = fgets($pipes[2]);
|
||||
\Log::error("Error OUTPUT: ".$err_out);
|
||||
@@ -271,7 +435,6 @@ class RestoreFromBackup extends Command
|
||||
$this->error($err_err);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!feof($sql_contents) || $bytes_read == 0) {
|
||||
return $this->error("Not at end of file for sql file, or zero bytes read. aborting!");
|
||||
}
|
||||
@@ -303,7 +466,7 @@ class RestoreFromBackup extends Command
|
||||
$fp = $za->getStream($ugly_file_name);
|
||||
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
|
||||
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
|
||||
while (($buffer = fgets($fp, self::$buffer_size)) !== false) {
|
||||
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
|
||||
fwrite($migrated_file, $buffer);
|
||||
}
|
||||
fclose($migrated_file);
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Notifications\ExpectedCheckinAdminNotification;
|
||||
use App\Notifications\ExpectedCheckinNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SendExpectedCheckinAlerts extends Command
|
||||
{
|
||||
@@ -43,25 +42,31 @@ class SendExpectedCheckinAlerts extends Command
|
||||
public function handle()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$whenNotify = Carbon::now();
|
||||
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval);
|
||||
|
||||
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
|
||||
|
||||
$this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
|
||||
|
||||
$this->info($whenNotify.' is deadline');
|
||||
$this->info($assets->count().' assets');
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->assigned && $asset->checkedOutToUser()) {
|
||||
Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email);
|
||||
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
|
||||
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
|
||||
$this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
|
||||
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
|
||||
}
|
||||
}
|
||||
|
||||
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
$this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
|
||||
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\Recipients;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\ExpiringAssetsNotification;
|
||||
use App\Notifications\SendUpcomingAuditNotification;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
@@ -45,40 +43,26 @@ class SendUpcomingAuditReport extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval);
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
|
||||
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
|
||||
|
||||
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) {
|
||||
|
||||
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
|
||||
// Send a rollup to the admin, if settings dictate
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new \App\Models\Recipients\AlertRecipient($item);
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
// Assets due for auditing
|
||||
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
|
||||
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
|
||||
|
||||
$assets = Asset::whereNotNull('next_audit_date')
|
||||
->DueOrOverdueForAudit($settings)
|
||||
->orderBy('last_audit_date', 'asc')->get();
|
||||
|
||||
if ($assets->count() > 0) {
|
||||
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
|
||||
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
|
||||
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
|
||||
$this->info('Audit report sent to '.$settings->alert_email);
|
||||
} else {
|
||||
$this->info('No assets to be audited. No report sent.');
|
||||
}
|
||||
} elseif ($settings->alert_email == '') {
|
||||
$this->error('Could not send email. No alert email configured in settings');
|
||||
} elseif (! $settings->audit_warning_days) {
|
||||
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
|
||||
} elseif ($settings->alerts_enabled != 1) {
|
||||
$this->info('Alerts are disabled in the settings. No mail will be sent');
|
||||
} else {
|
||||
$this->error('Something went wrong. :( ');
|
||||
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
|
||||
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
|
||||
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ToggleCustomfieldEncryption extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:customfield-encryption
|
||||
{fieldname : the db_column_name of the field}';
|
||||
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command should be used to convert an unencrypted custom field into a custom field and encrypt the associated data in the assets table for that column.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$fieldname = $this->argument('fieldname');
|
||||
|
||||
if ($field = CustomField::where('db_column', $fieldname)->first()) {
|
||||
|
||||
// If the field is not encrypted, make it encrypted and encrypt the data in the assets table for the
|
||||
// corresponding field.
|
||||
DB::transaction(function () use ($field) {
|
||||
|
||||
if ($field->field_encrypted == 0) {
|
||||
$assets = Asset::whereNotNull($field->db_column)->get();
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$asset->{$field->db_column} = encrypt($asset->{$field->db_column});
|
||||
$asset->save();
|
||||
}
|
||||
|
||||
$field->field_encrypted = 1;
|
||||
$field->save();
|
||||
|
||||
// This field is already encrypted. Do nothing.
|
||||
} else {
|
||||
$this->error('The custom field ' . $field->db_column.' is already encrypted. No action was taken.');
|
||||
}
|
||||
});
|
||||
|
||||
// No matching column name found
|
||||
} else {
|
||||
$this->error('No matching results for unencrypted custom fields with db_column name: ' . $fieldname.'. Please check the fieldname.');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+32
-6
@@ -11,6 +11,7 @@ use App\Models\CustomFieldset;
|
||||
use App\Models\Depreciation;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\License;
|
||||
use Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Image;
|
||||
@@ -715,18 +716,19 @@ class Helper
|
||||
*/
|
||||
public static function checkLowInventory()
|
||||
{
|
||||
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
|
||||
$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::whereNotNull('min_amt')->get();
|
||||
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
||||
$licenses = License::where('min_amt', '>', 0)->get();
|
||||
|
||||
$avail_consumables = 0;
|
||||
$items_array = [];
|
||||
$all_count = 0;
|
||||
|
||||
foreach ($consumables as $consumable) {
|
||||
$avail = $consumable->numRemaining();
|
||||
if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($avail < ($consumable->min_amt) + $alert_threshold) {
|
||||
if ($consumable->qty > 0) {
|
||||
$percent = number_format((($avail / $consumable->qty) * 100), 0);
|
||||
} else {
|
||||
@@ -745,7 +747,7 @@ class Helper
|
||||
|
||||
foreach ($accessories as $accessory) {
|
||||
$avail = $accessory->qty - $accessory->users_count;
|
||||
if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($avail < ($accessory->min_amt) + $alert_threshold) {
|
||||
if ($accessory->qty > 0) {
|
||||
$percent = number_format((($avail / $accessory->qty) * 100), 0);
|
||||
} else {
|
||||
@@ -764,7 +766,7 @@ class Helper
|
||||
|
||||
foreach ($components as $component) {
|
||||
$avail = $component->numRemaining();
|
||||
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($avail < ($component->min_amt) + $alert_threshold) {
|
||||
if ($component->qty > 0) {
|
||||
$percent = number_format((($avail / $component->qty) * 100), 0);
|
||||
} else {
|
||||
@@ -787,7 +789,7 @@ class Helper
|
||||
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
|
||||
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
|
||||
|
||||
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($avail < ($asset_model->min_amt) + $alert_threshold) {
|
||||
if ($avail > 0) {
|
||||
$percent = number_format((($avail / $total_owned) * 100), 0);
|
||||
} else {
|
||||
@@ -803,6 +805,26 @@ class Helper
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($licenses as $license){
|
||||
$avail = $license->remaincount();
|
||||
if ($avail < ($license->min_amt) + $alert_threshold) {
|
||||
if ($avail > 0) {
|
||||
$percent = number_format((($avail / $license->min_amt) * 100), 0);
|
||||
} else {
|
||||
$percent = 100;
|
||||
}
|
||||
|
||||
$items_array[$all_count]['id'] = $license->id;
|
||||
$items_array[$all_count]['name'] = $license->name;
|
||||
$items_array[$all_count]['type'] = 'licenses';
|
||||
$items_array[$all_count]['percent'] = $percent;
|
||||
$items_array[$all_count]['remaining'] = $avail;
|
||||
$items_array[$all_count]['min_amt'] = $license->min_amt;
|
||||
$all_count++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $items_array;
|
||||
}
|
||||
|
||||
@@ -820,7 +842,7 @@ class Helper
|
||||
$filetype = @finfo_file($finfo, $file);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif')) {
|
||||
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
|
||||
return $filetype;
|
||||
}
|
||||
|
||||
@@ -1084,6 +1106,8 @@ class Helper
|
||||
'jpeg' => 'far fa-image',
|
||||
'gif' => 'far fa-image',
|
||||
'png' => 'far fa-image',
|
||||
'webp' => 'far fa-image',
|
||||
'avif' => 'far fa-image',
|
||||
// word
|
||||
'doc' => 'far fa-file-word',
|
||||
'docx' => 'far fa-file-word',
|
||||
@@ -1119,6 +1143,8 @@ class Helper
|
||||
case 'jpeg':
|
||||
case 'gif':
|
||||
case 'png':
|
||||
case 'webp':
|
||||
case 'avif':
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Accessories;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
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 UploadFileRequest $request
|
||||
* @param int $accessoryId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $accessoryId = null)
|
||||
public function store(UploadFileRequest $request, $accessoryId = null)
|
||||
{
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
@@ -45,30 +44,7 @@ class AccessoriesFilesController extends Controller
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
$file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
|
||||
//Log the upload to the log
|
||||
$accessory->logUpload($file_name, e($request->input('notes')));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\AccessoriesTransformer;
|
||||
@@ -278,7 +279,7 @@ class AccessoriesController extends Controller
|
||||
public function checkout(Request $request, $accessoryId)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
|
||||
}
|
||||
|
||||
@@ -302,7 +303,7 @@ class AccessoriesController extends Controller
|
||||
'note' => $request->get('note'),
|
||||
]);
|
||||
|
||||
$accessory->logCheckout($request->input('note'), $user);
|
||||
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ class AssetMaintenancesController extends Controller
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin');
|
||||
$maintenances = AssetMaintenance::select('asset_maintenances.*')
|
||||
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$maintenances = $maintenances->TextSearch($request->input('search'));
|
||||
@@ -47,7 +48,7 @@ class AssetMaintenancesController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('supplier_id')) {
|
||||
$maintenances->where('supplier_id', '=', $request->input('supplier_id'));
|
||||
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_maintenance_type')) {
|
||||
@@ -70,10 +71,13 @@ class AssetMaintenancesController extends Controller
|
||||
'notes',
|
||||
'asset_tag',
|
||||
'asset_name',
|
||||
'serial',
|
||||
'user_id',
|
||||
'supplier',
|
||||
'is_warranty',
|
||||
'status_label',
|
||||
];
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
|
||||
@@ -90,6 +94,12 @@ class AssetMaintenancesController extends Controller
|
||||
case 'asset_name':
|
||||
$maintenances = $maintenances->OrderByAssetName($order);
|
||||
break;
|
||||
case 'serial':
|
||||
$maintenances = $maintenances->OrderByAssetSerial($order);
|
||||
break;
|
||||
case 'status_label':
|
||||
$maintenances = $maintenances->OrderStatusName($order);
|
||||
break;
|
||||
default:
|
||||
$maintenances = $maintenances->orderBy($sort, $order);
|
||||
break;
|
||||
|
||||
@@ -4,6 +4,10 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Http\Requests\StoreAssetRequest;
|
||||
use App\Http\Traits\MigratesLegacyAssetLocations;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
@@ -45,6 +49,8 @@ use Route;
|
||||
*/
|
||||
class AssetsController extends Controller
|
||||
{
|
||||
use MigratesLegacyAssetLocations;
|
||||
|
||||
/**
|
||||
* Returns JSON listing of all assets
|
||||
*
|
||||
@@ -53,7 +59,7 @@ class AssetsController extends Controller
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function index(Request $request, $audit = null)
|
||||
public function index(Request $request, $action = null, $upcoming_status = null)
|
||||
{
|
||||
|
||||
$filter_non_deprecable_assets = false;
|
||||
@@ -88,6 +94,7 @@ class AssetsController extends Controller
|
||||
'serial',
|
||||
'model_number',
|
||||
'last_checkout',
|
||||
'last_checkin',
|
||||
'notes',
|
||||
'expected_checkin',
|
||||
'order_number',
|
||||
@@ -105,6 +112,7 @@ class AssetsController extends Controller
|
||||
'requests_counter',
|
||||
'byod',
|
||||
'asset_eol_date',
|
||||
'requestable',
|
||||
];
|
||||
|
||||
$filter = [];
|
||||
@@ -147,17 +155,44 @@ class AssetsController extends Controller
|
||||
$assets->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
// This is used by the audit reporting routes
|
||||
if (Gate::allows('audit', Asset::class)) {
|
||||
switch ($audit) {
|
||||
case 'due':
|
||||
$assets->DueOrOverdueForAudit($settings);
|
||||
break;
|
||||
case 'overdue':
|
||||
$assets->overdueForAudit($settings);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Handle due and overdue audits and checkin dates
|
||||
*/
|
||||
switch ($action) {
|
||||
case 'audits':
|
||||
|
||||
switch ($upcoming_status) {
|
||||
case 'due':
|
||||
$assets->DueForAudit($settings);
|
||||
break;
|
||||
case 'overdue':
|
||||
$assets->OverdueForAudit();
|
||||
break;
|
||||
case 'due-or-overdue':
|
||||
$assets->DueOrOverdueForAudit($settings);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'checkins':
|
||||
switch ($upcoming_status) {
|
||||
case 'due':
|
||||
$assets->DueForCheckin($settings);
|
||||
break;
|
||||
case 'overdue':
|
||||
$assets->OverdueForCheckin();
|
||||
break;
|
||||
case 'due-or-overdue':
|
||||
$assets->DueOrOverdueForCheckin($settings);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End handling due and overdue audits and checkin dates
|
||||
*/
|
||||
|
||||
|
||||
// This is used by the sidenav, mostly
|
||||
@@ -584,6 +619,11 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($field->element == 'checkbox') {
|
||||
if(is_array($field_val)) {
|
||||
$field_val = implode(',', $field_val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$asset->{$field->db_column} = $field_val;
|
||||
@@ -607,6 +647,8 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
@@ -650,16 +692,26 @@ class AssetsController extends Controller
|
||||
$model = AssetModel::find($asset->model_id);
|
||||
|
||||
// Update custom fields
|
||||
$problems_updating_encrypted_custom_fields = false;
|
||||
if (($model) && (isset($model->fieldset))) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
$field_val = $request->input($field->db_column, null);
|
||||
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->element == 'checkbox') {
|
||||
if(is_array($field_val)) {
|
||||
$field_val = implode(',', $field_val);
|
||||
}
|
||||
}
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
|
||||
$field_val = Crypt::encrypt($field_val);
|
||||
} else {
|
||||
$problems_updating_encrypted_custom_fields = true;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
$asset->{$field->db_column} = $field_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +737,11 @@ class AssetsController extends Controller
|
||||
$asset->image = $asset->getImageUrl();
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
if ($problems_updating_encrypted_custom_fields) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
@@ -864,11 +920,9 @@ class AssetsController extends Controller
|
||||
*/
|
||||
public function checkin(Request $request, $asset_id)
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::with('model')->findOrFail($asset_id);
|
||||
$this->authorize('checkin', $asset);
|
||||
|
||||
|
||||
$target = $asset->assignedTo;
|
||||
if (is_null($target)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
@@ -879,9 +933,8 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
//$asset->last_checkout = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
|
||||
@@ -889,10 +942,16 @@ class AssetsController extends Controller
|
||||
$asset->name = $request->input('name');
|
||||
}
|
||||
|
||||
$this->migrateLegacyLocations($asset);
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
|
||||
if ($request->input('update_default_location')){
|
||||
$asset->rtd_location_id = $request->input('location_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('status_id')) {
|
||||
@@ -906,6 +965,23 @@ class AssetsController extends Controller
|
||||
$originalValues['action_date'] = $checkin_at;
|
||||
}
|
||||
|
||||
$asset->licenseseats->each(function (LicenseSeat $seat) {
|
||||
$seat->update(['assigned_to' => null]);
|
||||
});
|
||||
|
||||
// Get all pending Acceptances for this asset and delete them
|
||||
CheckoutAcceptance::pending()
|
||||
->whereHasMorph(
|
||||
'checkoutable',
|
||||
[Asset::class],
|
||||
function (Builder $query) use ($asset) {
|
||||
$query->where('id', $asset->id);
|
||||
})
|
||||
->get()
|
||||
->map(function ($acceptance) {
|
||||
$acceptance->delete();
|
||||
});
|
||||
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
|
||||
|
||||
@@ -1035,8 +1111,7 @@ class AssetsController extends Controller
|
||||
|
||||
$assets = Asset::select('assets.*')
|
||||
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
|
||||
->requestableAssets();
|
||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests');
|
||||
|
||||
|
||||
|
||||
@@ -1044,7 +1119,7 @@ class AssetsController extends Controller
|
||||
if ($request->filled('search')) {
|
||||
$assets->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
|
||||
// Search custom fields by column name
|
||||
foreach ($all_custom_fields as $field) {
|
||||
if ($request->filled($field->db_column_name())) {
|
||||
@@ -1074,6 +1149,7 @@ class AssetsController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
$assets->requestableAssets();
|
||||
|
||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
|
||||
|
||||
@@ -40,7 +40,9 @@ class CompaniesController extends Controller
|
||||
'components_count',
|
||||
];
|
||||
|
||||
$companies = Company::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
|
||||
$companies = Company::withCount(['assets as assets_count' => function ($query) {
|
||||
$query->AssetsForShow();
|
||||
}])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$companies->TextSearch($request->input('search'));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ConsumablesTransformer;
|
||||
@@ -11,6 +12,7 @@ use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ConsumablesController extends Controller
|
||||
{
|
||||
@@ -290,17 +292,9 @@ class ConsumablesController extends Controller
|
||||
]
|
||||
);
|
||||
|
||||
// Log checkout event
|
||||
$logaction = $consumable->logCheckout($request->input('note'), $user);
|
||||
$data['log_id'] = $logaction->id;
|
||||
$data['eula'] = $consumable->getEula();
|
||||
$data['first_name'] = $user->first_name;
|
||||
$data['item_name'] = $consumable->name;
|
||||
$data['checkout_date'] = $logaction->created_at;
|
||||
$data['note'] = $logaction->note;
|
||||
$data['require_acceptance'] = $consumable->requireAcceptance();
|
||||
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\GroupsTransformer;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
|
||||
|
||||
class GroupsController extends Controller
|
||||
@@ -25,7 +26,7 @@ class GroupsController extends Controller
|
||||
$this->authorize('view', Group::class);
|
||||
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
|
||||
|
||||
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at')->withCount('users as users_count');
|
||||
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$groups = $groups->TextSearch($request->input('search'));
|
||||
@@ -63,6 +64,7 @@ class GroupsController extends Controller
|
||||
$group = new Group;
|
||||
|
||||
$group->name = $request->input('name');
|
||||
$group->created_by = Auth::user()->id;
|
||||
$group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here
|
||||
|
||||
if ($group->save()) {
|
||||
|
||||
@@ -136,6 +136,7 @@ class LicensesController extends Controller
|
||||
'seats',
|
||||
'termination_date',
|
||||
'depreciation_id',
|
||||
'min_amt',
|
||||
];
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$licenses = $licenses->orderBy($sort, $order);
|
||||
|
||||
@@ -25,9 +25,27 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$allowed_columns = [
|
||||
'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
|
||||
'updated_at', 'manager_id', 'image',
|
||||
'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
|
||||
'id',
|
||||
'name',
|
||||
'address',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'country',
|
||||
'zip',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'manager_id',
|
||||
'image',
|
||||
'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',
|
||||
@@ -50,6 +68,7 @@ class LocationsController extends Controller
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
@@ -80,6 +99,10 @@ class LocationsController extends Controller
|
||||
$locations->where('locations.country', '=', $request->input('country'));
|
||||
}
|
||||
|
||||
if ($request->filled('manager_id')) {
|
||||
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
|
||||
}
|
||||
|
||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
@@ -212,7 +235,13 @@ class LocationsController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Location::class);
|
||||
$location = Location::findOrFail($id);
|
||||
$location = Location::withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')
|
||||
->findOrFail($id);
|
||||
|
||||
if (! $location->isDeletable()) {
|
||||
return response()
|
||||
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
|
||||
|
||||
@@ -32,14 +32,26 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
|
||||
$actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id'))
|
||||
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
|
||||
$actionlogs = $actionlogs->where(function($query) use ($request)
|
||||
{
|
||||
$query->where('item_id', '=', $request->input('item_id'))
|
||||
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')))
|
||||
->orWhere(function($query) use ($request)
|
||||
{
|
||||
$query->where('target_id', '=', $request->input('item_id'))
|
||||
->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->filled('action_type')) {
|
||||
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
if ($request->filled('user_id')) {
|
||||
$actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('action_source')) {
|
||||
$actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ class SettingsController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return Redirect
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function ajaxTestEmail()
|
||||
{
|
||||
@@ -170,7 +170,7 @@ class SettingsController extends Controller
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0.0]
|
||||
* @return Response
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function purgeBarcodes()
|
||||
{
|
||||
@@ -211,7 +211,7 @@ class SettingsController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0.0]
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
* @return array | JsonResponse
|
||||
*/
|
||||
public function showLoginAttempts(Request $request)
|
||||
{
|
||||
@@ -229,6 +229,12 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lists backup files
|
||||
*
|
||||
* @author [A. Gianotto]
|
||||
* @return array | JsonResponse
|
||||
*/
|
||||
public function listBackups() {
|
||||
$settings = Setting::getSettings();
|
||||
$path = 'app/backups';
|
||||
@@ -249,12 +255,12 @@ class SettingsController extends Controller
|
||||
'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),
|
||||
'backup_url' => config('app.url').'/settings/backups/download/'.basename($backup_files[$f]),
|
||||
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,15 +270,56 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Downloads a backup file.
|
||||
* We use response()->download() here instead of Storage::download() because Storage::download()
|
||||
* exhausts memory on larger files.
|
||||
*
|
||||
* @author [A. Gianotto]
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function downloadBackup($file) {
|
||||
|
||||
$path = 'app/backups';
|
||||
if (Storage::exists($path.'/'.$file)) {
|
||||
$path = storage_path('app/backups');
|
||||
|
||||
if (Storage::exists('app/backups/'.$file)) {
|
||||
$headers = ['ContentType' => 'application/zip'];
|
||||
return Storage::download($path.'/'.$file, $file, $headers);
|
||||
return response()->download($path.'/'.$file, $file, $headers);
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines and downloads the latest backup
|
||||
*
|
||||
* @author [A. Gianotto]
|
||||
* @since [v6.3.1]
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function downloadLatestBackup() {
|
||||
|
||||
$fileData = collect();
|
||||
foreach (Storage::files('app/backups') as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) == 'zip') {
|
||||
$fileData->push([
|
||||
'file' => $file,
|
||||
'date' => Storage::lastModified($file)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$newest = $fileData->sortByDesc('date')->first();
|
||||
if (Storage::exists($newest['file'])) {
|
||||
$headers = ['ContentType' => 'application/zip'];
|
||||
return response()->download(storage_path($newest['file']), basename($newest['file']), $headers);
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -73,6 +73,7 @@ class UsersController extends Controller
|
||||
'users.end_date',
|
||||
'users.vip',
|
||||
'users.autoassign_licenses',
|
||||
'users.website',
|
||||
|
||||
])->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');
|
||||
@@ -122,6 +123,10 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.country', '=', $request->input('country'));
|
||||
}
|
||||
|
||||
if ($request->filled('website')) {
|
||||
$users = $users->where('users.website', '=', $request->input('website'));
|
||||
}
|
||||
|
||||
if ($request->filled('zip')) {
|
||||
$users = $users->where('users.zip', '=', $request->input('zip'));
|
||||
}
|
||||
@@ -254,6 +259,7 @@ class UsersController extends Controller
|
||||
'start_date',
|
||||
'end_date',
|
||||
'autoassign_licenses',
|
||||
'website',
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -267,6 +273,7 @@ class UsersController extends Controller
|
||||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
// Apply companyable scope
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
|
||||
@@ -274,11 +281,6 @@ class UsersController extends Controller
|
||||
$offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
||||
\Log::debug('Requested offset: '. $request->input('offset'));
|
||||
\Log::debug('App offset: '. app('api_offset_value'));
|
||||
\Log::debug('Actual offset: '. $offset);
|
||||
\Log::debug('Limit: '. $limit);
|
||||
|
||||
$total = $users->count();
|
||||
$users = $users->skip($offset)->take($limit)->get();
|
||||
|
||||
@@ -402,7 +404,10 @@ class UsersController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
return (new UsersTransformer)->transformUser($user);
|
||||
}
|
||||
@@ -422,6 +427,8 @@ class UsersController extends Controller
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
$user = User::findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
/**
|
||||
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
@@ -458,6 +465,7 @@ class UsersController extends Controller
|
||||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
}
|
||||
|
||||
$user->permissions = $permissions_array;
|
||||
}
|
||||
|
||||
@@ -480,6 +488,7 @@ class UsersController extends Controller
|
||||
|
||||
// Check if the request has groups passed and has a value
|
||||
if ($request->filled('groups')) {
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'groups.*' => 'integer|exists:permission_groups,id',
|
||||
]);
|
||||
@@ -487,10 +496,19 @@ class UsersController extends Controller
|
||||
if ($validator->fails()){
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
|
||||
}
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// The groups field has been passed but it is null, so we should blank it out
|
||||
} elseif ($request->has('groups')) {
|
||||
$user->groups()->sync([]);
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -511,37 +529,43 @@ class UsersController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if (($user->assets) && ($user->assets->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
|
||||
}
|
||||
if ($user) {
|
||||
|
||||
if (($user->licenses) && ($user->licenses->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->licenses->count().' license(s) associated with them and cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (($user->accessories) && ($user->accessories->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->accessories->count().' accessories associated with them.'));
|
||||
}
|
||||
|
||||
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->managedLocations()->count().' locations that they manage.'));
|
||||
}
|
||||
|
||||
if ($user->delete()) {
|
||||
|
||||
// Remove the user's avatar if they have one
|
||||
if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('avatars/'.$user->avatar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if (($user->assets) && ($user->assets->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
|
||||
if (($user->licenses) && ($user->licenses->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (($user->accessories) && ($user->accessories->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.'));
|
||||
}
|
||||
|
||||
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.'));
|
||||
}
|
||||
|
||||
if ($user->delete()) {
|
||||
|
||||
// Remove the user's avatar if they have one
|
||||
if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('avatars/' . $user->avatar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
|
||||
@@ -559,7 +583,31 @@ class UsersController extends Controller
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', Asset::class);
|
||||
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
|
||||
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('view', $user);
|
||||
|
||||
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
|
||||
|
||||
|
||||
// Filter on category ID
|
||||
if ($request->filled('category_id')) {
|
||||
$assets = $assets->InCategory($request->input('category_id'));
|
||||
}
|
||||
|
||||
|
||||
// Filter on model ID
|
||||
if ($request->filled('model_id')) {
|
||||
|
||||
$model_ids = $request->input('model_id');
|
||||
if (!is_array($model_ids)) {
|
||||
$model_ids = array($model_ids);
|
||||
}
|
||||
$assets = $assets->InModelList($model_ids);
|
||||
}
|
||||
|
||||
$assets = $assets->get();
|
||||
|
||||
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
|
||||
}
|
||||
@@ -575,7 +623,10 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function emailAssetList(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
if (empty($user->email)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
|
||||
@@ -599,6 +650,7 @@ class UsersController extends Controller
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', Consumable::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('update', $user);
|
||||
$consumables = $user->consumables;
|
||||
return (new ConsumablesTransformer)->transformConsumables($consumables, $consumables->count(), $request);
|
||||
}
|
||||
@@ -615,6 +667,7 @@ class UsersController extends Controller
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('view', $user);
|
||||
$this->authorize('view', Accessory::class);
|
||||
$accessories = $user->accessories;
|
||||
|
||||
@@ -635,6 +688,7 @@ class UsersController extends Controller
|
||||
$this->authorize('view', License::class);
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$this->authorize('update', $user);
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
@@ -658,9 +712,20 @@ class UsersController extends Controller
|
||||
if ($request->filled('id')) {
|
||||
try {
|
||||
$user = User::find($request->get('id'));
|
||||
$this->authorize('update', $user);
|
||||
$user->two_factor_secret = null;
|
||||
$user->two_factor_enrolled = 0;
|
||||
$user->save();
|
||||
$user->saveQuietly();
|
||||
|
||||
// Log the reset
|
||||
$logaction = new Actionlog();
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->target_id = $user->id;
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->item_id = $user->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('2FA reset');
|
||||
|
||||
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -148,30 +148,20 @@ class AssetMaintenancesController extends Controller
|
||||
*/
|
||||
public function edit($assetMaintenanceId = null)
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
$this->authorize('update', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the improvement management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} elseif (! $assetMaintenance->asset) {
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', 'The asset associated with this maintenance does not exist.');
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
if ($assetMaintenance->completion_date == '0000-00-00') {
|
||||
$assetMaintenance->completion_date = null;
|
||||
}
|
||||
|
||||
if ($assetMaintenance->start_date == '0000-00-00') {
|
||||
$assetMaintenance->start_date = null;
|
||||
}
|
||||
|
||||
if ($assetMaintenance->cost == '0.00') {
|
||||
$assetMaintenance->cost = null;
|
||||
}
|
||||
|
||||
// Prepare Improvement Type List
|
||||
$assetMaintenanceType = [
|
||||
@@ -203,8 +193,10 @@ class AssetMaintenancesController extends Controller
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')
|
||||
->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
|
||||
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
|
||||
// Redirect to the asset maintenance management page
|
||||
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
|
||||
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -442,7 +443,6 @@ class AssetModelsController extends Controller
|
||||
$del_count = 0;
|
||||
|
||||
foreach ($models as $model) {
|
||||
\Log::debug($model->id);
|
||||
|
||||
if ($model->assets_count > 0) {
|
||||
$del_error_count++;
|
||||
@@ -452,8 +452,6 @@ class AssetModelsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
\Log::debug($del_count);
|
||||
\Log::debug($del_error_count);
|
||||
|
||||
if ($del_error_count == 0) {
|
||||
return redirect()->route('models.index')
|
||||
@@ -489,11 +487,11 @@ class AssetModelsController extends Controller
|
||||
* @param array $defaultValues
|
||||
* @return void
|
||||
*/
|
||||
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
|
||||
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool
|
||||
{
|
||||
$data = array();
|
||||
foreach ($defaultValues as $customFieldId => $defaultValue) {
|
||||
$customField = \App\Models\CustomField::find($customFieldId);
|
||||
$customField = CustomField::find($customFieldId);
|
||||
|
||||
$data[$customField->db_column] = $defaultValue;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,25 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
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 UploadFileRequest $request
|
||||
* @param int $modelId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@since [v1.0]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $modelId = null)
|
||||
public function store(UploadFileRequest $request, $modelId = null)
|
||||
{
|
||||
if (! $model = AssetModel::find($modelId)) {
|
||||
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
@@ -37,29 +36,9 @@ class AssetModelsFilesController extends Controller
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'model-'.$model->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
|
||||
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
|
||||
|
||||
// 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')));
|
||||
$model->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('general.file_upload_success'));
|
||||
|
||||
@@ -6,8 +6,10 @@ use App\Events\CheckoutableCheckedIn;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetCheckinRequest;
|
||||
use App\Http\Traits\MigratesLegacyAssetLocations;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\LicenseSeat;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
@@ -15,6 +17,8 @@ use Illuminate\Support\Facades\View;
|
||||
|
||||
class AssetCheckinController extends Controller
|
||||
{
|
||||
use MigratesLegacyAssetLocations;
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to check an asset back into inventory.
|
||||
*
|
||||
@@ -35,6 +39,12 @@ class AssetCheckinController extends Controller
|
||||
|
||||
$this->authorize('checkin', $asset);
|
||||
|
||||
// This asset is already checked in, redirect
|
||||
|
||||
if (is_null($asset->assignedTo)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
|
||||
}
|
||||
|
||||
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto);
|
||||
}
|
||||
|
||||
@@ -67,11 +77,9 @@ class AssetCheckinController extends Controller
|
||||
}
|
||||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
//$asset->last_checkout = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->assigned_type = null;
|
||||
$asset->accepted = null;
|
||||
$asset->name = $request->get('name');
|
||||
|
||||
@@ -79,24 +87,7 @@ class AssetCheckinController extends Controller
|
||||
$asset->status_id = e($request->get('status_id'));
|
||||
}
|
||||
|
||||
// This is just meant to correct legacy issues where some user data would have 0
|
||||
// as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
|
||||
// rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
|
||||
// people (and their data) in the long run
|
||||
|
||||
if ($asset->rtd_location_id == '0') {
|
||||
\Log::debug('Manually override the RTD location IDs');
|
||||
\Log::debug('Original RTD Location ID: '.$asset->rtd_location_id);
|
||||
$asset->rtd_location_id = '';
|
||||
\Log::debug('New RTD Location ID: '.$asset->rtd_location_id);
|
||||
}
|
||||
|
||||
if ($asset->location_id == '0') {
|
||||
\Log::debug('Manually override the location IDs');
|
||||
\Log::debug('Original Location ID: '.$asset->location_id);
|
||||
$asset->location_id = '';
|
||||
\Log::debug('New Location ID: '.$asset->location_id);
|
||||
}
|
||||
$this->migrateLegacyLocations($asset);
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
@@ -117,12 +108,9 @@ class AssetCheckinController extends Controller
|
||||
$checkin_at = $request->get('checkin_at');
|
||||
}
|
||||
|
||||
if(!empty($asset->licenseseats->all())){
|
||||
foreach ($asset->licenseseats as $seat){
|
||||
$seat->assigned_to = null;
|
||||
$seat->save();
|
||||
}
|
||||
}
|
||||
$asset->licenseseats->each(function (LicenseSeat $seat) {
|
||||
$seat->update(['assigned_to' => null]);
|
||||
});
|
||||
|
||||
// Get all pending Acceptances for this asset and delete them
|
||||
$acceptances = CheckoutAcceptance::pending()->whereHasMorph('checkoutable',
|
||||
|
||||
@@ -4,26 +4,25 @@ namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class AssetFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Upload a file to the server.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param AssetFileRequest $request
|
||||
* @param UploadFileRequest $request
|
||||
* @param int $assetId
|
||||
* @return Redirect
|
||||
* @since [v1.0]
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@since [v1.0]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $assetId = null)
|
||||
public function store(UploadFileRequest $request, $assetId = null)
|
||||
{
|
||||
if (! $asset = Asset::find($assetId)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
@@ -37,30 +36,9 @@ class AssetFilesController extends Controller
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'hardware-'.$asset->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/assets/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
} else {
|
||||
Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
|
||||
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
|
||||
|
||||
$asset->logUpload($file_name, e($request->get('notes')));
|
||||
$asset->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
|
||||
|
||||
@@ -102,6 +102,10 @@ class AssetsController extends Controller
|
||||
{
|
||||
$this->authorize(Asset::class);
|
||||
|
||||
// There are a lot more rules to add here but prevents
|
||||
// errors around `asset_tags` not being present below.
|
||||
$this->validate($request, ['asset_tags' => ['required', 'array']]);
|
||||
|
||||
// Handle asset tags - there could be one, or potentially many.
|
||||
// This is only necessary on create, not update, since bulk editing is handled
|
||||
// differently
|
||||
@@ -146,7 +150,8 @@ class AssetsController extends Controller
|
||||
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
}
|
||||
|
||||
if ($asset->assigned_to == '') {
|
||||
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
|
||||
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
}
|
||||
|
||||
@@ -521,31 +526,33 @@ class AssetsController extends Controller
|
||||
public function getBarCode($assetId = null)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$asset = Asset::find($assetId);
|
||||
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
|
||||
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
|
||||
|
||||
if (isset($asset->id, $asset->asset_tag)) {
|
||||
if (file_exists($barcode_file)) {
|
||||
$header = ['Content-type' => 'image/png'];
|
||||
if (isset($asset->id, $asset->asset_tag)) {
|
||||
if (file_exists($barcode_file)) {
|
||||
$header = ['Content-type' => 'image/png'];
|
||||
|
||||
return response()->file($barcode_file, $header);
|
||||
} else {
|
||||
// Calculate barcode width in pixel based on label width (inch)
|
||||
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
|
||||
return response()->file($barcode_file, $header);
|
||||
} else {
|
||||
// Calculate barcode width in pixel based on label width (inch)
|
||||
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
|
||||
|
||||
$barcode = new \Com\Tecnick\Barcode\Barcode();
|
||||
try {
|
||||
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
|
||||
file_put_contents($barcode_file, $barcode_obj->getPngData());
|
||||
$barcode = new \Com\Tecnick\Barcode\Barcode();
|
||||
try {
|
||||
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
|
||||
file_put_contents($barcode_file, $barcode_obj->getPngData());
|
||||
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('The barcode format is invalid.');
|
||||
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('The barcode format is invalid.');
|
||||
|
||||
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
|
||||
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -734,11 +741,11 @@ class AssetsController extends Controller
|
||||
|
||||
if ($isCheckinHeaderExplicit) {
|
||||
|
||||
//if checkin date header exists, assume that empty or future date is still checked out
|
||||
//if checkin is before todays date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items is checked out
|
||||
// if checkin date header exists, assume that empty or future date is still checked out
|
||||
// if checkin is before today's date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items are checked out
|
||||
|
||||
if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date))
|
||||
) {
|
||||
if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date)))
|
||||
{
|
||||
//only do this if item is checked out
|
||||
$asset->assigned_to = $user->id;
|
||||
$asset->assigned_type = User::class;
|
||||
@@ -847,11 +854,11 @@ class AssetsController extends Controller
|
||||
return view('hardware/audit-due');
|
||||
}
|
||||
|
||||
public function overdueForAudit()
|
||||
public function dueForCheckin()
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
$this->authorize('checkin', Asset::class);
|
||||
|
||||
return view('hardware/audit-overdue');
|
||||
return view('hardware/checkin-due');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\View\Label;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
use App\Models\CustomField;
|
||||
@@ -93,6 +94,59 @@ class BulkAssetsController extends Controller
|
||||
|
||||
$assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
|
||||
|
||||
$assets = $assets->get();
|
||||
|
||||
if ($assets->isEmpty()) {
|
||||
Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
|
||||
}
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
foreach($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
|
||||
if ($request->filled('bulk_actions')) {
|
||||
|
||||
|
||||
switch ($request->input('bulk_actions')) {
|
||||
case 'labels':
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
return (new Label)
|
||||
->with('assets', $assets)
|
||||
->with('settings', Setting::getSettings())
|
||||
->with('bulkedit', true)
|
||||
->with('count', 0);
|
||||
|
||||
case 'delete':
|
||||
$this->authorize('delete', Asset::class);
|
||||
$assets->each(function ($assets) {
|
||||
$this->authorize('delete', $assets);
|
||||
});
|
||||
|
||||
return view('hardware/bulk-delete')->with('assets', $assets);
|
||||
|
||||
case 'restore':
|
||||
$this->authorize('update', Asset::class);
|
||||
$assets = Asset::withTrashed()->find($asset_ids);
|
||||
$assets->each(function ($asset) {
|
||||
$this->authorize('delete', $asset);
|
||||
});
|
||||
return view('hardware/bulk-restore')->with('assets', $assets);
|
||||
|
||||
case 'edit':
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('models', $models->pluck(['model']))
|
||||
->with('modelNames', $modelNames);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sort_override) {
|
||||
case 'model':
|
||||
$assets->OrderModels($order);
|
||||
@@ -128,54 +182,6 @@ class BulkAssetsController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
$assets = $assets->get();
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
foreach($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
|
||||
if ($request->filled('bulk_actions')) {
|
||||
|
||||
|
||||
switch ($request->input('bulk_actions')) {
|
||||
case 'labels':
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
return (new Label)
|
||||
->with('assets', $assets)
|
||||
->with('settings', Setting::getSettings())
|
||||
->with('bulkedit', true)
|
||||
->with('count', 0);
|
||||
|
||||
case 'delete':
|
||||
$this->authorize('delete', Asset::class);
|
||||
$assets->each(function ($assets) {
|
||||
$this->authorize('delete', $assets);
|
||||
});
|
||||
|
||||
return view('hardware/bulk-delete')->with('assets', $assets);
|
||||
|
||||
case 'restore':
|
||||
$this->authorize('update', Asset::class);
|
||||
$assets = Asset::withTrashed()->find($asset_ids);
|
||||
$assets->each(function ($asset) {
|
||||
$this->authorize('delete', $asset);
|
||||
});
|
||||
return view('hardware/bulk-restore')->with('assets', $assets);
|
||||
|
||||
case 'edit':
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('models', $models->pluck(['model']))
|
||||
->with('modelNames', $modelNames);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'No action selected');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Components;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
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 UploadFileRequest $request
|
||||
* @param int $componentId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $componentId = null)
|
||||
public function store(UploadFileRequest $request, $componentId = null)
|
||||
{
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
@@ -43,30 +42,7 @@ class ComponentsFilesController extends Controller
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
$file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
|
||||
|
||||
//Log the upload to the log
|
||||
$component->logUpload($file_name, e($request->input('notes')));
|
||||
|
||||
@@ -76,7 +76,6 @@ class ConsumableCheckoutController extends Controller
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||
}
|
||||
|
||||
|
||||
$admin_user = Auth::user();
|
||||
$assigned_to = e($request->input('assigned_to'));
|
||||
|
||||
|
||||
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Consumables;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
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 UploadFileRequest $request
|
||||
* @param int $consumableId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $consumableId = null)
|
||||
public function store(UploadFileRequest $request, $consumableId = null)
|
||||
{
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
|
||||
@@ -42,30 +41,7 @@ class ConsumablesFilesController extends Controller
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
$file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
|
||||
|
||||
//Log the upload to the log
|
||||
$consumable->logUpload($file_name, e($request->input('notes')));
|
||||
|
||||
@@ -260,7 +260,7 @@ class CustomFieldsController extends Controller
|
||||
|
||||
$field->name = trim(e($request->get("name")));
|
||||
$field->element = e($request->get("element"));
|
||||
$field->field_values = e($request->get("field_values"));
|
||||
$field->field_values = $request->get("field_values");
|
||||
$field->user_id = Auth::id();
|
||||
$field->help_text = $request->get("help_text");
|
||||
$field->show_in_email = $show_in_email;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to User Groups for
|
||||
@@ -63,6 +64,7 @@ class GroupsController extends Controller
|
||||
$group = new Group();
|
||||
$group->name = $request->input('name');
|
||||
$group->permissions = json_encode($request->input('permission'));
|
||||
$group->created_by = Auth::user()->id;
|
||||
|
||||
if ($group->save()) {
|
||||
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Labels\Label;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
@@ -13,6 +14,7 @@ use App\Models\Setting;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use App\View\Label as LabelView;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class LabelsController extends Controller
|
||||
@@ -20,9 +22,9 @@ class LabelsController extends Controller
|
||||
/**
|
||||
* Returns the Label view with test data
|
||||
*
|
||||
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
||||
* @param string $labelName
|
||||
* @param string $labelName
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
||||
*/
|
||||
public function show(string $labelName)
|
||||
{
|
||||
@@ -65,6 +67,20 @@ class LabelsController extends Controller
|
||||
$exampleAsset->model->category->id = 999999;
|
||||
$exampleAsset->model->category->name = trans('admin/labels/table.example_category');
|
||||
|
||||
$customFieldColumns = CustomField::where('field_encrypted', '=', 0)->pluck('db_column');
|
||||
|
||||
collect(explode(';', Setting::getSettings()->label2_fields))
|
||||
->filter()
|
||||
->each(function ($item) use ($customFieldColumns, $exampleAsset) {
|
||||
$pair = explode('=', $item);
|
||||
|
||||
if (array_key_exists(1, $pair)) {
|
||||
if ($customFieldColumns->contains($pair[1])) {
|
||||
$exampleAsset->{$pair[1]} = "{{$pair[0]}}";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$settings = Setting::getSettings();
|
||||
if (request()->has('settings')) {
|
||||
$overrides = request()->get('settings');
|
||||
|
||||
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Licenses;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\License;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class LicenseFilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Validates and stores files associated with a license.
|
||||
*
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param AssetFileRequest $request
|
||||
* @param UploadFileRequest $request
|
||||
* @param int $licenseId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @todo Switch to using the AssetFileRequest form request validator.
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $licenseId = null)
|
||||
public function store(UploadFileRequest $request, $licenseId = null)
|
||||
{
|
||||
$license = License::find($licenseId);
|
||||
|
||||
@@ -38,30 +37,7 @@ class LicenseFilesController extends Controller
|
||||
}
|
||||
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'license-'.$license->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/licenses/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/licenses/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
$file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
|
||||
|
||||
//Log the upload to the log
|
||||
$license->logUpload($file_name, e($request->input('notes')));
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Licenses for
|
||||
@@ -99,6 +100,7 @@ class LicensesController extends Controller
|
||||
$license->category_id = $request->input('category_id');
|
||||
$license->termination_date = $request->input('termination_date');
|
||||
$license->user_id = Auth::id();
|
||||
$license->min_amt = $request->input('min_amt');
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
|
||||
@@ -176,6 +178,7 @@ class LicensesController extends Controller
|
||||
$license->manufacturer_id = $request->input('manufacturer_id');
|
||||
$license->supplier_id = $request->input('supplier_id');
|
||||
$license->category_id = $request->input('category_id');
|
||||
$license->min_amt = $request->input('min_amt');
|
||||
|
||||
if ($license->save()) {
|
||||
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
|
||||
@@ -245,12 +248,6 @@ class LicensesController extends Controller
|
||||
$available_seats_count = $license->availCount()->count();
|
||||
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
|
||||
|
||||
\Log::debug('Total: '.$total_seats_count);
|
||||
\Log::debug('Users: '.$users_count);
|
||||
\Log::debug('Available: '.$available_seats_count);
|
||||
\Log::debug('Checkedout: '.$checkedout_seats_count);
|
||||
|
||||
|
||||
$this->authorize('view', $license);
|
||||
return view('licenses.view', compact('license'))
|
||||
->with('users_count', $users_count)
|
||||
@@ -293,4 +290,106 @@ class LicensesController extends Controller
|
||||
->with('item', $license)
|
||||
->with('maintained_list', $maintained_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports Licenses to CSV
|
||||
*
|
||||
* @author [G. Martinez]
|
||||
* @since [v6.3]
|
||||
* @return StreamedResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getExportLicensesCsv()
|
||||
{
|
||||
$this->authorize('view', License::class);
|
||||
\Debugbar::disable();
|
||||
|
||||
$response = new StreamedResponse(function () {
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
$licenses= License::with('company',
|
||||
'manufacturer',
|
||||
'category',
|
||||
'supplier',
|
||||
'adminuser',
|
||||
'assignedusers')
|
||||
->orderBy('created_at', 'DESC');
|
||||
Company::scopeCompanyables($licenses)
|
||||
->chunk(500, function ($licenses) use ($handle) {
|
||||
$headers = [
|
||||
// strtolower to prevent Excel from trying to open it as a SYLK file
|
||||
strtolower(trans('general.id')),
|
||||
trans('general.company'),
|
||||
trans('general.name'),
|
||||
trans('general.serial_number'),
|
||||
trans('general.purchase_date'),
|
||||
trans('general.purchase_cost'),
|
||||
trans('general.order_number'),
|
||||
trans('general.licenses_available'),
|
||||
trans('admin/licenses/table.seats'),
|
||||
trans('general.created_by'),
|
||||
trans('general.depreciation'),
|
||||
trans('general.updated_at'),
|
||||
trans('admin/licenses/table.deleted_at'),
|
||||
trans('general.email'),
|
||||
trans('admin/hardware/form.fully_depreciated'),
|
||||
trans('general.supplier'),
|
||||
trans('admin/licenses/form.expiration'),
|
||||
trans('admin/licenses/form.purchase_order'),
|
||||
trans('admin/licenses/form.termination_date'),
|
||||
trans('admin/licenses/form.maintained'),
|
||||
trans('general.manufacturer'),
|
||||
trans('general.category'),
|
||||
trans('general.min_amt'),
|
||||
trans('admin/licenses/form.reassignable'),
|
||||
trans('general.notes'),
|
||||
trans('general.created_at'),
|
||||
];
|
||||
|
||||
fputcsv($handle, $headers);
|
||||
|
||||
foreach ($licenses as $license) {
|
||||
// Add a new row with data
|
||||
$values = [
|
||||
$license->id,
|
||||
$license->company ? $license->company->name: '',
|
||||
$license->name,
|
||||
$license->serial,
|
||||
$license->purchase_date,
|
||||
$license->purchase_cost,
|
||||
$license->order_number,
|
||||
$license->free_seat_count,
|
||||
$license->seats,
|
||||
$license->adminuser->present()->fullName(),
|
||||
$license->depreciation ? $license->depreciation->name: '',
|
||||
$license->updated_at,
|
||||
$license->deleted_at,
|
||||
$license->email,
|
||||
( $license->depreciate == '1') ? trans('general.yes') : trans('general.no'),
|
||||
($license->supplier) ? $license->supplier->name: '',
|
||||
$license->expiration_date,
|
||||
$license->purchase_order,
|
||||
$license->termination_date,
|
||||
( $license->maintained == '1') ? trans('general.yes') : trans('general.no'),
|
||||
$license->manufacturer ? $license->manufacturer->name: '',
|
||||
$license->category ? $license->category->name: '',
|
||||
$license->min_amt,
|
||||
( $license->reassignable == '1') ? trans('general.yes') : trans('general.no'),
|
||||
$license->notes,
|
||||
$license->created_at,
|
||||
];
|
||||
|
||||
fputcsv($handle, $values);
|
||||
}
|
||||
});
|
||||
|
||||
// Close the output stream
|
||||
fclose($handle);
|
||||
}, 200, [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
'Content-Disposition' => 'attachment; filename="licenses-'.date('Y-m-d-his').'.csv"',
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Locations for
|
||||
@@ -168,7 +169,7 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('delete', Location::class);
|
||||
if (is_null($location = Location::find($locationId))) {
|
||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
|
||||
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if ($location->users()->count() > 0) {
|
||||
@@ -238,7 +239,7 @@ class LocationsController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $locationId
|
||||
* @since [v6.0.14]
|
||||
* @return View
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function getClone($locationId = null)
|
||||
{
|
||||
@@ -272,8 +273,102 @@ class LocationsController extends Controller
|
||||
|
||||
}
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that allows the user to bulk delete locations
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.3.1]
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function postBulkDelete(Request $request)
|
||||
{
|
||||
$locations_raw_array = $request->input('ids');
|
||||
|
||||
// Make sure some IDs have been selected
|
||||
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
|
||||
$locations = Location::whereIn('id', $locations_raw_array)
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')->get();
|
||||
|
||||
$valid_count = 0;
|
||||
foreach ($locations as $location) {
|
||||
if ($location->isDeletable()) {
|
||||
$valid_count++;
|
||||
}
|
||||
}
|
||||
return view('locations/bulk-delete', compact('locations'))->with('valid_count', $valid_count);
|
||||
}
|
||||
|
||||
return redirect()->route('models.index')
|
||||
->with('error', 'You must select at least one model to edit.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that locations can be deleted and deletes them if they can
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.3.1]
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function postBulkDeleteStore(Request $request) {
|
||||
$locations_raw_array = $request->input('ids');
|
||||
|
||||
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
|
||||
$locations = Location::whereIn('id', $locations_raw_array)
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('rtd_assets as rtd_assets_count')
|
||||
->withCount('children as children_count')
|
||||
->withCount('users as users_count')->get();
|
||||
|
||||
$success_count = 0;
|
||||
$error_count = 0;
|
||||
|
||||
foreach ($locations as $location) {
|
||||
|
||||
// Can we delete this location?
|
||||
if ($location->isDeletable()) {
|
||||
$location->delete();
|
||||
$success_count++;
|
||||
} else {
|
||||
$error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
\Log::debug('Success count: '.$success_count);
|
||||
\Log::debug('Error count: '.$error_count);
|
||||
// Complete success
|
||||
if ($success_count == count($locations_raw_array)) {
|
||||
return redirect()
|
||||
->route('locations.index')
|
||||
->with('success', trans_choice('general.bulk.delete.success', $success_count,
|
||||
['object_type' => trans_choice('general.location_plural', $success_count), 'count' => $success_count]
|
||||
));
|
||||
}
|
||||
|
||||
// Partial success
|
||||
if ($error_count > 0) {
|
||||
return redirect()
|
||||
->route('locations.index')
|
||||
->with('warning', trans('general.bulk.delete.partial',
|
||||
['success' => $success_count, 'error' => $error_count, 'object_type' => trans('general.locations')]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Nothing was selected - return to the index
|
||||
return redirect()
|
||||
->route('locations.index')
|
||||
->with('error', trans('general.bulk.nothing_selected',
|
||||
['object_type' => trans('general.locations')]
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,9 +295,9 @@ class ReportsController extends Controller
|
||||
$actionlog->present()->actionType(),
|
||||
e($actionlog->itemType()),
|
||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||
($actionlog->item->serial) ? $actionlog->item->serial : null,
|
||||
($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
|
||||
($actionlog->item->model) ? $actionlog->item->model->model_number : null,
|
||||
($actionlog->item) ? $actionlog->item->serial : null,
|
||||
(($actionlog->item) && ($actionlog->item->model)) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
|
||||
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
|
||||
$target_name,
|
||||
($actionlog->note) ? e($actionlog->note) : '',
|
||||
$actionlog->log_meta,
|
||||
@@ -616,7 +616,7 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('url')) {
|
||||
$header[] = trans('admin/manufacturers/table.url');
|
||||
$header[] = trans('general.url');
|
||||
}
|
||||
|
||||
|
||||
@@ -686,20 +686,27 @@ class ReportsController extends Controller
|
||||
|
||||
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
|
||||
}
|
||||
|
||||
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
|
||||
$checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
|
||||
$checkout_end = \Carbon::parse($request->input('checkout_date_end'))->endOfDay();
|
||||
$checkout_end = \Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
|
||||
|
||||
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
|
||||
$actionlogassets = Actionlog::where('action_type','=', 'checkout')
|
||||
->where('item_type', 'LIKE', '%Asset%',)
|
||||
->whereBetween('action_date',[$checkout_start, $checkout_end])
|
||||
->pluck('item_id');
|
||||
|
||||
$assets->whereIn('assets.id',$actionlogassets);
|
||||
}
|
||||
|
||||
if (($request->filled('checkin_date_start'))) {
|
||||
$assets->whereBetween('last_checkin', [
|
||||
Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
|
||||
$checkin_start = \Carbon::parse($request->input('checkin_date_start'))->startOfDay();
|
||||
// use today's date is `checkin_date_end` is not provided
|
||||
Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
|
||||
]);
|
||||
$checkin_end = \Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
|
||||
|
||||
$assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]);
|
||||
}
|
||||
//last checkin is exporting, but currently is a date and not a datetime in the custom report ONLY.
|
||||
|
||||
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
|
||||
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
|
||||
@@ -1156,16 +1163,24 @@ class ReportsController extends Controller
|
||||
$logItem = $logItem_res[0];
|
||||
}
|
||||
|
||||
if (!$assetItem->assignedTo->locale){
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$assetItem->assignedTo,
|
||||
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$assetItem->assignedTo,
|
||||
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
||||
);
|
||||
// Only send notification if assigned
|
||||
if ($assetItem->assignedTo) {
|
||||
|
||||
if (!$assetItem->assignedTo->locale) {
|
||||
Notification::locale(Setting::getSettings()->locale)->send(
|
||||
$assetItem->assignedTo,
|
||||
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
||||
);
|
||||
} else {
|
||||
Notification::send(
|
||||
$assetItem->assignedTo,
|
||||
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($assetItem->assignedTo->email == ''){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
|
||||
}
|
||||
|
||||
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
|
||||
|
||||
@@ -20,6 +20,7 @@ use DB;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Image;
|
||||
use Input;
|
||||
use Redirect;
|
||||
@@ -28,6 +29,7 @@ use App\Http\Requests\SlackSettingsRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to Settings for
|
||||
@@ -356,6 +358,7 @@ class SettingsController extends Controller
|
||||
}
|
||||
|
||||
$setting->default_eula_text = $request->input('default_eula_text');
|
||||
$setting->load_remote = $request->input('load_remote', 0);
|
||||
$setting->thumbnail_max_h = $request->input('thumbnail_max_h');
|
||||
$setting->privacy_policy_link = $request->input('privacy_policy_link');
|
||||
|
||||
@@ -420,68 +423,46 @@ class SettingsController extends Controller
|
||||
|
||||
// Only allow the site name and CSS to be changed if lock_passwords is false
|
||||
// Because public demos make people act like dicks
|
||||
|
||||
if (! config('app.lock_passwords')) {
|
||||
$setting->site_name = $request->input('site_name');
|
||||
$setting->custom_css = $request->input('custom_css');
|
||||
}
|
||||
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
|
||||
|
||||
if ('1' == $request->input('clear_logo')) {
|
||||
if ('1' == $request->input('clear_logo')) {
|
||||
Storage::disk('public')->delete($setting->logo);
|
||||
$setting->logo = null;
|
||||
$setting->logo = null;
|
||||
$setting->brand = 1;
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
|
||||
|
||||
|
||||
if ('1' == $request->input('clear_email_logo')) {
|
||||
Storage::disk('public')->delete($setting->email_logo);
|
||||
$setting->email_logo = null;
|
||||
// If they are uploading an image, validate it and upload it
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
|
||||
|
||||
|
||||
if ('1' == $request->input('clear_label_logo')) {
|
||||
Storage::disk('public')->delete($setting->label_logo);
|
||||
$setting->label_logo = null;
|
||||
}
|
||||
|
||||
|
||||
// If the user wants to clear the favicon...
|
||||
if ($request->hasFile('favicon')) {
|
||||
$favicon_image = $favicon_upload = $request->file('favicon');
|
||||
$favicon_ext = $favicon_image->getClientOriginalExtension();
|
||||
$setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext;
|
||||
|
||||
if (($favicon_image->getClientOriginalExtension() != 'ico') && ($favicon_image->getClientOriginalExtension() != 'svg')) {
|
||||
$favicon_upload = Image::make($favicon_image->getRealPath())->resize(null, 36, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($favicon_file_name, (string) $favicon_upload->encode());
|
||||
} else {
|
||||
Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon')));
|
||||
}
|
||||
|
||||
|
||||
// Remove Current image if exists
|
||||
if (($setting->favicon) && (file_exists($favicon_file_name))) {
|
||||
Storage::disk('public')->delete($favicon_file_name);
|
||||
}
|
||||
} elseif ('1' == $request->input('clear_favicon')) {
|
||||
Storage::disk('public')->delete($setting->clear_favicon);
|
||||
$setting->favicon = null;
|
||||
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
|
||||
|
||||
// If they are uploading an image, validate it and upload it
|
||||
}
|
||||
|
||||
if ('1' == $request->input('clear_email_logo')) {
|
||||
Storage::disk('public')->delete($setting->email_logo);
|
||||
$setting->email_logo = null;
|
||||
// If they are uploading an image, validate it and upload it
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
|
||||
|
||||
if ('1' == $request->input('clear_label_logo')) {
|
||||
Storage::disk('public')->delete($setting->label_logo);
|
||||
$setting->label_logo = null;
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'favicon', '', 'favicon');
|
||||
|
||||
// If the user wants to clear the favicon...
|
||||
if ('1' == $request->input('clear_favicon')) {
|
||||
Storage::disk('public')->delete($setting->favicon);
|
||||
$setting->favicon = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($setting->save()) {
|
||||
return redirect()->route('settings.index')
|
||||
@@ -519,6 +500,19 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function postSecurity(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'pwd_secure_complexity' => 'array',
|
||||
'pwd_secure_complexity.*' => [
|
||||
Rule::in([
|
||||
'disallow_same_pwd_as_user_fields',
|
||||
'letters',
|
||||
'numbers',
|
||||
'symbols',
|
||||
'case_diff',
|
||||
])
|
||||
]
|
||||
]);
|
||||
|
||||
if (is_null($setting = Setting::getSettings())) {
|
||||
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
|
||||
}
|
||||
@@ -635,21 +629,21 @@ class SettingsController extends Controller
|
||||
// Check if the audit interval has changed - if it has, we want to update ALL of the assets audit dates
|
||||
if ($request->input('audit_interval') != $setting->audit_interval) {
|
||||
|
||||
// Be careful - this could be a negative number
|
||||
// This could be a negative number if the user is trying to set the audit interval to a lower number than it was before
|
||||
$audit_diff_months = ((int)$request->input('audit_interval') - (int)($setting->audit_interval));
|
||||
|
||||
// Batch update the dates. We have to use this method to avoid time limit exceeded errors on very large datasets,
|
||||
// but it DOES mean this change doesn't get logged in the action logs, since it skips the observer.
|
||||
// @see https://stackoverflow.com/questions/54879160/laravel-observer-not-working-on-bulk-insert
|
||||
$affected = Asset::whereNotNull('next_audit_date')
|
||||
->whereNull('deleted_at')
|
||||
->update(
|
||||
['next_audit_date' => DB::raw('DATE_ADD(next_audit_date, INTERVAL '.$audit_diff_months.' MONTH)')]
|
||||
);
|
||||
|
||||
\Log::debug($affected .' assets affected by audit interval update');
|
||||
|
||||
|
||||
// Grab all of the assets that have an existing next_audit_date
|
||||
$assets = Asset::whereNotNull('next_audit_date')->get();
|
||||
|
||||
// Update all of the assets' next_audit_date values
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
if ($asset->next_audit_date != '') {
|
||||
$old_next_audit = new \DateTime($asset->next_audit_date);
|
||||
$asset->next_audit_date = $old_next_audit->modify($audit_diff_months.' month')->format('Y-m-d');
|
||||
$asset->forceSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$alert_email = rtrim($request->input('alert_email'), ',');
|
||||
@@ -810,10 +804,9 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function getLabels()
|
||||
{
|
||||
return view('settings.labels', [
|
||||
'setting' => Setting::getSettings(),
|
||||
'customFields' => CustomField::all(),
|
||||
]);
|
||||
return view('settings.labels')
|
||||
->with('setting', Setting::getSettings())
|
||||
->with('customFields', CustomField::where('field_encrypted', '=', 0)->get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,14 +4,13 @@ namespace App\Http\Controllers\Users;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class UserFilesController extends Controller
|
||||
@@ -19,14 +18,14 @@ class UserFilesController extends Controller
|
||||
/**
|
||||
* Return JSON response with a list of user details for the getIndex() view.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.6]
|
||||
* @param AssetFileRequest $request
|
||||
* @param UploadFileRequest $request
|
||||
* @param int $userId
|
||||
* @return string JSON
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*@author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.6]
|
||||
*/
|
||||
public function store(AssetFileRequest $request, $userId = null)
|
||||
public function store(UploadFileRequest $request, $userId = null)
|
||||
{
|
||||
$user = User::find($userId);
|
||||
$destinationPath = config('app.private_uploads').'/users';
|
||||
@@ -41,31 +40,7 @@ class UserFilesController extends Controller
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = 'user-'.$user->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/users/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Storage::put('private_uploads/users/'.$file_name, file_get_contents($file));
|
||||
}
|
||||
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
|
||||
|
||||
//Log the uploaded file to the log
|
||||
$logAction = new Actionlog();
|
||||
|
||||
@@ -182,8 +182,13 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
if ($user = User::find($id)) {
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$this->authorize('update', User::class);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
if ($user) {
|
||||
|
||||
$permissions = config('permissions');
|
||||
$groups = Group::pluck('name', 'id');
|
||||
|
||||
@@ -210,106 +215,109 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function update(SaveUserRequest $request, $id = null)
|
||||
{
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
|
||||
// Thanks, jerks. You are why we can't have nice things. - snipe
|
||||
|
||||
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
|
||||
return redirect()->route('users.index')->with('error', 'Permission denied. You cannot update user information for superadmins on the demo.');
|
||||
return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo'));
|
||||
}
|
||||
|
||||
try {
|
||||
$user = User::findOrFail($id);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
$this->authorize('update', $user);
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
// User is valid - continue...
|
||||
if ($user) {
|
||||
$this->authorize('update', $user);
|
||||
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only save groups if the user is a super user
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// Update the user
|
||||
if ($request->filled('username')) {
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
}
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->route('users.index')
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
//\Log::debug(print_r($user, true));
|
||||
|
||||
// Was the user updated?
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->route('users.index')
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,12 +331,13 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function destroy($id = null)
|
||||
{
|
||||
try {
|
||||
// Get user information
|
||||
$user = User::findOrFail($id);
|
||||
// Authorize takes care of many of our logic checks now.
|
||||
$this->authorize('delete', User::class);
|
||||
|
||||
$this->authorize('delete', User::class);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
|
||||
if ($user) {
|
||||
// Check if we are not trying to delete ourselves
|
||||
if ($user->id === Auth::id()) {
|
||||
// Redirect to the user management page
|
||||
@@ -362,16 +371,12 @@ class UsersController extends Controller
|
||||
|
||||
// Delete the user
|
||||
$user->delete();
|
||||
|
||||
// Prepare the success message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Prepare the error message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,59 +432,25 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function show($userId = null)
|
||||
{
|
||||
if (! $user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($userId)) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
}
|
||||
// Make sure the user can view users at all
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$userlog = $user->userlog->load('item');
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($userId);
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user);
|
||||
|
||||
return view('users/view', compact('user', 'userlog'))
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspend a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $id
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getUnsuspend($id = null)
|
||||
{
|
||||
try {
|
||||
// Get user information
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
// Check if we are not trying to unsuspend ourselves
|
||||
if ($user->id === Auth::id()) {
|
||||
// Prepare the error message
|
||||
$error = trans('admin/users/message.error.unsuspend');
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('error', $error);
|
||||
}
|
||||
|
||||
// Do we have permission to unsuspend this user?
|
||||
if ($user->isSuperUser() && ! Auth::user()->isSuperUser()) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('error', 'Insufficient permissions!');
|
||||
}
|
||||
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.unsuspend'));
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
if ($user) {
|
||||
$userlog = $user->userlog->load('item');
|
||||
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a view containing a pre-populated new user form,
|
||||
* populated with some fields from an existing user.
|
||||
@@ -493,22 +464,34 @@ class UsersController extends Controller
|
||||
public function getClone(Request $request, $id = null)
|
||||
{
|
||||
$this->authorize('create', User::class);
|
||||
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
try {
|
||||
// Get the user information
|
||||
$user_to_clone = User::withTrashed()->find($id);
|
||||
|
||||
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user_to_clone = Company::scopeCompanyables($user_to_clone)->find($id);
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user_to_clone);
|
||||
|
||||
|
||||
if ($user_to_clone) {
|
||||
|
||||
|
||||
$user = clone $user_to_clone;
|
||||
|
||||
// Blank out some fields
|
||||
$user->first_name = '';
|
||||
$user->last_name = '';
|
||||
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
||||
$user->id = null;
|
||||
|
||||
// Get this user groups
|
||||
// Get this user's groups
|
||||
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
|
||||
|
||||
// Get all the available permissions
|
||||
$permissions = config('permissions');
|
||||
$clonedPermissions = $user_to_clone->decodePermissions();
|
||||
@@ -517,16 +500,14 @@ class UsersController extends Controller
|
||||
|
||||
// Show the page
|
||||
return view('users/edit', compact('permissions', 'userPermissions'))
|
||||
->with('user', $user)
|
||||
->with('groups', Group::pluck('name', 'id'))
|
||||
->with('userGroups', $userGroups)
|
||||
->with('clone_user', $user_to_clone);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Prepare the error message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
->with('user', $user)
|
||||
->with('groups', Group::pluck('name', 'id'))
|
||||
->with('userGroups', $userGroups)
|
||||
->with('clone_user', $user_to_clone);
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -546,8 +527,20 @@ class UsersController extends Controller
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
|
||||
User::with('assets', 'accessories', 'consumables', 'department', 'licenses', 'manager', 'groups', 'userloc', 'company')
|
||||
->orderBy('created_at', 'DESC')
|
||||
$users = User::with(
|
||||
'assets',
|
||||
'accessories',
|
||||
'consumables',
|
||||
'department',
|
||||
'licenses',
|
||||
'manager',
|
||||
'groups',
|
||||
'userloc',
|
||||
'company'
|
||||
)->orderBy('created_at', 'DESC');
|
||||
|
||||
// FMCS scoping
|
||||
Company::scopeCompanyables($users)
|
||||
->chunk(500, function ($users) use ($handle) {
|
||||
$headers = [
|
||||
// strtolower to prevent Excel from trying to open it as a SYLK file
|
||||
@@ -565,7 +558,7 @@ class UsersController extends Controller
|
||||
trans('general.licenses'),
|
||||
trans('general.accessories'),
|
||||
trans('general.consumables'),
|
||||
trans('admin/users/table.groups'),
|
||||
trans('general.groups'),
|
||||
trans('general.notes'),
|
||||
trans('admin/users/table.activated'),
|
||||
trans('general.created_at'),
|
||||
@@ -626,7 +619,11 @@ class UsersController extends Controller
|
||||
public function printInventory($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$show_user = User::where('id', $id)->withTrashed()->first();
|
||||
$show_user = Company::scopeCompanyables(User::where('id', $id)->withTrashed()->first());
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $show_user);
|
||||
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
|
||||
$accessories = $show_user->accessories()->get();
|
||||
$consumables = $show_user->consumables()->get();
|
||||
@@ -651,16 +648,23 @@ class UsersController extends Controller
|
||||
{
|
||||
$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 = Company::scopeCompanyables(User::find($id));
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user);
|
||||
|
||||
if ($user) {
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -672,19 +676,19 @@ class UsersController extends Controller
|
||||
*/
|
||||
public function sendPasswordReset($id)
|
||||
{
|
||||
if (($user = User::find($id)) && ($user->activated == '1') && ($user->email != '') && ($user->ldap_import == '0')) {
|
||||
if (($user = Company::scopeCompanyables(User::find($id))) && ($user->activated == '1') && ($user->email != '') && ($user->ldap_import == '0')) {
|
||||
$credentials = ['email' => trim($user->email)];
|
||||
|
||||
try {
|
||||
|
||||
Password::sendResetLink($credentials);
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/users/message.password_reset_sent', ['email' => $user->email]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', ' Error sending email. :( ');
|
||||
return redirect()->back()->with('error', trans('general.error_sending_email'));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'User is not activated, is LDAP synced, or does not have an email address ');
|
||||
return redirect()->back()->with('error', trans('general.pwd_reset_not_sent'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,21 @@ class Importer extends Component
|
||||
'field_map' => 'array'
|
||||
];
|
||||
|
||||
/**
|
||||
* This is used in resources/views/livewire/importer.blade.php, and we kinda shouldn't need to check for
|
||||
* activeFile here, but there's some UI goofiness that allows this to crash out on some imports.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_field_map()
|
||||
{
|
||||
\Log::debug("header row is: ".print_r($this->activeFile->header_row,true));
|
||||
\Log::debug("Field map is: ".print_r($this->field_map,true));
|
||||
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
|
||||
return json_encode(array_filter($tmp));
|
||||
$tmp = array();
|
||||
if ($this->activeFile) {
|
||||
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
|
||||
$tmp = array_filter($tmp);
|
||||
}
|
||||
return json_encode($tmp);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,29 +31,37 @@ class SlackSettingsForm extends Component
|
||||
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
||||
'webhook_botname' => 'string|nullable',
|
||||
];
|
||||
public $messages = [
|
||||
'webhook_endpoint.starts_with' => 'your webhook endpoint should begin with http://, https:// or other protocol.',
|
||||
];
|
||||
|
||||
|
||||
public function mount() {
|
||||
$this->webhook_text= [
|
||||
"slack" => array(
|
||||
"slack" => array(
|
||||
"name" => trans('admin/settings/general.slack') ,
|
||||
"icon" => 'fab fa-slack',
|
||||
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
|
||||
"link" => 'https://api.slack.com/messaging/webhooks',
|
||||
"icon" => 'fab fa-slack',
|
||||
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
|
||||
"link" => 'https://api.slack.com/messaging/webhooks',
|
||||
"test" => "testWebhook"
|
||||
),
|
||||
"general"=> array(
|
||||
"name" => trans('admin/settings/general.general_webhook'),
|
||||
"icon" => "fab fa-hashtag",
|
||||
"placeholder" => trans('general.url'),
|
||||
"link" => "",
|
||||
"test" => "testWebhook"
|
||||
),
|
||||
"google" => array(
|
||||
"name" => trans('admin/settings/general.google_workspaces'),
|
||||
"icon" => "fa-brands fa-google",
|
||||
"placeholder" => "https://chat.googleapis.com/v1/spaces/xxxxxxxx/messages?key=xxxxxx",
|
||||
"link" => "https://developers.google.com/chat/how-tos/webhooks#register_the_incoming_webhook",
|
||||
"test" => "googleWebhookTest"
|
||||
),
|
||||
"microsoft" => array(
|
||||
"name" => trans('admin/settings/general.ms_teams'),
|
||||
"icon" => "fa-brands fa-microsoft",
|
||||
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
|
||||
"link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1",
|
||||
"test" => "msTeamTestWebhook"
|
||||
),
|
||||
];
|
||||
|
||||
@@ -64,10 +72,14 @@ class SlackSettingsForm extends Component
|
||||
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
|
||||
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"];
|
||||
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"];
|
||||
$this->webhook_endpoint = $this->setting->webhook_endpoint;
|
||||
$this->webhook_channel = $this->setting->webhook_channel;
|
||||
$this->webhook_botname = $this->setting->webhook_botname;
|
||||
$this->webhook_options = $this->setting->webhook_selected;
|
||||
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
||||
$this->webhook_channel = '#NA';
|
||||
}
|
||||
|
||||
|
||||
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
||||
@@ -87,10 +99,14 @@ class SlackSettingsForm extends Component
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
|
||||
$this->webhook_endpoint = null;
|
||||
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
|
||||
$this->webhook_test = $this->webhook_text[$this->webhook_selected]["test"];
|
||||
if($this->webhook_selected != 'slack'){
|
||||
$this->isDisabled= '';
|
||||
$this->save_button = trans('general.save');
|
||||
}
|
||||
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
||||
$this->webhook_channel = '#NA';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -151,6 +167,7 @@ class SlackSettingsForm extends Component
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function clearSettings(){
|
||||
|
||||
if (Helper::isDemoMode()) {
|
||||
@@ -187,7 +204,35 @@ class SlackSettingsForm extends Component
|
||||
}
|
||||
|
||||
}
|
||||
public function msTeamTestWebhook(){
|
||||
public function googleWebhookTest(){
|
||||
|
||||
$payload = [
|
||||
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
];
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'content-type' => 'applications/json',
|
||||
])->post($this->webhook_endpoint,
|
||||
$payload)->throw();
|
||||
|
||||
|
||||
if (($response->getStatusCode() == 302) || ($response->getStatusCode() == 301)) {
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
}
|
||||
|
||||
$this->isDisabled='';
|
||||
$this->save_button = trans('general.save');
|
||||
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$this->isDisabled='disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
}
|
||||
public function msTeamTestWebhook(){
|
||||
|
||||
$payload =
|
||||
[
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use App\Models\Asset;
|
||||
use Auth;
|
||||
use Closure;
|
||||
use App\Models\Setting;
|
||||
|
||||
class AssetCountForSidebar
|
||||
{
|
||||
@@ -24,6 +25,13 @@ class AssetCountForSidebar
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_assets = Asset::RTD()->count();
|
||||
view()->share('total_assets', $total_assets);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_deployed_sidebar = Asset::Deployed()->count();
|
||||
view()->share('total_deployed_sidebar', $total_deployed_sidebar);
|
||||
@@ -59,6 +67,44 @@ class AssetCountForSidebar
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$settings = Setting::getSettings();
|
||||
view()->share('settings', $settings);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_due_for_audit = Asset::DueForAudit($settings)->count();
|
||||
view()->share('total_due_for_audit', $total_due_for_audit);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_overdue_for_audit = Asset::OverdueForAudit()->count();
|
||||
view()->share('total_overdue_for_audit', $total_overdue_for_audit);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_due_for_checkin = Asset::DueForCheckin($settings)->count();
|
||||
view()->share('total_due_for_checkin', $total_due_for_checkin);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_overdue_for_checkin = Asset::OverdueForCheckin()->count();
|
||||
view()->share('total_overdue_for_checkin', $total_overdue_for_checkin);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
view()->share('total_due_and_overdue_for_checkin', ($total_due_for_checkin + $total_overdue_for_checkin));
|
||||
view()->share('total_due_and_overdue_for_audit', ($total_due_for_audit + $total_overdue_for_audit));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ use \App\Helpers\Helper;
|
||||
|
||||
class CheckLocale
|
||||
{
|
||||
private function warn_legacy_locale($language, $source)
|
||||
{
|
||||
if ($language != Helper::mapLegacyLocale($language)) {
|
||||
\Log::warning("$source $language and should be updated to be ".Helper::mapLegacyLocale($language));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle the locale for the user, default to settings otherwise.
|
||||
*
|
||||
@@ -22,24 +28,23 @@ class CheckLocale
|
||||
|
||||
// Default app settings from config
|
||||
$language = config('app.locale');
|
||||
$this->warn_legacy_locale($language, "APP_LOCALE in .env is set to");
|
||||
|
||||
if ($settings = Setting::getSettings()) {
|
||||
|
||||
// User's preference
|
||||
if (($request->user()) && ($request->user()->locale)) {
|
||||
$language = $request->user()->locale;
|
||||
$this->warn_legacy_locale($language, "username ".$request->user()->username." (".$request->user()->id.") has a language");
|
||||
|
||||
// App setting preference
|
||||
} elseif ($settings->locale != '') {
|
||||
$language = $settings->locale;
|
||||
$this->warn_legacy_locale($language, "App Settings is set to");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (config('app.locale') != Helper::mapLegacyLocale($language)) {
|
||||
\Log::warning('Your current APP_LOCALE in your .env is set to "'.config('app.locale').'" and should be updated to be "'.Helper::mapLegacyLocale($language).'" in '.base_path().'/.env. Translations may display unexpectedly until this is updated.');
|
||||
}
|
||||
|
||||
|
||||
\App::setLocale(Helper::mapLegacyLocale($language));
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class AssetFileRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
|
||||
|
||||
return [
|
||||
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp|max:'.$max_file_size,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,9 @@ class ImageUploadRequest extends Request
|
||||
{
|
||||
|
||||
return [
|
||||
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp',
|
||||
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp',
|
||||
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
|
||||
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
|
||||
'favicon' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,image/x-icon,image/vnd.microsoft.icon,ico',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -97,22 +98,36 @@ class ImageUploadRequest extends Request
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$ext = $image->guessExtension();
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
|
||||
|
||||
\Log::info('File name will be: '.$file_name);
|
||||
\Log::debug('File extension is: '.$ext);
|
||||
|
||||
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
|
||||
if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
|
||||
// If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing
|
||||
// icons or avif, and webp support and needs to be compiled into gd for resizing to be available
|
||||
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
|
||||
|
||||
\Log::debug('Not an SVG or webp - resize');
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
} elseif($image->getMimeType() == 'image/svg+xml') {
|
||||
// If the file is an SVG, we need to clean it and NOT encode it
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($image->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
Storage::disk('public')->put($path . '/' . $file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
} else {
|
||||
|
||||
try {
|
||||
$upload = Image::make($image->getRealPath())->setFileInfoFromPath($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
})->orientate();
|
||||
|
||||
} catch(NotReadableException $e) {
|
||||
\Log::debug($e);
|
||||
$validator = \Validator::make([], []);
|
||||
@@ -124,35 +139,12 @@ class ImageUploadRequest extends Request
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
|
||||
|
||||
} else {
|
||||
// If the file is a webp, we need to just move it since webp support
|
||||
// needs to be compiled into gd for resizing to be available
|
||||
if ($image->getClientOriginalExtension() == 'webp') {
|
||||
\Log::debug('This is a webp, just move it');
|
||||
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
|
||||
// If the file is an SVG, we need to clean it and NOT encode it
|
||||
} else {
|
||||
\Log::debug('This is an SVG');
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($image->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
Storage::disk('public')->put($path.'/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Current image if exists
|
||||
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
|
||||
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
|
||||
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Setting;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreAssetRequest extends ImageUploadRequest
|
||||
@@ -27,6 +30,8 @@ class StoreAssetRequest extends ImageUploadRequest
|
||||
? Company::getIdForCurrentUser($this->company_id)
|
||||
: $this->company_id;
|
||||
|
||||
$this->parseLastAuditDate();
|
||||
|
||||
$this->merge([
|
||||
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
|
||||
'company_id' => $idForCurrentUser,
|
||||
@@ -41,10 +46,52 @@ class StoreAssetRequest extends ImageUploadRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = array_merge(
|
||||
(new Asset)->getRules(),
|
||||
$modelRules = (new Asset)->getRules();
|
||||
|
||||
if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) {
|
||||
// If purchase_cost was submitted as a string with a comma separator
|
||||
// then we need to ignore the normal numeric rules.
|
||||
// Since the original rules still live on the model they will be run
|
||||
// right before saving (and after purchase_cost has been
|
||||
// converted to a float via setPurchaseCostAttribute).
|
||||
$modelRules = $this->removeNumericRulesFromPurchaseCost($modelRules);
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$modelRules,
|
||||
parent::rules(),
|
||||
);
|
||||
}
|
||||
|
||||
private function parseLastAuditDate(): void
|
||||
{
|
||||
if ($this->input('last_audit_date')) {
|
||||
try {
|
||||
$lastAuditDate = Carbon::parse($this->input('last_audit_date'));
|
||||
|
||||
$this->merge([
|
||||
'last_audit_date' => $lastAuditDate->startOfDay()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
} catch (InvalidFormatException $e) {
|
||||
// we don't need to do anything here...
|
||||
// we'll keep the provided date in an
|
||||
// invalid format so validation picks it up later
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function removeNumericRulesFromPurchaseCost(array $rules): array
|
||||
{
|
||||
$purchaseCost = $rules['purchase_cost'];
|
||||
|
||||
// If rule is in "|" format then turn it into an array
|
||||
if (is_string($purchaseCost)) {
|
||||
$purchaseCost = explode('|', $purchaseCost);
|
||||
}
|
||||
|
||||
$rules['purchase_cost'] = array_filter($purchaseCost, function ($rule) {
|
||||
return $rule !== 'numeric' && $rule !== 'gte:0';
|
||||
});
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class UploadFileRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
|
||||
|
||||
return [
|
||||
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes (if needed) and Saves a file to the appropriate location
|
||||
* Returns the 'short' (storage-relative) filename
|
||||
*
|
||||
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
|
||||
* a way to merge them or extend one into the other?
|
||||
*/
|
||||
public function handleFile(string $dirname, string $name_prefix, $file): string
|
||||
{
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
|
||||
|
||||
|
||||
\Log::debug("Your filetype IS: ".$file->getMimeType());
|
||||
// Check for SVG and sanitize it
|
||||
if ($file->getMimeType() === 'image/svg+xml') {
|
||||
\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($dirname.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
|
||||
\Log::debug("Here are the '$put_results' (should be 0 or 1 or true or false or something?)");
|
||||
}
|
||||
return $file_name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Traits;
|
||||
|
||||
use App\Models\Asset;
|
||||
|
||||
trait MigratesLegacyAssetLocations
|
||||
{
|
||||
/**
|
||||
* This is just meant to correct legacy issues where some user data would have 0
|
||||
* as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
|
||||
* rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
|
||||
* people (and their data) in the long run
|
||||
* @param Asset $asset
|
||||
* @return void
|
||||
*/
|
||||
private function migrateLegacyLocations(Asset $asset): void
|
||||
{
|
||||
if ($asset->rtd_location_id == '0') {
|
||||
\Log::debug('Manually override the RTD location IDs');
|
||||
\Log::debug('Original RTD Location ID: ' . $asset->rtd_location_id);
|
||||
$asset->rtd_location_id = '';
|
||||
\Log::debug('New RTD Location ID: ' . $asset->rtd_location_id);
|
||||
}
|
||||
|
||||
if ($asset->location_id == '0') {
|
||||
\Log::debug('Manually override the location IDs');
|
||||
\Log::debug('Original Location ID: ' . $asset->location_id);
|
||||
$asset->location_id = '';
|
||||
\Log::debug('New Location ID: ' . $asset->location_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,20 @@ class AssetMaintenancesTransformer
|
||||
'id' => (int) $assetmaintenance->asset->id,
|
||||
'name'=> ($assetmaintenance->asset->name) ? e($assetmaintenance->asset->name) : null,
|
||||
'asset_tag'=> e($assetmaintenance->asset->asset_tag),
|
||||
|
||||
'serial'=> e($assetmaintenance->asset->serial),
|
||||
'deleted_at'=> e($assetmaintenance->asset->deleted_at),
|
||||
'created_at'=> e($assetmaintenance->asset->created_at),
|
||||
] : null,
|
||||
'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [
|
||||
'id' => (int) $assetmaintenance->asset->model->id,
|
||||
'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null,
|
||||
] : null,
|
||||
'status_label' => ($assetmaintenance->asset->assetstatus) ? [
|
||||
'id' => (int) $assetmaintenance->asset->assetstatus->id,
|
||||
'name'=> e($assetmaintenance->asset->assetstatus->name),
|
||||
'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()),
|
||||
'status_meta' => e($assetmaintenance->asset->present()->statusMeta),
|
||||
] : null,
|
||||
'company' => (($assetmaintenance->asset) && ($assetmaintenance->asset->company)) ? [
|
||||
'id' => (int) $assetmaintenance->asset->company->id,
|
||||
'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null,
|
||||
@@ -64,7 +72,7 @@ class AssetMaintenancesTransformer
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => Gate::allows('update', Asset::class),
|
||||
'update' => (Gate::allows('update', Asset::class) && ($assetmaintenance->asset->deleted_at=='')) ? true : false,
|
||||
'delete' => Gate::allows('delete', Asset::class),
|
||||
];
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class AssetsTransformer
|
||||
'name'=> e($asset->model->name),
|
||||
] : null,
|
||||
'byod' => ($asset->byod ? true : false),
|
||||
'requestable' => ($asset->requestable ? true : false),
|
||||
|
||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
|
||||
@@ -87,6 +88,7 @@ class AssetsTransformer
|
||||
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
|
||||
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
|
||||
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
|
||||
'last_checkin' => Helper::getFormattedDateObject($asset->last_checkin, 'datetime'),
|
||||
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
|
||||
'checkin_counter' => (int) $asset->checkin_counter,
|
||||
|
||||
@@ -26,6 +26,7 @@ class GroupsTransformer
|
||||
'name' => e($group->name),
|
||||
'permissions' => json_decode($group->permissions),
|
||||
'users_count' => (int) $group->users_count,
|
||||
'created_by' => ($group->admin) ? e($group->admin->present()->fullName) : null,
|
||||
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),
|
||||
];
|
||||
|
||||
@@ -27,8 +27,8 @@ class LicensesTransformer
|
||||
'company' => ($license->company) ? ['id' => (int) $license->company->id, 'name'=> e($license->company->name)] : null,
|
||||
'manufacturer' => ($license->manufacturer) ? ['id' => (int) $license->manufacturer->id, 'name'=> e($license->manufacturer->name)] : null,
|
||||
'product_key' => (Gate::allows('viewKeys', License::class)) ? e($license->serial) : '------------',
|
||||
'order_number' => e($license->order_number),
|
||||
'purchase_order' => e($license->purchase_order),
|
||||
'order_number' => ($license->order_number) ? e($license->order_number) : null,
|
||||
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
|
||||
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
||||
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
||||
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
||||
@@ -38,8 +38,9 @@ class LicensesTransformer
|
||||
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
||||
'seats' => (int) $license->seats,
|
||||
'free_seats_count' => (int) $license->free_seats_count,
|
||||
'license_name' => e($license->license_name),
|
||||
'license_email' => e($license->license_email),
|
||||
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
|
||||
'license_name' => ($license->license_name) ? e($license->license_name) : null,
|
||||
'license_email' => ($license->license_email) ? e($license->license_email) : null,
|
||||
'reassignable' => ($license->reassignable == 1) ? true : false,
|
||||
'maintained' => ($license->maintained == 1) ? true : false,
|
||||
'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null,
|
||||
|
||||
@@ -65,6 +65,9 @@ class LocationsTransformer
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => Gate::allows('update', Location::class) ? true : false,
|
||||
'delete' => $location->isDeletable(),
|
||||
'bulk_selectable' => [
|
||||
'delete' => $location->isDeletable()
|
||||
],
|
||||
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
||||
];
|
||||
|
||||
|
||||
@@ -46,10 +46,9 @@ class AccessoryImporter extends ItemImporter
|
||||
$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.
|
||||
// $accessory->unsetEventDispatcher();
|
||||
// This sets an attribute on the Loggable trait for the action log
|
||||
$accessory->setImported(true);
|
||||
if ($accessory->save()) {
|
||||
$accessory->logCreate('Imported using CSV Importer');
|
||||
$this->log('Accessory '.$this->item['name'].' was created');
|
||||
|
||||
return;
|
||||
|
||||
@@ -135,10 +135,10 @@ class AssetImporter extends ItemImporter
|
||||
$asset->{$custom_field} = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// This sets an attribute on the Loggable trait for the action log
|
||||
$asset->setImported(true);
|
||||
if ($asset->save()) {
|
||||
|
||||
$asset->logCreate(trans('general.importer.import_note'));
|
||||
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
|
||||
|
||||
// If we have a target to checkout to, lets do so.
|
||||
|
||||
@@ -48,10 +48,10 @@ class ComponentImporter extends ItemImporter
|
||||
$this->log('No matching component, creating one');
|
||||
$component = new Component;
|
||||
$component->fill($this->sanitizeItemForStoring($component));
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
$component->unsetEventDispatcher();
|
||||
|
||||
// This sets an attribute on the Loggable trait for the action log
|
||||
$component->setImported(true);
|
||||
if ($component->save()) {
|
||||
$component->logCreate('Imported using CSV Importer');
|
||||
$this->log('Component '.$this->item['name'].' was created');
|
||||
|
||||
// If we have an asset tag, checkout to that asset.
|
||||
|
||||
@@ -45,10 +45,10 @@ class ConsumableImporter extends ItemImporter
|
||||
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
|
||||
$this->item['min_amt'] = trim($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();
|
||||
|
||||
// This sets an attribute on the Loggable trait for the action log
|
||||
$consumable->setImported(true);
|
||||
if ($consumable->save()) {
|
||||
$consumable->logCreate('Imported using CSV Importer');
|
||||
$this->log('Consumable '.$this->item['name'].' was created');
|
||||
|
||||
return;
|
||||
|
||||
@@ -85,10 +85,10 @@ class LicenseImporter extends ItemImporter
|
||||
} else {
|
||||
$license->fill($this->sanitizeItemForStoring($license));
|
||||
}
|
||||
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
|
||||
// $license->unsetEventDispatcher();
|
||||
|
||||
// This sets an attribute on the Loggable trait for the action log
|
||||
$license->setImported(true);
|
||||
if ($license->save()) {
|
||||
$license->logCreate('Imported using csv importer');
|
||||
$this->log('License '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
|
||||
|
||||
// Lets try to checkout seats if the fields exist and we have seats.
|
||||
|
||||
@@ -60,16 +60,18 @@ class CheckoutableListener
|
||||
if ($this->shouldSendWebhookNotification()) {
|
||||
|
||||
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
|
||||
if(Setting::getSettings()->webhook_selected =='slack') {
|
||||
|
||||
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
|
||||
|
||||
|
||||
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
|
||||
->notify($this->getCheckoutNotification($event));
|
||||
}
|
||||
}
|
||||
} catch (ClientException $e) {
|
||||
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
|
||||
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkout notification: " . $e->getMessage());
|
||||
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ class CheckoutableListener
|
||||
);
|
||||
}
|
||||
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
|
||||
if(Setting::getSettings()->webhook_selected =='slack') {
|
||||
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
|
||||
|
||||
if ($this->shouldSendWebhookNotification()) {
|
||||
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
|
||||
@@ -122,9 +124,9 @@ class CheckoutableListener
|
||||
}
|
||||
|
||||
} catch (ClientException $e) {
|
||||
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
|
||||
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
Log::error("Exception caught during checkin notification: " . $e->getMessage());
|
||||
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->category->require_acceptance;
|
||||
return $this->category->require_acceptance ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,9 @@ class Actionlog extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// This is to manually set the source (via setActionSource()) for determineActionSource()
|
||||
protected ?string $source = null;
|
||||
|
||||
protected $presenter = \App\Presenters\ActionlogPresenter::class;
|
||||
use SoftDeletes;
|
||||
use Presentable;
|
||||
@@ -341,7 +344,12 @@ class Actionlog extends SnipeModel
|
||||
* @since v6.3.0
|
||||
* @return string
|
||||
*/
|
||||
public function determineActionSource() {
|
||||
public function determineActionSource(): string
|
||||
{
|
||||
// This is a manually set source
|
||||
if($this->source) {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
// This is an API call
|
||||
if (((request()->header('content-type') && (request()->header('accept'))=='application/json'))
|
||||
@@ -358,4 +366,10 @@ class Actionlog extends SnipeModel
|
||||
return 'cli/unknown';
|
||||
|
||||
}
|
||||
|
||||
// Manually sets $this->source for determineActionSource()
|
||||
public function setActionSource($source = null): void
|
||||
{
|
||||
$this->source = $source;
|
||||
}
|
||||
}
|
||||
|
||||
+70
-9
@@ -74,9 +74,9 @@ class Asset extends Depreciable
|
||||
'eol_explicit' => 'boolean',
|
||||
'last_checkout' => 'datetime',
|
||||
'last_checkin' => 'datetime',
|
||||
'expected_checkin' => 'date',
|
||||
'expected_checkin' => 'datetime:m-d-Y',
|
||||
'last_audit_date' => 'datetime',
|
||||
'next_audit_date' => 'date',
|
||||
'next_audit_date' => 'datetime:m-d-Y',
|
||||
'model_id' => 'integer',
|
||||
'status_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
@@ -96,7 +96,10 @@ class Asset extends Depreciable
|
||||
'company_id' => 'nullable|integer|exists:companies,id',
|
||||
'warranty_months' => 'nullable|numeric|digits_between:0,240',
|
||||
'last_checkout' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'last_checkin' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'expected_checkin' => 'nullable|date',
|
||||
'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'next_audit_date' => 'nullable|date|after:last_audit_date',
|
||||
'location_id' => 'nullable|exists:locations,id',
|
||||
'rtd_location_id' => 'nullable|exists:locations,id',
|
||||
'purchase_date' => 'nullable|date|date_format:Y-m-d',
|
||||
@@ -167,6 +170,8 @@ class Asset extends Depreciable
|
||||
'expected_checkin',
|
||||
'next_audit_date',
|
||||
'last_audit_date',
|
||||
'last_checkin',
|
||||
'last_checkout',
|
||||
'asset_eol_date',
|
||||
];
|
||||
|
||||
@@ -1158,10 +1163,11 @@ class Asset extends Depreciable
|
||||
public function scopeDueForAudit($query, $settings)
|
||||
{
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
|
||||
|
||||
return $query->whereNotNull('assets.next_audit_date')
|
||||
->where('assets.next_audit_date', '>=', Carbon::now())
|
||||
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
|
||||
->whereBetween('assets.next_audit_date', [$today->format('Y-m-d'), $interval_date])
|
||||
->where('assets.archived', '=', 0)
|
||||
->NotArchived();
|
||||
}
|
||||
@@ -1183,7 +1189,7 @@ class Asset extends Depreciable
|
||||
public function scopeOverdueForAudit($query)
|
||||
{
|
||||
return $query->whereNotNull('assets.next_audit_date')
|
||||
->where('assets.next_audit_date', '<', Carbon::now())
|
||||
->where('assets.next_audit_date', '<', Carbon::now()->format('Y-m-d'))
|
||||
->where('assets.archived', '=', 0)
|
||||
->NotArchived();
|
||||
}
|
||||
@@ -1204,14 +1210,69 @@ class Asset extends Depreciable
|
||||
|
||||
public function scopeDueOrOverdueForAudit($query, $settings)
|
||||
{
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
|
||||
return $query->whereNotNull('assets.next_audit_date')
|
||||
->whereRaw('DATE_SUB('.DB::getTablePrefix()."assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
|
||||
return $query->where(function ($query) {
|
||||
$query->OverdueForAudit();
|
||||
})->orWhere(function ($query) use ($settings) {
|
||||
$query->DueForAudit($settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope for Assets that are DUE for checkin, based on the assets.expected_checkin
|
||||
* and settings.audit_warning_days. It checks to see if assets.expected_checkin is now
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.4.0
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
|
||||
public function scopeDueForCheckin($query, $settings)
|
||||
{
|
||||
$interval = $settings->audit_warning_days ?? 0;
|
||||
$today = Carbon::now();
|
||||
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
|
||||
|
||||
return $query->whereNotNull('assets.expected_checkin')
|
||||
->whereBetween('assets.expected_checkin', [$today->format('Y-m-d'), $interval_date])
|
||||
->where('assets.archived', '=', 0)
|
||||
->whereNotNull('assets.assigned_to')
|
||||
->NotArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for Assets that are overdue for checkin OR overdue
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.4.0
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOverdueForCheckin($query)
|
||||
{
|
||||
return $query->whereNotNull('assets.expected_checkin')
|
||||
->where('assets.expected_checkin', '<', Carbon::now()->format('Y-m-d'))
|
||||
->where('assets.archived', '=', 0)
|
||||
->whereNotNull('assets.assigned_to')
|
||||
->NotArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for Assets that are due for checkin OR overdue
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.4.0
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeDueOrOverdueForCheckin($query, $settings)
|
||||
{
|
||||
return $query->where(function ($query) {
|
||||
$query->OverdueForCheckin();
|
||||
})->orWhere(function ($query) use ($settings) {
|
||||
$query->DueForCheckin($settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope for Archived assets counting
|
||||
@@ -1560,7 +1621,7 @@ class Asset extends Depreciable
|
||||
*
|
||||
* In short, this set of statements tells the query builder to ONLY query against an
|
||||
* actual field that's being passed if it doesn't meet known relational fields. This
|
||||
* allows us to query custom fields directly in the assetsv table
|
||||
* allows us to query custom fields directly in the assets table
|
||||
* (regardless of their name) and *skip* any fields that we already know can only be
|
||||
* searched through relational searches that we do earlier in this method.
|
||||
*
|
||||
|
||||
@@ -62,7 +62,15 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['title', 'notes', 'asset_maintenance_type', 'cost', 'start_date', 'completion_date'];
|
||||
protected $searchableAttributes =
|
||||
[
|
||||
'title',
|
||||
'notes',
|
||||
'asset_maintenance_type',
|
||||
'cost',
|
||||
'start_date',
|
||||
'completion_date'
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
@@ -70,9 +78,10 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'asset' => ['name', 'asset_tag'],
|
||||
'asset' => ['name', 'asset_tag', 'serial'],
|
||||
'asset.model' => ['name', 'model_number'],
|
||||
'asset.supplier' => ['name'],
|
||||
'asset.assetstatus' => ['name'],
|
||||
'supplier' => ['name'],
|
||||
];
|
||||
|
||||
@@ -197,6 +206,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
->orderBy('suppliers_maintenances.name', $order);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to order on admin user
|
||||
*
|
||||
@@ -239,4 +249,33 @@ class AssetMaintenance extends Model implements ICompanyableChild
|
||||
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
|
||||
->orderBy('assets.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on serial
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderByAssetSerial($query, $order)
|
||||
{
|
||||
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
|
||||
->orderBy('assets.serial', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on status label name
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderStatusName($query, $order)
|
||||
{
|
||||
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
|
||||
->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id')
|
||||
->orderBy('maintained_asset_status.name', $order);
|
||||
}
|
||||
}
|
||||
|
||||
+142
-64
@@ -81,26 +81,6 @@ final class Company extends SnipeModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
if (Auth::user()) {
|
||||
$company_id = Auth::user()->company_id;
|
||||
} else {
|
||||
$company_id = null;
|
||||
}
|
||||
|
||||
$table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
|
||||
|
||||
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
|
||||
return $query->where($table.$column, '=', $company_id);
|
||||
} else {
|
||||
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getIdFromInput($unescaped_input)
|
||||
{
|
||||
@@ -113,6 +93,14 @@ final class Company extends SnipeModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the company id for the current user taking into
|
||||
* account the full multiple company support setting
|
||||
* and if the current user is a super user.
|
||||
*
|
||||
* @param $unescaped_input
|
||||
* @return int|mixed|string|null
|
||||
*/
|
||||
public static function getIdForCurrentUser($unescaped_input)
|
||||
{
|
||||
if (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
@@ -133,25 +121,49 @@ final class Company extends SnipeModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the current user should have access to the model.
|
||||
* I hate this method and I think it should be refactored.
|
||||
*
|
||||
* @param $companyable
|
||||
* @return bool|void
|
||||
*/
|
||||
public static function isCurrentUserHasAccess($companyable)
|
||||
{
|
||||
// When would this even happen tho??
|
||||
if (is_null($companyable)) {
|
||||
return false;
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
} elseif (!$companyable instanceof Company && !\Schema::hasColumn($companyable->getModel()->getTable(), 'company_id')) {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
return true;
|
||||
} else {
|
||||
if (Auth::user()) {
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
$companyable_company_id = $companyable->company_id;
|
||||
}
|
||||
|
||||
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser();
|
||||
// If FMCS is not enabled, everyone has access, return true
|
||||
if (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Again, where would this happen? But check that $companyable is not a string
|
||||
if (!is_string($companyable)) {
|
||||
$company_table = $companyable->getModel()->getTable();
|
||||
try {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
if (!$companyable instanceof Company && !\Schema::hasColumn($company_table, 'company_id')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Auth::user()) {
|
||||
\Log::warning('Companyable is '.$companyable);
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
$companyable_company_id = $companyable->company_id;
|
||||
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function isCurrentUserAuthorized()
|
||||
@@ -182,6 +194,10 @@ final class Company extends SnipeModel
|
||||
&& ($this->users()->count() === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $unescaped_input
|
||||
* @return int|mixed|string|null
|
||||
*/
|
||||
public static function getIdForUser($unescaped_input)
|
||||
{
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser()) {
|
||||
@@ -191,38 +207,6 @@ final class Company extends SnipeModel
|
||||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyables($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// If not logged in and hitting this, assume we are on the command line and don't scope?'
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) {
|
||||
return $query;
|
||||
} else {
|
||||
return static::scopeCompanyablesDirectly($query, $column, $table_name);
|
||||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyableChildren(array $companyable_names, $query)
|
||||
{
|
||||
if (count($companyable_names) == 0) {
|
||||
throw new Exception('No Companyable Children to scope');
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser())) {
|
||||
return $query;
|
||||
} else {
|
||||
$f = function ($q) {
|
||||
static::scopeCompanyablesDirectly($q);
|
||||
};
|
||||
|
||||
$q = $query->where(function ($q) use ($companyable_names, $f) {
|
||||
$q2 = $q->whereHas($companyable_names[0], $f);
|
||||
|
||||
for ($i = 1; $i < count($companyable_names); $i++) {
|
||||
$q2 = $q2->orWhereHas($companyable_names[$i], $f);
|
||||
}
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
@@ -253,4 +237,98 @@ final class Company extends SnipeModel
|
||||
{
|
||||
return $this->hasMany(Component::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* START COMPANY SCOPING FOR FMCS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows the user to access items associated with that company if FMCS is enabled.
|
||||
*
|
||||
* This method is the one that the CompanyableTrait uses to contrain queries automatically, however that trait CANNOT be
|
||||
* applied to the user's model, since it causes an infinite loop against the authenticated user.
|
||||
*
|
||||
* @todo - refactor that trait to handle the user's model as well.
|
||||
*
|
||||
* @author [A. Gianotto] <snipe@snipe.net>
|
||||
* @param $query
|
||||
* @param $column
|
||||
* @param $table_name
|
||||
* @return mixed
|
||||
*/
|
||||
public static function scopeCompanyables($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// If not logged in and hitting this, assume we are on the command line and don't scope?'
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) {
|
||||
return $query;
|
||||
} else {
|
||||
\Log::debug('Fire scopeCompanyablesDirectly.');
|
||||
return static::scopeCompanyablesDirectly($query, $column, $table_name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// Get the company ID of the logged in user, or set it to null if there is no company assicoated with the user
|
||||
if (Auth::user()) {
|
||||
\Log::debug('Admin company is: '.Auth::user()->company_id);
|
||||
$company_id = Auth::user()->company_id;
|
||||
} else {
|
||||
$company_id = null;
|
||||
}
|
||||
|
||||
// Dynamically get the table name if it's not passed in, based on the model we're querying against
|
||||
$table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
|
||||
\Log::debug('Model is: '.$query->getModel());
|
||||
|
||||
\Log::debug('Table is: '.$table);
|
||||
|
||||
// If the column exists in the table, use it to scope the query
|
||||
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
|
||||
return $query->where($table.$column, '=', $company_id);
|
||||
} else {
|
||||
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I legit do not know what this method does, but we can't remove it (yet).
|
||||
*
|
||||
* This gets invoked by CompanyableChildScope, but I'm not sure what it does.
|
||||
*
|
||||
* @author [A. Gianotto] <snipe@snipe.net>
|
||||
* @param array $companyable_names
|
||||
* @param $query
|
||||
* @return mixed
|
||||
*/
|
||||
public static function scopeCompanyableChildren(array $companyable_names, $query)
|
||||
{
|
||||
\Log::debug('Company Names in scopeCompanyableChildren: '.print_r($companyable_names, true));
|
||||
|
||||
if (count($companyable_names) == 0) {
|
||||
throw new Exception('No Companyable Children to scope');
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser())) {
|
||||
return $query;
|
||||
} else {
|
||||
$f = function ($q) {
|
||||
\Log::debug('scopeCompanyablesDirectly firing ');
|
||||
static::scopeCompanyablesDirectly($q);
|
||||
};
|
||||
|
||||
$q = $query->where(function ($q) use ($companyable_names, $f) {
|
||||
$q2 = $q->whereHas($companyable_names[0], $f);
|
||||
|
||||
for ($i = 1; $i < count($companyable_names); $i++) {
|
||||
$q2 = $q2->orWhereHas($companyable_names[$i], $f);
|
||||
}
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,13 @@ namespace App\Models;
|
||||
trait CompanyableTrait
|
||||
{
|
||||
/**
|
||||
* Boot the companyable trait for a model.
|
||||
* This trait is used to scope models to the current company. To use this scope on companyable models,
|
||||
* we use the "use Companyable;" statement at the top of the mode.
|
||||
*
|
||||
* We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be
|
||||
* applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users.
|
||||
*
|
||||
* @see \App\Models\Company\Company::scopeCompanyables()
|
||||
* @return void
|
||||
*/
|
||||
public static function bootCompanyableTrait()
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Models;
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class CustomFieldset extends Model
|
||||
@@ -92,8 +94,19 @@ class CustomFieldset extends Model
|
||||
|
||||
array_push($rule, $field->attributes['format']);
|
||||
$rules[$field->db_column_name()] = $rule;
|
||||
//add not_array to rules for all fields
|
||||
$rules[$field->db_column_name()][] = 'not_array';
|
||||
|
||||
// add not_array to rules for all fields but checkboxes
|
||||
if ($field->element != 'checkbox') {
|
||||
$rules[$field->db_column_name()][] = 'not_array';
|
||||
}
|
||||
|
||||
if ($field->element == 'checkbox') {
|
||||
$rules[$field->db_column_name()][] = 'checkboxes';
|
||||
}
|
||||
|
||||
if ($field->element == 'radio') {
|
||||
$rules[$field->db_column_name()][] = 'radio_buttons';
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
||||
@@ -58,6 +58,18 @@ class Group extends SnipeModel
|
||||
return $this->belongsToMany(\App\Models\User::class, 'users_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that created the group
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JSON permissions into array
|
||||
*
|
||||
|
||||
@@ -160,75 +160,27 @@ class DefaultLabel extends RectangleSheet
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
}
|
||||
|
||||
// Fields
|
||||
// Render the selected fields with their labels
|
||||
$fieldsDone = 0;
|
||||
if ($settings->labels_display_name && $fieldsDone < $this->getSupportFields()) {
|
||||
if ($asset->name) {
|
||||
static::writeText(
|
||||
$pdf, 'N: '.$asset->name,
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
if ($settings->labels_display_company_name && $fieldsDone < $this->getSupportFields()) {
|
||||
if ($asset->company) {
|
||||
static::writeText(
|
||||
$pdf, 'C: '.$asset->company->name,
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
if ($settings->labels_display_tag && $fieldsDone < $this->getSupportFields()) {
|
||||
if ($asset->asset_tag) {
|
||||
static::writeText(
|
||||
$pdf, 'T: '.$asset->asset_tag,
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
if ($settings->labels_display_serial && $fieldsDone < $this->getSupportFields()) {
|
||||
if ($asset->serial) {
|
||||
static::writeText(
|
||||
$pdf, 'S: '.$asset->serial,
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
if ($settings->labels_display_model && $fieldsDone < $this->getSupportFields()) {
|
||||
if ($asset->model) {
|
||||
static::writeText(
|
||||
$pdf, 'M: '.$asset->model->name,
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
if ($fieldsDone < $this->getSupportFields()) {
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
|
||||
// Actually write the selected fields and their matching values
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -18,8 +18,14 @@ class FieldOption {
|
||||
// assignedTo directly on the asset is a special case where
|
||||
// we want to avoid returning the property directly
|
||||
// and instead return the entity's presented name.
|
||||
if ($dataPath[0] === 'assignedTo'){
|
||||
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
|
||||
if ($dataPath[0] === 'assignedTo') {
|
||||
if ($asset->relationLoaded('assignedTo')) {
|
||||
// If the "assignedTo" relationship was eager loaded then the way to get the
|
||||
// relationship changes from $asset->assignedTo to $asset->assigned.
|
||||
return $asset->assigned ? $asset->assigned->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $dataPath->reduce(function ($myValue, $path) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\Label;
|
||||
|
||||
abstract class TZe_18mm extends Label
|
||||
{
|
||||
private const HEIGHT = 18.00;
|
||||
private const MARGIN_SIDES = 3.20;
|
||||
private const MARGIN_ENDS = 3.20;
|
||||
|
||||
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); }
|
||||
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
|
||||
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
|
||||
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
class TZe_18mm_A extends TZe_18mm
|
||||
{
|
||||
private const BARCODE_SIZE = 3.20;
|
||||
private const BARCODE_MARGIN = 0.30;
|
||||
private const TEXT_SIZE_MOD = 1.00;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 50.0; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return false; }
|
||||
public function getSupportFields() { return 1; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return false; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$pa->x1, $pa->y1, $pa->w, self::BARCODE_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
$currentY = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN;
|
||||
$usableHeight = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN;
|
||||
$fontSize = $usableHeight + self::TEXT_SIZE_MOD;
|
||||
|
||||
$tagWidth = $pa->w / 3;
|
||||
$fieldWidth = $pa->w / 3 * 2;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $currentY,
|
||||
'freemono', 'b', $fontSize, 'L',
|
||||
$tagWidth, $usableHeight, true, 0, 0
|
||||
);
|
||||
|
||||
if ($record->get('fields')->count() >= 1) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('fields')->values()->get(0)['value'],
|
||||
$pa->x1 + ($tagWidth), $currentY,
|
||||
'freemono', 'b', $fontSize, 'R',
|
||||
$fieldWidth, $usableHeight, true, 0, 0
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
|
||||
class LabelWriter_1933081 extends LabelWriter
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.80;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 2.80;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 89; }
|
||||
public function getHeight() { return 25; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 5; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', 'b', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
|
||||
class LabelWriter_2112283 extends LabelWriter
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.80;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 2.80;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 54; }
|
||||
public function getHeight() { return 25; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 5; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', 'b', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+22
-2
@@ -53,6 +53,7 @@ class License extends Depreciable
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'expiration_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'termination_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'min_amt' => 'numeric|nullable|gte:0',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -80,7 +81,9 @@ class License extends Depreciable
|
||||
'serial',
|
||||
'supplier_id',
|
||||
'termination_date',
|
||||
'free_seat_count',
|
||||
'user_id',
|
||||
'min_amt',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
@@ -112,6 +115,7 @@ class License extends Depreciable
|
||||
'category' => ['name'],
|
||||
'depreciation' => ['name'],
|
||||
];
|
||||
protected $appends = ['free_seat_count'];
|
||||
|
||||
/**
|
||||
* Update seat counts when the license is updated
|
||||
@@ -278,6 +282,16 @@ class License extends Depreciable
|
||||
}
|
||||
$this->attributes['termination_date'] = $value;
|
||||
}
|
||||
/**
|
||||
* Sets free_seat_count attribute
|
||||
*
|
||||
* @author G. Martinez
|
||||
* @since [v6.3]
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFreeSeatCountAttribute(){
|
||||
return $this->attributes['free_seat_count'] = $this->remaincount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> company relationship
|
||||
@@ -500,7 +514,13 @@ class License extends Depreciable
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available seats remaining
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return int
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the number of total available seats for this license
|
||||
@@ -577,7 +597,7 @@ class License extends Depreciable
|
||||
$taken = $this->assigned_seats_count;
|
||||
$diff = ($total - $taken);
|
||||
|
||||
return $diff;
|
||||
return (int) $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,10 @@ class Location extends SnipeModel
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not this location can be deleted
|
||||
* Determine whether or not this location can be deleted.
|
||||
*
|
||||
* This method requires the eager loading of the relationships in order to determine whether
|
||||
* it can be deleted. It's tempting to load those here, but that increases the query load considerably.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
@@ -103,10 +106,12 @@ class Location extends SnipeModel
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assignedAssets()->count() === 0)
|
||||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->users()->count() === 0);
|
||||
&& ($this->assets_count === 0)
|
||||
&& ($this->assigned_assets_count === 0)
|
||||
&& ($this->children_count === 0)
|
||||
&& ($this->users_count === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
trait Loggable
|
||||
{
|
||||
// an attribute for setting whether or not the item was imported
|
||||
public ?bool $imported = false;
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
@@ -18,6 +21,11 @@ trait Loggable
|
||||
return $this->morphMany(Actionlog::class, 'item');
|
||||
}
|
||||
|
||||
public function setImported(bool $bool): void
|
||||
{
|
||||
$this->imported = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
|
||||
@@ -352,7 +352,6 @@ class Setting extends Model
|
||||
'ldap_client_tls_cert',
|
||||
'ldap_default_group',
|
||||
'ldap_dept',
|
||||
'ldap_emp_num',
|
||||
'ldap_phone_field',
|
||||
'ldap_jobtitle',
|
||||
'ldap_manager',
|
||||
|
||||
+3
-1
@@ -67,6 +67,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'gravatar',
|
||||
'vip',
|
||||
'autoassign_licenses',
|
||||
'website',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -120,6 +121,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'phone',
|
||||
'jobtitle',
|
||||
'employee_num',
|
||||
'website',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -335,7 +337,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id');
|
||||
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -38,13 +43,17 @@ class CheckinAccessoryNotification extends Notification
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
@@ -54,34 +63,8 @@ class CheckinAccessoryNotification extends Notification
|
||||
if ($this->target instanceof User && $this->target->email != '') {
|
||||
\Log::debug('The target is a user');
|
||||
|
||||
/**
|
||||
* Send an email if the asset requires acceptance,
|
||||
* so the user can accept or decline the asset
|
||||
*/
|
||||
if (($this->item->requireAcceptance()) || ($this->item->getEula()) || ($this->item->checkin_email())) {
|
||||
$notifyBy[] = 'mail';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email if the asset requires acceptance,
|
||||
* so the user can accept or decline the asset
|
||||
*/
|
||||
if ($this->item->requireAcceptance()) {
|
||||
\Log::debug('This accessory requires acceptance');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email if the item has a EULA, since the user should always receive it
|
||||
*/
|
||||
if ($this->item->getEula()) {
|
||||
\Log::debug('This accessory has a EULA');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email if an email should be sent at checkin/checkout
|
||||
*/
|
||||
if ($this->item->checkin_email()) {
|
||||
\Log::debug('This accessory has a checkin_email()');
|
||||
$notifyBy[] = 'mail';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +115,32 @@ class CheckinAccessoryNotification extends Notification
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Accessory_Checkin_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.checked_into').': '.$item->location->name ? $item->location->name : '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
|
||||
trans('admin/hardware/form.notes').": ".$note ?: '',
|
||||
)
|
||||
->onClick(route('accessories.show', $item->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -10,6 +10,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -46,12 +51,16 @@ class CheckinAssetNotification extends Notification
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
\Log::debug('use webhook');
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
@@ -108,6 +117,33 @@ class CheckinAssetNotification extends Notification
|
||||
->fact(trans('admin/hardware/form.status'), $item->assetstatus->name)
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Asset_Checkin_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.checked_into') ?: '',
|
||||
$item->location->name ? $item->location->name : '',
|
||||
trans('admin/hardware/form.status').": ".$item->assetstatus->name,
|
||||
)
|
||||
->onClick(route('hardware.show', $item->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -43,12 +48,16 @@ class CheckinLicenseSeatNotification extends Notification
|
||||
{
|
||||
$notifyBy = [];
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
@@ -113,6 +122,34 @@ class CheckinLicenseSeatNotification extends Notification
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.License_Checkin_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.checkedin_from') ?: '',
|
||||
$target->present()->fullName() ?: '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
|
||||
)
|
||||
->onClick(route('licenses.show', $item->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -37,13 +42,17 @@ class CheckoutAccessoryNotification extends Notification
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
@@ -123,6 +132,34 @@ class CheckoutAccessoryNotification extends Notification
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Accessory_Checkout_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.assigned_to') ?: '',
|
||||
$target->present()->name ?: '',
|
||||
trans('admin/consumables/general.remaining').": ". $item->numRemaining(),
|
||||
)
|
||||
->onClick(route('users.show', $target->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -11,6 +11,13 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\Enums\Icon;
|
||||
use NotificationChannels\GoogleChat\Enums\ImageStyle;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -54,13 +61,20 @@ class CheckoutAssetNotification extends Notification
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
|
||||
return [MicrosoftTeamsChannel::class];
|
||||
}
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
|
||||
if ((Setting::getSettings()) && (Setting::getSettings()->webhook_selected == 'slack')) {
|
||||
\Log::debug('use webhook');
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
@@ -143,6 +157,33 @@ class CheckoutAssetNotification extends Notification
|
||||
|
||||
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Asset_Checkout_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.assigned_to') ?: '',
|
||||
$target->present()->name ?: '',
|
||||
$note ?: '',
|
||||
)
|
||||
->onClick(route('users.show', $target->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -44,13 +49,17 @@ class CheckoutConsumableNotification extends Notification
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
@@ -128,6 +137,33 @@ class CheckoutConsumableNotification extends Notification
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Consumable_checkout_notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.assigned_to') ?: '',
|
||||
$target->present()->fullName() ?: '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
|
||||
)
|
||||
->onClick(route('users.show', $target->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
@@ -43,15 +48,18 @@ class CheckoutLicenseSeatNotification extends Notification
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
|
||||
$notifyBy = [];
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'google'){
|
||||
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft'){
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack') {
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
@@ -129,6 +137,33 @@ class CheckoutLicenseSeatNotification extends Notification
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.License_Checkout_Notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.assigned_to') ?: '',
|
||||
$target->present()->name ?: '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
|
||||
)
|
||||
->onClick(route('users.show', $target->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
|
||||
@@ -38,6 +38,9 @@ class AccessoryObserver
|
||||
$logAction->item_id = $accessory->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
if($accessory->imported) {
|
||||
$logAction->setActionSource('importer');
|
||||
}
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,9 @@ class AssetObserver
|
||||
$logAction->item_id = $asset->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
if($asset->imported) {
|
||||
$logAction->setActionSource('importer');
|
||||
}
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ class ComponentObserver
|
||||
$logAction->item_id = $component->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
if($component->imported) {
|
||||
$logAction->setActionSource('importer');
|
||||
}
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ class ConsumableObserver
|
||||
$logAction->item_id = $consumable->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
if($consumable->imported) {
|
||||
$logAction->setActionSource('importer');
|
||||
}
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ class LicenseObserver
|
||||
$logAction->item_id = $license->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
if($license->imported) {
|
||||
$logAction->setActionSource('importer');
|
||||
}
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
|
||||
@@ -35,16 +35,50 @@ abstract class SnipePermissionsPolicy
|
||||
|
||||
public function before(User $user, $ability, $item)
|
||||
{
|
||||
// Lets move all company related checks here.
|
||||
if ($item instanceof \App\Models\SnipeModel && ! Company::isCurrentUserHasAccess($item)) {
|
||||
return false;
|
||||
}
|
||||
// If an admin, they can do all asset related tasks.
|
||||
/**
|
||||
* If an admin, they can do all item related tasks, but ARE constrained by FMCSA company access.
|
||||
* That scoping happens on the model level (except for the Users model) via the Companyable trait.
|
||||
*
|
||||
* This does lead to some inconsistencies in the responses, since attempting to edit assets,
|
||||
* accessories, etc (anything other than users) will result in a Forbidden error, whereas the users
|
||||
* area will redirect with "That user doesn't exist" since the scoping is handled directly on those queries.
|
||||
*
|
||||
* The *superuser* global permission gets handled in the AuthServiceProvider before() method.
|
||||
*
|
||||
* @see https://snipe-it.readme.io/docs/permissions
|
||||
*/
|
||||
|
||||
if ($user->hasAccess('admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we got here by $this→authorize('something', $actualModel) then we can continue on Il but if we got here
|
||||
* via $this→authorize('something', Model::class) then calling Company:: isCurrentUserHasAccess($item) gets weird.
|
||||
* Bail out here by returning "nothing" and allow the relevant method lower in this class to be called and handle authorization.
|
||||
*/
|
||||
if (!$item instanceof Model){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Company::isCurrentUserHasAccess() method from the company model handles the check for FMCS already so we
|
||||
* don't have to do that here.
|
||||
*/
|
||||
if (!Company::isCurrentUserHasAccess($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* These methods handle the generic view/create/edit/delete permissions for the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function index(User $user)
|
||||
{
|
||||
return $user->hasAccess($this->columnName().'.view');
|
||||
|
||||
@@ -41,6 +41,7 @@ class AccessoryPresenter extends Presenter
|
||||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'switchable' => false,
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'accessoriesLinkFormatter',
|
||||
], [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user