Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2daaf30eb | |||
| 6800098469 | |||
| 468805bab5 | |||
| cd49f65c0b | |||
| dcbd216e2c | |||
| 008ba83128 | |||
| 697cb4fe6d | |||
| 8a395d63a1 | |||
| 4d1e28ad03 | |||
| 47eccb665e | |||
| 150a2b572e | |||
| 6bc0df73e8 | |||
| 16dcc2d3ed | |||
| 43c286c61f | |||
| aa4bdd4eb3 | |||
| e869b1fd53 | |||
| 13ad69ee75 | |||
| 642d1e9f36 | |||
| 3792d3fdd2 | |||
| 317335e79f | |||
| fbb3684022 | |||
| b91e914a61 | |||
| fc3672c119 | |||
| 9d2c4a4dee | |||
| fe6295a5e4 | |||
| aa8dfcf89f | |||
| e8e1ada776 | |||
| 58ea1592bb | |||
| f3785acf4d | |||
| 545d023a18 | |||
| 473211397f | |||
| e28d4fa588 | |||
| e08327be95 | |||
| 418ce75a9f | |||
| 6fe520a55d | |||
| 0418d390f0 | |||
| b3d5bb75a3 | |||
| 5346ebba94 | |||
| 6c2c36469a | |||
| 1fa9f80c03 | |||
| 4efac7827b | |||
| b45dc09f57 | |||
| 76425ca2a5 | |||
| e609130291 | |||
| 41eb2464e2 | |||
| 8aec28fad6 | |||
| e55fc4d047 | |||
| e40762cf7f | |||
| 2d3ddd1c7a | |||
| 637438ccf6 | |||
| 647f47cdfe | |||
| a14d1b981d | |||
| 5217141dfb | |||
| b28a25f544 | |||
| d55ae44638 | |||
| f29e813332 | |||
| d883084830 | |||
| 658252125e | |||
| 35bef6625a | |||
| 342b399191 | |||
| 2754277068 | |||
| 4cc29b76f0 | |||
| 7059a57be1 | |||
| c229d69cec | |||
| 3c28242a2e | |||
| c41aa8ac7f | |||
| ecb11c3a12 | |||
| 4f8542b71b | |||
| bb7b655fd2 | |||
| 3134279a95 | |||
| 751abe1438 | |||
| d214bd0b06 | |||
| 3b16a65988 | |||
| 918239961a | |||
| 272cc1adac | |||
| 16e3416225 | |||
| 8012c966b0 | |||
| a0e547c7fb | |||
| 44a91f0a2b | |||
| 4537ab3d54 | |||
| 05b4170116 | |||
| 92e0c59f89 | |||
| 1bad8e1fc2 | |||
| 1fd574779b | |||
| a37d69fcf1 | |||
| 3a4ca5acad | |||
| 704a2ad858 | |||
| 3a30f04b51 | |||
| f2a3c2a349 | |||
| f9e190eb32 | |||
| de6b663833 | |||
| 8dd3ae8a37 | |||
| 03b4a87a65 | |||
| c7f4a93c17 | |||
| 071a642509 | |||
| e39a75f2cc | |||
| ea4a37d8ef | |||
| d7363a41c8 | |||
| 789c4b4df8 | |||
| 12a2d8ad49 | |||
| e47fc9527b | |||
| 820ae09a6f | |||
| fb54a21e12 | |||
| b39fa1d987 | |||
| 28cc8f9d69 | |||
| a54e50961e | |||
| 82dc57aa18 | |||
| 9ac4efb912 | |||
| a4e6b3c022 | |||
| c752e3bb86 | |||
| 934f43e104 | |||
| 1a8622dc3b | |||
| e557f03cc2 | |||
| 30cf7eb750 | |||
| ccdd6ef5e0 | |||
| 13a02ff241 | |||
| c93096b767 | |||
| 94a275f239 | |||
| d8d7ed4465 | |||
| 736777e1e6 | |||
| 3849bb838d | |||
| 0ef8aba36f | |||
| f566a71658 | |||
| 0dd11575c5 | |||
| d3d26cb988 | |||
| 07fbbf5f7f | |||
| 0dfb51f642 | |||
| 853e809ff8 | |||
| 605f214597 | |||
| d9c1a548f1 | |||
| 582c3b987b | |||
| ffd9bbd7c6 | |||
| ffb87a0a87 | |||
| 8f04342fc2 | |||
| d6434fa55a | |||
| b374834999 | |||
| 5e4455f042 | |||
| f47a28f790 | |||
| ca677038b0 | |||
| a50ddb4a5d | |||
| daf6e9fa4b | |||
| 31630e3677 | |||
| 2f0f9586b1 | |||
| a98cc01766 | |||
| 345946d6d4 | |||
| 76861c0c30 | |||
| 7062b0acaa | |||
| 0ec885bf18 | |||
| dfd6cf0d29 | |||
| b64d7e38f2 | |||
| 371e142bae | |||
| b944945377 | |||
| a8e2f72e25 | |||
| e9601bcf13 | |||
| b89dcfbb8e | |||
| 0909d94c8f | |||
| 68094b23b9 | |||
| fdacd5d5b5 | |||
| f38f6f57ae | |||
| a3353b8496 | |||
| f463c02783 | |||
| 08e09a3215 | |||
| 039a5dde7d | |||
| 3b3bdb6535 | |||
| 1968aebf78 | |||
| a0617edaf0 | |||
| 723e4ebd15 | |||
| fb789eb048 | |||
| 4c66267f3b | |||
| f7577bff52 | |||
| 322fd76353 | |||
| c12d261fc8 | |||
| a991bdd136 | |||
| 9cd8460e1e | |||
| 72a61783a3 | |||
| f0d05a7461 | |||
| 88624ff2c0 | |||
| f71ee699f4 | |||
| ce83d3aec1 | |||
| 60b70a4074 | |||
| 1ff094dd47 | |||
| ef8007a890 | |||
| d50a5d1880 | |||
| a2efe3fa67 | |||
| a9782b7e00 | |||
| 4c4101fe64 | |||
| 9c5ee060d3 | |||
| 7cf424b914 | |||
| e8ff7cc09c | |||
| dfd9fcc5e0 | |||
| b83980299a | |||
| 5a709a83e7 | |||
| 66bb897ac4 | |||
| 67471a3a77 | |||
| adba450b40 | |||
| 7acf449ed3 | |||
| 955d8313b2 | |||
| 1b9c3c93ad | |||
| a2667a7520 | |||
| 099ec3f550 | |||
| 6e6a021928 | |||
| 1bd37b49e9 | |||
| d224f3177e | |||
| 6474249ea8 | |||
| 7612c5d852 | |||
| e7b3daa80c | |||
| 477bd27cba | |||
| e4204a6dd1 | |||
| 11bf3f5285 | |||
| 576b8f8c48 | |||
| cacb000693 | |||
| 4f772468e6 | |||
| 542332ea0c | |||
| b6d105c4f4 | |||
| 27a2ed5c79 | |||
| 8a977c66f0 | |||
| 82504c9764 | |||
| 1e6a3b8c45 | |||
| 785ba3b5ed | |||
| d1d89f872a | |||
| 58d2e658d2 | |||
| 86205bc996 | |||
| 4533982345 | |||
| 65c2f27644 | |||
| 88eb73d160 | |||
| f4bd307421 | |||
| 059ead5af5 | |||
| 02dd0669dc | |||
| b3b9efbaff | |||
| a6f4e53e2b | |||
| fe470b32fc | |||
| 474e32ea2a | |||
| b9e4b2a0ba | |||
| 087fe19ae7 | |||
| 22d7d2a04e | |||
| b8887bd3e3 | |||
| 65c2d75c04 | |||
| dccd6fd4b1 | |||
| 7f8bdbd252 | |||
| 00896a8615 | |||
| 44b48a954a | |||
| 0be882dc35 | |||
| 2a4d30a748 | |||
| 5fb18af245 | |||
| 697824007a | |||
| cd9f2f7fb7 | |||
| 15c19a7ab7 | |||
| ac0b22d696 | |||
| 1ed7cd27b4 | |||
| e143e0c5ee | |||
| 42601fecbf | |||
| 7c09de3ea8 | |||
| 094859cfe8 | |||
| 037489a6c0 | |||
| 5defd5ec0a | |||
| e66b19aca0 | |||
| fd96ff0160 | |||
| 1ccedf6426 | |||
| 8adeee41e6 | |||
| 19d19a4c42 | |||
| 383b5b43bb | |||
| 379c5f6790 | |||
| e55d02fd59 | |||
| 942bedaf06 | |||
| 09c1298e03 | |||
| fe699737eb | |||
| dd9c00195b | |||
| e60fa15414 | |||
| 43ff7261b2 | |||
| 8c13a4c5c9 | |||
| 69944853f9 | |||
| 45b5a8b6ab | |||
| 35dd0d8def | |||
| 8b70a7f216 | |||
| 182a5ea5fc | |||
| ea54f540de | |||
| ec5836dab4 | |||
| e399015427 | |||
| c80595e05c | |||
| 8fad377114 | |||
| f4675927a0 | |||
| a3c8c3757a | |||
| ddcb8d8dd9 | |||
| 245f0aff72 | |||
| ba92501670 | |||
| 9aad981895 | |||
| a24d734ee9 | |||
| c7fb542dc3 | |||
| 1ebbc767f1 | |||
| a34cb8b862 | |||
| d36b4c1bd3 | |||
| d27fe7d4b9 | |||
| d55393a3d9 | |||
| ef27c35d1d | |||
| 0d3388b3ae | |||
| fffe5da2e0 | |||
| dd3c3a1428 | |||
| 16bd2cde0f | |||
| e1069ac234 | |||
| 8b6e8898c1 | |||
| 2cb06165c4 | |||
| b46c455e0a | |||
| 304ccaa30e | |||
| f672bdd3b7 | |||
| eec5163e7f | |||
| f460d881da | |||
| 27a495223d | |||
| bc54144284 | |||
| cc7325074e | |||
| 04ff472428 | |||
| 3a1cf26e98 | |||
| e5deb4b413 | |||
| f54d83ed59 | |||
| 39193a71b7 | |||
| 83b906b153 | |||
| 191f955333 | |||
| 548ae7ad22 | |||
| 1d4f4b92de | |||
| 0b3becee7a | |||
| 95f568b934 | |||
| 95bd58487a | |||
| 8a5d426ccd | |||
| e8da6d8bac | |||
| ad9eef6393 | |||
| c3cc20fe0c | |||
| f817027fc2 | |||
| 45d7e9b134 | |||
| 75fc624ec6 | |||
| e0b2dc043a | |||
| 7edec26e73 | |||
| aae178a606 | |||
| 778787db3c | |||
| b8231f420b | |||
| 9b522006f3 | |||
| 84a14918bb | |||
| 473553c464 | |||
| dac877f184 | |||
| 354f04534e | |||
| ca2619ca12 | |||
| 78b36c7886 | |||
| ca849db316 | |||
| 816d2f99cc | |||
| c1b57064d4 | |||
| 9b80e86306 | |||
| d954c841e6 | |||
| 562546a853 | |||
| 4860440306 | |||
| b72a9cc6fd | |||
| 823203641d | |||
| b2fe3c36e9 | |||
| 1911480d3a | |||
| c48e19d236 | |||
| 863b30a07a | |||
| f3c500014c | |||
| 4f8710db63 | |||
| 54cb38603b | |||
| 5cf66851f7 | |||
| a6816c5e15 | |||
| 463ef02fae | |||
| b29276579f | |||
| dbe0a3352b | |||
| 6fec3f4303 | |||
| f9b5a67dc2 | |||
| 2189a74d6a | |||
| 44e2796645 | |||
| 70d0347fe9 | |||
| 2f94bf96ff | |||
| 417a1e624a | |||
| 34f13eaa8a | |||
| 1f7ae08d76 | |||
| 3a4e20ae82 | |||
| 2ab0abc8a1 | |||
| 373aeb26da | |||
| d838662f48 | |||
| ff122bd07c | |||
| 5e626e75ad | |||
| a7a26bb154 | |||
| af9f9d2523 | |||
| d13e1b32e7 | |||
| ad598425ef | |||
| ec2ce1dff8 | |||
| 5194711a6a | |||
| 4197e613b2 | |||
| 25f3e4d41a | |||
| 589a5d0d8b | |||
| e8fbb0608f | |||
| d707f2d391 | |||
| 1be0a8d911 | |||
| 0d860c0b72 | |||
| 9d94bd6567 | |||
| ae35af55d1 | |||
| 23fe11c71e | |||
| b4e578c95e | |||
| 53db266b69 | |||
| fb201e61f3 | |||
| e1c8f155bb | |||
| 5c97e45d00 | |||
| 8009ee79db | |||
| 0a085af0a0 | |||
| c2cdf5dae1 | |||
| 23ca124e94 | |||
| c7106a1df2 | |||
| 3f4a4e72a7 | |||
| b78aba787f | |||
| 96c851468c | |||
| e8fc895e51 | |||
| a5ccef5e96 | |||
| 059190f002 | |||
| 6428cc259f | |||
| cb0b234b80 | |||
| 8caf29975f | |||
| 95501bf57c | |||
| 75a73d095f | |||
| 3e47d9e689 | |||
| 51efd9b413 | |||
| bd0b00e361 | |||
| 6bd72125bd | |||
| 1415c8c6e2 | |||
| 41253b2680 | |||
| bdac0f16ce | |||
| dde2b946da | |||
| 0b408218b0 | |||
| 5e6ebb9a65 | |||
| 8e72da42e8 | |||
| 309c0ff66f | |||
| eb6357636a | |||
| adb166fb8d | |||
| 4a9ced192b | |||
| 6c8de181cf | |||
| 5a1e5f740d | |||
| 690970b4aa | |||
| f63710fb5c | |||
| 7667b0a216 | |||
| 931ca98c02 | |||
| 225b6ef310 | |||
| 13ed2a05a3 | |||
| aae2106658 | |||
| 81cb29db5e | |||
| 05f6d0bd5e | |||
| 4bf1566d2a | |||
| 0c3115553e | |||
| 942fee4d43 | |||
| ba461d0114 | |||
| 3ae783a9c7 | |||
| d494afcf01 | |||
| 57065b8936 | |||
| 40a38486b9 | |||
| 29158dece5 | |||
| f6e4e17e5c | |||
| 8185f264a4 | |||
| 04d3938f0c | |||
| f109218e16 | |||
| 049213c3da | |||
| 9c1a71e04a | |||
| a0b05bac8d | |||
| d2ede75de8 | |||
| 6e994c209f | |||
| f33b6a3e26 | |||
| dfc2aa35f4 | |||
| f71d8b2f07 | |||
| 248eb0885c | |||
| 754842f620 | |||
| 43eacff265 | |||
| fdffe76b9f | |||
| 58801dbb0b | |||
| e9ab90eafc | |||
| 495d7e057d | |||
| 9f8ea9a243 | |||
| 3a2b54fd47 | |||
| 71334b5eae | |||
| f124249e1c | |||
| e451ca1a5c | |||
| 3e111fca6e | |||
| df64e0e291 | |||
| 51c9a7d4f8 | |||
| d8d713426d | |||
| 7dd789d5c0 | |||
| 2d8c8e8f48 | |||
| 7b510d5f2b | |||
| 221f5f5ca7 | |||
| d4634e83c9 | |||
| 6c3ffa7918 | |||
| 27e5832a28 | |||
| 2d0bd2b292 | |||
| b96f2f738d | |||
| ff895739f4 | |||
| de4f7bd274 | |||
| 25461caa2c | |||
| bfac0fc811 | |||
| 057f0be20a | |||
| 7980e2ab2e | |||
| f99f6d32fa | |||
| d088b39fb2 | |||
| 6bfd2fd5ad | |||
| 6778cf79a0 | |||
| ac4103281c | |||
| 03af417e89 | |||
| 77362b3b41 | |||
| db8c29b901 | |||
| 3d219f578e | |||
| 8c3511a00b | |||
| c1ae406049 | |||
| 1ef3112628 | |||
| 0325cbad64 | |||
| 6dc89fc2b0 | |||
| b25676c96b | |||
| 14e95c47e1 | |||
| dc43c4f68a | |||
| 0e7ac5c95e | |||
| c7f90f304d | |||
| 78a0b06a61 | |||
| 40f487beae | |||
| 2992ca1fa2 | |||
| 15f9c6af67 | |||
| 7a19da4f64 | |||
| 07f501408f | |||
| 53e449899b | |||
| b2f1ca95df | |||
| f3ab384e70 | |||
| 61bf1e91e7 | |||
| 1c54feea5b | |||
| 7423570e6b | |||
| b946a6066d | |||
| 9c25dd4eb6 | |||
| e69fcf10b7 | |||
| 937e2813e9 | |||
| a84081068d | |||
| d9857d955b | |||
| 66251bfd8a | |||
| 677f7a446e | |||
| fdce6f98ff | |||
| 5a00685c81 | |||
| 18f0b1cc3d | |||
| 6da43bc940 | |||
| 98187773f7 | |||
| 3757c7e353 | |||
| 3800cdd0da | |||
| 1ed57d30e0 | |||
| a1b0eaf128 | |||
| f7df2ae403 | |||
| 793cf4823b | |||
| c0d8ff1676 | |||
| b0b1c96ab5 | |||
| 8e00191b69 | |||
| a94f19a68e | |||
| 0cd558593d | |||
| d03e042eda | |||
| 8b183490ba | |||
| 228c59e6ed | |||
| 2dd7690d2d | |||
| 1520c9c6a7 | |||
| 4bdb1cf552 | |||
| 2349f54013 | |||
| 2bbbc8158b | |||
| 2f5b4c8e1b | |||
| 99122ccb50 | |||
| 6936efd387 | |||
| f0bf16d784 | |||
| 9a68a747a0 | |||
| 81cc9f3700 | |||
| 5714824aa7 | |||
| e79818967d | |||
| 32ed70259e | |||
| 35ab4a4cce | |||
| 1a651b33fc | |||
| 73f355f3a8 | |||
| b3881a43a7 | |||
| c2c666aef0 | |||
| 5962f1b627 | |||
| a4a3c03a7c | |||
| 201728989f | |||
| dea175bd40 | |||
| 23b1e8da27 | |||
| e883a61a48 | |||
| 16fcc6dc44 | |||
| 4f9ebf6cde | |||
| 49b6a1cc53 | |||
| 787f619a6b | |||
| 505ca48da2 | |||
| 6b8c0f9e88 | |||
| 3fdf4dea1b | |||
| 6dbcec2310 | |||
| 75f3cb3079 | |||
| c0074baa26 | |||
| 4f9587da57 | |||
| 4b2d35e05d | |||
| 321a34a9d3 | |||
| c1d484b5df | |||
| fd524c5f14 | |||
| e4cf59c034 | |||
| efac2259db | |||
| ea644f8d47 | |||
| dd5f256450 | |||
| f4617d8d5b | |||
| 32be88d796 | |||
| 219d92dcfb | |||
| d4c838a979 | |||
| ddabe7cc9f | |||
| c03079944a | |||
| cd5546e4a1 | |||
| d52d32fed9 | |||
| 7363d4e223 | |||
| c8c5dddcc1 | |||
| 1651dbb68d | |||
| d95adcae37 | |||
| 8a8575bf2d | |||
| 590cb6a15b | |||
| aeb8dfb07d | |||
| a60510c063 | |||
| 8841f04333 | |||
| ab6ca0e066 | |||
| ec2afee57d | |||
| b10ea0c25c | |||
| 686d1aaae7 | |||
| 0e57e4836e | |||
| 45e8e9bbed | |||
| 38cdcdf064 | |||
| 78343ddb7c | |||
| 74c64c1fc5 | |||
| ea0f505db7 | |||
| b37c4553ef | |||
| b0f4015bb4 | |||
| dc414d3552 | |||
| 0cefc7de15 | |||
| 9a0c66bd30 | |||
| d8e92d29c0 | |||
| 93a62c87c2 | |||
| 89eff23e44 | |||
| 41b0756286 | |||
| f9ef49f886 | |||
| 51f3857a31 | |||
| a2e47d19fc | |||
| bbf6e5e69e | |||
| b8951e2b54 | |||
| 37ce68af5b | |||
| 29851c626f | |||
| 1f60afa8e3 | |||
| 64aea8d374 | |||
| 7344bbdd7f | |||
| 782abe1cf3 | |||
| 2d3318feb6 | |||
| 78dca7fd32 | |||
| 2a8851bbd7 | |||
| 69c38bf686 | |||
| 039d159cbd | |||
| 17f1772143 | |||
| 3a86547ef5 | |||
| 4bc804e876 | |||
| 2997de2a66 | |||
| 0cfeab8c50 | |||
| c706759b5c | |||
| 1432403c9f | |||
| 67e6089805 | |||
| d9cdb92014 | |||
| ea4d5e69bc | |||
| 2da4578aa5 | |||
| 1a8fb5bcfe | |||
| 0d4ca218c5 | |||
| 4a6250a08c | |||
| 0ffd40a217 | |||
| 33d819fd41 | |||
| 90c1bfe03d | |||
| 3702dd87ce | |||
| 4d9d73483f | |||
| 4e5b8fa213 | |||
| a8643e5d35 | |||
| f95a904d90 | |||
| a45ce468cc | |||
| 3580bdae86 | |||
| 38f3843c23 | |||
| f7ae5db4f3 | |||
| 6fbd20a11b | |||
| de1141491a | |||
| 2d45ebe103 | |||
| 656344b1c7 | |||
| 7a23398c0c | |||
| 02ca5248b1 | |||
| b1179c7b1e | |||
| a4173e3881 | |||
| a2a078015f | |||
| 1e0c58c3cf | |||
| cddae4869f | |||
| a7a578bf51 | |||
| fc044528b8 | |||
| 214a1af61a | |||
| ccc7b78f62 | |||
| c2854f1bcb | |||
| 22b0b9b090 | |||
| 583a0412fe | |||
| a16604285c | |||
| 37f5cfa8ce | |||
| a63ea560ae | |||
| 79d9cff22a | |||
| 39289dd18e | |||
| 8b94b208e3 | |||
| 6fc8982478 | |||
| cc370084d1 | |||
| cd573c484d | |||
| df31a76906 | |||
| 4a54586690 | |||
| 62b4c79150 | |||
| a69cb6204a | |||
| c5d3b3ab81 | |||
| cfe9c687f9 | |||
| 91cf683f3d | |||
| 2e22eeade5 | |||
| 53d8008a5f | |||
| b363524305 | |||
| 4c3354b778 | |||
| e7917d1f93 | |||
| 6b729502c6 | |||
| be6f367565 | |||
| f0b14513db | |||
| ab12ad1140 | |||
| 9c79fc19cb | |||
| fd2a057d38 | |||
| b00333c9f5 | |||
| c9f0f5fe8b | |||
| a61ccbb965 | |||
| 91394f7262 | |||
| 3ba79600fb | |||
| 935f5018cc | |||
| 3c4f254583 | |||
| cafe8f3074 | |||
| 19cf170dd9 | |||
| 596846a94a | |||
| b92d1d3ec7 | |||
| 4b51424723 | |||
| 0f8ee22053 | |||
| 86812b024d | |||
| dc155846ca | |||
| 7a9ea91be0 | |||
| 02dcb0fbd7 | |||
| 690bfbe16a | |||
| cd582be851 | |||
| 760844de6f | |||
| e6dc61d2cf | |||
| 4c7b731acc | |||
| 35dfaddd79 | |||
| d3bcc48ae5 | |||
| 25996cbe03 | |||
| 4b01909806 | |||
| 41537a2449 | |||
| 5f9af7d509 | |||
| febfcd4803 | |||
| cad76f671a | |||
| 4104d7260f | |||
| 3587017591 | |||
| 8040d08e8c | |||
| 2f7ca37666 | |||
| abad579306 | |||
| 72295c093a | |||
| cd8cb445ba | |||
| 2dc1182582 | |||
| 93ad0d02b9 | |||
| 48c85966d7 | |||
| a0aed19f95 | |||
| f77d871dff | |||
| 3674f46ce0 | |||
| cfa342c81a | |||
| 563a252d18 | |||
| 2bdf3d3f44 | |||
| 60eab08735 | |||
| 90c1631820 | |||
| dceb77d4d4 | |||
| 46e3e8a3dd | |||
| 56090bc645 | |||
| fd73bee850 | |||
| 2564da648b | |||
| b59dcc7b30 | |||
| d020ab455d | |||
| 05a3f20d51 | |||
| 69ab42b3f9 | |||
| 0080ac92e7 | |||
| 9508a97852 | |||
| 0fa956403c | |||
| 181f96ad33 | |||
| 04e28c2d34 | |||
| f045008237 | |||
| fa9116b184 | |||
| 155977de2d | |||
| 378dc968b7 | |||
| c089f6eff2 | |||
| e506ed7950 | |||
| 07744ca7bd | |||
| 0c39c7a53d | |||
| fce620b54f | |||
| 33fe2b57f7 | |||
| 45019c65f1 | |||
| acd182e3d2 | |||
| 48b8cfcef9 | |||
| f626413502 | |||
| b80d52a9c3 | |||
| ee4b8f4694 | |||
| ca1198ceb2 | |||
| b31e000165 | |||
| 7ce230fadc | |||
| 6fbb484dfd | |||
| 956bd384b0 | |||
| 7e22663326 | |||
| c3e4d9d773 | |||
| f08babfa8a | |||
| 4ef0005e7c | |||
| dbda288a44 | |||
| eaf9332096 | |||
| 5282ca47b3 | |||
| 23a1b2d60a | |||
| 1f12137245 | |||
| 6c89f08193 | |||
| 295f0dd1f5 | |||
| 1fef992fa8 | |||
| e23ee7540d | |||
| dd8c6e92db | |||
| f4630d67be | |||
| 6f6f99f64e | |||
| 6f89e8b0d8 | |||
| 6815a16a5f | |||
| 0c3d03a4ce | |||
| 8048d06853 | |||
| 50cc69c427 | |||
| 021e54adaa | |||
| 9d0a15990e | |||
| 51da747809 | |||
| 0dd7cc9967 | |||
| 49f2573f36 | |||
| bc80f672ee | |||
| 49383ddbe0 | |||
| 9a3a796e17 | |||
| e8c2b84d24 | |||
| 4f8951f6f1 | |||
| 85b712e915 | |||
| ca244911ed | |||
| 942681bee5 | |||
| 459c95064e | |||
| b5ea8b8a4f | |||
| aec64fa64a | |||
| 03938d0f32 | |||
| 296c9a723d | |||
| 2eac03db4a | |||
| c5b09ed955 | |||
| 4ee3cbf60e | |||
| 39ab545cf5 | |||
| e5cb68cc5e | |||
| b8b2543b0d | |||
| bc0f666906 | |||
| 581655f756 | |||
| 2aa50859b3 | |||
| 0b0fdd8aa5 | |||
| 8f3a237ea0 | |||
| 151719a91c | |||
| 825df2cf75 | |||
| b66cd313b9 | |||
| d508374c57 | |||
| f50b622eb8 | |||
| bef4224e14 | |||
| 249b188654 | |||
| 2170343958 | |||
| 1cf7784bb8 | |||
| 8a0517fb5d | |||
| 90828e3a87 | |||
| 45636b8114 | |||
| e457b2e98d | |||
| 0617480f73 |
@@ -2837,6 +2837,51 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AndrewSav",
|
||||
"name": "Andrew Savinykh",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/658865?v=4",
|
||||
"profile": "https://github.com/AndrewSav",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kenchan0130",
|
||||
"name": "Tadayuki Onishi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1155067?v=4",
|
||||
"profile": "https://kenchan0130.github.io",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "floschoepfer",
|
||||
"name": "Florian",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/112496896?v=4",
|
||||
"profile": "https://github.com/floschoepfer",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "spencerrlongg",
|
||||
"name": "Spencer Long",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7305753?v=4",
|
||||
"profile": "http://spencerlong.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "marcusmoore",
|
||||
"name": "Marcus Moore",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1141514?v=4",
|
||||
"profile": "https://github.com/marcusmoore",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=snipeit-local
|
||||
DB_USERNAME=snipeit-local
|
||||
DB_PASSWORD=snipeit-local
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
DB_PREFIX=null
|
||||
DB_DUMP_PATH='/Applications/MAMP/Library/bin'
|
||||
#DB_DUMP_PATH=
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SSL DATABASE SETTINGS
|
||||
@@ -1,75 +0,0 @@
|
||||
# --------------------------------------------
|
||||
# REQUIRED: BASIC APP SETTINGS
|
||||
# --------------------------------------------
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='US/Pacific'
|
||||
APP_LOCALE=en
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite_testing
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=testing.sqlite
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
# --------------------------------------------
|
||||
MAIL_DRIVER=log
|
||||
MAIL_HOST=email-smtp.us-west-2.amazonaws.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=YOURUSERNAME
|
||||
MAIL_PASSWORD=YOURPASSWORD
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDR=you@example.com
|
||||
MAIL_FROM_NAME=Snipe-IT
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: IMAGE LIBRARY
|
||||
# This should be gd or imagick
|
||||
# --------------------------------------------
|
||||
IMAGE_LIB=gd
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: AWS SETTINGS
|
||||
# --------------------------------------------
|
||||
AWS_SECRET_ACCESS_KEY=null
|
||||
AWS_ACCESS_KEY_ID=null
|
||||
AWS_DEFAULT_REGION=null
|
||||
AWS_BUCKET=null
|
||||
AWS_BUCKET_ROOT=null
|
||||
AWS_URL=null
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: CACHE SETTINGS
|
||||
# --------------------------------------------
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
# --------------------------------------------
|
||||
SESSION_LIFETIME=12000
|
||||
EXPIRE_ON_CLOSE=false
|
||||
ENCRYPT=false
|
||||
COOKIE_NAME=snipeittest_session
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: APP LOG FORMAT
|
||||
# --------------------------------------------
|
||||
LOG_CHANNEL=single
|
||||
LOG_LEVEL=debug
|
||||
@@ -0,0 +1,19 @@
|
||||
# --------------------------------------------
|
||||
# REQUIRED: BASIC APP SETTINGS
|
||||
# --------------------------------------------
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
DB_PASSWORD=null
|
||||
@@ -2,8 +2,6 @@ name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request]: "
|
||||
labels: ["feature request"]
|
||||
assignees:
|
||||
- snipe
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
@@ -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.2.0
|
||||
uses: codacy/codacy-analysis-cli-action@v4.3.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
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Crowdin Action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
jobs:
|
||||
upload-sources-to-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Crowdin push
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.alpine
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
.couscous
|
||||
.DS_Store
|
||||
.env
|
||||
.env.dusk.*
|
||||
!.env.dusk.example
|
||||
.env.testing
|
||||
phpstan.neon
|
||||
.idea
|
||||
/bin/
|
||||
/bootstrap/compiled.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
 [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
@@ -60,6 +60,7 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
@@ -139,7 +140,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
+29
-43
@@ -1,65 +1,51 @@
|
||||
# Using the Test Suite
|
||||
# Running the Test Suite
|
||||
|
||||
This document is targeted at developers looking to make modifications to
|
||||
this application's code base and want to run the existing test suite.
|
||||
This document is targeted at developers looking to make modifications to this application's code base and want to run the existing test suite.
|
||||
|
||||
Before starting, follow the [instructions](README.md#installation) for installing the application locally and ensure you can load it in a browser properly.
|
||||
|
||||
## Setup
|
||||
## Unit and Feature Tests
|
||||
|
||||
Follow the instructions for installing the application locally,
|
||||
making sure to have also run the [database migrations](link to db migrations).
|
||||
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
|
||||
|
||||
`cp .env.testing.example .env.testing`
|
||||
> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
|
||||
|
||||
## Unit Tests
|
||||
Now you are ready to run the entire test suite from your terminal:
|
||||
|
||||
The application will use values in the `.env.testing` file located
|
||||
in the root directory to override the
|
||||
default settings and/or other values that exist in your `.env` files.
|
||||
`php artisan test`
|
||||
|
||||
Make sure to modify the section in `.env.testing` that has the
|
||||
database settings. In the example below, it is connecting to the
|
||||
[MariaDB](link-to-maria-db) server that is used if you install the
|
||||
application using [Docker](https://docker.com).
|
||||
To run individual test files, you can pass the path to the test that you want to run:
|
||||
|
||||
```dotenv
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_DATABASE=snipeit
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=changeme1234
|
||||
```
|
||||
|
||||
To run the entire unit test suite, use the following command from your terminal:
|
||||
|
||||
`php artisan test --env=testing`
|
||||
|
||||
To run individual test files, you can pass the path to the test that
|
||||
you want to run.
|
||||
|
||||
`php artisan test --env=testing tests/Unit/AccessoryTest.php`
|
||||
`php artisan test tests/Unit/AccessoryTest.php`
|
||||
|
||||
## Browser Tests
|
||||
|
||||
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them.
|
||||
When troubleshooting any problems, make sure that your `.env` file is configured
|
||||
correctly to run the existing application.
|
||||
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
|
||||
|
||||
### Test Setup
|
||||
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
|
||||
|
||||
Your application needs to be configued and up and running in order for the browser
|
||||
tests to actually run. When running the tests locally, you can start the application
|
||||
using the following command:
|
||||
`cp .env.dusk.example .env.dusk.local`
|
||||
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
|
||||
|
||||
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
|
||||
|
||||
### Running Browser Tests
|
||||
|
||||
Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
|
||||
|
||||
`php artisan serve`
|
||||
|
||||
|
||||
To run the test suite use the following command from another terminal tab or window:
|
||||
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
|
||||
|
||||
`php artisan dusk`
|
||||
|
||||
To run individual test files, you can pass the path to the test that you want to run.
|
||||
To run individual test files, you can pass the path to the test that you want to run:
|
||||
|
||||
`php artisan dusk tests/Browser/LoginTest.php`
|
||||
|
||||
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
|
||||
|
||||
`php artisan dusk:chrome-driver --detect`
|
||||
|
||||
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.
|
||||
|
||||
@@ -56,7 +56,7 @@ class CheckoutLicenseToAllUsers extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
$users = User::whereNull('deleted_at')->with('licenses')->get();
|
||||
$users = User::whereNull('deleted_at')->where('autoassign_licenses', '==', 1)->with('licenses')->get();
|
||||
|
||||
if ($users->count() > $license->getAvailSeatsCountAttribute()) {
|
||||
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
|
||||
|
||||
@@ -3,14 +3,29 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use \App\Models\User;
|
||||
|
||||
|
||||
class CreateAdmin extends Command
|
||||
{
|
||||
|
||||
/** @mixin User **/
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
* App\Console\CreateAdmin
|
||||
* @property mixed $first_name
|
||||
* @property string $last_name
|
||||
* @property string $username
|
||||
* @property string $email
|
||||
* @property string $permissions
|
||||
* @property string $password
|
||||
* @property boolean $activated
|
||||
* @property boolean $show_in_list
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property mixed $created_by
|
||||
*/
|
||||
|
||||
|
||||
|
||||
protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?}';
|
||||
|
||||
/**
|
||||
@@ -30,11 +45,7 @@ class CreateAdmin extends Command
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$first_name = $this->option('first_name');
|
||||
@@ -47,7 +58,7 @@ class CreateAdmin extends Command
|
||||
if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) {
|
||||
$this->info('ERROR: All fields are required.');
|
||||
} else {
|
||||
$user = new \App\Models\User;
|
||||
$user = new User;
|
||||
$user->first_name = $first_name;
|
||||
$user->last_name = $last_name;
|
||||
$user->username = $username;
|
||||
|
||||
@@ -44,12 +44,18 @@ class LdapSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
|
||||
exit();
|
||||
}
|
||||
|
||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
@@ -68,7 +74,7 @@ class LdapSync extends Command
|
||||
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::info($e);
|
||||
Log::info($e);
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -78,7 +84,7 @@ class LdapSync extends Command
|
||||
try {
|
||||
if ($this->option('base_dn') != '') {
|
||||
$search_base = $this->option('base_dn');
|
||||
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
||||
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
||||
} else {
|
||||
$search_base = null;
|
||||
}
|
||||
@@ -92,7 +98,7 @@ class LdapSync extends Command
|
||||
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::info($e);
|
||||
Log::info($e);
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -102,16 +108,16 @@ class LdapSync extends Command
|
||||
|
||||
if ($this->option('location') != '') {
|
||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
||||
LOG::debug('Location name '.$this->option('location').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
Log::debug('Location name '.$this->option('location').' passed');
|
||||
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
} elseif ($this->option('location_id') != '') {
|
||||
$location = Location::where('id', '=', $this->option('location_id'))->first();
|
||||
LOG::debug('Location ID '.$this->option('location_id').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
Log::debug('Location ID '.$this->option('location_id').' passed');
|
||||
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
}
|
||||
|
||||
if (! isset($location)) {
|
||||
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
||||
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
||||
}
|
||||
|
||||
/* Process locations with explicitly defined OUs, if doing a full import. */
|
||||
@@ -127,7 +133,7 @@ class LdapSync extends Command
|
||||
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
|
||||
|
||||
if (count($ldap_ou_locations) > 0) {
|
||||
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
|
||||
Log::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
|
||||
}
|
||||
|
||||
// Inject location information fields
|
||||
@@ -145,7 +151,7 @@ class LdapSync extends Command
|
||||
$json_summary = ['error' => true, 'error_message' => trans('admin/users/message.error.ldap_could_not_search').' Location: '.$ldap_loc['name'].' (ID: '.$ldap_loc['id'].') cannot connect to "'.$ldap_loc['ldap_ou'].'" - '.$e->getMessage(), 'summary' => []];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::info($e);
|
||||
Log::info($e);
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -191,18 +197,18 @@ class LdapSync extends Command
|
||||
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
$item = [];
|
||||
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : '';
|
||||
$item['employee_number'] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : '';
|
||||
$item['lastname'] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : '';
|
||||
$item['firstname'] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : '';
|
||||
$item['email'] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : '';
|
||||
$item['ldap_location_override'] = isset($results[$i]['ldap_location_override']) ? $results[$i]['ldap_location_override'] : '';
|
||||
$item['location_id'] = isset($results[$i]['location_id']) ? $results[$i]['location_id'] : '';
|
||||
$item['telephone'] = isset($results[$i][$ldap_result_phone][0]) ? $results[$i][$ldap_result_phone][0] : '';
|
||||
$item['jobtitle'] = isset($results[$i][$ldap_result_jobtitle][0]) ? $results[$i][$ldap_result_jobtitle][0] : '';
|
||||
$item['country'] = isset($results[$i][$ldap_result_country][0]) ? $results[$i][$ldap_result_country][0] : '';
|
||||
$item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : '';
|
||||
$item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : '';
|
||||
$item['username'] = $results[$i][$ldap_result_username][0] ?? '';
|
||||
$item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? '';
|
||||
$item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? '';
|
||||
$item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? '';
|
||||
$item['email'] = $results[$i][$ldap_result_email][0] ?? '';
|
||||
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
|
||||
$item['location_id'] = $results[$i]['location_id'] ?? '';
|
||||
$item['telephone'] = $results[$i][$ldap_result_phone][0] ?? '';
|
||||
$item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? '';
|
||||
$item['country'] = $results[$i][$ldap_result_country][0] ?? '';
|
||||
$item['department'] = $results[$i][$ldap_result_dept][0] ?? '';
|
||||
$item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
|
||||
|
||||
|
||||
$department = Department::firstOrCreate([
|
||||
@@ -303,17 +309,18 @@ class LdapSync extends Command
|
||||
$user->activated = 0;
|
||||
} */
|
||||
$enabled_accounts = [
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
|
||||
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
|
||||
];
|
||||
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
|
||||
class NormalizeUserNames extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:normalize-names';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Normalizes weirdly formatted names as first-letter upercased';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$users = User::get();
|
||||
$this->info($users->count() . ' users');
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->first_name = ucwords(strtolower($user->first_name));
|
||||
$user->last_name = ucwords(strtolower($user->last_name));
|
||||
$user->email = strtolower($user->email);
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class RestoreFromBackup extends Command
|
||||
$boring_files[] = $raw_path;
|
||||
continue;
|
||||
}
|
||||
if (@pathinfo($raw_path)['extension'] == 'sql') {
|
||||
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
|
||||
\Log::debug("Found a sql file!");
|
||||
$sqlfiles[] = $raw_path;
|
||||
$sqlfile_indices[] = $i;
|
||||
@@ -214,7 +214,7 @@ class RestoreFromBackup extends Command
|
||||
$env_vars['MYSQL_PWD'] = config('database.connections.mysql.password');
|
||||
// TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this)
|
||||
// we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't?
|
||||
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').'/mysql';
|
||||
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').\DIRECTORY_SEPARATOR.'mysql'.(\DIRECTORY_SEPARATOR == '\\' ? ".exe" : "");
|
||||
if( ! file_exists($mysql_binary) ) {
|
||||
return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\User;
|
||||
|
||||
class UserMerged
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $from_user, User $to_user, User $admin)
|
||||
{
|
||||
$this->merged_from = $from_user;
|
||||
$this->merged_to = $to_user;
|
||||
$this->admin = $admin;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
|
||||
use Log;
|
||||
use Throwable;
|
||||
use JsonException;
|
||||
@@ -28,6 +29,7 @@ class Handler extends ExceptionHandler
|
||||
\Intervention\Image\Exception\NotSupportedException::class,
|
||||
\League\OAuth2\Server\Exception\OAuthServerException::class,
|
||||
JsonException::class,
|
||||
SCIMException::class, //these generally don't need to be reported
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -41,7 +43,9 @@ class Handler extends ExceptionHandler
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if ($this->shouldReport($exception)) {
|
||||
\Log::error($exception);
|
||||
if (class_exists(\Log::class)) {
|
||||
\Log::error($exception);
|
||||
}
|
||||
return parent::report($exception);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +55,7 @@ class Handler extends ExceptionHandler
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Exception $e
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
|
||||
*/
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
@@ -68,6 +72,9 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'invalid JSON'), 422);
|
||||
}
|
||||
|
||||
if ($e instanceof SCIMException) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'invalid SCIM Request'), 400);
|
||||
}
|
||||
|
||||
// Handle Ajax requests that fail because the model doesn't exist
|
||||
if ($request->ajax() || $request->wantsJson()) {
|
||||
@@ -111,8 +118,8 @@ class Handler extends ExceptionHandler
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Auth\AuthenticationException $exception
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
|
||||
+102
-25
@@ -334,7 +334,11 @@ class Helper
|
||||
'#92896B',
|
||||
];
|
||||
|
||||
$total_colors = count($colors);
|
||||
|
||||
if ($index >= $total_colors) {
|
||||
$index = $index - $total_colors;
|
||||
}
|
||||
|
||||
return $colors[$index];
|
||||
}
|
||||
@@ -528,20 +532,23 @@ class Helper
|
||||
* @since [v2.5]
|
||||
* @return array
|
||||
*/
|
||||
public static function categoryTypeList()
|
||||
public static function categoryTypeList($selection=null)
|
||||
{
|
||||
$category_types = [
|
||||
'' => '',
|
||||
'accessory' => 'Accessory',
|
||||
'asset' => 'Asset',
|
||||
'consumable' => 'Consumable',
|
||||
'component' => 'Component',
|
||||
'license' => 'License',
|
||||
'accessory' => trans('general.accessory'),
|
||||
'asset' => trans('general.asset'),
|
||||
'consumable' => trans('general.consumable'),
|
||||
'component' => trans('general.component'),
|
||||
'license' => trans('general.license'),
|
||||
];
|
||||
|
||||
if($selection != null){
|
||||
return $category_types[$selection];
|
||||
}
|
||||
else
|
||||
return $category_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of custom fields in an array to make a dropdown menu
|
||||
*
|
||||
@@ -1092,6 +1099,15 @@ class Helper
|
||||
return $file_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Universal helper to show file size in human-readable formats
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 5.0
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function formatFilesizeUnits($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
@@ -1121,30 +1137,91 @@ class Helper
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is weird but used by the side nav to determine which URL to point the user to
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 5.0
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function SettingUrls(){
|
||||
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
|
||||
|
||||
return $settings;
|
||||
}
|
||||
public static function AgeFormat($date) {
|
||||
$year = Carbon::parse($date)
|
||||
->diff(now())->y;
|
||||
$month = Carbon::parse($date)
|
||||
->diff(now())->m;
|
||||
$days = Carbon::parse($date)
|
||||
->diff(now())->d;
|
||||
$age='';
|
||||
if ($year) {
|
||||
$age .= $year.'y ';
|
||||
}
|
||||
if ($month) {
|
||||
$age .= $month.'m ';
|
||||
}
|
||||
if ($days) {
|
||||
$age .= $days.'d';
|
||||
}
|
||||
|
||||
return $age;
|
||||
|
||||
/**
|
||||
* Generic helper (largely used by livewire right now) that returns the font-awesome icon
|
||||
* for the object type.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function iconTypeByItem($item) {
|
||||
|
||||
switch ($item) {
|
||||
case 'asset':
|
||||
return 'fas fa-barcode';
|
||||
break;
|
||||
case 'accessory':
|
||||
return 'fas fa-keyboard';
|
||||
break;
|
||||
case 'component':
|
||||
return 'fas fa-hdd';
|
||||
break;
|
||||
case 'consumable':
|
||||
return 'fas fa-tint';
|
||||
break;
|
||||
case 'license':
|
||||
return 'far fa-save';
|
||||
break;
|
||||
case 'location':
|
||||
return 'fas fa-map-marker-alt';
|
||||
break;
|
||||
case 'user':
|
||||
return 'fas fa-user';
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is a shorter way to see if the app is in demo mode.
|
||||
*
|
||||
* This makes it cleanly available in blades and in controllers, e.g.
|
||||
*
|
||||
* Blade:
|
||||
* {{ Helper::isDemoMode() ? ' disabled' : ''}} for form blades where we need to disable a form
|
||||
*
|
||||
* Controller:
|
||||
* if (Helper::isDemoMode()) {
|
||||
* // don't allow the thing
|
||||
* }
|
||||
* @todo - use this everywhere else in the app where we have very long if/else config('app.lock_passwords') stuff
|
||||
*/
|
||||
public static function isDemoMode() {
|
||||
if (config('app.lock_passwords') === true) {
|
||||
return true;
|
||||
\Log::debug('app locked!');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
|
||||
* it seemed pretty safe to do here. Don't you judge me.
|
||||
*/
|
||||
public static function showDemoModeFieldWarning() {
|
||||
if (Helper::isDemoMode()) {
|
||||
return "<p class=\"text-warning\"><i class=\"fas fa-lock\"></i>" . trans('general.feature_disabled') . "</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class AccessoriesController extends Controller
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize(Accessory::class);
|
||||
|
||||
// create a new model instance
|
||||
$accessory = new Accessory();
|
||||
|
||||
@@ -82,7 +83,6 @@ class AccessoriesController extends Controller
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory created?
|
||||
@@ -115,6 +115,33 @@ class AccessoriesController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to clone an accessory.
|
||||
*
|
||||
* @author [J. Vinsmoke]
|
||||
* @param int $accessoryId
|
||||
* @since [v6.0]
|
||||
* @return View
|
||||
*/
|
||||
public function getClone($accessoryId = null)
|
||||
{
|
||||
|
||||
$this->authorize('create', Accesory::class);
|
||||
|
||||
// Check if the asset exists
|
||||
if (is_null($accessory_to_clone = Accessory::find($accessoryId))) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->route('accessory.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory = clone $accessory_to_clone;
|
||||
$accessory->id = null;
|
||||
$accessory->location_id = null;
|
||||
|
||||
return view('accessories/edit')
|
||||
->with('item', $accessory);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save edited Accessory from form post
|
||||
@@ -127,45 +154,47 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function update(ImageUploadRequest $request, $accessoryId = null)
|
||||
{
|
||||
if (is_null($accessory = Accessory::find($accessoryId))) {
|
||||
if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$accessory->users_count"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory updated?
|
||||
if ($accessory->save()) {
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$min = $accessory->numCheckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$this->authorize($accessory);
|
||||
|
||||
// Update the accessory data
|
||||
$accessory->name = request('name');
|
||||
$accessory->location_id = request('location_id');
|
||||
$accessory->min_amt = request('min_amt');
|
||||
$accessory->category_id = request('category_id');
|
||||
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
||||
$accessory->manufacturer_id = request('manufacturer_id');
|
||||
$accessory->order_number = request('order_number');
|
||||
$accessory->model_number = request('model_number');
|
||||
$accessory->purchase_date = request('purchase_date');
|
||||
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
|
||||
$accessory->qty = request('qty');
|
||||
$accessory->supplier_id = request('supplier_id');
|
||||
$accessory->notes = request('notes');
|
||||
|
||||
$accessory = $request->handleImages($accessory);
|
||||
|
||||
// Was the accessory updated?
|
||||
if ($accessory->save()) {
|
||||
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
||||
}
|
||||
|
||||
@@ -217,7 +246,7 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function show($accessoryID = null)
|
||||
{
|
||||
$accessory = Accessory::find($accessoryID);
|
||||
$accessory = Accessory::withCount('users as users_count')->find($accessoryID);
|
||||
$this->authorize('view', $accessory);
|
||||
if (isset($accessory->id)) {
|
||||
return view('accessories/view', compact('accessory'));
|
||||
|
||||
@@ -222,8 +222,8 @@ class AcceptanceController extends Controller
|
||||
'item_model' => $display_model,
|
||||
'item_serial' => $item->serial,
|
||||
'eula' => $item->getEula(),
|
||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format),
|
||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
|
||||
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
||||
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
||||
@@ -273,7 +273,7 @@ class AcceptanceController extends Controller
|
||||
'item_tag' => $item->asset_tag,
|
||||
'item_model' => $display_model,
|
||||
'item_serial' => $item->serial,
|
||||
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format),
|
||||
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
|
||||
'assigned_to' => $assigned_to,
|
||||
'company_name' => $branding_settings->site_name,
|
||||
'date_settings' => $branding_settings->date_display_format,
|
||||
|
||||
@@ -26,7 +26,10 @@ class AccessoriesController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Accessory::class);
|
||||
if ($request->user()->cannot('reports.view')) {
|
||||
$this->authorize('view', Accessory::class);
|
||||
}
|
||||
|
||||
|
||||
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
|
||||
// Relations will be handled in query scopes a little further down.
|
||||
@@ -41,10 +44,13 @@ class AccessoriesController extends Controller
|
||||
'min_amt',
|
||||
'company_id',
|
||||
'notes',
|
||||
'users_count',
|
||||
'qty',
|
||||
];
|
||||
|
||||
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier');
|
||||
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
|
||||
->withCount('users as users_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$accessories = $accessories->TextSearch($request->input('search'));
|
||||
|
||||
@@ -100,6 +100,8 @@ class AssetsController extends Controller
|
||||
'checkout_counter',
|
||||
'checkin_counter',
|
||||
'requests_counter',
|
||||
'byod',
|
||||
'asset_eol_date',
|
||||
];
|
||||
|
||||
$filter = [];
|
||||
@@ -127,7 +129,6 @@ class AssetsController extends Controller
|
||||
// They are also used by the individual searches on detail pages like
|
||||
// locations, etc.
|
||||
|
||||
|
||||
// Search custom fields by column name
|
||||
foreach ($all_custom_fields as $field) {
|
||||
if ($request->filled($field->db_column_name())) {
|
||||
@@ -135,7 +136,6 @@ class AssetsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('status_id')) {
|
||||
$assets->where('assets.status_id', '=', $request->input('status_id'));
|
||||
}
|
||||
@@ -172,6 +172,10 @@ class AssetsController extends Controller
|
||||
$assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('asset_eol_date')) {
|
||||
$assets->where('assets.asset_eol_date', '=', $request->input('asset_eol_date'));
|
||||
}
|
||||
|
||||
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
|
||||
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
|
||||
->where('assets.assigned_type', '=', $request->input('assigned_type'));
|
||||
@@ -189,6 +193,10 @@ class AssetsController extends Controller
|
||||
$assets->ByDepreciationId($request->input('depreciation_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('byod')) {
|
||||
$assets->where('assets.byod', '=', $request->input('byod'));
|
||||
}
|
||||
|
||||
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
@@ -266,6 +274,11 @@ class AssetsController extends Controller
|
||||
// more sad, horrible workarounds for laravel bugs when doing full text searches
|
||||
$assets->where('assets.assigned_to', '>', '0');
|
||||
break;
|
||||
case 'byod':
|
||||
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
|
||||
// sidebar nav links a little less chaotic
|
||||
$assets->where('assets.byod', '=', '1');
|
||||
break;
|
||||
default:
|
||||
|
||||
if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) {
|
||||
@@ -847,7 +860,8 @@ class AssetsController extends Controller
|
||||
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
|
||||
$expected_checkin = request('expected_checkin', null);
|
||||
$note = request('note', null);
|
||||
$asset_name = request('name', null);
|
||||
// Using `->has` preserves the asset name if the name parameter was not included in request.
|
||||
$asset_name = request()->has('name') ? request('name') : $asset->name;
|
||||
|
||||
// Set the location ID to the RTD location id if there is one
|
||||
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
|
||||
@@ -927,18 +941,21 @@ class AssetsController extends Controller
|
||||
* @since [v6.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkinByTag(Request $request)
|
||||
public function checkinByTag(Request $request, $tag = null)
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first();
|
||||
if(null == $tag && null !== ($request->input('asset_tag'))) {
|
||||
$tag = $request->input('asset_tag');
|
||||
}
|
||||
$asset = Asset::where('asset_tag', $tag)->first();
|
||||
|
||||
if ($asset) {
|
||||
return $this->checkin($request, $asset->id);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset'=> e($request->input('asset_tag'))
|
||||
], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
|
||||
'asset'=> e($tag)
|
||||
], 'Asset with tag '.e($tag).' not found'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Category;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
{
|
||||
@@ -23,10 +24,50 @@ class CategoriesController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Category::class);
|
||||
$allowed_columns = ['id', 'name', 'category_type', 'category_type', 'use_default_eula', 'eula_text', 'require_acceptance', 'checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count', 'licenses_count', 'image'];
|
||||
$allowed_columns = [
|
||||
'id',
|
||||
'name',
|
||||
'category_type',
|
||||
'category_type',
|
||||
'use_default_eula',
|
||||
'eula_text',
|
||||
'require_acceptance',
|
||||
'checkin_email',
|
||||
'assets_count',
|
||||
'accessories_count',
|
||||
'consumables_count',
|
||||
'components_count',
|
||||
'licenses_count',
|
||||
'image',
|
||||
];
|
||||
|
||||
$categories = Category::select([
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'name', 'category_type',
|
||||
'use_default_eula',
|
||||
'eula_text',
|
||||
'require_acceptance',
|
||||
'checkin_email',
|
||||
'image'
|
||||
])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
|
||||
|
||||
|
||||
/*
|
||||
* This checks to see if we should override the Admin Setting to show archived assets in list.
|
||||
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
|
||||
* may actually need to fetch assets that are archived.
|
||||
*
|
||||
* @see \App\Models\Category::showableAssets()
|
||||
*/
|
||||
if ($request->input('archived')=='true') {
|
||||
$categories = $categories->withCount('assets as assets_count');
|
||||
} else {
|
||||
$categories = $categories->withCount('showableAssets as assets_count');
|
||||
}
|
||||
|
||||
|
||||
$categories = Category::select(['id', 'created_at', 'updated_at', 'name', 'category_type', 'use_default_eula', 'eula_text', 'require_acceptance', 'checkin_email', 'image'])
|
||||
->withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$categories = $categories->TextSearch($request->input('search'));
|
||||
@@ -107,7 +148,7 @@ class CategoriesController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
return (new CategoriesTransformer)->transformCategory($category);
|
||||
|
||||
}
|
||||
@@ -126,8 +167,14 @@ class CategoriesController extends Controller
|
||||
{
|
||||
$this->authorize('update', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
|
||||
// Don't allow the user to change the category_type once it's been created
|
||||
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
|
||||
);
|
||||
}
|
||||
$category->fill($request->all());
|
||||
$category->category_type = strtolower($request->input('category_type'));
|
||||
$category = $request->handleImages($category);
|
||||
|
||||
if ($category->save()) {
|
||||
@@ -148,7 +195,7 @@ class CategoriesController extends Controller
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
|
||||
if (! $category->isDeletable()) {
|
||||
return response()->json(
|
||||
|
||||
@@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', CustomFieldset::class);
|
||||
$this->authorize('index', CustomField::class);
|
||||
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
|
||||
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
|
||||
@@ -49,7 +49,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
$fieldset->fill($request->all());
|
||||
|
||||
@@ -89,7 +89,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
$fieldset = new CustomFieldset;
|
||||
$fieldset->fill($request->all());
|
||||
|
||||
@@ -109,7 +109,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', CustomFieldset::class);
|
||||
$this->authorize('delete', CustomField::class);
|
||||
$fieldset = CustomFieldset::findOrFail($id);
|
||||
|
||||
$modelsCount = $fieldset->models->count();
|
||||
@@ -136,7 +136,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function fields($id)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
$set = CustomFieldset::findOrFail($id);
|
||||
$fields = $set->fields;
|
||||
|
||||
@@ -153,7 +153,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function fieldsWithDefaultValues($fieldsetId, $modelId)
|
||||
{
|
||||
$this->authorize('view', CustomFieldset::class);
|
||||
$this->authorize('view', CustomField::class);
|
||||
|
||||
$set = CustomFieldset::findOrFail($fieldsetId);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Http\Transformers\GroupsTransformer;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
class GroupsController extends Controller
|
||||
{
|
||||
/**
|
||||
@@ -19,6 +20,8 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('superadmin');
|
||||
|
||||
$this->authorize('view', Group::class);
|
||||
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
|
||||
|
||||
@@ -59,9 +62,11 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', Group::class);
|
||||
$this->authorize('superadmin');
|
||||
$group = new Group;
|
||||
$group->fill($request->all());
|
||||
|
||||
$group->name = $request->input('name');
|
||||
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
|
||||
|
||||
if ($group->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));
|
||||
@@ -80,7 +85,7 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', Group::class);
|
||||
$this->authorize('superadmin');
|
||||
$group = Group::findOrFail($id);
|
||||
|
||||
return (new GroupsTransformer)->transformGroup($group);
|
||||
@@ -97,9 +102,11 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', Group::class);
|
||||
$this->authorize('superadmin');
|
||||
$group = Group::findOrFail($id);
|
||||
$group->fill($request->all());
|
||||
|
||||
$group->name = $request->input('name');
|
||||
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
|
||||
|
||||
if ($group->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.update.success')));
|
||||
@@ -118,9 +125,8 @@ class GroupsController extends Controller
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', Group::class);
|
||||
$this->authorize('superadmin');
|
||||
$group = Group::findOrFail($id);
|
||||
$this->authorize('delete', $group);
|
||||
$group->delete();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/groups/message.delete.success')));
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Import;
|
||||
use Artisan;
|
||||
use Illuminate\Database\Eloquent\JsonEncodingException;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -35,7 +36,7 @@ class ImportController extends Controller
|
||||
* Process and store a CSV upload file.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
@@ -56,7 +57,7 @@ class ImportController extends Controller
|
||||
'text/tsv', ])) {
|
||||
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
|
||||
}
|
||||
|
||||
//TODO: is there a lighter way to do this?
|
||||
@@ -64,7 +65,19 @@ class ImportController extends Controller
|
||||
ini_set('auto_detect_line_endings', '1');
|
||||
}
|
||||
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
|
||||
try {
|
||||
$import->header_row = $reader->fetchOne(0);
|
||||
} catch (JsonEncodingException $e) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.import.header_row_has_malformed_characters')
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
//duplicate headers check
|
||||
$duplicate_headers = [];
|
||||
@@ -82,11 +95,22 @@ class ImportController extends Controller
|
||||
}
|
||||
}
|
||||
if (count($duplicate_headers) > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)), 500); //should this be '4xx'?
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)),422);
|
||||
}
|
||||
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
try {
|
||||
// Grab the first row to display via ajax as the user picks fields
|
||||
$import->first_row = $reader->fetchOne(1);
|
||||
} catch (JsonEncodingException $e) {
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'error',
|
||||
null,
|
||||
trans('admin/hardware/message.import.content_row_has_malformed_characters')
|
||||
),
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
$date = date('Y-m-d-his');
|
||||
$fixed_filename = str_slug($file->getClientOriginalName());
|
||||
@@ -102,18 +126,25 @@ class ImportController extends Controller
|
||||
}
|
||||
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
|
||||
$import->file_path = $file_name;
|
||||
$import->filesize = null;
|
||||
|
||||
if (!file_exists($path.'/'.$file_name)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 500);
|
||||
}
|
||||
|
||||
$import->filesize = filesize($path.'/'.$file_name);
|
||||
|
||||
$import->save();
|
||||
$results[] = $import;
|
||||
}
|
||||
$results = (new ImportsTransformer)->transformImports($results);
|
||||
|
||||
return [
|
||||
return response()->json([
|
||||
'files' => $results,
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 500);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 422);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +158,7 @@ class ImportController extends Controller
|
||||
$this->authorize('import');
|
||||
|
||||
// Run a backup immediately before processing
|
||||
if ($request->has('run-backup')) {
|
||||
if ($request->get('run-backup')) {
|
||||
\Log::debug('Backup manually requested via importer');
|
||||
Artisan::call('backup:run');
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Passport\TokenRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use DB;
|
||||
|
||||
class ProfileController extends Controller
|
||||
|
||||
@@ -57,8 +57,12 @@ class ReportsController extends Controller
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
$total = $actionlogs->count();
|
||||
|
||||
// Check to make sure the limit is not higher than the max allowed
|
||||
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
|
||||
|
||||
|
||||
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
|
||||
|
||||
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
@@ -143,47 +143,6 @@ class SettingsController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function slacktest(SlackSettingsRequest $request)
|
||||
{
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
// If validation passes, continue to the curl request
|
||||
$slack = new Client([
|
||||
'base_url' => e($request->input('slack_endpoint')),
|
||||
'defaults' => [
|
||||
'exceptions' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
$payload = json_encode(
|
||||
[
|
||||
'channel' => e($request->input('slack_channel')),
|
||||
'text' => trans('general.slack_test_msg'),
|
||||
'username' => e($request->input('slack_botname')),
|
||||
'icon_emoji' => ':heart:',
|
||||
]);
|
||||
|
||||
try {
|
||||
$slack->post($request->input('slack_endpoint'), ['body' => $payload]);
|
||||
return response()->json(['message' => 'Success'], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => 'Please check the channel name and webhook endpoint URL ('.e($request->input('slack_endpoint')).'). Slack responded with: '.$e->getMessage()], 400);
|
||||
}
|
||||
|
||||
//}
|
||||
return response()->json(['message' => 'Something went wrong :( '], 400);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the email configuration
|
||||
*
|
||||
@@ -312,7 +271,7 @@ class SettingsController extends Controller
|
||||
$headers = ['ContentType' => 'application/zip'];
|
||||
return Storage::download($path.'/'.$file, $file, $headers);
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'File not found'));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ class StatuslabelsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
$statuslabels = Statuslabel::withCount('assets')->get();
|
||||
$total = Array();
|
||||
|
||||
foreach ($statuslabels as $statuslabel) {
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
@@ -69,6 +70,7 @@ class UsersController extends Controller
|
||||
'users.ldap_import',
|
||||
'users.start_date',
|
||||
'users.end_date',
|
||||
'users.vip',
|
||||
|
||||
])->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');
|
||||
@@ -149,6 +151,18 @@ class UsersController extends Controller
|
||||
$users = $users->where('remote', '=', $request->input('remote'));
|
||||
}
|
||||
|
||||
if ($request->filled('vip')) {
|
||||
$users = $users->where('vip', '=', $request->input('vip'));
|
||||
}
|
||||
|
||||
if ($request->filled('two_factor_enrolled')) {
|
||||
$users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled'));
|
||||
}
|
||||
|
||||
if ($request->filled('two_factor_optin')) {
|
||||
$users = $users->where('two_factor_optin', '=', $request->input('two_factor_optin'));
|
||||
}
|
||||
|
||||
if ($request->filled('start_date')) {
|
||||
$users = $users->where('users.start_date', '=', $request->input('start_date'));
|
||||
}
|
||||
@@ -157,7 +171,6 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.end_date', '=', $request->input('end_date'));
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('assets_count')) {
|
||||
$users->has('assets', '=', $request->input('assets_count'));
|
||||
}
|
||||
@@ -179,7 +192,6 @@ class UsersController extends Controller
|
||||
}
|
||||
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
// case we override with the actual count, so we should return 0 items.
|
||||
@@ -205,14 +217,51 @@ class UsersController extends Controller
|
||||
case 'company':
|
||||
$users = $users->OrderCompany($order);
|
||||
break;
|
||||
case 'first_name':
|
||||
$users->orderBy('first_name', $order);
|
||||
$users->orderBy('last_name', $order);
|
||||
break;
|
||||
case 'last_name':
|
||||
$users->orderBy('last_name', $order);
|
||||
$users->orderBy('first_name', $order);
|
||||
break;
|
||||
default:
|
||||
$allowed_columns =
|
||||
[
|
||||
'last_name', 'first_name', 'email', 'jobtitle', 'username', 'employee_num',
|
||||
'assets', 'accessories', 'consumables', 'licenses', 'groups', 'activated', 'created_at',
|
||||
'two_factor_enrolled', 'two_factor_optin', 'last_login', 'assets_count', 'licenses_count',
|
||||
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
|
||||
'country', 'zip', 'id', 'ldap_import', 'remote', 'start_date', 'end_date',
|
||||
'last_name',
|
||||
'first_name',
|
||||
'email',
|
||||
'jobtitle',
|
||||
'username',
|
||||
'employee_num',
|
||||
'assets',
|
||||
'accessories',
|
||||
'consumables',
|
||||
'licenses',
|
||||
'groups',
|
||||
'activated',
|
||||
'created_at',
|
||||
'two_factor_enrolled',
|
||||
'two_factor_optin',
|
||||
'last_login',
|
||||
'assets_count',
|
||||
'licenses_count',
|
||||
'consumables_count',
|
||||
'accessories_count',
|
||||
'phone',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'country',
|
||||
'zip',
|
||||
'id',
|
||||
'ldap_import',
|
||||
'two_factor_optin',
|
||||
'two_factor_enrolled',
|
||||
'remote',
|
||||
'vip',
|
||||
'start_date',
|
||||
'end_date',
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -251,9 +300,11 @@ class UsersController extends Controller
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$users = $users->SimpleNameSearch($request->get('search'))
|
||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||
$users = $users->where(function ($query) use ($request) {
|
||||
$query->SimpleNameSearch($request->get('search'))
|
||||
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
|
||||
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
|
||||
});
|
||||
}
|
||||
|
||||
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
|
||||
@@ -414,6 +465,13 @@ 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',
|
||||
]);
|
||||
|
||||
if ($validator->fails()){
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
|
||||
}
|
||||
$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')) {
|
||||
@@ -507,9 +565,10 @@ class UsersController extends Controller
|
||||
if (empty($user->email)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
|
||||
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,8 +82,8 @@ class AssetModelsController extends Controller
|
||||
$model->user_id = Auth::id();
|
||||
$model->requestable = Request::has('requestable');
|
||||
|
||||
if ($request->input('custom_fieldset') != '') {
|
||||
$model->fieldset_id = e($request->input('custom_fieldset'));
|
||||
if ($request->input('fieldset_id') != '') {
|
||||
$model->fieldset_id = e($request->input('fieldset_id'));
|
||||
}
|
||||
|
||||
$model = $request->handleImages($model);
|
||||
@@ -160,10 +160,10 @@ class AssetModelsController extends Controller
|
||||
|
||||
$this->removeCustomFieldsDefaultValues($model);
|
||||
|
||||
if ($request->input('custom_fieldset') == '') {
|
||||
if ($request->input('fieldset_id') == '') {
|
||||
$model->fieldset_id = null;
|
||||
} else {
|
||||
$model->fieldset_id = $request->input('custom_fieldset');
|
||||
$model->fieldset_id = $request->input('fieldset_id');
|
||||
|
||||
if ($this->shouldAddDefaultValues($request->input())) {
|
||||
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
|
||||
@@ -444,7 +444,7 @@ class AssetModelsController extends Controller
|
||||
{
|
||||
return ! empty($input['add_default_values'])
|
||||
&& ! empty($input['default_values'])
|
||||
&& ! empty($input['custom_fieldset']);
|
||||
&& ! empty($input['fieldset_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ class AssetCheckoutController extends Controller
|
||||
public function create($assetId)
|
||||
{
|
||||
// Check if the asset exists
|
||||
if (is_null($asset = Asset::find(e($assetId)))) {
|
||||
if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use App\Models\User;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -142,10 +142,12 @@ class AssetsController extends Controller
|
||||
$asset->warranty_months = request('warranty_months', null);
|
||||
$asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost'));
|
||||
$asset->purchase_date = request('purchase_date', null);
|
||||
$asset->asset_eol_date = request('asset_eol_date', null);
|
||||
$asset->assigned_to = request('assigned_to', null);
|
||||
$asset->supplier_id = request('supplier_id', null);
|
||||
$asset->requestable = request('requestable', 0);
|
||||
$asset->rtd_location_id = request('rtd_location_id', null);
|
||||
$asset->byod = request('byod', 0);
|
||||
|
||||
if (! empty($settings->audit_interval)) {
|
||||
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
@@ -311,6 +313,8 @@ class AssetsController extends Controller
|
||||
$asset->status_id = $request->input('status_id', null);
|
||||
$asset->warranty_months = $request->input('warranty_months', null);
|
||||
$asset->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
|
||||
$asset->asset_eol_date = request('asset_eol_date', null);
|
||||
|
||||
$asset->purchase_date = $request->input('purchase_date', null);
|
||||
$asset->supplier_id = $request->input('supplier_id', null);
|
||||
$asset->expected_checkin = $request->input('expected_checkin', null);
|
||||
@@ -318,6 +322,7 @@ class AssetsController extends Controller
|
||||
// If the box isn't checked, it's not in the request at all.
|
||||
$asset->requestable = $request->filled('requestable');
|
||||
$asset->rtd_location_id = $request->input('rtd_location_id', null);
|
||||
$asset->byod = $request->input('byod', 0);
|
||||
|
||||
if ($asset->assigned_to == '') {
|
||||
$asset->location_id = $request->input('rtd_location_id', null);
|
||||
@@ -622,7 +627,11 @@ class AssetsController extends Controller
|
||||
$csv->setHeaderOffset(0);
|
||||
$header = $csv->getHeader();
|
||||
$isCheckinHeaderExplicit = in_array('checkin date', (array_map('strtolower', $header)));
|
||||
$results = $csv->getRecords();
|
||||
try {
|
||||
$results = $csv->getRecords();
|
||||
} catch (\Exception $e) {
|
||||
return back()->with('error', trans('general.error_in_import_file', ['error' => $e->getMessage()]));
|
||||
}
|
||||
$item = [];
|
||||
$status = [];
|
||||
$status['error'] = [];
|
||||
|
||||
@@ -102,8 +102,11 @@ class BulkAssetsController extends Controller
|
||||
|| ($request->filled('company_id'))
|
||||
|| ($request->filled('status_id'))
|
||||
|| ($request->filled('model_id'))
|
||||
|| ($request->filled('next_audit_date'))
|
||||
|| ($request->filled('null_purchase_date'))
|
||||
|| ($request->filled('null_expected_checkin_date'))
|
||||
|| ($request->filled('null_next_audit_date'))
|
||||
|
||||
) {
|
||||
foreach ($assets as $assetId) {
|
||||
|
||||
@@ -116,7 +119,8 @@ class BulkAssetsController extends Controller
|
||||
->conditionallyAddItem('requestable')
|
||||
->conditionallyAddItem('status_id')
|
||||
->conditionallyAddItem('supplier_id')
|
||||
->conditionallyAddItem('warranty_months');
|
||||
->conditionallyAddItem('warranty_months')
|
||||
->conditionallyAddItem('next_audit_date');
|
||||
|
||||
if ($request->input('null_purchase_date')=='1') {
|
||||
$this->update_array['purchase_date'] = null;
|
||||
@@ -126,6 +130,10 @@ class BulkAssetsController extends Controller
|
||||
$this->update_array['expected_checkin'] = null;
|
||||
}
|
||||
|
||||
if ($request->input('null_next_audit_date')=='1') {
|
||||
$this->update_array['next_audit_date'] = null;
|
||||
}
|
||||
|
||||
if ($request->filled('purchase_cost')) {
|
||||
$this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
|
||||
}
|
||||
@@ -288,7 +296,8 @@ class BulkAssetsController extends Controller
|
||||
foreach ($asset_ids as $asset_id) {
|
||||
$asset = Asset::findOrFail($asset_id);
|
||||
$this->authorize('checkout', $asset);
|
||||
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), null);
|
||||
|
||||
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
|
||||
|
||||
if ($target->location_id != '') {
|
||||
$asset->location_id = $target->location_id;
|
||||
|
||||
@@ -109,9 +109,9 @@ class CustomFieldsController extends Controller
|
||||
|
||||
|
||||
if ($request->filled('custom_format')) {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
$field->format = $request->get('custom_format');
|
||||
} else {
|
||||
$field->format = e($request->get('format'));
|
||||
$field->format = $request->get('format');
|
||||
}
|
||||
|
||||
if ($field->save()) {
|
||||
|
||||
@@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
return view('custom_fields.fieldsets.edit');
|
||||
return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', CustomFieldset::class);
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$cfset = new CustomFieldset([
|
||||
'name' => e($request->get('name')),
|
||||
@@ -110,31 +110,52 @@ class CustomFieldsetsController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* What the actual fuck, Brady?
|
||||
* Presents edit form for fieldset
|
||||
*
|
||||
* @todo Uhh, build this?
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $id
|
||||
* @since [v1.8]
|
||||
* @return Fuckall
|
||||
* @since [v6.0.14]
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
//
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
|
||||
}
|
||||
|
||||
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* GET IN THE SEA BRADY.
|
||||
* Saves updated fieldset data
|
||||
*
|
||||
* @todo Uhh, build this too?
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $id
|
||||
* @since [v1.8]
|
||||
* @return Fuckall
|
||||
* @since [v6.0.14]
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update($id)
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
$this->authorize('create', CustomField::class);
|
||||
|
||||
if ($fieldset = CustomFieldset::find($id)) {
|
||||
|
||||
$fieldset->name = $request->input('name');
|
||||
|
||||
if ($fieldset->save()) {
|
||||
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +223,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function makeFieldRequired($fieldset_id, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
$fields[$field->id] = ['required' => 1];
|
||||
@@ -220,7 +241,7 @@ class CustomFieldsetsController extends Controller
|
||||
*/
|
||||
public function makeFieldOptional($fieldset_id, $field_id)
|
||||
{
|
||||
$this->authorize('update', CustomFieldset::class);
|
||||
$this->authorize('update', CustomField::class);
|
||||
$field = CustomField::findOrFail($field_id);
|
||||
$fieldset = CustomFieldset::findOrFail($fieldset_id);
|
||||
$fields[$field->id] = ['required' => 0];
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Transformers\ImportsTransformer;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Import;
|
||||
|
||||
class ImportsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('import');
|
||||
$imports = (new ImportsTransformer)->transformImports(Import::latest()->get());
|
||||
|
||||
return view('importer/import')->with('imports', $imports);
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,12 @@ class LicenseCheckinController extends Controller
|
||||
}
|
||||
|
||||
$license = License::find($licenseSeat->license_id);
|
||||
|
||||
// LicenseSeat is not assigned, it can't be checked in
|
||||
if (is_null($licenseSeat->assigned_to) && is_null($licenseSeat->asset_id)) {
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkin.error'));
|
||||
}
|
||||
|
||||
$this->authorize('checkout', $license);
|
||||
|
||||
if (! $license->reassignable) {
|
||||
|
||||
@@ -91,29 +91,30 @@ class LicenseFilesController extends Controller
|
||||
*/
|
||||
public function destroy($licenseId = null, $fileId = null)
|
||||
{
|
||||
$license = License::find($licenseId);
|
||||
if ($license = License::find($licenseId)) {
|
||||
|
||||
// the asset is valid
|
||||
if (isset($license->id)) {
|
||||
$this->authorize('update', $license);
|
||||
$log = Actionlog::find($fileId);
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('licenses/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('licenses/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
if ($log = Actionlog::find($fileId)) {
|
||||
|
||||
// Remove the file if one exists
|
||||
if (Storage::exists('licenses/'.$log->filename)) {
|
||||
try {
|
||||
Storage::delete('licenses/'.$log->filename);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', trans('admin/hardware/message.deletefile.success'));
|
||||
return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist'));
|
||||
}
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
|
||||
}
|
||||
|
||||
@@ -129,7 +130,6 @@ class LicenseFilesController extends Controller
|
||||
*/
|
||||
public function show($licenseId = null, $fileId = null, $download = true)
|
||||
{
|
||||
\Log::info('Private filesystem is: '.config('filesystems.default'));
|
||||
$license = License::find($licenseId);
|
||||
|
||||
// the license is valid
|
||||
|
||||
@@ -227,6 +227,36 @@ class LocationsController extends Controller
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that presents a form to clone a location.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $locationId
|
||||
* @since [v6.0.14]
|
||||
* @return View
|
||||
*/
|
||||
public function getClone($locationId = null)
|
||||
{
|
||||
$this->authorize('create', Location::class);
|
||||
|
||||
// Check if the asset exists
|
||||
if (is_null($location_to_clone = Location::find($locationId))) {
|
||||
// Redirect to the asset management page
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$location = clone $location_to_clone;
|
||||
|
||||
// unset these values
|
||||
$location->id = null;
|
||||
$location->image = null;
|
||||
|
||||
return view('locations/edit')
|
||||
->with('item', $location);
|
||||
}
|
||||
|
||||
|
||||
public function print_all_assigned($id)
|
||||
{
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
|
||||
@@ -17,7 +17,7 @@ class ModalController extends Controller
|
||||
* @author [A. Gianotto] [<snipe@snipe.net]
|
||||
* @return View
|
||||
*/
|
||||
function show ($type, $itemId = null) {
|
||||
public function show ($type, $itemId = null) {
|
||||
|
||||
// These values should correspond to a file in resources/views/modals/
|
||||
$allowed_types = [
|
||||
|
||||
@@ -8,7 +8,7 @@ use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -67,37 +67,9 @@ class ProfileController extends Controller
|
||||
$user->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
// Handle the avatar upload and/or delete if necessary
|
||||
app('\App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($request->input('avatar_delete') == 1) {
|
||||
$user->avatar = null;
|
||||
}
|
||||
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
$path = 'avatars';
|
||||
|
||||
if (! Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->makeDirectory($path, 775);
|
||||
}
|
||||
|
||||
$upload = $image = $request->file('avatar');
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = 'avatar-'.str_random(18).'.'.$ext;
|
||||
|
||||
if ($image->getClientOriginalExtension() != 'svg') {
|
||||
$upload = Image::make($image->getRealPath())->resize(84, 84);
|
||||
}
|
||||
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
|
||||
|
||||
// Remove Current image if exists
|
||||
if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) {
|
||||
Storage::disk('public')->delete($path.'/'.$user->avatar);
|
||||
}
|
||||
|
||||
$user->avatar = $file_name;
|
||||
}
|
||||
|
||||
if ($user->save()) {
|
||||
return redirect()->route('profile')->with('success', 'Account successfully updated');
|
||||
|
||||
@@ -51,9 +51,8 @@ class ReportsController extends Controller
|
||||
public function getAccessoryReport()
|
||||
{
|
||||
$this->authorize('reports.view');
|
||||
$accessories = Accessory::orderBy('created_at', 'DESC')->with('company')->get();
|
||||
|
||||
return view('reports/accessories', compact('accessories'));
|
||||
return view('reports/accessories');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +284,7 @@ class ReportsController extends Controller
|
||||
|
||||
$row = [
|
||||
$actionlog->created_at,
|
||||
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '',
|
||||
($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '',
|
||||
$actionlog->present()->actionType(),
|
||||
e($actionlog->itemType()),
|
||||
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
|
||||
@@ -1025,7 +1024,11 @@ class ReportsController extends Controller
|
||||
if (is_null($acceptance->created_at)){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||
} else {
|
||||
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
|
||||
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
|
||||
if ($logItem_res->isEmpty()){
|
||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
||||
}
|
||||
$logItem = $logItem_res[0];
|
||||
}
|
||||
|
||||
if(!$assetItem->assignedTo->locale){
|
||||
@@ -1120,8 +1123,6 @@ class ReportsController extends Controller
|
||||
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
|
||||
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
|
||||
$rows[] = implode(',', $row);
|
||||
} else {
|
||||
// Log the error maybe?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,18 +65,27 @@ class SettingsController extends Controller
|
||||
$start_settings['db_error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$protocol = array_key_exists('HTTPS', $_SERVER) && ('on' == $_SERVER['HTTPS']) ? 'https://' : 'http://';
|
||||
if (array_key_exists("HTTP_X_FORWARDED_PROTO", $_SERVER)) {
|
||||
$protocol = $_SERVER["HTTP_X_FORWARDED_PROTO"] . "://";
|
||||
} elseif (array_key_exists('HTTPS', $_SERVER) && ('on' == $_SERVER['HTTPS'])) {
|
||||
$protocol = "https://";
|
||||
} else {
|
||||
$protocol = "http://";
|
||||
}
|
||||
|
||||
$host = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : null;
|
||||
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null;
|
||||
if (('http://' === $protocol && '80' != $port) || ('https://' === $protocol && '443' != $port)) {
|
||||
$host .= ':'.$port;
|
||||
if (array_key_exists("HTTP_X_FORWARDED_HOST", $_SERVER)) {
|
||||
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
|
||||
} else {
|
||||
$host = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : null;
|
||||
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null;
|
||||
if (('http://' === $protocol && '80' != $port) || ('https://' === $protocol && '443' != $port)) {
|
||||
$host .= ':'.$port;
|
||||
}
|
||||
}
|
||||
$pageURL = $protocol.$host.$_SERVER['REQUEST_URI'];
|
||||
|
||||
$start_settings['url_valid'] = (url('/').'/setup' === $pageURL);
|
||||
|
||||
$start_settings['url_config'] = url('/');
|
||||
$start_settings['url_config'] = url('/').'/setup';
|
||||
$start_settings['url_valid'] = ($start_settings['url_config'] === $pageURL);
|
||||
$start_settings['real_url'] = $pageURL;
|
||||
$start_settings['php_version_min'] = true;
|
||||
|
||||
@@ -111,17 +120,17 @@ class SettingsController extends Controller
|
||||
$start_settings['prod'] = true;
|
||||
}
|
||||
|
||||
$start_settings['owner'] = '';
|
||||
|
||||
if (function_exists('posix_getpwuid')) { // Probably Linux
|
||||
$owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME']));
|
||||
$start_settings['owner'] = $owner['name'];
|
||||
} else { // Windows
|
||||
// TODO: Is there a way of knowing if a windows user has elevated permissions
|
||||
// This just gets the user name, which likely isn't 'root'
|
||||
// $start_settings['owner'] = getenv('USERNAME');
|
||||
$start_settings['owner'] = '';
|
||||
// This *should* be an array, but we've seen this return a bool in some chrooted environments
|
||||
if (is_array($owner)) {
|
||||
$start_settings['owner'] = $owner['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if (('root' === $start_settings['owner']) || ('0' === $start_settings['owner'])) {
|
||||
if (($start_settings['owner'] === 'root') || ($start_settings['owner'] === '0')) {
|
||||
$start_settings['owner_is_admin'] = true;
|
||||
} else {
|
||||
$start_settings['owner_is_admin'] = false;
|
||||
@@ -679,33 +688,6 @@ class SettingsController extends Controller
|
||||
return view('settings.slack', compact('setting'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a form to allow a super admin to update settings.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
*
|
||||
* @since [v1.0]
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function postSlack(SlackSettingsRequest $request)
|
||||
{
|
||||
if (is_null($setting = Setting::getSettings())) {
|
||||
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
|
||||
}
|
||||
|
||||
$setting->slack_endpoint = $request->input('slack_endpoint');
|
||||
$setting->slack_channel = $request->input('slack_channel');
|
||||
$setting->slack_botname = $request->input('slack_botname');
|
||||
|
||||
if ($setting->save()) {
|
||||
return redirect()->route('settings.index')
|
||||
->with('success', trans('admin/settings/message.update.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($setting->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a form to allow a super admin to update settings.
|
||||
*
|
||||
@@ -807,7 +789,7 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function getPhpInfo()
|
||||
{
|
||||
if (true === config('app.debug')) {
|
||||
if (config('app.debug') === true) {
|
||||
return view('settings.phpinfo');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Users;
|
||||
|
||||
use App\Events\UserMerged;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
@@ -13,6 +14,7 @@ use App\Models\LicenseSeat;
|
||||
use App\Models\ConsumableAssignment;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -21,7 +23,7 @@ use Illuminate\Support\Facades\Password;
|
||||
class BulkUsersController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns a view that confirms the user's a bulk delete will be applied to.
|
||||
* Returns a view that confirms the user's a bulk action will be applied to.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.7]
|
||||
@@ -35,16 +37,32 @@ class BulkUsersController extends Controller
|
||||
|
||||
// Make sure there were users selected
|
||||
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
|
||||
|
||||
// Get the list of affected users
|
||||
$user_raw_array = request('ids');
|
||||
$users = User::whereIn('id', $user_raw_array)
|
||||
->with('groups', 'assets', 'licenses', 'accessories')->get();
|
||||
|
||||
// bulk edit, display the bulk edit form
|
||||
if ($request->input('bulk_actions') == 'edit') {
|
||||
return view('users/bulk-edit', compact('users'))
|
||||
->with('groups', Group::pluck('name', 'id'));
|
||||
|
||||
// bulk delete, display the bulk delete confirmation form
|
||||
} elseif ($request->input('bulk_actions') == 'delete') {
|
||||
return view('users/confirm-bulk-delete')->with('users', $users)->with('statuslabel_list', Helper::statusLabelList());
|
||||
|
||||
// merge, confirm they have at least 2 users selected and display the merge screen
|
||||
} elseif ($request->input('bulk_actions') == 'merge') {
|
||||
|
||||
if (($request->filled('ids')) && (count($request->input('ids')) > 1)) {
|
||||
return view('users/confirm-merge')->with('users', $users);
|
||||
// Not enough users selected, send them back
|
||||
} else {
|
||||
return redirect()->back()->with('error', trans('general.not_enough_users_selected', ['count' => 2]));
|
||||
}
|
||||
|
||||
// bulk password reset, just do the thing
|
||||
} elseif ($request->input('bulk_actions') == 'bulkpasswordreset') {
|
||||
foreach ($users as $user) {
|
||||
if (($user->activated == '1') && ($user->email != '')) {
|
||||
@@ -59,7 +77,7 @@ class BulkUsersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'No users selected');
|
||||
return redirect()->back()->with('error', trans('general.no_users_selected'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +94,7 @@ class BulkUsersController extends Controller
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
if ((! $request->filled('ids')) || $request->input('ids') <= 0) {
|
||||
return redirect()->back()->with('error', 'No users selected');
|
||||
return redirect()->back()->with('error', trans('general.no_users_selected'));
|
||||
}
|
||||
$user_raw_array = $request->input('ids');
|
||||
|
||||
@@ -105,6 +123,11 @@ class BulkUsersController extends Controller
|
||||
'warning' => trans('admin/users/message.bulk_manager_warn'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($request->input('null_location_id')=='1') {
|
||||
$this->update_array['location_id'] = null;
|
||||
}
|
||||
|
||||
if (! $manager_conflict) {
|
||||
$this->conditionallyAddItem('manager_id');
|
||||
}
|
||||
@@ -163,11 +186,11 @@ class BulkUsersController extends Controller
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
if ((! $request->filled('ids')) || (count($request->input('ids')) == 0)) {
|
||||
return redirect()->back()->with('error', 'No users selected');
|
||||
return redirect()->back()->with('error', trans('general.no_users_selected'));
|
||||
}
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('users.index')->with('error', 'Bulk delete is not enabled in this installation');
|
||||
return redirect()->route('users.index')->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
$user_raw_array = request('ids');
|
||||
@@ -249,4 +272,80 @@ class BulkUsersController extends Controller
|
||||
$logAction->logaction('checkin from');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save bulk-edited users
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function merge(Request $request)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
if (config('app.lock_passwords')) {
|
||||
return redirect()->route('users.index')->with('error', trans('general.feature_disabled'));
|
||||
}
|
||||
|
||||
$user_ids_to_merge = $request->input('ids_to_merge');
|
||||
$user_ids_to_merge = array_diff($user_ids_to_merge, array($request->input('merge_into_id')));
|
||||
|
||||
if ((!$request->filled('merge_into_id')) || (count($user_ids_to_merge) < 1)) {
|
||||
return redirect()->back()->with('error', trans('general.no_users_selected'));
|
||||
}
|
||||
|
||||
// Get the users
|
||||
$merge_into_user = User::find($request->input('merge_into_id'));
|
||||
$users_to_merge = User::whereIn('id', $user_ids_to_merge)->with('assets', 'licenses', 'consumables','accessories')->get();
|
||||
$admin = User::find(Auth::user()->id);
|
||||
|
||||
// Walk users
|
||||
foreach ($users_to_merge as $user_to_merge) {
|
||||
|
||||
foreach ($user_to_merge->assets as $asset) {
|
||||
\Log::debug('Updating asset: '.$asset->asset_tag . ' to '.$merge_into_user->id);
|
||||
$asset->assigned_to = $request->input('merge_into_id');
|
||||
$asset->save();
|
||||
}
|
||||
|
||||
foreach ($user_to_merge->licenses as $license) {
|
||||
\Log::debug('Updating license pivot: '.$license->id . ' to '.$merge_into_user->id);
|
||||
$user_to_merge->licenses()->updateExistingPivot($license->id, ['assigned_to' => $merge_into_user->id]);
|
||||
}
|
||||
|
||||
foreach ($user_to_merge->consumables as $consumable) {
|
||||
\Log::debug('Updating consumable pivot: '.$consumable->id . ' to '.$merge_into_user->id);
|
||||
$user_to_merge->consumables()->updateExistingPivot($consumable->id, ['assigned_to' => $merge_into_user->id]);
|
||||
}
|
||||
|
||||
foreach ($user_to_merge->accessories as $accessory) {
|
||||
$user_to_merge->accessories()->updateExistingPivot($accessory->id, ['assigned_to' => $merge_into_user->id]);
|
||||
}
|
||||
|
||||
foreach ($user_to_merge->userlog as $log) {
|
||||
$log->target_id = $user_to_merge->id;
|
||||
$log->save();
|
||||
}
|
||||
|
||||
User::where('manager_id', '=', $user_to_merge->id)->update(['manager_id' => $merge_into_user->id]);
|
||||
|
||||
foreach ($user_to_merge->managedLocations as $managedLocation) {
|
||||
$managedLocation->manager_id = $merge_into_user->id;
|
||||
$managedLocation->save();
|
||||
}
|
||||
|
||||
$user_to_merge->delete();
|
||||
//$user_to_merge->save();
|
||||
|
||||
event(new UserMerged($user_to_merge, $merge_into_user, $admin));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('success', trans('general.merge_success', ['count' => $users_to_merge->count(), 'into_username' => $merge_into_user->username]));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ class UsersController extends Controller
|
||||
$user->created_by = Auth::user()->id;
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses= $request->input('autoassign_licenses', 1);
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
$permissions_array = $request->input('permission');
|
||||
@@ -131,7 +132,7 @@ class UsersController extends Controller
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// we have to invoke the
|
||||
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
if ($request->filled('groups')) {
|
||||
@@ -271,9 +272,11 @@ class UsersController extends Controller
|
||||
$user->activated = $request->input('activated', 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', 1);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
@@ -296,7 +299,7 @@ class UsersController extends Controller
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
//\Log::debug(print_r($user, true));
|
||||
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\CustomField;
|
||||
use Livewire\Component;
|
||||
|
||||
use App\Models\Import;
|
||||
use Storage;
|
||||
|
||||
use Log;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
|
||||
|
||||
class Importer extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public $files;
|
||||
|
||||
public $progress; //upload progress - '-1' means don't show
|
||||
public $progress_message;
|
||||
public $progress_bar_class;
|
||||
|
||||
public $message; //status/error message?
|
||||
public $message_type; //success/error?
|
||||
|
||||
//originally from ImporterFile
|
||||
public $import_errors; //
|
||||
public ?Import $activeFile = null;
|
||||
public $importTypes;
|
||||
public $columnOptions;
|
||||
public $statusType;
|
||||
public $statusText;
|
||||
public $update;
|
||||
public $send_welcome;
|
||||
public $run_backup;
|
||||
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
|
||||
public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason?
|
||||
|
||||
protected $rules = [
|
||||
'files.*.file_path' => 'required|string',
|
||||
'files.*.created_at' => 'required|string',
|
||||
'files.*.filesize' => 'required|integer',
|
||||
'activeFile' => 'Import',
|
||||
'activeFile.import_type' => 'string',
|
||||
'activeFile.field_map' => 'array',
|
||||
'activeFile.header_row' => 'array',
|
||||
'field_map' => 'array'
|
||||
];
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// all of these 'statics', alas, may have to change to something else to handle translations?
|
||||
// I'm not sure. Maybe I use them to 'populate' the translations? TBH, I don't know yet.
|
||||
static $general = [
|
||||
'category' => 'Category',
|
||||
'company' => 'Company',
|
||||
'email' => 'Email',
|
||||
'item_name' => 'Item Name',
|
||||
'location' => 'Location',
|
||||
'maintained' => 'Maintained',
|
||||
'manufacturer' => 'Manufacturer',
|
||||
'notes' => 'Notes',
|
||||
'order_number' => 'Order Number',
|
||||
'purchase_cost' => 'Purchase Cost',
|
||||
'purchase_date' => 'Purchase Date',
|
||||
'quantity' => 'Quantity',
|
||||
'requestable' => 'Requestable',
|
||||
'serial' => 'Serial Number',
|
||||
'supplier' => 'Supplier',
|
||||
'username' => 'Username',
|
||||
'department' => 'Department',
|
||||
];
|
||||
|
||||
static $accessories = [
|
||||
'model_number' => 'Model Number',
|
||||
];
|
||||
|
||||
static $assets = [
|
||||
'asset_tag' => 'Asset Tag',
|
||||
'asset_model' => 'Model Name',
|
||||
'byod' => 'BYOD',
|
||||
'checkout_class' => 'Checkout Type',
|
||||
'checkout_location' => 'Checkout Location',
|
||||
'image' => 'Image Filename',
|
||||
'model_number' => 'Model Number',
|
||||
'full_name' => 'Full Name',
|
||||
'status' => 'Status',
|
||||
'warranty_months' => 'Warranty Months',
|
||||
];
|
||||
|
||||
static $consumables = [
|
||||
'item_no' => "Item Number",
|
||||
'model_number' => "Model Number",
|
||||
'min_amt' => "Minimum Quantity",
|
||||
];
|
||||
|
||||
static $licenses = [
|
||||
'asset_tag' => 'Assigned To Asset',
|
||||
'expiration_date' => 'Expiration Date',
|
||||
'full_name' => 'Full Name',
|
||||
'license_email' => 'Licensed To Email',
|
||||
'license_name' => 'Licensed To Name',
|
||||
'purchase_order' => 'Purchase Order',
|
||||
'reassignable' => 'Reassignable',
|
||||
'seats' => 'Seats',
|
||||
];
|
||||
|
||||
static $users = [
|
||||
'employee_num' => 'Employee Number',
|
||||
'first_name' => 'First Name',
|
||||
'jobtitle' => 'Job Title',
|
||||
'last_name' => 'Last Name',
|
||||
'phone_number' => 'Phone Number',
|
||||
'manager_first_name' => 'Manager First Name',
|
||||
'manager_last_name' => 'Manager Last Name',
|
||||
'activated' => 'Activated',
|
||||
'address' => 'Address',
|
||||
'city' => 'City',
|
||||
'state' => 'State',
|
||||
'country' => 'Country',
|
||||
'vip' => 'VIP'
|
||||
];
|
||||
|
||||
//array of "real fieldnames" to a list of aliases for that field
|
||||
static $aliases = [
|
||||
'model_number' =>
|
||||
[
|
||||
'model',
|
||||
'model no',
|
||||
'model no.',
|
||||
'model number',
|
||||
'model num',
|
||||
'model num.'
|
||||
],
|
||||
'warranty_months' =>
|
||||
[
|
||||
'Warranty',
|
||||
'Warranty Months'
|
||||
],
|
||||
'qty' =>
|
||||
[
|
||||
'QTY',
|
||||
'Quantity'
|
||||
],
|
||||
'min_amt' =>
|
||||
[
|
||||
'Min Amount',
|
||||
'Min QTY'
|
||||
],
|
||||
'next_audit_date' =>
|
||||
[
|
||||
'Next Audit',
|
||||
],
|
||||
|
||||
|
||||
];
|
||||
|
||||
private function getColumns($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'asset':
|
||||
$results = self::$general + self::$assets;
|
||||
break;
|
||||
case 'accessory':
|
||||
$results = self::$general + self::$accessories;
|
||||
break;
|
||||
case 'consumable':
|
||||
$results = self::$general + self::$consumables;
|
||||
break;
|
||||
case 'license':
|
||||
$results = self::$general + self::$licenses;
|
||||
break;
|
||||
case 'user':
|
||||
$results = self::$general + self::$users;
|
||||
break;
|
||||
default:
|
||||
$results = self::$general;
|
||||
}
|
||||
asort($results, SORT_FLAG_CASE | SORT_STRING);
|
||||
if ($type == "asset") {
|
||||
// add Custom Fields after a horizontal line
|
||||
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
|
||||
foreach (CustomField::orderBy('name')->get() as $field) {
|
||||
$results[$field->db_column_name()] = $field->name;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function updating($name, $new_import_type)
|
||||
{
|
||||
if ($name == "activeFile.import_type") {
|
||||
\Log::info("WE ARE CHANGING THE import_type!!!!! TO: " . $new_import_type);
|
||||
\Log::info("so, what's \$this->>field_map at?: " . print_r($this->field_map, true));
|
||||
// go through each header, find a matching field to try and map it to.
|
||||
foreach ($this->activeFile->header_row as $i => $header) {
|
||||
// do we have something mapped already?
|
||||
if (array_key_exists($i, $this->field_map)) {
|
||||
// yes, we do. Is it valid for this type of import?
|
||||
// (e.g. the import type might have been changed...?)
|
||||
if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) {
|
||||
//yes, this key *is* valid. Continue on to the next field.
|
||||
continue;
|
||||
} else {
|
||||
//no, this key is *INVALID* for this import type. Better set it to null
|
||||
// and we'll hope that the aliases or something else picks it up.
|
||||
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
|
||||
} // TODO - strictly speaking, this isn't necessary here I don't think.
|
||||
}
|
||||
// first, check for exact matches
|
||||
foreach ($this->columnOptions[$new_import_type] as $value => $text) {
|
||||
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
|
||||
$this->field_map[$i] = $value;
|
||||
continue 2; //don't bother with the alias check, go to the next header
|
||||
}
|
||||
}
|
||||
// if you got here, we didn't find a match. Try the aliases
|
||||
foreach (self::$aliases as $key => $alias_values) {
|
||||
foreach ($alias_values as $alias_value) {
|
||||
if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE!
|
||||
// Make *absolutely* sure that this key actually _exists_ in this import type -
|
||||
// you can trigger this by importing accessories with a 'Warranty' column (which don't exist
|
||||
// in "Accessories"!)
|
||||
if (array_key_exists($key, $this->columnOptions[$new_import_type])) {
|
||||
$this->field_map[$i] = $key;
|
||||
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// and if you got here, we got nothing. Let's recommend 'null'
|
||||
$this->field_map[$i] = null; // Booooo :(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function boot() { // FIXME - delete or undelete.
|
||||
///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever.
|
||||
}
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->authorize('import');
|
||||
$this->progress = -1; // '-1' means 'don't show the progressbar'
|
||||
$this->progress_bar_class = 'progress-bar-warning';
|
||||
\Log::info("Hey, we are calling MOUNT (in the importer-file) !!!!!!!!"); //fcuk
|
||||
$this->importTypes = [
|
||||
'asset' => trans('general.assets'),
|
||||
'accessory' => trans('general.accessories'),
|
||||
'consumable' => trans('general.consumables'),
|
||||
'component' => trans('general.components'),
|
||||
'license' => trans('general.licenses'),
|
||||
'user' => trans('general.users'),
|
||||
];
|
||||
|
||||
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
|
||||
foreach($this->importTypes AS $type => $name) {
|
||||
$this->columnOptions[$type] = $this->getColumns($type);
|
||||
}
|
||||
if ($this->activeFile) {
|
||||
$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : [];
|
||||
}
|
||||
}
|
||||
|
||||
public function selectFile($id)
|
||||
{
|
||||
\Log::info("TOGGLE EVENT FIRED!");
|
||||
\Log::error("The ID we are trying to find is AS FOLLOWS: ".$id);
|
||||
$this->activeFile = Import::find($id);
|
||||
$this->field_map = null;
|
||||
foreach($this->activeFile->header_row as $element) {
|
||||
if(isset($this->activeFile->field_map[$element])) {
|
||||
$this->field_map[] = $this->activeFile->field_map[$element];
|
||||
} else {
|
||||
$this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings
|
||||
}
|
||||
}
|
||||
//$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : []; // this is wrong
|
||||
$this->file_id = $id;
|
||||
$this->import_errors = null;
|
||||
$this->statusText = null;
|
||||
\Log::error("The import type we are about to try and load up is gonna be this: ".$this->activeFile->import_type);
|
||||
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
// TODO: why don't we just do File::find($id)? This seems dumb.
|
||||
foreach($this->files as $file) {
|
||||
\Log::debug("File id is: ".$file->id);
|
||||
if($id == $file->id) {
|
||||
if(Storage::delete('private_uploads/imports/'.$file->file_path)) {
|
||||
$file->delete();
|
||||
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_success');
|
||||
$this->message_type = 'success';
|
||||
return;
|
||||
} else {
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_error');
|
||||
$this->message_type = 'danger';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.
|
||||
return view('livewire.importer')
|
||||
->extends('layouts.default')
|
||||
->section('content');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Livewire\Component;
|
||||
use App\Models\Setting;
|
||||
use App\Helpers\Helper;
|
||||
|
||||
class SlackSettingsForm extends Component
|
||||
{
|
||||
public $webhook_endpoint;
|
||||
public $webhook_channel;
|
||||
public $webhook_botname;
|
||||
public $isDisabled ='' ;
|
||||
public $webhook_name;
|
||||
public $webhook_link;
|
||||
public $webhook_placeholder;
|
||||
public $webhook_icon;
|
||||
public $webhook_selected;
|
||||
public array $webhook_text;
|
||||
|
||||
public Setting $setting;
|
||||
|
||||
protected $rules = [
|
||||
'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
|
||||
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
||||
'webhook_botname' => 'string|nullable',
|
||||
];
|
||||
|
||||
public function mount() {
|
||||
$this->webhook_text= [
|
||||
"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',
|
||||
),
|
||||
"general"=> array(
|
||||
"name" => trans('admin/settings/general.general_webhook'),
|
||||
"icon" => "fab fa-hashtag",
|
||||
"placeholder" => "",
|
||||
"link" => "",
|
||||
),
|
||||
];
|
||||
|
||||
$this->setting = Setting::getSettings();
|
||||
$this->save_button = trans('general.save');
|
||||
$this->webhook_selected = $this->setting->webhook_selected;
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
|
||||
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"];
|
||||
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
|
||||
$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->setting->webhook_selected == 'general'){
|
||||
$this->isDisabled='';
|
||||
}
|
||||
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
||||
$this->isDisabled= '';
|
||||
}
|
||||
|
||||
}
|
||||
public function updated($field) {
|
||||
|
||||
if($this->webhook_selected != 'general') {
|
||||
$this->validateOnly($field, $this->rules);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedWebhookSelected() {
|
||||
$this->webhook_name = $this->webhook_text[$this->webhook_selected]['name'];
|
||||
$this->webhook_icon = $this->webhook_text[$this->webhook_selected]["icon"]; ;
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
|
||||
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
|
||||
if($this->webhook_selected != 'slack'){
|
||||
$this->isDisabled= '';
|
||||
$this->save_button = trans('general.save');
|
||||
}
|
||||
}
|
||||
|
||||
private function isButtonDisabled() {
|
||||
if($this->webhook_selected == 'slack') {
|
||||
if (empty($this->webhook_endpoint)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
if (empty($this->webhook_channel)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->isButtonDisabled();
|
||||
return view('livewire.slack-settings-form');
|
||||
}
|
||||
|
||||
public function testWebhook(){
|
||||
|
||||
$webhook = new Client([
|
||||
'base_url' => e($this->webhook_endpoint),
|
||||
'defaults' => [
|
||||
'exceptions' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
$payload = json_encode(
|
||||
[
|
||||
'channel' => e($this->webhook_channel),
|
||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
'username' => e($this->webhook_botname),
|
||||
'icon_emoji' => ':heart:',
|
||||
]);
|
||||
|
||||
try {
|
||||
|
||||
$webhook->post($this->webhook_endpoint, ['body' => $payload]);
|
||||
$this->isDisabled='';
|
||||
$this->save_button = trans('general.save');
|
||||
return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$this->isDisabled= 'disabled';
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_misc'));
|
||||
|
||||
}
|
||||
|
||||
public function clearSettings(){
|
||||
|
||||
if (Helper::isDemoMode()) {
|
||||
session()->flash('error',trans('general.feature_disabled'));
|
||||
} else {
|
||||
$this->webhook_endpoint = '';
|
||||
$this->webhook_channel = '';
|
||||
$this->webhook_botname = '';
|
||||
$this->setting->webhook_endpoint = '';
|
||||
$this->setting->webhook_channel = '';
|
||||
$this->setting->webhook_botname = '';
|
||||
|
||||
$this->setting->save();
|
||||
|
||||
session()->flash('success', trans('admin/settings/message.update.success'));
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
if (Helper::isDemoMode()) {
|
||||
session()->flash('error',trans('general.feature_disabled'));
|
||||
} else {
|
||||
if ($this->webhook_selected != 'general') {
|
||||
$this->validate($this->rules);
|
||||
}
|
||||
|
||||
$this->setting->webhook_selected = $this->webhook_selected;
|
||||
$this->setting->webhook_endpoint = $this->webhook_endpoint;
|
||||
$this->setting->webhook_channel = $this->webhook_channel;
|
||||
$this->setting->webhook_botname = $this->webhook_botname;
|
||||
|
||||
$this->setting->save();
|
||||
|
||||
session()->flash('success',trans('admin/settings/message.update.success'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,13 @@ class AssetCountForSidebar
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_byod_sidebar = Asset::where('byod', '=', '1')->count();
|
||||
view()->share('total_byod_sidebar', $total_byod_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class CheckForTwoFactor
|
||||
/**
|
||||
* Routes to ignore for Two Factor Auth
|
||||
*/
|
||||
const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout'];
|
||||
public const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout'];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class CheckPermissions
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ class AssetFileRequest extends Request
|
||||
$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,webp|max:'.$max_file_size,
|
||||
'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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use enshrined\svgSanitize\Sanitizer;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use App\Http\Traits\ConvertsBase64ToFiles;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
|
||||
|
||||
class ImageUploadRequest extends Request
|
||||
{
|
||||
@@ -63,11 +65,13 @@ class ImageUploadRequest extends Request
|
||||
* @param string $path location for uploaded images, defaults to uploads/plural of item type.
|
||||
* @return SnipeModel Target asset is being checked out to.
|
||||
*/
|
||||
public function handleImages($item, $w = 600, $form_fieldname = null, $path = null, $db_fieldname = 'image')
|
||||
public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
|
||||
{
|
||||
|
||||
$type = strtolower(class_basename(get_class($item)));
|
||||
|
||||
if (is_null($path)) {
|
||||
|
||||
$path = str_plural($type);
|
||||
|
||||
if ($type == 'assetmodel') {
|
||||
@@ -79,49 +83,47 @@ class ImageUploadRequest extends Request
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($form_fieldname)) {
|
||||
$form_fieldname = 'image';
|
||||
}
|
||||
|
||||
// This is dumb, but we need it for overriding field names for exceptions like avatars and logo uploads
|
||||
if (is_null($db_fieldname)) {
|
||||
$use_db_field = $form_fieldname;
|
||||
} else {
|
||||
$use_db_field = $db_fieldname;
|
||||
}
|
||||
|
||||
|
||||
// ConvertBase64ToFiles just changes object type,
|
||||
// as it cannot currently insert files to $this->files
|
||||
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
|
||||
$image=$this->offsetGet($form_fieldname);
|
||||
$image = $this->offsetGet($form_fieldname);
|
||||
\Log::debug('Image is an instance of UploadedFile');
|
||||
} elseif ($this->hasFile($form_fieldname)) {
|
||||
$image = $this->file($form_fieldname);
|
||||
\Log::debug('Just use regular upload for '.$form_fieldname);
|
||||
} else {
|
||||
if ($this->hasFile($form_fieldname)) {
|
||||
$image = $this->file($form_fieldname);
|
||||
}
|
||||
\Log::debug('No image found for form fieldname: '.$form_fieldname);
|
||||
}
|
||||
|
||||
if (isset($image)) {
|
||||
\Log::debug($image);
|
||||
|
||||
if (!config('app.lock_passwords')) {
|
||||
|
||||
$ext = $image->getClientOriginalExtension();
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.str_random(10).'.'.$ext;
|
||||
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
|
||||
|
||||
\Log::info('File name will be: '.$file_name);
|
||||
\Log::debug('File extension is: '.$ext);
|
||||
|
||||
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
|
||||
|
||||
\Log::debug('Not an SVG or webp - resize');
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
|
||||
try {
|
||||
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
} catch(NotReadableException $e) {
|
||||
\Log::debug($e);
|
||||
$validator = \Validator::make([], []);
|
||||
$validator->errors()->add($form_fieldname, trans('general.unaccepted_image_type', ['mimetype' => $image->getClientMimeType()]));
|
||||
|
||||
throw new \Illuminate\Validation\ValidationException($validator);
|
||||
}
|
||||
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
|
||||
|
||||
} else {
|
||||
// If the file is a webp, we need to just move it since webp support
|
||||
// needs to be compiled into gd for resizing to be available
|
||||
@@ -146,30 +148,30 @@ class ImageUploadRequest extends Request
|
||||
}
|
||||
|
||||
// Remove Current image if exists
|
||||
if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) {
|
||||
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
|
||||
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
|
||||
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
|
||||
}
|
||||
}
|
||||
|
||||
$item->{$use_db_field} = $file_name;
|
||||
$item->{$db_fieldname} = $file_name;
|
||||
}
|
||||
|
||||
|
||||
// If the user isn't uploading anything new but wants to delete their old image, do so
|
||||
} else {
|
||||
if ($this->input('image_delete') == '1') {
|
||||
\Log::debug('Deleting image');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
|
||||
$item->{$use_db_field} = null;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
} elseif ($this->input('image_delete') == '1') {
|
||||
\Log::debug('Deleting image');
|
||||
try {
|
||||
Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
|
||||
$item->{$db_fieldname} = null;
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $item;
|
||||
|
||||
@@ -39,14 +39,12 @@ class SaveUserRequest extends FormRequest
|
||||
|
||||
// Brand new user
|
||||
case 'POST':
|
||||
{
|
||||
$rules['first_name'] = 'required|string|min:1';
|
||||
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
|
||||
if ($this->request->get('ldap_import') == false) {
|
||||
$rules['password'] = Setting::passwordComplexityRulesSaving('store').'|confirmed';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Save all fields
|
||||
case 'PUT':
|
||||
@@ -57,12 +55,11 @@ class SaveUserRequest extends FormRequest
|
||||
|
||||
// Save only what's passed
|
||||
case 'PATCH':
|
||||
{
|
||||
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
|
||||
break;
|
||||
}
|
||||
|
||||
default:break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class SlackSettingsRequest 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()
|
||||
{
|
||||
return [
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:"https://hooks.slack.com"|nullable',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
'slack_botname' => 'string|nullable',
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Accessory;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
@@ -38,7 +38,8 @@ class AccessoriesTransformer
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
|
||||
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
|
||||
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
|
||||
'remaining_qty' => $accessory->numRemaining(),
|
||||
'remaining_qty' => (int) $accessory->numRemaining(),
|
||||
'users_count' => $accessory->users_count,
|
||||
|
||||
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),
|
||||
@@ -50,6 +51,8 @@ class AccessoriesTransformer
|
||||
'checkin' => false,
|
||||
'update' => Gate::allows('update', Accessory::class),
|
||||
'delete' => Gate::allows('delete', Accessory::class),
|
||||
'clone' => Gate::allows('create', Accessory::class),
|
||||
|
||||
];
|
||||
|
||||
$permissions_array['user_can_checkout'] = false;
|
||||
|
||||
@@ -60,12 +60,14 @@ class ActionlogsTransformer
|
||||
if ($actionlog->action_type == 'accepted') {
|
||||
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
|
||||
} else {
|
||||
if ($actionlog->itemType() == 'asset') {
|
||||
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'license') {
|
||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'user') {
|
||||
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
if ($actionlog->item) {
|
||||
if ($actionlog->itemType() == 'asset') {
|
||||
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'license') {
|
||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'user') {
|
||||
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Transformers;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetMaintenance;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class AssetMaintenancesTransformer
|
||||
@@ -45,6 +45,10 @@ class AssetMaintenancesTransformer
|
||||
'name'=> e($assetmaintenance->asset->location->name),
|
||||
|
||||
] : null,
|
||||
'rtd_location' => ($assetmaintenance->asset->defaultLoc) ? [
|
||||
'id' => (int) $assetmaintenance->asset->defaultLoc->id,
|
||||
'name'=> e($assetmaintenance->asset->defaultLoc->name),
|
||||
] : null,
|
||||
'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null,
|
||||
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null,
|
||||
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\AssetModel;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Transformers;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
|
||||
@@ -35,8 +35,11 @@ class AssetsTransformer
|
||||
'id' => (int) $asset->model->id,
|
||||
'name'=> e($asset->model->name),
|
||||
] : null,
|
||||
'byod' => ($asset->byod ? true : false),
|
||||
|
||||
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
|
||||
'eol' => ($asset->purchase_date != '') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null,
|
||||
'eol' => ($asset->model->eol != '') ? $asset->model->eol : null,
|
||||
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
|
||||
'status_label' => ($asset->assetstatus) ? [
|
||||
'id' => (int) $asset->assetstatus->id,
|
||||
'name'=> e($asset->assetstatus->name),
|
||||
@@ -81,7 +84,7 @@ class AssetsTransformer
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
|
||||
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
|
||||
'age' => $asset->purchase_date ? Helper::AgeFormat($asset->purchase_date) : '',
|
||||
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
|
||||
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
|
||||
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
|
||||
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
|
||||
@@ -190,8 +193,6 @@ class AssetsTransformer
|
||||
'email'=> ($asset->assigned->email) ? e($asset->assigned->email) : null,
|
||||
'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null,
|
||||
'type' => 'user',
|
||||
'eula_pdf' => e($asset->acceptanceLog->last()->filename),
|
||||
'signature_file' => e($asset->acceptanceLog->last()->accept_signature),
|
||||
] : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Category;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
@@ -22,18 +22,38 @@ class CategoriesTransformer
|
||||
|
||||
public function transformCategory(Category $category = null)
|
||||
{
|
||||
|
||||
// We only ever use item_count for categories in this transformer, so it makes sense to keep it
|
||||
// simple and do this switch here.
|
||||
switch ($category->category_type) {
|
||||
case 'asset':
|
||||
$category->item_count = $category->assets_count;
|
||||
break;
|
||||
case 'accessory':
|
||||
$category->item_count = $category->accessories_count;
|
||||
break;
|
||||
case 'consumable':
|
||||
$category->item_count = $category->consumables_count;
|
||||
break;
|
||||
case 'component':
|
||||
$category->item_count = $category->components_count;
|
||||
break;
|
||||
default:
|
||||
$category->item_count = 0;
|
||||
}
|
||||
|
||||
if ($category) {
|
||||
$array = [
|
||||
'id' => (int) $category->id,
|
||||
'name' => e($category->name),
|
||||
'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null,
|
||||
'category_type' => ucwords(e($category->category_type)),
|
||||
'category_type' => Helper::categoryTypeList($category->category_type),
|
||||
'has_eula' => ($category->getEula() ? true : false),
|
||||
'use_default_eula' => ($category->use_default_eula=='1' ? true : false),
|
||||
'eula' => ($category->getEula()),
|
||||
'checkin_email' => ($category->checkin_email == '1'),
|
||||
'require_acceptance' => ($category->require_acceptance == '1'),
|
||||
'item_count' => (int) $category->itemCount(),
|
||||
'item_count' => (int) $category->item_count,
|
||||
'assets_count' => (int) $category->assets_count,
|
||||
'accessories_count' => (int) $category->accessories_count,
|
||||
'consumables_count' => (int) $category->consumables_count,
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Company;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Models\Asset;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ComponentsAssetsTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Component;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Consumable;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Department;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Transformers;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Depreciable;
|
||||
use App\Models\Depreciation;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class DepreciationsTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Group;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class GroupsTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class LicenseSeatsTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\License;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class LicensesTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Location;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
@@ -63,6 +63,7 @@ class LocationsTransformer
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => Gate::allows('update', Location::class) ? true : false,
|
||||
'delete' => $location->isDeletable(),
|
||||
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
|
||||
];
|
||||
|
||||
$array += $permissions_array;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Manufacturer;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Models\PredefinedKit;
|
||||
use App\Models\SnipeModel;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Statuslabel;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class StatuslabelsTransformer
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Supplier;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\User;
|
||||
use Gate;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class UsersTransformer
|
||||
@@ -36,6 +36,7 @@ class UsersTransformer
|
||||
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
|
||||
] : null,
|
||||
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
|
||||
'vip' => ($user->vip == '1') ? true : false,
|
||||
'phone' => ($user->phone) ? e($user->phone) : null,
|
||||
'website' => ($user->website) ? e($user->website) : null,
|
||||
'address' => ($user->address) ? e($user->address) : null,
|
||||
@@ -56,8 +57,8 @@ class UsersTransformer
|
||||
'permissions' => $user->decodePermissions(),
|
||||
'activated' => ($user->activated == '1') ? true : false,
|
||||
'ldap_import' => ($user->ldap_import == '1') ? true : false,
|
||||
'two_factor_activated' => ($user->two_factor_active()) ? true : false,
|
||||
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
|
||||
'two_factor_optin' => ($user->two_factor_active()) ? true : false,
|
||||
'assets_count' => (int) $user->assets_count,
|
||||
'licenses_count' => (int) $user->licenses_count,
|
||||
'accessories_count' => (int) $user->accessories_count,
|
||||
|
||||
@@ -76,10 +76,12 @@ class AssetImporter extends ItemImporter
|
||||
}
|
||||
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
|
||||
$this->item['image'] = $this->findCsvMatch($row, 'image');
|
||||
$this->item['requestable'] = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'));
|
||||
$asset->requestable = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'));
|
||||
$this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
|
||||
$asset->requestable = $this->item['requestable'];
|
||||
$this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
|
||||
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
|
||||
$this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
|
||||
|
||||
|
||||
// If no status ID is found
|
||||
if (! array_key_exists('status_id', $this->item) && ! $editingAsset) {
|
||||
@@ -135,7 +137,7 @@ class AssetImporter extends ItemImporter
|
||||
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
|
||||
//-- the class that needs to use it (command importer or GUI importer inside the project).
|
||||
if (isset($target)) {
|
||||
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'));
|
||||
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -74,7 +74,7 @@ class ItemImporter extends Importer
|
||||
|
||||
$this->item['purchase_date'] = null;
|
||||
if ($this->findCsvMatch($row, 'purchase_date') != '') {
|
||||
$this->item['purchase_date'] = date('Y-m-d 00:00:01', strtotime($this->findCsvMatch($row, 'purchase_date')));
|
||||
$this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date')));
|
||||
}
|
||||
|
||||
$this->item['last_audit_date'] = null;
|
||||
@@ -90,7 +90,7 @@ class ItemImporter extends Importer
|
||||
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
||||
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
||||
$this->item['user_id'] = $this->user_id;
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial number');
|
||||
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
|
||||
// NO need to call this method if we're running the user import.
|
||||
// TODO: Merge these methods.
|
||||
$this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class');
|
||||
@@ -222,11 +222,11 @@ class ItemImporter extends Importer
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||
|
||||
|
||||
if(!empty($asset_modelNumber)){
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
}
|
||||
|
||||
|
||||
$asset_model->update($item);
|
||||
$asset_model->save();
|
||||
$this->log('Asset Model Updated');
|
||||
|
||||
@@ -80,6 +80,11 @@ class LicenseImporter extends ItemImporter
|
||||
$checkout_target = $this->item['checkout_target'];
|
||||
$asset = Asset::where('asset_tag', $asset_tag)->first();
|
||||
$targetLicense = $license->freeSeat();
|
||||
|
||||
if (is_null($targetLicense)){
|
||||
return;
|
||||
}
|
||||
|
||||
if ($checkout_target) {
|
||||
$targetLicense->assigned_to = $checkout_target->id;
|
||||
$targetLicense->user_id = Auth::id();
|
||||
|
||||
@@ -58,6 +58,8 @@ class UserImporter extends ItemImporter
|
||||
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
|
||||
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
|
||||
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
|
||||
$this->item['vip'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0;
|
||||
|
||||
|
||||
$user_department = $this->findCsvMatch($row, 'department');
|
||||
if ($this->shouldUpdateField($user_department)) {
|
||||
|
||||
@@ -36,8 +36,10 @@
|
||||
| name | | |
|
||||
| email | | |
|
||||
| username | | |
|
||||
| address | address | User |
|
||||
| city | city | User |
|
||||
| state | state | User |
|
||||
| country | country | User |
|
||||
| address | address | User |
|
||||
| city | city | User |
|
||||
| state | state | User |
|
||||
| country | country | User |
|
||||
| vip | vip | User |
|
||||
| byod | byod | Asset |
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ use App\Events\ItemDeclined;
|
||||
use App\Events\LicenseCheckedIn;
|
||||
use App\Events\LicenseCheckedOut;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\User;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Events\UserMerged;
|
||||
|
||||
class LogListener
|
||||
{
|
||||
@@ -87,6 +89,43 @@ class LogListener
|
||||
$logaction->save();
|
||||
}
|
||||
|
||||
|
||||
public function onUserMerged(UserMerged $event)
|
||||
{
|
||||
|
||||
$to_from_array = [
|
||||
'to_id' => $event->merged_to->id,
|
||||
'to_username' => $event->merged_to->username,
|
||||
'from_id' => $event->merged_from->id,
|
||||
'from_username' => $event->merged_from->username,
|
||||
];
|
||||
|
||||
// Add a record to the users being merged FROM
|
||||
\Log::debug('Users merged: '.$event->merged_from->id .' ('.$event->merged_from->username.') merged into '. $event->merged_to->id. ' ('.$event->merged_to->username.')');
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_id = $event->merged_from->id;
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->target_id = $event->merged_to->id;
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->action_type = 'merged';
|
||||
$logaction->note = trans('general.merged_log_this_user_from', $to_from_array);
|
||||
$logaction->user_id = $event->admin->id;
|
||||
$logaction->save();
|
||||
|
||||
// Add a record to the users being merged TO
|
||||
$logaction = new Actionlog();
|
||||
$logaction->target_id = $event->merged_from->id;
|
||||
$logaction->target_type = User::class;
|
||||
$logaction->item_id = $event->merged_to->id;
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->action_type = 'merged';
|
||||
$logaction->note = trans('general.merged_log_this_user_into', $to_from_array);
|
||||
$logaction->user_id = $event->admin->id;
|
||||
$logaction->save();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
@@ -99,6 +138,7 @@ class LogListener
|
||||
'CheckoutableCheckedOut',
|
||||
'CheckoutAccepted',
|
||||
'CheckoutDeclined',
|
||||
'UserMerged',
|
||||
];
|
||||
|
||||
foreach ($list as $event) {
|
||||
@@ -108,4 +148,6 @@ class LogListener
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class Accessory extends SnipeModel
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
|
||||
@@ -327,20 +328,6 @@ class Accessory extends SnipeModel
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within an accessory are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
$checkedout = $this->users->count();
|
||||
|
||||
return $checkedout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items of an accessory remain
|
||||
@@ -351,11 +338,11 @@ class Accessory extends SnipeModel
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
$checkedout = $this->users->count();
|
||||
$checkedout = $this->users_count;
|
||||
$total = $this->qty;
|
||||
$remaining = $total - $checkedout;
|
||||
|
||||
return $remaining;
|
||||
return (int) $remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+51
-38
@@ -34,9 +34,9 @@ class Asset extends Depreciable
|
||||
use CompanyableTrait;
|
||||
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
|
||||
|
||||
const LOCATION = 'location';
|
||||
const ASSET = 'asset';
|
||||
const USER = 'user';
|
||||
public const LOCATION = 'location';
|
||||
public const ASSET = 'asset';
|
||||
public const USER = 'user';
|
||||
|
||||
use Acceptable;
|
||||
|
||||
@@ -84,17 +84,18 @@ class Asset extends Depreciable
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'purchase_date' => 'datetime',
|
||||
'purchase_date' => 'date',
|
||||
'last_checkout' => 'datetime',
|
||||
'expected_checkin' => 'datetime',
|
||||
'expected_checkin' => 'date',
|
||||
'last_audit_date' => 'datetime',
|
||||
'next_audit_date' => 'datetime',
|
||||
'next_audit_date' => 'date',
|
||||
'model_id' => 'integer',
|
||||
'status_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
'location_id' => 'integer',
|
||||
'rtd_company_id' => 'integer',
|
||||
'supplier_id' => 'integer',
|
||||
'byod' => 'boolean',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
@@ -104,17 +105,16 @@ class Asset extends Depreciable
|
||||
'company_id' => 'integer|nullable',
|
||||
'warranty_months' => 'numeric|nullable|digits_between:0,240',
|
||||
'physical' => 'numeric|max:1|nullable',
|
||||
'checkout_date' => 'date|max:10|min:10|nullable',
|
||||
'checkin_date' => 'date|max:10|min:10|nullable',
|
||||
'last_checkout' => 'date_format:Y-m-d H:i:s|nullable',
|
||||
'expected_checkin' => 'date|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'rtd_location_id' => 'exists:locations,id|nullable',
|
||||
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
|
||||
'status' => 'integer',
|
||||
'purchase_date' => 'date|date_format:Y-m-d|nullable',
|
||||
'serial' => 'unique_serial|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'next_audit_date' => 'date|nullable',
|
||||
'last_audit_date' => 'date|nullable',
|
||||
'supplier_id' => 'exists:suppliers,id|nullable',
|
||||
'asset_eol_date' => 'date|max:10|min:10|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -143,6 +143,10 @@ class Asset extends Depreciable
|
||||
'requestable',
|
||||
'last_checkout',
|
||||
'expected_checkin',
|
||||
'byod',
|
||||
'asset_eol_date',
|
||||
'last_audit_date',
|
||||
'next_audit_date',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
@@ -165,6 +169,7 @@ class Asset extends Depreciable
|
||||
'expected_checkin',
|
||||
'next_audit_date',
|
||||
'last_audit_date',
|
||||
'asset_eol_date',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -178,11 +183,19 @@ class Asset extends Depreciable
|
||||
'company' => ['name'],
|
||||
'defaultLoc' => ['name'],
|
||||
'location' => ['name'],
|
||||
'model' => ['name', 'model_number'],
|
||||
'model' => ['name', 'model_number', 'eol'],
|
||||
'model.category' => ['name'],
|
||||
'model.manufacturer' => ['name'],
|
||||
];
|
||||
|
||||
// To properly set the expected checkin as Y-m-d
|
||||
public function setExpectedCheckinAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['expected_checkin'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles the custom field validation for assets
|
||||
@@ -314,14 +327,10 @@ class Asset extends Depreciable
|
||||
}
|
||||
|
||||
$this->last_checkout = $checkout_at;
|
||||
$this->name = $name;
|
||||
|
||||
$this->assignedTo()->associate($target);
|
||||
|
||||
|
||||
if ($name != null) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
if ($location != null) {
|
||||
$this->location_id = $location;
|
||||
} else {
|
||||
@@ -538,6 +547,28 @@ class Asset extends Depreciable
|
||||
return strtolower(class_basename($this->assigned_type));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is annoying, but because we don't say "assets" in our route names, we have to make an exception here
|
||||
* @todo - normalize the route names - API endpoint URLS can stay the same
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.1.0]
|
||||
* @return string
|
||||
*/
|
||||
public function targetShowRoute()
|
||||
{
|
||||
$route = str_plural($this->assignedType());
|
||||
if ($route=='assets') {
|
||||
return 'hardware';
|
||||
}
|
||||
|
||||
return $route;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the asset's location based on default RTD location
|
||||
*
|
||||
@@ -587,24 +618,6 @@ class Asset extends Depreciable
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log info for acceptances on this asset.
|
||||
* To use this as a single record, invoke it using ->last(), for
|
||||
* example, $asset->lastAcceptedLog->last().
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.14]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function acceptanceLog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'accepted')
|
||||
->orderBy('created_at', 'desc')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of checkouts for this asset
|
||||
*
|
||||
@@ -1652,9 +1665,9 @@ class Asset extends Depreciable
|
||||
*/
|
||||
public function scopeOrderManufacturer($query, $order)
|
||||
{
|
||||
return $query->join('models', 'assets.model_id', '=', 'models.id')
|
||||
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')
|
||||
->orderBy('manufacturers.name', $order);
|
||||
return $query->join('models as order_asset_model', 'assets.model_id', '=', 'order_asset_model.id')
|
||||
->join('manufacturers as manufacturer_order', 'order_asset_model.manufacturer_id', '=', 'manufacturer_order.id')
|
||||
->orderBy('manufacturer_order.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-3
@@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Model for Categories. Categories are a higher-level group
|
||||
@@ -97,6 +98,7 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->itemCount() == 0);
|
||||
}
|
||||
@@ -150,7 +152,10 @@ class Category extends SnipeModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the category
|
||||
* Get the number of items in the category. This should NEVER be used in
|
||||
* a collection of categories, as you'll end up with an n+1 query problem.
|
||||
*
|
||||
* It should only be used in a single category context.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
@@ -158,6 +163,11 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function itemCount()
|
||||
{
|
||||
|
||||
if (isset($this->{Str::plural($this->category_type).'_count'})) {
|
||||
return $this->{Str::plural($this->category_type).'_count'};
|
||||
}
|
||||
|
||||
switch ($this->category_type) {
|
||||
case 'asset':
|
||||
return $this->assets()->count();
|
||||
@@ -169,9 +179,10 @@ class Category extends SnipeModel
|
||||
return $this->consumables()->count();
|
||||
case 'license':
|
||||
return $this->licenses()->count();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return '0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +194,25 @@ class Category extends SnipeModel
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id');
|
||||
return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> assets relationship but also takes into consideration
|
||||
* the setting to show archived in lists.
|
||||
*
|
||||
* We could have complicated the assets() method above, but keeping this separate
|
||||
* should give us more flexibility if we need to return actually archived assets
|
||||
* by their category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.1.0]
|
||||
* @see \App\Models\Asset::scopeAssetsForShow()
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function showableAssets()
|
||||
{
|
||||
return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id')->AssetsForShow();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,12 +16,19 @@ class CheckoutAcceptance extends Model
|
||||
'declined_at' => 'datetime',
|
||||
];
|
||||
|
||||
// Get the mail recipient from the config
|
||||
public function routeNotificationForMail(): string
|
||||
/**
|
||||
* Get the mail recipient from the config
|
||||
*
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public function routeNotificationForMail()
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
return (config('mail.reply_to.address')) ? config('mail.reply_to.address') : '' ;
|
||||
$recipients_string = explode(',', Setting::getSettings()->alert_email);
|
||||
$recipients = array_map('trim', $recipients_string);
|
||||
|
||||
return array_filter($recipients);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ class Component extends SnipeModel
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_date' => 'date|nullable',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
];
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class Consumable extends SnipeModel
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@ class CustomField extends Model
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const PREDEFINED_FORMATS = [
|
||||
public const PREDEFINED_FORMATS = [
|
||||
'ANY' => '',
|
||||
'CUSTOM REGEX' => '',
|
||||
'ALPHA' => 'alpha',
|
||||
@@ -238,7 +238,7 @@ class CustomField extends Model
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
* @return string
|
||||
*/
|
||||
public function db_column_name()
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ class Depreciation extends SnipeModel
|
||||
// Declare the rules for the form validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:3|max:255|unique:depreciations,name',
|
||||
'months' => 'required|max:3600|integer',
|
||||
'months' => 'required|max:3600|integer|gt:0',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,11 @@ class Group extends SnipeModel
|
||||
'name' => 'required|min:2|max:255',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'permissions'
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether the model should inject it's identifier to the unique
|
||||
* validation rules before attempting validation. If this property
|
||||
|
||||
+10
-10
@@ -217,16 +217,16 @@ class Ldap extends Model
|
||||
$ldap_result_manager = Setting::getSettings()->ldap_manager;
|
||||
// Get LDAP user data
|
||||
$item = [];
|
||||
$item['username'] = isset($ldapattributes[$ldap_result_username][0]) ? $ldapattributes[$ldap_result_username][0] : '';
|
||||
$item['employee_number'] = isset($ldapattributes[$ldap_result_emp_num][0]) ? $ldapattributes[$ldap_result_emp_num][0] : '';
|
||||
$item['lastname'] = isset($ldapattributes[$ldap_result_last_name][0]) ? $ldapattributes[$ldap_result_last_name][0] : '';
|
||||
$item['firstname'] = isset($ldapattributes[$ldap_result_first_name][0]) ? $ldapattributes[$ldap_result_first_name][0] : '';
|
||||
$item['email'] = isset($ldapattributes[$ldap_result_email][0]) ? $ldapattributes[$ldap_result_email][0] : '';
|
||||
$item['telephone'] = isset($ldapattributes[$ldap_result_phone][0]) ? $ldapattributes[$ldap_result_phone][0] : '';
|
||||
$item['jobtitle'] = isset($ldapattributes[$ldap_result_jobtitle][0]) ? $ldapattributes[$ldap_result_jobtitle][0] : '';
|
||||
$item['country'] = isset($ldapattributes[$ldap_result_country][0]) ? $ldapattributes[$ldap_result_country][0] : '';
|
||||
$item['department'] = isset($ldapattributes[$ldap_result_dept][0]) ? $ldapattributes[$ldap_result_dept][0] : '';
|
||||
$item['manager'] = isset($ldapattributes[$ldap_result_manager][0]) ? $ldapattributes[$ldap_result_manager][0] : '';
|
||||
$item['username'] = $ldapattributes[$ldap_result_username][0] ?? '';
|
||||
$item['employee_number'] = $ldapattributes[$ldap_result_emp_num][0] ?? '';
|
||||
$item['lastname'] = $ldapattributes[$ldap_result_last_name][0] ?? '';
|
||||
$item['firstname'] = $ldapattributes[$ldap_result_first_name][0] ?? '';
|
||||
$item['email'] = $ldapattributes[$ldap_result_email][0] ?? '';
|
||||
$item['telephone'] = $ldapattributes[$ldap_result_phone][0] ?? '';
|
||||
$item['jobtitle'] = $ldapattributes[$ldap_result_jobtitle][0] ?? '';
|
||||
$item['country'] = $ldapattributes[$ldap_result_country][0] ?? '';
|
||||
$item['department'] = $ldapattributes[$ldap_result_dept][0] ?? '';
|
||||
$item['manager'] = $ldapattributes[$ldap_result_manager][0] ?? '';
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ class License extends Depreciable
|
||||
'purchase_date' => 'datetime',
|
||||
'expiration_date' => 'datetime',
|
||||
'termination_date' => 'datetime',
|
||||
'seats' => 'integer',
|
||||
'category_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
];
|
||||
@@ -50,6 +49,9 @@ class License extends Depreciable
|
||||
'category_id' => 'required|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'purchase_cost'=> 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
'expiration_date' => 'date_format:Y-m-d|nullable',
|
||||
'termination_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -365,7 +367,7 @@ class License extends Depreciable
|
||||
*/
|
||||
public function assignedusers()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'license_seats', 'assigned_to', 'license_id');
|
||||
return $this->belongsToMany(\App\Models\User::class, 'license_seats', 'license_id', 'assigned_to');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,8 +93,12 @@ trait Loggable
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$log = new Actionlog;
|
||||
$log->target_type = get_class($target);
|
||||
$log->target_id = $target->id;
|
||||
|
||||
if($target != null){
|
||||
$log->target_type = get_class($target);
|
||||
$log->target_id = $target->id;
|
||||
|
||||
}
|
||||
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
|
||||
@@ -31,7 +31,7 @@ class Setting extends Model
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SETUP_CHECK_KEY = 'snipeit_setup_check';
|
||||
public const SETUP_CHECK_KEY = 'snipeit_setup_check';
|
||||
|
||||
/**
|
||||
* Whether the model should inject it's identifier to the unique
|
||||
@@ -83,6 +83,9 @@ class Setting extends Model
|
||||
'email_domain',
|
||||
'email_format',
|
||||
'username_format',
|
||||
'webhook_endpoint',
|
||||
'webhook_channel',
|
||||
'webhook_botname',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -262,7 +265,7 @@ class Setting extends Model
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
return self::getSettings()->slack_endpoint;
|
||||
return self::getSettings()->webhook_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+195
-133
@@ -12,94 +12,9 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
|
||||
{
|
||||
public function getUserConfig()
|
||||
{
|
||||
$config = parent::getUserConfig();
|
||||
|
||||
// Much of this is copied verbatim from the library, then adjusted for our needs
|
||||
$config['class'] = SCIMUser::class;
|
||||
|
||||
unset($config['mapping']['example:name:space']);
|
||||
|
||||
$config['map_unmapped'] = false; // anything we don't explicitly map will _not_ show up.
|
||||
|
||||
$core_namespace = 'urn:ietf:params:scim:schemas:core:2.0:User';
|
||||
$core = $core_namespace.':';
|
||||
$mappings =& $config['mapping'][$core_namespace]; //grab this entire key, we don't want to be repeating ourselves
|
||||
|
||||
//username - *REQUIRED*
|
||||
$config['validations'][$core.'userName'] = 'required';
|
||||
$mappings['userName'] = AttributeMapping::eloquent('username');
|
||||
|
||||
//human name - *FIRST NAME REQUIRED*
|
||||
$config['validations'][$core.'name.givenName'] = 'required';
|
||||
$config['validations'][$core.'name.familyName'] = 'string'; //not required
|
||||
|
||||
$mappings['name']['familyName'] = AttributeMapping::eloquent("last_name");
|
||||
$mappings['name']['givenName'] = AttributeMapping::eloquent("first_name");
|
||||
$mappings['name']['formatted'] = (new AttributeMapping())->ignoreWrite()->setRead(
|
||||
function (&$object) {
|
||||
return $object->getFullNameAttribute();
|
||||
}
|
||||
);
|
||||
|
||||
// externalId support
|
||||
$config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta
|
||||
// note that the mapping is *not* namespaced like the other $mappings
|
||||
$config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid');
|
||||
|
||||
$config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT...
|
||||
$config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here)
|
||||
|
||||
$mappings['emails'] = [[
|
||||
"value" => AttributeMapping::eloquent("email"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]];
|
||||
|
||||
//active
|
||||
$config['validations'][$core.'active'] = 'boolean';
|
||||
|
||||
$mappings['active'] = AttributeMapping::eloquent('activated');
|
||||
|
||||
//phone
|
||||
$config['validations'][$core.'phoneNumbers'] = 'nullable|array';
|
||||
$config['validations'][$core.'phoneNumbers.*.value'] = 'string'; // another one where want to say 'we don't _need_ a phone number, but if you have one it better have a value.
|
||||
|
||||
$mappings['phoneNumbers'] = [[
|
||||
"value" => AttributeMapping::eloquent("phone"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]];
|
||||
|
||||
//address
|
||||
$config['validations'][$core.'addresses'] = 'nullable|array';
|
||||
$config['validations'][$core.'addresses.*.streetAddress'] = 'string';
|
||||
$config['validations'][$core.'addresses.*.locality'] = 'string';
|
||||
$config['validations'][$core.'addresses.*.region'] = 'nullable|string';
|
||||
$config['validations'][$core.'addresses.*.postalCode'] = 'nullable|string';
|
||||
$config['validations'][$core.'addresses.*.country'] = 'string';
|
||||
|
||||
$mappings['addresses'] = [[
|
||||
'type' => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
|
||||
'streetAddress' => AttributeMapping::eloquent("address"),
|
||||
'locality' => AttributeMapping::eloquent("city"),
|
||||
'region' => AttributeMapping::eloquent("state"),
|
||||
'postalCode' => AttributeMapping::eloquent("zip"),
|
||||
'country' => AttributeMapping::eloquent("country"),
|
||||
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
|
||||
]];
|
||||
|
||||
//title
|
||||
$config['validations'][$core.'title'] = 'string';
|
||||
$mappings['title'] = AttributeMapping::eloquent('jobtitle');
|
||||
|
||||
//Preferred Language
|
||||
$config['validations'][$core.'preferredLanguage'] = 'string';
|
||||
$mappings['preferredLanguage'] = AttributeMapping::eloquent('locale');
|
||||
|
||||
/*
|
||||
/*
|
||||
more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?):
|
||||
- website
|
||||
- notes?
|
||||
@@ -108,66 +23,213 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
|
||||
- company_id to "organization?"
|
||||
*/
|
||||
|
||||
$enterprise_namespace = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User';
|
||||
$ent = $enterprise_namespace.':';
|
||||
|
||||
// we remove the 'example' namespace and add the Enterprise one
|
||||
$config['mapping']['schemas'] = AttributeMapping::constant( [$core_namespace, $enterprise_namespace] )->ignoreWrite();
|
||||
$user_prefix = 'urn:ietf:params:scim:schemas:core:2.0:User:';
|
||||
$enterprise_prefix = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:';
|
||||
|
||||
$config['validations'][$ent.'employeeNumber'] = 'string';
|
||||
$config['validations'][$ent.'department'] = 'string';
|
||||
$config['validations'][$ent.'manager'] = 'nullable';
|
||||
$config['validations'][$ent.'manager.value'] = 'string';
|
||||
return [
|
||||
|
||||
$config['mapping'][$enterprise_namespace] = [
|
||||
'employeeNumber' => AttributeMapping::eloquent('employee_num'),
|
||||
'department' =>(new AttributeMapping())->setAdd( // FIXME parent?
|
||||
function ($value, &$object) {
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
}
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
// Set to 'null' to make use of auth.providers.users.model (App\User::class)
|
||||
'class' => SCIMUser::class,
|
||||
|
||||
'validations' => [
|
||||
$user_prefix . 'userName' => 'required',
|
||||
$user_prefix . 'name.givenName' => 'required',
|
||||
$user_prefix . 'name.familyName' => 'nullable|string',
|
||||
$user_prefix . 'externalId' => 'nullable|string',
|
||||
$user_prefix . 'emails' => 'nullable|array',
|
||||
$user_prefix . 'emails.*.value' => 'nullable|email',
|
||||
$user_prefix . 'active' => 'boolean',
|
||||
$user_prefix . 'phoneNumbers' => 'nullable|array',
|
||||
$user_prefix . 'phoneNumbers.*.value' => 'nullable|string',
|
||||
$user_prefix . 'addresses' => 'nullable|array',
|
||||
$user_prefix . 'addresses.*.streetAddress' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.locality' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.region' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.postalCode' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.country' => 'nullable|string',
|
||||
$user_prefix . 'title' => 'nullable|string',
|
||||
$user_prefix . 'preferredLanguage' => 'nullable|string',
|
||||
|
||||
// Enterprise validations:
|
||||
$enterprise_prefix . 'employeeNumber' => 'nullable|string',
|
||||
$enterprise_prefix . 'department' => 'nullable|string',
|
||||
$enterprise_prefix . 'manager' => 'nullable',
|
||||
$enterprise_prefix . 'manager.value' => 'nullable|string'
|
||||
],
|
||||
|
||||
'singular' => 'User',
|
||||
'schema' => [Schema::SCHEMA_USER],
|
||||
|
||||
//eager loading
|
||||
'withRelations' => [],
|
||||
'map_unmapped' => false,
|
||||
// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped',
|
||||
'description' => 'User Account',
|
||||
|
||||
// Map a SCIM attribute to an attribute of the object.
|
||||
'mapping' => [
|
||||
|
||||
'id' => AttributeMapping::eloquent("id")->disableWrite(),
|
||||
|
||||
'externalId' => AttributeMapping::eloquent('scim_externalid'), // FIXME - I have a PR that changes a lot of this.
|
||||
|
||||
'meta' => [
|
||||
'created' => AttributeMapping::eloquent("created_at")->disableWrite(),
|
||||
'lastModified' => AttributeMapping::eloquent("updated_at")->disableWrite(),
|
||||
|
||||
'location' => (new AttributeMapping())->setRead(
|
||||
function ($object) {
|
||||
return route(
|
||||
'scim.resource',
|
||||
[
|
||||
'resourceType' => 'Users',
|
||||
'resourceObject' => $object->id
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
)->setRead(
|
||||
function (&$object) {
|
||||
return $object->department ? $object->department->name : null;
|
||||
}
|
||||
),
|
||||
'manager' => [
|
||||
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool.
|
||||
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing
|
||||
'value' => (new AttributeMapping())->setAdd(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
)->disableWrite(),
|
||||
|
||||
'resourceType' => AttributeMapping::constant("User")
|
||||
],
|
||||
|
||||
'schemas' => AttributeMapping::constant(
|
||||
[
|
||||
'urn:ietf:params:scim:schemas:core:2.0:User',
|
||||
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
||||
]
|
||||
)->ignoreWrite(),
|
||||
|
||||
'urn:ietf:params:scim:schemas:core:2.0:User' => [
|
||||
|
||||
'userName' => AttributeMapping::eloquent("username"),
|
||||
|
||||
'name' => [
|
||||
'formatted' => (new AttributeMapping())->ignoreWrite()->setRead(
|
||||
function (&$object) {
|
||||
return $object->getFullNameAttribute();
|
||||
}
|
||||
),
|
||||
'familyName' => AttributeMapping::eloquent("last_name"),
|
||||
'givenName' => AttributeMapping::eloquent("first_name"),
|
||||
'middleName' => null,
|
||||
'honorificPrefix' => null,
|
||||
'honorificSuffix' => null
|
||||
],
|
||||
|
||||
'displayName' => null,
|
||||
'nickName' => null,
|
||||
'profileUrl' => null,
|
||||
'title' => AttributeMapping::eloquent('jobtitle'),
|
||||
'userType' => null,
|
||||
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
|
||||
'locale' => null, // see RFC5646
|
||||
'timezone' => null, // see RFC6557
|
||||
'active' => AttributeMapping::eloquent('activated'),
|
||||
|
||||
'password' => AttributeMapping::eloquent('password')->disableRead(),
|
||||
|
||||
// Multi-Valued Attributes
|
||||
'emails' => [[
|
||||
"value" => AttributeMapping::eloquent("email"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]],
|
||||
|
||||
'phoneNumbers' => [[
|
||||
"value" => AttributeMapping::eloquent("phone"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]],
|
||||
|
||||
'ims' => [[
|
||||
"value" => null,
|
||||
"display" => null,
|
||||
"type" => null,
|
||||
"primary" => null
|
||||
]], // Instant messaging addresses for the User
|
||||
|
||||
'photos' => [[
|
||||
"value" => null,
|
||||
"display" => null,
|
||||
"type" => null,
|
||||
"primary" => null
|
||||
]],
|
||||
|
||||
'addresses' => [[
|
||||
'type' => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
|
||||
'streetAddress' => AttributeMapping::eloquent("address"),
|
||||
'locality' => AttributeMapping::eloquent("city"),
|
||||
'region' => AttributeMapping::eloquent("state"),
|
||||
'postalCode' => AttributeMapping::eloquent("zip"),
|
||||
'country' => AttributeMapping::eloquent("country"),
|
||||
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
|
||||
]],
|
||||
|
||||
'groups' => [[
|
||||
'value' => null,
|
||||
'$ref' => null,
|
||||
'display' => null,
|
||||
'type' => null,
|
||||
'type' => null
|
||||
]],
|
||||
|
||||
'entitlements' => null,
|
||||
'roles' => null,
|
||||
'x509Certificates' => null
|
||||
],
|
||||
|
||||
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => [
|
||||
'employeeNumber' => AttributeMapping::eloquent('employee_num'),
|
||||
'department' => (new AttributeMapping())->setAdd( // FIXME parent?
|
||||
function ($value, &$object) {
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
}
|
||||
}
|
||||
)->setRead(
|
||||
function (&$object) {
|
||||
return $object->manager_id;
|
||||
}
|
||||
return $object->department ? $object->department->name : null;
|
||||
}
|
||||
),
|
||||
'manager' => [
|
||||
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool.
|
||||
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing
|
||||
'value' => (new AttributeMapping())->setAdd(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
}
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
}
|
||||
}
|
||||
)->setRead(
|
||||
function (&$object) {
|
||||
return $object->manager_id;
|
||||
}
|
||||
),
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ trait Searchable
|
||||
}
|
||||
// I put this here because I only want to add the concat one time in the end of the user relation search
|
||||
if($relation == 'user') {
|
||||
$query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%$term%"]);
|
||||
$query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%{$term}%"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -195,7 +195,7 @@ trait Searchable
|
||||
*/
|
||||
private function getSearchableAttributes()
|
||||
{
|
||||
return isset($this->searchableAttributes) ? $this->searchableAttributes : [];
|
||||
return $this->searchableAttributes ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +205,7 @@ trait Searchable
|
||||
*/
|
||||
private function getSearchableRelations()
|
||||
{
|
||||
return isset($this->searchableRelations) ? $this->searchableRelations : [];
|
||||
return $this->searchableRelations ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user