Compare commits
631 Commits
f83bc634a3
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 83dc15ec9f | |||
| 3320a26ada | |||
| f0e0feeeb9 | |||
| 762b31083e | |||
| 358673cada | |||
| eed49c4745 | |||
| 8eb0c644f3 | |||
| 6f7d8c5ef8 | |||
| ed992d2903 | |||
| aee3467918 | |||
| 087ef47e15 | |||
| aabb419e7d | |||
| 3a7c1f4bb7 | |||
| 36eb81d8e8 | |||
| d4ab2e2dc7 | |||
| 8cd023e224 | |||
| 8788f22a81 | |||
| fd7930b139 | |||
| a68f54be80 | |||
| 038bd7bd26 | |||
| 69a97c0044 | |||
| 26a470fe1e | |||
| 80c791c7e1 | |||
| 05ce600e09 | |||
| c6477f7757 | |||
| 8498af24f9 | |||
| 171a48a411 | |||
| d63c92e36a | |||
| 8929236e85 | |||
| 84e65340b6 | |||
| a1b6fa8911 | |||
| 1ca75a4ed4 | |||
| db7b2e9f46 | |||
| b67fdaee78 | |||
| fd250996b2 | |||
| 797223e976 | |||
| 5961b40349 | |||
| 447dc5a7dc | |||
| 5406e7e3db | |||
| 685a1fd4f3 | |||
| f93f6d14ff | |||
| d15a68285c | |||
| f3233eb19b | |||
| a763a7b230 | |||
| f76fb95fcd | |||
| 5b6faf93f9 | |||
| 88a8010a18 | |||
| 5ceeec7fdd | |||
| 9697b37263 | |||
| 18b667d24b | |||
| c154196090 | |||
| 1306770f2e | |||
| ab063aeb23 | |||
| 36631dc201 | |||
| 4851a50279 | |||
| 03cb227b5d | |||
| 1612d91ac6 | |||
| e27e2b5bc0 | |||
| b18407f64b | |||
| 46e34d4dcc | |||
| 839d865d7c | |||
| 10e6a09640 | |||
| e747b180aa | |||
| 57e281b46f | |||
| 847f2f57a7 | |||
| 23def197c8 | |||
| b9e4395297 | |||
| 0a19621949 | |||
| 162419069b | |||
| b74d5ac34a | |||
| 26b658acf8 | |||
| 25c626be45 | |||
| b038537d1c | |||
| 4b6e461b32 | |||
| dfab716014 | |||
| aa61dfef15 | |||
| 7858823205 | |||
| 4bb4d189e6 | |||
| 9143f7a967 | |||
| ad57a54407 | |||
| fd6ec9b876 | |||
| db5b1b9da3 | |||
| a8c588f41f | |||
| b97fa8a337 | |||
| 2d706fb628 | |||
| 9233c65351 | |||
| 7120453ec8 | |||
| 215d3777db | |||
| 22f45f98ff | |||
| 99aa63f0e1 | |||
| e701fd5543 | |||
| e85f25c21d | |||
| f74fbc3092 | |||
| f29ed77969 | |||
| 31a7c9d012 | |||
| afdf784215 | |||
| 0a35370a81 | |||
| 2cb9417bbf | |||
| 35f1a15fc7 | |||
| 39c062eafc | |||
| 8027155bde | |||
| c6be4c38a6 | |||
| eedb5d45d4 | |||
| 6d6e3c68f5 | |||
| 14727ce6b9 | |||
| 52d354bc9f | |||
| 9732763fc8 | |||
| 4bc61c370c | |||
| 62037febe2 | |||
| fd25dacaee | |||
| 397d82a458 | |||
| 2d86789938 | |||
| f9d304484e | |||
| f0bdefd805 | |||
| b4637ddeca | |||
| fb2de52739 | |||
| 90f496c684 | |||
| f7057bd7cb | |||
| afac2f175e | |||
| 44b7e4cf84 | |||
| 39fd110388 | |||
| da4de58b08 | |||
| 211f1e2638 | |||
| 64ef6c2a8e | |||
| 0ff8299fdc | |||
| 37f8bec215 | |||
| 7b94e0daa0 | |||
| e9752ddcb7 | |||
| 26b85d1892 | |||
| 527e3909e8 | |||
| ce63b9325e | |||
| 77799add6d | |||
| b72ea70242 | |||
| d23d471de4 | |||
| 3ac01a6f00 | |||
| ffb679626e | |||
| c8aa6394f5 | |||
| 1260270c7d | |||
| 97ac1807b0 | |||
| 260c617e35 | |||
| 689b0a12f7 | |||
| dbe25c8a77 | |||
| 7b881be0a7 | |||
| 44241a5010 | |||
| 6202665862 | |||
| 9cf4367521 | |||
| 2dae908074 | |||
| 66590a2627 | |||
| 36c6e5dd2b | |||
| 80624eebfe | |||
| 05fc3b0048 | |||
| 4bfd89d358 | |||
| 4ce59663ad | |||
| 0e42cc45d1 | |||
| a18197b31c | |||
| c3340605f2 | |||
| 0e0e1cf2a2 | |||
| eac42923fd | |||
| 0db8da9810 | |||
| f9a75e8aba | |||
| c567fd1adf | |||
| 71e57d6eb1 | |||
| 7ce5dd0941 | |||
| a860db05d3 | |||
| 1b9d62c69e | |||
| bdc9c8fdde | |||
| 784ef238a2 | |||
| 746ddea4b2 | |||
| d1733b2848 | |||
| 13e6f6dcf2 | |||
| 2e17136f01 | |||
| 19b9369e77 | |||
| c6bc9ce4b0 | |||
| b9a8d5ffd2 | |||
| 1f7a92c8ea | |||
| 899ba89a2b | |||
| 2eafd998c0 | |||
| b0cfc629f9 | |||
| 3da6977daf | |||
| e52c3b7b45 | |||
| 6c56fb7af7 | |||
| dc3d6a5c0b | |||
| 8272192815 | |||
| 1c8782db3c | |||
| cc2bd15658 | |||
| 080e8bdcd9 | |||
| 0ea8a48591 | |||
| d3bb32eb97 | |||
| af2ddba2fa | |||
| 8191970086 | |||
| b948f401d1 | |||
| 5f9ce3f0e7 | |||
| 2c11be5796 | |||
| a1a74565a4 | |||
| 77607608e7 | |||
| 33eb70261d | |||
| a1aefe7bbe | |||
| a929a8d859 | |||
| e2d8dee1a6 | |||
| e7662a304e | |||
| 7efc81235d | |||
| 8efbb4c4a7 | |||
| d3b9fab7ce | |||
| 93378a5a2d | |||
| 5fefdccd17 | |||
| a18f25dc84 | |||
| 8a9369bbaf | |||
| 397eb4651b | |||
| ec15b07f21 | |||
| 2970007d2b | |||
| ee9d5943c9 | |||
| f232a862ac | |||
| 0fcb7dc9f8 | |||
| ccf895c8fb | |||
| 95aa7c9cf5 | |||
| 9ec4fd9ead | |||
| dd33cb6873 | |||
| 4de952967d | |||
| 5b87d02d9e | |||
| 7d08e7ec7c | |||
| 4816bcf024 | |||
| 37dd64e4c4 | |||
| 29af6c75cf | |||
| 591d93d699 | |||
| 5cd00a1ff1 | |||
| fdf34afcd3 | |||
| c1d730ac98 | |||
| 5f73db0a59 | |||
| 54a7e1dae9 | |||
| 33706bb195 | |||
| 9e8055bfee | |||
| 4fefc8c053 | |||
| 2aa398b1f0 | |||
| 91ee36c121 | |||
| dc55a855c3 | |||
| 1f96cce839 | |||
| 8b5968e1da | |||
| 9d2223af3b | |||
| afdd50489f | |||
| b85b01f2d2 | |||
| fdecff66d5 | |||
| e5f8ca953e | |||
| 0d01affb89 | |||
| ee7e463d36 | |||
| ea52e72086 | |||
| 6dd1ca3aad | |||
| 885dcf9b8f | |||
| 545cbe5dd4 | |||
| 74700fcc19 | |||
| 1b76f0fece | |||
| f47f472aaa | |||
| bfac5a92cc | |||
| 23ca040ac0 | |||
| 21b28a0d06 | |||
| 76533287dd | |||
| dbffb288d6 | |||
| 17611a9009 | |||
| 90c126a014 | |||
| cfffe77c93 | |||
| 588d573703 | |||
| 7d342216f8 | |||
| 5184544d6f | |||
| 70f4dac177 | |||
| a7e027177c | |||
| c2972aa015 | |||
| 4cf2ef98d0 | |||
| 1322511cfd | |||
| ed3ca4e6ea | |||
| 75f17f5846 | |||
| f13a5d3c01 | |||
| 6d3fd04232 | |||
| 47a2b051ba | |||
| 40625572e6 | |||
| fde81a3985 | |||
| da8fc64458 | |||
| d535f3d9c5 | |||
| 43750fb8f5 | |||
| 4522ddde8d | |||
| dd4a83043a | |||
| 96765aac84 | |||
| b3e3c72be5 | |||
| d90df7c46f | |||
| 20c1f898a1 | |||
| 666c4ad180 | |||
| eca8b34d88 | |||
| ec024e6fba | |||
| f35d651ffc | |||
| 2aed8052d0 | |||
| 2570785030 | |||
| c7482016ce | |||
| 20ab5a8f08 | |||
| 29c411a23c | |||
| eeb08bc563 | |||
| 316583d108 | |||
| fb278bfdd6 | |||
| 138448212e | |||
| 762ac838b6 | |||
| 93cb9f5e81 | |||
| 4c9725b282 | |||
| a8274dd640 | |||
| 2792b06d79 | |||
| 3c0188fba4 | |||
| 5f65e1a339 | |||
| 709044efef | |||
| bac8ed2be0 | |||
| 7c7bea3666 | |||
| 670fd20e44 | |||
| 338f5ed6f6 | |||
| 53d406ae74 | |||
| 29f836e615 | |||
| 38b7e9b72c | |||
| f2519dc987 | |||
| 8c7f3e13ed | |||
| 0343a8e4a7 | |||
| 57912b60ae | |||
| 2370a2b344 | |||
| f065eb5cb6 | |||
| 3a0161a0bb | |||
| 88f0e8138e | |||
| 7071b3792a | |||
| cef2e672cb | |||
| ec1e98d898 | |||
| 338734db7a | |||
| 411f2a4d8f | |||
| 9ea4dd2ecf | |||
| 903feebcdb | |||
| 2037b27c4c | |||
| beb84339e2 | |||
| df9f964f11 | |||
| 3aea111392 | |||
| 55b7ce7061 | |||
| f826a0e409 | |||
| 00ee7484df | |||
| 6728a541a8 | |||
| c3aed5e9fe | |||
| 2e691df76b | |||
| 89d389c76d | |||
| 7e3eb9522f | |||
| a94077874d | |||
| fee6f5c69d | |||
| 2b4060770a | |||
| da9feee83b | |||
| 0906eb6548 | |||
| 121186a0b9 | |||
| 841fea5103 | |||
| 81e55ea0ec | |||
| 3ee8ec05be | |||
| a5ad7a0ec6 | |||
| 011aa65d19 | |||
| 6e386d95ee | |||
| ef11a5a011 | |||
| d7f3b0156b | |||
| 5936cdb61c | |||
| abb63c293f | |||
| b1699cd10c | |||
| 048c4a398f | |||
| 57143d538e | |||
| 4d3260b2ca | |||
| 0b3ea4811a | |||
| 30c4103212 | |||
| 080ba99b2f | |||
| 7bd586b619 | |||
| 2a5c81ecc3 | |||
| 63b0a1d4c5 | |||
| 885a5b652b | |||
| c19f70031c | |||
| 7c9968063a | |||
| a955932086 | |||
| 8933753fe0 | |||
| 797e66fec3 | |||
| 0d042deaf2 | |||
| 5e767e77a9 | |||
| afff43abd6 | |||
| bcc7d7974e | |||
| 50e8fea3e9 | |||
| 377f4993f8 | |||
| 8be4bafce1 | |||
| bdd0454240 | |||
| f52816da32 | |||
| 7c88dd595d | |||
| 028a9af4aa | |||
| b5daa5a4ba | |||
| 811cf02f16 | |||
| 3b007261d6 | |||
| dfc5b8866a | |||
| d8252fd77f | |||
| 2d518e54ce | |||
| 46c75f14b6 | |||
| 10d800959f | |||
| 0b5da05d42 | |||
| 8f04833b6a | |||
| 61bf838108 | |||
| 6ab8c8c61f | |||
| bedc53df7f | |||
| 91bf29089e | |||
| ca452f9ade | |||
| c47d826f8e | |||
| 3e8112ec0b | |||
| 67d7675e11 | |||
| f32a4300f0 | |||
| 1b30f7e0ea | |||
| 33f5c3d0dd | |||
| 58342532cd | |||
| 06d8a065dc | |||
| ce7c430046 | |||
| 19679875df | |||
| 4c08938403 | |||
| 6d885caedf | |||
| 8f428a5c18 | |||
| 9acc8102e8 | |||
| 116347036f | |||
| c4199c5557 | |||
| 98ed9f6f8e | |||
| 236601fcf6 | |||
| 373483b4eb | |||
| 95536edec5 | |||
| e486c22bfc | |||
| bc407a7c20 | |||
| bc26c37315 | |||
| 74ee34634e | |||
| f1e43c911b | |||
| 8570b73257 | |||
| c97478ba8a | |||
| 6f03e76540 | |||
| 365c51f6c8 | |||
| 953775d9ca | |||
| 683e2d69d0 | |||
| a45be35d6c | |||
| 9a0ea70034 | |||
| d95f84bff7 | |||
| 7cee99e8e0 | |||
| ade9608e72 | |||
| 0dcdcfc829 | |||
| a5b680c273 | |||
| 2d8fe939a3 | |||
| 1f8616e4d6 | |||
| 5d686b28a4 | |||
| c047e36a62 | |||
| 2855967e81 | |||
| 439a52eb0f | |||
| 6a4d1761f3 | |||
| 77a2508c71 | |||
| fd26c90098 | |||
| 4e6eb3191b | |||
| 2cf8f5e7ec | |||
| bc8bc86b74 | |||
| 0b39f08ec7 | |||
| 100accfe34 | |||
| b3b24b08c4 | |||
| 74da043805 | |||
| 8eda3d40c3 | |||
| d74d06a3b0 | |||
| 1797e0c91e | |||
| a23f492621 | |||
| 02384d3404 | |||
| 547390286b | |||
| 596f3a55c1 | |||
| 30b17be43e | |||
| adcb663925 | |||
| e8883cfbf8 | |||
| fd469bdc22 | |||
| 22534b4cbf | |||
| b5c979058f | |||
| 6e751b552f | |||
| 3e3a0f1de7 | |||
| 87783ad99c | |||
| f881b42f7f | |||
| d118f5bce7 | |||
| c12cca2de9 | |||
| 9c290a66ca | |||
| 5d99fef4ed | |||
| 9310c12536 | |||
| e9ad3d8a92 | |||
| d5fdb227dd | |||
| 460d5df27f | |||
| fb4f74d48f | |||
| 52f9b632fb | |||
| af2b0a7faa | |||
| 7de632d9fc | |||
| 6403f371fe | |||
| 717f11e3d5 | |||
| 026473dd59 | |||
| 16b8637192 | |||
| f9e1715de1 | |||
| e045ff3f98 | |||
| fd1d136842 | |||
| a8bcd26cdd | |||
| 9c4c11efc9 | |||
| d405f2aa99 | |||
| 09f57dcce0 | |||
| 3a3ebec2bf | |||
| 6bffbba726 | |||
| 7bb57ded70 | |||
| 36280f0235 | |||
| b068157f87 | |||
| 164e26af91 | |||
| 0157f34949 | |||
| b64cee25d6 | |||
| cdf4e5188a | |||
| 514fae950c | |||
| d64b18c9fb | |||
| 2cb6a59c96 | |||
| c0b6f0c696 | |||
| 146f692a46 | |||
| b7fa26d1e9 | |||
| a1c8fc787e | |||
| ef2275c54d | |||
| 0495ded5c2 | |||
| 1d86258926 | |||
| 8258e7ad29 | |||
| 42bd7c36de | |||
| 0c1fc53331 | |||
| 2b117cdea4 | |||
| 77564c0f46 | |||
| 27e581c40a | |||
| 33a5e0a410 | |||
| 2a9e38fdea | |||
| 71d850531d | |||
| 2fb6db9bd4 | |||
| d0db73624e | |||
| b71d1c300b | |||
| bdb33801c9 | |||
| 7a3e9395e6 | |||
| 8d78de304b | |||
| 9c105565d1 | |||
| 69f360eae8 | |||
| 5e31ea1129 | |||
| 8a13fbfc0d | |||
| 5e64d0ecf6 | |||
| 3b8c867c72 | |||
| e55d30d61b | |||
| 57bceba425 | |||
| 04925de3dc | |||
| 8fa390a5e6 | |||
| a26de022e2 | |||
| 05fd4d35ff | |||
| b44a100bad | |||
| ff5533bee1 | |||
| af07182682 | |||
| 3f04015669 | |||
| b773f767b7 | |||
| 8f49173141 | |||
| 6e2f357f14 | |||
| be68e4f21e | |||
| d532b767fb | |||
| 2c6bf1ad5c | |||
| cf6a71f5e2 | |||
| aed8aa7e8f | |||
| f3ed7d6ea7 | |||
| 1b08f543b0 | |||
| 4b5f6a3b1e | |||
| c791d44ac0 | |||
| 030a331cd5 | |||
| 7719737db7 | |||
| 180b00e8ed | |||
| 7814deb02d | |||
| b3f38d5d98 | |||
| 7db84a060a | |||
| 48de0b0168 | |||
| e2ab4e58fe | |||
| 02bbca628f | |||
| 5816a53b11 | |||
| 4779493fad | |||
| ebe2022164 | |||
| 9faae13e7d | |||
| 196367adc1 | |||
| 96ffc48f84 | |||
| afe27bc8ec | |||
| 7a8dafa49b | |||
| b7ec814231 | |||
| 597cdf4a52 | |||
| a48617db7d | |||
| 506f999920 | |||
| 8fc0370f70 | |||
| 780a1c140b | |||
| bea4db8386 | |||
| 55ba3f154b | |||
| e43cff9762 | |||
| f9011a3dc2 | |||
| 28ce5f929e | |||
| 0bfe3aa264 | |||
| d6cd0df5ec | |||
| 08a4ba52f8 | |||
| e55665e0f3 | |||
| 797e1e10ba | |||
| cb28d1b553 | |||
| 909162d49f | |||
| f987d6e7b6 | |||
| 4a548b2278 | |||
| 14303963b9 | |||
| e79d2d9e91 | |||
| 60c51e6d94 | |||
| c5f004b7af | |||
| 888e265a73 | |||
| fada0b7595 | |||
| c534a7f846 | |||
| 5a3ce23a4b | |||
| 2ee8ee21d2 | |||
| 162052a2d1 | |||
| dabb797060 | |||
| 54d5363f09 | |||
| 85e030b93a | |||
| 1af97e7bb6 | |||
| 49d8bd23f4 | |||
| 2f699ed10d | |||
| a30f887489 | |||
| bdf5df0923 | |||
| 79bea0f9c7 | |||
| 088cfefee2 | |||
| 1baefb5f9c | |||
| 504f245eb5 | |||
| 088fd91617 | |||
| 238c09357e | |||
| c2b5b68568 | |||
| 789ce4947d | |||
| ef8304c617 | |||
| 3ed9f8e1fc | |||
| d9775ab315 | |||
| 8717130865 | |||
| 044aca6026 | |||
| 97e554fc20 | |||
| b5ca510f1f | |||
| 4d7ebeba85 | |||
| 382b83e690 | |||
| d016eacb57 | |||
| 8001389e39 | |||
| 0a944bd549 | |||
| 804a8bba18 | |||
| f6398c5035 | |||
| 025e145f6f | |||
| d006d75e53 |
21
.cursor/rules/entreprise/00-checklist-nouveau-projet.mdc
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
description: Checklist agent — monter un nouveau projet sur le modèle Progiweb
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Checklist nouveau projet (agent)
|
||||
|
||||
Quand l'utilisateur demande de reproduire le modèle Reader / Inscription / CRM :
|
||||
|
||||
1. Créer la structure `.cursor/rules/{entreprise,projet,ops}/` (copier `entreprise/` depuis un projet déjà aligné).
|
||||
2. Remplir `ops/20-parametres.mdc` (URLs Gitea, Bitbucket, chemins serveur, webhook, SSH).
|
||||
3. Remplir `ops/20-git-deploy-playbook.mdc` (layout git root : `$HOME` vs `public_html`).
|
||||
4. Remplir `projet/` (stack, conventions, préfixe JIRA).
|
||||
5. Windows : remote Gitea, branche `dev`, `.gitignore` secrets / runtime.
|
||||
6. Versionner `deploy_dev.php` au bon endroit (docroot) **avant** d'activer le webhook.
|
||||
7. Serveur : `~/deploy/deploy_dev.sh` (chemins absolus), git init/fetch sur le bon root, tester `curl` webhook.
|
||||
8. Gitea : webhook push → URL `deploy_dev.php` ; miroir Bitbucket avec sync **à chaque soumission** + intervalle filet (ex. `1h`).
|
||||
9. Test bout en bout : push `dev` → commit visible sur Gitea, serveur, Bitbucket.
|
||||
10. Secrets uniquement dans 1Password (référencer le titre de l'item dans `ops/20-parametres.mdc`).
|
||||
|
||||
Ne pas inventer Bitbucket SSH sur le serveur : le serveur tire depuis Gitea local (`127.0.0.1:3000`) en général.
|
||||
42
.cursor/rules/entreprise/00-cout-benefice.mdc
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
description: Progiweb — toujours proposer l’option au meilleur coût-bénéfice (pas l’essai-erreur)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Coût-bénéfice — qualité d’approche
|
||||
|
||||
Référentiel agent. L’utilisateur veut **toujours coder au meilleur** : proposer d’abord la façon qui minimise le temps total (humain + agent) pour un résultat commercialement fiable.
|
||||
|
||||
## Règle
|
||||
|
||||
Avant d’implémenter (surtout UI print, layout, mapping, calibrage, « ça a l’air bon ») :
|
||||
|
||||
1. **Identifier** si la tâche invite à l’essai-erreur (deviner des mm/%, relire des screenshots, retoucher au feeling).
|
||||
2. **Proposer d’abord** l’option au meilleur coût-bénéfice — même si elle demande 30–60 min de setup.
|
||||
3. **Ne pas** enchaîner des itérations « change un chiffre → redeploie → regarde » comme stratégie principale.
|
||||
4. Si plusieurs options : présenter **brièvement** (1–3) avec coût / bénéfice, **recommander** la meilleure, attendre OK si le setup est plus large que demandé.
|
||||
|
||||
## Préférer (exemples)
|
||||
|
||||
| Anti-pattern (cher) | Meilleure option (souvent) |
|
||||
|---|---|
|
||||
| Deviner positions PDF / CSS au feeling | GUI / outils de calibrage → vérité mesurée (JSON, %) |
|
||||
| 15 essais de mapping colonnes | Preview + template sauvegardé + règles claires |
|
||||
| Hardcoder FR/EN partout | Source unique (Info / config) |
|
||||
| Patch local qui casse au 2ᵉ cas | Modèle extensible minimal (N boîtes, catalogue champs) |
|
||||
| Changer le format d’impression « pour que ça marche chez moi » | **Demander le scénario volume** (1 test vs 2000 en file) avant de coder |
|
||||
|
||||
**Cas vécu MSIN-4485** : calibrateur drag-and-drop des boîtes a coûté **moins** que la série d’essais PDF. Ce genre de levier = défaut à proposer.
|
||||
|
||||
**Impression / papier** : avant de changer Lettre vs demi-feuille vs coupe, poser la question du **jour J** (file d’attente, volume). Ne pas imposer la coupe comme solution « technique » sans accord produit.
|
||||
|
||||
## Ce que ce n’est pas
|
||||
|
||||
- Pas d’over-engineering « au cas où » sans besoin.
|
||||
- Pas de gros refactor hors périmètre.
|
||||
- Le meilleur coût-bénéfice reste **minimal mais juste** : vérité mesurable + extensible léger > génie fragile.
|
||||
|
||||
## À chaque proposition d’approche
|
||||
|
||||
- Dire clairement : « Je recommande X parce que Y (moins d’aller-retours / une calibration / réutilisable). »
|
||||
- Si l’utilisateur pousse l’essai-erreur : rappeler une fois l’alternative, puis suivre s’il insiste.
|
||||
43
.cursor/rules/entreprise/00-git-modele.mdc
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
description: Progiweb — modèle Git / Gitea / Bitbucket / serveur (entreprise)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Modèle Git entreprise (Progiweb)
|
||||
|
||||
Référentiel agent. Détails par projet → `ops/20-parametres.mdc` et `ops/20-git-deploy-playbook.mdc`.
|
||||
|
||||
## Rôles
|
||||
|
||||
| Système | Rôle |
|
||||
|---|---|
|
||||
| **Gitea** (`git.progiweb.ca`) | Source de vérité — push depuis Windows |
|
||||
| **Bitbucket** | Miroir / backup opérationnel hors serveurs |
|
||||
| **Serveur (cPanel)** | Déploiement uniquement — **jamais** de commits sur le serveur |
|
||||
|
||||
## Branches
|
||||
|
||||
- Travail quotidien : **`dev`**
|
||||
- Prod : **`master`** (quand le projet l'utilise ; ne pas inventer le flux prod)
|
||||
- Ne pas pousser `master` sans demande explicite
|
||||
|
||||
## Flux nominal
|
||||
|
||||
```
|
||||
Windows --push--> Gitea(dev) --webhook--> serveur (deploy_dev)
|
||||
|
|
||||
+--miroir push--> Bitbucket(dev)
|
||||
```
|
||||
|
||||
## Déploiement (règles absolues)
|
||||
|
||||
- Script shell hors webroot : typiquement `~/deploy/deploy_dev.sh`
|
||||
- Déclencheur versionné dans le **docroot** : `deploy_dev.php` (sinon `git clean -fd` le détruit)
|
||||
- Le script fait : `git fetch` → `git reset --hard FETCH_HEAD` → `git clean` **ciblé** (jamais un clean aveugle sur tout le home cPanel)
|
||||
- Via PHP/Apache, **`HOME` est souvent absent** → chemins **absolus** dans le `.sh`
|
||||
- Auth Gitea Windows : token (éviter OAuth HTTP « Bad Request »)
|
||||
- Auth miroir Bitbucket : user `x-token-auth` + Repository Access Token (1Password) — pas le mot de passe web
|
||||
|
||||
## Nouveau projet
|
||||
|
||||
Suivre `entreprise/00-checklist-nouveau-projet.mdc` et recopier `entreprise/` + remplir `ops/` + `projet/`.
|
||||
31
.cursor/rules/entreprise/00-workflow.mdc
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
description: Progiweb — workflow agent (entreprise). Toujours s'y référer.
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Workflow entreprise (Progiweb)
|
||||
|
||||
Référentiel agent — pas une doc humaine. S'appliquer à tous les projets Progiweb montés sur ce modèle.
|
||||
|
||||
## Avant de modifier du code
|
||||
|
||||
- Demander le **numéro JIRA** si l'utilisateur ne l'a pas fourni (préfixe selon le projet, voir `projet/`).
|
||||
- Ne pas bloquer une simple question ou une lecture seule.
|
||||
|
||||
## Portée
|
||||
|
||||
- **Changement minimal** : uniquement ce qui est demandé.
|
||||
- Pas de refactor large, renommage massif ou nettoyage opportuniste sans accord explicite.
|
||||
- Cohérence locale (fichier / module) plutôt que modernisation globale.
|
||||
- **Approche** : toujours le meilleur **coût-bénéfice** (voir `entreprise/00-cout-benefice.mdc`) — calibrage / vérité mesurable avant essai-erreur.
|
||||
|
||||
## Incohérences voisines
|
||||
|
||||
1. Signaler brièvement.
|
||||
2. Proposer une correction ciblée.
|
||||
3. Attendre confirmation avant de sortir du périmètre.
|
||||
|
||||
## Fin de tâche
|
||||
|
||||
- Résumer : quoi, où, déploiement / SQL / étapes manuelles restantes.
|
||||
- Ne pas créer de README ou docs hors demande explicite (les règles agent vivent dans `.cursor/rules/`).
|
||||
23
.cursor/rules/ops/20-git-deploy-playbook.mdc
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
description: MS1 Inscription — playbook Git/deploy (ops)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Playbook Git / deploy — MS1 Inscription
|
||||
|
||||
## Layout
|
||||
|
||||
- Le dépôt Git **est** le site (racine web = racine git), modèle historique MS1.
|
||||
- Différent de Reader (git root = `$HOME` avec `ci4app` + `public_html` frères).
|
||||
|
||||
## Deploy
|
||||
|
||||
1. `~/deploy/deploy_dev.sh` : fetch `dev` → reset --hard → clean (adapter les chemins au docroot Inscription).
|
||||
2. `deploy_dev.php` versionné à la racine — obligatoire avant webhook (sinon `git clean` l'efface).
|
||||
3. Chemins absolus dans le `.sh` (PHP sans `HOME`).
|
||||
|
||||
## À vérifier / aligner si besoin
|
||||
|
||||
- Miroir Bitbucket : case « Synchroniser quand les révisions sont soumis » + intervalle `1h`.
|
||||
- Webhook Gitea → URL HTTPS du `deploy_dev.php` d'environnement dev.
|
||||
- Même philosophie entreprise que Reader (voir `entreprise/00-git-modele.mdc`).
|
||||
41
.cursor/rules/ops/20-parametres.mdc
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
description: MS1 Inscription — paramètres figés (ops)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Paramètres — MS1 Inscription
|
||||
|
||||
Secrets → 1Password uniquement.
|
||||
|
||||
## Git / remotes
|
||||
|
||||
| Clé | Valeur |
|
||||
|---|---|
|
||||
| Gitea (Windows) | `http://git.progiweb.ca/stephan/ms1inscription-v5.git` |
|
||||
| Branche travail | `dev` |
|
||||
| Branche prod | `master` |
|
||||
| Bitbucket miroir | `https://bitbucket.org/progiwebinc/ms1inscription-v5.git` |
|
||||
|
||||
## Miroir
|
||||
|
||||
| Clé | Valeur |
|
||||
|---|---|
|
||||
| Modèle | Comme Reader : sync **à chaque soumission** + intervalle filet (ex. `1h`) |
|
||||
| Auth Bitbucket | `x-token-auth` + access token (item 1Password dédié Inscription si distinct) |
|
||||
|
||||
## Déploiement
|
||||
|
||||
| Clé | Valeur |
|
||||
|---|---|
|
||||
| User / home typique | `devinscription` / `/home/devinscription` |
|
||||
| Git root serveur | **Docroot du site** (pas le home entier — layout ≠ Reader) |
|
||||
| Script | `/home/devinscription/deploy/deploy_dev.sh` |
|
||||
| Webhook PHP | `deploy_dev.php` à la **racine du dépôt** (versionné) |
|
||||
| Contenu PHP | `shell_exec('/home/devinscription/deploy/deploy_dev.sh 2>&1')` |
|
||||
|
||||
## SSH / SQLTools
|
||||
|
||||
| Clé | Valeur |
|
||||
|---|---|
|
||||
| Port SSH | **7822** |
|
||||
| Exemple | `.vscode/settings.json.example` |
|
||||
32
.cursor/rules/projet/10-css-js-version-bump.mdc
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
description: Incrémenter _VERSION_CODE après modification CSS/JS servis par Minify
|
||||
globs: css/**/*.css,js/**/*.js,min/groupsConfig.php,inc_footer_scripts.php,inc_header.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Cache busting CSS / JS (MS1 inscription)
|
||||
|
||||
Les feuilles de style et scripts du site public passent par Minify :
|
||||
|
||||
- CSS : `/min/g=css&version=v{_VERSION_CODE}` (`inc_header.php`)
|
||||
- JS : `/min/index.php?g=js&version=v{_VERSION_CODE}`
|
||||
|
||||
`_VERSION_CODE` est défini dans **`php/inc_settings.php`** (environnement local/dev).
|
||||
|
||||
## Obligation
|
||||
|
||||
Après **toute modification** d’un fichier inclus dans `min/groupsConfig.php` (ex. `css/style.css`, `css/ms1-doc.css`, JS du groupe `js`) **ou** d’un asset chargé avec `?version=v{_VERSION_CODE}` :
|
||||
|
||||
1. Incrémenter le **dernier segment** de `_VERSION_CODE` dans `php/inc_settings.php`
|
||||
Ex. `4.72.659` → `4.72.660`
|
||||
2. Mettre à jour `_DATE_CODE` à la date du jour (YYYY-MM-DD).
|
||||
|
||||
Ne pas oublier cette étape dans le même changement que le CSS/JS — sinon le navigateur garde l’ancien bundle en cache.
|
||||
|
||||
## Vérification rapide
|
||||
|
||||
Le pied de page affiche : `code (v{_VERSION_CODE})` — utile pour confirmer que la nouvelle version est déployée.
|
||||
|
||||
## Autres environnements
|
||||
|
||||
Fichiers séparés si déploiement ciblé : `php/inc_settingsprod.php`, `inc_settings_baloo.php`, etc. En dev local, c’est **`php/inc_settings.php`** qui compte.
|
||||
60
.cursor/rules/projet/10-documentation.mdc
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
description: Documentation du code et des changements MS1
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Documentation MS1
|
||||
|
||||
## Commentaires dans le code
|
||||
|
||||
- Documenter la **logique métier non évidente** (règles d'inscription, paiement, permissions, états annulés, etc.).
|
||||
- Pour une nouvelle fonction ou un bloc important : courte description du **pourquoi**, pas seulement du quoi.
|
||||
- Référencer le ticket : `// MSIN-4405` ou bloc PHPDoc `* MSIN-4405 — …`.
|
||||
- Langue : **français** pour les commentaires métier (comme le reste du projet), sauf si le fichier est entièrement en anglais.
|
||||
|
||||
## Libellés UI (table `info`)
|
||||
|
||||
- Tout texte affiché à l’utilisateur (FR/EN) passe par **Info** (`afficheTexte` / `fxInscrGestionT` / clés `info_clef`), **jamais** codé en dur dans le PHP/JS.
|
||||
- Ça inclut aussi : en-têtes de colonnes, compteurs (« 12 inscriptions »), pastilles, `title`/`aria-label`, messages d’invite — **même si `aria-hidden`**.
|
||||
- Interdit : `if ($strLangue === 'fr') { echo '…'; } else { echo '…'; }`, ternaires FR/EN, chaînes collées dans un `echo` HTML.
|
||||
- Nouvelle clé = script SQL `sql/MSIN-xxxx-….sql` (INSERT dans `info`, fr + en) **dans le même lot** que le PHP.
|
||||
- Les fallbacks PHP FR/EN (`fxInscrGestionFallbackTexte`, etc.) = filet de secours uniquement ; **pas** la source de vérité, et ne remplacent **pas** le script SQL.
|
||||
|
||||
Avant de terminer une tâche UI : rechercher dans les fichiers touchés les motifs `=== 'fr'`, `=== 'en'`, `$blnFr ?`, et les libellés français littéraux hors commentaires.
|
||||
## AJAX multi-pages (Info)
|
||||
|
||||
Tout endpoint AJAX qui lit/écrit des libellés Info et peut être appelé depuis plusieurs pages (compte, hub, `/mobile`) doit **forcer le contexte `compte.php`** avant les includes utiles :
|
||||
|
||||
```php
|
||||
define('MS1_…_AJAX', true); // optionnel, pattern existant
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
```
|
||||
|
||||
Objectif : éviter les clés créées sous `info_prg=ajax_….php` et les libellés manquants selon la page appelante. Références : `ajax_promoteur_hub.php`, `ajax_bib_range.php`, `ajax_inscr_gestion.php`.
|
||||
|
||||
## Scripts SQL
|
||||
|
||||
En-tête obligatoire en tête de fichier :
|
||||
|
||||
```sql
|
||||
-- MSIN-xxxx — Titre court
|
||||
-- Contexte / prérequis (scripts à exécuter avant)
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
```
|
||||
|
||||
- Indiquer l'ordre d'exécution si plusieurs scripts forment une migration.
|
||||
- Préférer les scripts **idempotents** quand c'est possible (pattern déjà utilisé dans `sql/`).
|
||||
- **Ne jamais** documenter / conseiller d’exécuter ces scripts hors **dev préprod** (voir `projet/10-workflow.mdc` — Promotion BD).
|
||||
|
||||
## Résumé des changements (agent)
|
||||
|
||||
À la fin d'une implémentation, fournir :
|
||||
|
||||
1. **Quoi** — comportement ajouté ou corrigé.
|
||||
2. **Où** — fichiers principaux touchés.
|
||||
3. **Déploiement** — SQL **dev préprod seulement**, puis Navicat structure + outil sync pour les autres env ; `_VERSION_CODE` / config / étapes manuelles.
|
||||
|
||||
Ne pas créer de fichiers README ou docs hors demande explicite.
|
||||
102
.cursor/rules/projet/10-hors-transaction-import.mdc
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
description: Projet MS1 hors transaction — import / saisie rapide / ChronoTrack (cadrage produit)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Projet MSIN-4482 — entrée hors transaction (import + terrain)
|
||||
|
||||
JIRA : **MSIN-4482**.
|
||||
Orthographe produit : **ChronoTrack**.
|
||||
Doc de cadrage — source de vérité pour le développement de ce chantier.
|
||||
|
||||
## Intention
|
||||
|
||||
Utiliser la suite MS1 (gestion, ChronoTrack, outils promoteur existants) pour des clients **sans** tunnel d’inscription / paiement en ligne MS1.
|
||||
Les participants arrivent surtout par **import fichier** (puis saisie rapide plus tard) ; ensuite sync ChronoTrack.
|
||||
|
||||
Aussi utile plus tard le **jour J** sur une course **avec** inscription en ligne : correction / ajout rapide **sans** transaction financière.
|
||||
|
||||
## Décisions actées
|
||||
|
||||
| Sujet | Décision |
|
||||
|---|---|
|
||||
| Source terrain | Toujours **`resultats_*`** — **pas de panier / `no_panier`** pour ces lignes |
|
||||
| Type événement | **`inscriptions_types_evenements`** — libellé FR **Sans inscription en ligne** / EN **No online registration** (`sql/MSIN-4482-type-sans-inscription-en-ligne.sql`) ; l’événement pointe via `inscriptions_evenements.te_id` |
|
||||
| Paiement MS1 | Hors scope ; **interdit** le « panier à 0 $ » |
|
||||
| Clé unique | ID du fichier client = **`external_id` ChronoTrack** au push ; même fichier = fallback import CT |
|
||||
| Questions import | **Option C** : matcher ; non matchées → preview + **création confirmée** |
|
||||
| Hub promoteur | **Ne pas toucher pour l’instant** — hub actuel tel quel |
|
||||
| Qui est promoteur | Opérateur MS1 se met **lui-même** promoteur ; droits clients = kits **minimisés via l’existant** |
|
||||
| Mini-hub | **Plus tard**, au fur et à mesure — pas un livrable immédiat |
|
||||
| Waiver | **Hors scope** |
|
||||
| Édition / saisie rapide | Après l’import (phase suivante) |
|
||||
| Finances | Marqueur d’**origine** ; import / saisie rapide exclus des rapports paiement |
|
||||
|
||||
## Origines participant (marqueur)
|
||||
|
||||
| Code | Canal |
|
||||
|---|---|
|
||||
| `inscription_en_ligne` | Flux payant actuel |
|
||||
| `import_fichier` | Bulk modèle fichier |
|
||||
| `saisie_rapide` | Création unitaire / jour J (plus tard) |
|
||||
|
||||
## Phases (réalignées)
|
||||
|
||||
1. **UI import superadm** — dropzone, mapping colonnes + épreuves, templates, bouton **Importer**
|
||||
2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier) — en place
|
||||
3. **Mapping pays** (valeurs fichier → `pay_id`) — **étape 2**
|
||||
4. **ChronoTrack** — push avec `par_external_id`
|
||||
5. **Saisie / édition** — fiche rapide (+ jour J)
|
||||
6. **Hub minimisé** — seulement si/quand nécessaire
|
||||
|
||||
## UI import (décisions)
|
||||
|
||||
- Emplacement v1 : **superadm fiche événement** (panneau sous ChronoTrack ; ancre « Import »)
|
||||
- Colonne fichier pour l’épreuve → match vers `epr_id` de l’événement
|
||||
- UX : dropzone ludique + mapping guidé
|
||||
- Template mapping : table `inscriptions_import_templates` (empreinte en-têtes + eve/com)
|
||||
|
||||
## Exemple fichier client (référence)
|
||||
|
||||
Fichier type : export ~Échappée (en-têtes déjà normalisés ; l’original client peut différer).
|
||||
|
||||
| Colonne exemple | Cible MS1 |
|
||||
|---|---|
|
||||
| Attendee # | `par_external_id` (clé unique / CT) |
|
||||
| First Name / Last Name | `par_prenom` / `par_nom` |
|
||||
| Ticket Type | `epr` (valeurs → épreuves MS1) |
|
||||
| No dossard | `no_bib` |
|
||||
| Gender (Male/Female/Other) | `par_sexe` (normaliser à l’écriture) |
|
||||
| Age / Birth Date | `par_age` / `par_naissance` |
|
||||
| Nationalité, urgence, allergies, condition médicale, langue… | questions (option C) |
|
||||
|
||||
**Visibilité liste / CT** : les coquilles `resultats_epreuves_commandees` doivent avoir **`pec_actif = 1`** (sinon absentes de la liste inscriptions et du sync). Les `I{eve}X…` = faux `no_commande` technique, pas un vrai panier.
|
||||
|
||||
~1800 lignes, ~10–12 valeurs distinctes de Ticket Type (distances / jours).
|
||||
|
||||
## Fichiers
|
||||
|
||||
| Rôle | Fichier |
|
||||
|---|---|
|
||||
| SQL templates | `sql/MSIN-4482-import-resultats-templates.sql` |
|
||||
| SQL type eve | `sql/MSIN-4482-type-sans-inscription-en-ligne.sql` |
|
||||
| SQL fix liste | `sql/MSIN-4482-fix-pec-actif-import.sql` |
|
||||
| Logique + UI | `php/inc_fx_import_resultats.php` |
|
||||
| AJAX | `superadm/ajax_import_resultats.php` |
|
||||
| Branche fiche eve | `superadm/php/inc_form.php` |
|
||||
| CSS | `superadm/css/style_blue.css` |
|
||||
|
||||
## Hors scope (maintenant)
|
||||
|
||||
- Hub promoteur / mini-hub
|
||||
- Waiver
|
||||
- Écriture `resultats_*` (prochaine étape après UI)
|
||||
- Import inverse CT → MS1
|
||||
|
||||
## Règles agent
|
||||
|
||||
- Changement minimal ; référencer **MSIN-4482**.
|
||||
- **Pas de panier** pour ces participants — écrire dans `resultats_*`.
|
||||
- **Ne pas modifier le hub promoteur** tant que ce n’est pas redemandé.
|
||||
- Ne pas inventer de waiver pour « signature de dossier ».
|
||||
- SQL : **dev préprod** seulement, puis Navicat + sync outil.
|
||||
38
.cursor/rules/projet/10-php-conventions.mdc
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
description: Conventions PHP legacy MS1 inscription (site + superadm)
|
||||
globs: *.php, php/**/*.php, superadm/**/*.php, ajax*.php, auto_*.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Conventions PHP MS1
|
||||
|
||||
Code applicatif legacy (racine, `php/`, `superadm/`). Ne pas imposer ces règles à `v3_ci4/`, `rapports/libs/`, vendors ou librairies tierces.
|
||||
|
||||
## Style à respecter
|
||||
|
||||
- **Suivre le fichier ouvert** : indentation, `array()`, guillemets, style des `if` — ne pas « moderniser » PHP dans un diff parallèle.
|
||||
- **Préfixes de variables** du projet : `$str`, `$int`, `$tab`, `$bln`, `$mem`, `$v…` pour config globale.
|
||||
- **Fonctions métier** : préfixe `fx` (`fxNbItemsPanier`, `inc_fonctions.php`). Réutiliser les helpers existants avant d'en créer.
|
||||
- **Includes** : `php/inc_settings.php`, `php/inc_fonctions.php`, patterns `inc_*` / `inc_fx_*` déjà en place.
|
||||
|
||||
## Qualité
|
||||
|
||||
- Code **propre et lisible** dans la zone modifiée ; pas de code mort ni de `var_dump` laissé en prod.
|
||||
- **Pas de sur-ingénierie** : pas d'abstraction pour un seul appel, pas de classes si le module voisin est procédural.
|
||||
- Sécurité : échapper les sorties HTML, requêtes préparées / fonctions BD existantes — ne pas introduire de concat SQL brute.
|
||||
|
||||
## Version et assets
|
||||
|
||||
- CSS/JS servis par Minify : voir la règle `css-js-version-bump` — incrémenter `_VERSION_CODE` et `_DATE_CODE` dans `php/inc_settings.php`.
|
||||
|
||||
## Exemple de référence ticket
|
||||
|
||||
```php
|
||||
// MSIN-4290
|
||||
define('QR_SECRET_KEY', '…');
|
||||
|
||||
/**
|
||||
* MSIN-4405 — Détermine, en LECTURE SEULE, pourquoi une inscription est annulée.
|
||||
*/
|
||||
function fx…()
|
||||
```
|
||||
11
.cursor/rules/projet/10-stack.mdc
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
description: MS1 Inscription — stack et préfixe JIRA (projet)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Projet MS1 Inscription — stack
|
||||
|
||||
- Stack : PHP legacy (racine / `php/` / `superadm/`) + modules CI4 optionnels (`v3_ci4/`).
|
||||
- Préfixe JIRA : **`MSIN-xxxx`**.
|
||||
- Scripts SQL : `sql/MSIN-xxxx-description-courte.sql`.
|
||||
- Workspace local typique : `C:\dev_v4\ms1inscription4`.
|
||||
45
.cursor/rules/projet/10-v2.mdc
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
description: MS1 modules v2 — bib v4 et gestion inscriptions (hors legacy)
|
||||
globs: js/v2/**,php/inc_v2_assets.php,inc_tableau_gestion_epreuves.php,inc_tableau_inscriptions_gestion.php,ajax_bib_range.php,ajax_inscr_gestion.php,php/inc_fx_promoteur.php,php/inc_fx_inscriptions_gestion.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Modules v2 MS1
|
||||
|
||||
## Périmètre v2 (non prod, développement actif)
|
||||
|
||||
- **Bib assignments v4** — `#module-bib`, `fxShowBibTool4`, `ajax_bib_range.php`
|
||||
- **Gestion inscriptions v2** — `#module-inscr-gestion`, `inc_fx_inscriptions_gestion.php`, `ajax_inscr_gestion.php`
|
||||
|
||||
## Règle JS
|
||||
|
||||
- **Ne jamais** ajouter de code v2 dans `inc_footer_scripts.php`.
|
||||
- Nouveau JS v2 → `js/v2/` (fichier existant ou nouveau selon le module).
|
||||
- Enregistrer le script sur la page concernée : `fxV2RegisterScript('bib-assignments')` ou `'inscr-gestion'`.
|
||||
- Config PHP → JS : `data-*` sur le module (bib) ou `window.MS1_V2_INSCR_GESTION` (inscr).
|
||||
|
||||
## Legacy (ne pas toucher sauf pont minimal)
|
||||
|
||||
- `.btn_bib` jQuery, `fxShowBibTool2/3`, `inc_tableau_dossards.php`
|
||||
|
||||
## Fichiers de référence
|
||||
|
||||
| Rôle | Fichier |
|
||||
|------|---------|
|
||||
| Enregistrement / rendu scripts | `php/inc_v2_assets.php` |
|
||||
| Bib v4 client | `js/v2/bib-assignments.js` |
|
||||
| Inscr gestion client | `js/v2/inscr-gestion.js` |
|
||||
|
||||
## Visuel panneaux v2 (standard)
|
||||
|
||||
Réutiliser le même langage couleur que la **fiche** (`inscr-gestion-fiche-section--*`) et le **détail dossards** (`#module-bib .bib-tool-detail`) — pas inventer une nouvelle palette par écran.
|
||||
|
||||
| Rôle | Classes | En-tête / accent | Corps |
|
||||
|------|---------|------------------|-------|
|
||||
| Info / lecture | `--details`, panneau `--search`, **liste résultats** | fond `#e7f1ff`, bordure gauche `#007bff`, texte `#004085` | `#f5f9ff` / hover ligne `#f0f6ff` |
|
||||
| Gestion / attention | `--gestion` | `#fff8e6` / `#e0a800` / `#664d03` | `#fffdf5` |
|
||||
| Édition / ok | `--editable` | `#e8f5e9` / `#28a745` / `#155724` | `#f4faf5` |
|
||||
| Outil caméra | panneau `--qr` | `#f3eef9` / `#6f42c1` | `#faf8fd` |
|
||||
| Détail bib | `.bib-tool-detail` | bordure `#2f5fd0`, titre bleu | corps `#f4f8fc` |
|
||||
|
||||
Conventions communes : `border-radius` 6–8px, bordure gauche 4px sur les en-têtes de section/panneau, labels uppercase discrets (`11–12px`, `#6c757d` / `#9aa0a6`) quand on est en mode « carte ». Ne pas aplatir en gris `#f8f9fa` seul sur un panneau métier important.
|
||||
49
.cursor/rules/projet/10-workflow.mdc
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Workflow MS1 — JIRA, validation avant gros changements, code legacy, promotion BD
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Workflow MS1 inscription
|
||||
|
||||
## Numéro JIRA (MSIN-xxxx)
|
||||
|
||||
- Au début d'une tâche de développement (nouvelle feature, correctif, refactor), **demander le numéro JIRA** si l'utilisateur ne l'a pas fourni.
|
||||
- Préfixe courant features : `MSIN-xxxx`. Continuité / ops BD peut aussi être `CON-xxxx`.
|
||||
- Ne pas bloquer une simple question ou une lecture du code — seulement quand on s'apprête à modifier des fichiers.
|
||||
- Référencer le ticket dans le code et les scripts SQL : `// MSIN-4290` ou `-- MSIN-4379 — description`.
|
||||
- Nommer les nouveaux scripts SQL : `sql/MSIN-xxxx-description-courte.sql`.
|
||||
|
||||
## Promotion BD (règle absolue)
|
||||
|
||||
Ordre **non négociable** pour faire passer structure + données vers d’autres environnements :
|
||||
|
||||
1. **Scripts SQL** (`sql/MSIN-*.sql`) → exécutés **uniquement** sur **dev préprod**.
|
||||
2. **Navicat** → copie de **structure** (préprod → autres env : dev prod, client préprod, client prod).
|
||||
3. **Outil soft sync** (`php/sync_static_db.php`, CON-333) → peupler / aligner les **données** (manquants + corrections).
|
||||
|
||||
**Interdit** : demander ou suggérer de « rejouer le SQL » sur prod, préprod client, ou tout env autre que **dev préprod**.
|
||||
|
||||
- Table absente / Erreur lecture ailleurs = d’abord **structure Navicat** depuis préprod, **puis** sync outil.
|
||||
- `info` : jamais de DELETE (auto-création `afficheTexte`). Soft only.
|
||||
- Catalogues (droits, pages, doc…) : pas d’auto-création ; l’alignement données passe par l’outil, pas par un second import SQL.
|
||||
|
||||
## Portée des changements
|
||||
|
||||
- **Changement minimal** : corriger uniquement ce qui est demandé.
|
||||
- Pas de refactor large, renommage massif ou « nettoyage » opportuniste sans accord explicite.
|
||||
- Pour le code legacy (~10 ans), privilégier la cohérence locale (fichier / module touché) plutôt qu'une modernisation globale.
|
||||
- Approche : **meilleur coût-bénéfice** (`entreprise/00-cout-benefice.mdc`) — ex. calibrateur GUI plutôt qu’une série d’essais PDF.
|
||||
|
||||
## Incohérences repérées
|
||||
|
||||
Quand du code voisin contredit le pattern du fichier ou du module :
|
||||
|
||||
1. **Signaler** brièvement l'incohérence à l'utilisateur.
|
||||
2. **Proposer** une correction ciblée ou l'alignement sur le pattern dominant du module.
|
||||
3. **Attendre confirmation** avant de corriger du code hors périmètre de la tâche.
|
||||
|
||||
## Fin de tâche
|
||||
|
||||
- Résumer ce qui a changé (fichiers, comportement).
|
||||
- Pour le SQL : rappeler qu’il s’exécute **seulement en dev préprod**, puis structure Navicat + sync outil pour le reste — **pas** « exécuter ce script en prod ».
|
||||
- Mentionner les étapes manuelles restantes (déploiement code, Navicat, sync soft, cache).
|
||||
7
.gitignore
vendored
@ -7,6 +7,12 @@
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Composer artifacts (ne jamais committer vendor/)
|
||||
/v3_ci4/vendor/
|
||||
|
||||
# IDE — ne jamais committer les connexions SQL (copier settings.json.example)
|
||||
.vscode/settings.json
|
||||
|
||||
# Compiled Java class files
|
||||
*.class
|
||||
|
||||
@ -68,6 +74,7 @@ Thumbs.db
|
||||
/test/
|
||||
/tpp/
|
||||
/qr/
|
||||
/output/
|
||||
/bp/
|
||||
/fnq/
|
||||
/images/backgrounds/
|
||||
|
||||
30
.htaccess
@ -1,24 +1,21 @@
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# ====================================
|
||||
# CI4 ROUTES PRIORITAIRES
|
||||
# ====================================
|
||||
# Redirect www — prod client seulement (en dev le redirect etait commente pour la meme raison)
|
||||
RewriteCond %{HTTP_HOST} !^www\. [NC]
|
||||
RewriteCond %{HTTP_HOST} !^preprod\. [NC]
|
||||
RewriteCond %{HTTP_HOST} !\.progiweb\.net$ [NC]
|
||||
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=302,L]
|
||||
|
||||
# CI4 (dev / v3) — inoffensif en prod si v3 absent
|
||||
RewriteRule ^test/?$ v3/index.php/test [E=IS_CI4:1,L]
|
||||
RewriteRule ^admin-dashboard/?$ v3/index.php/admin-dashboard [E=IS_CI4:1,L]
|
||||
RewriteRule ^resultats-nouveaux/?$ v3/index.php/resultats-nouveaux [E=IS_CI4:1,L]
|
||||
|
||||
# ====================================
|
||||
# STOP SI CI4
|
||||
# ====================================
|
||||
|
||||
RewriteCond %{ENV:IS_CI4} =1
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ====================================
|
||||
# LEGACY ROUTES
|
||||
# ====================================
|
||||
RewriteCond %{SCRIPT_FILENAME} !-d
|
||||
RewriteCond %{SCRIPT_FILENAME} !-f
|
||||
|
||||
RewriteRule ^kc/([\w\-]+)/([\w\-]+)/?$ ./kc.php?param1=$1¶m2=$2 [NC,QSA]
|
||||
|
||||
@ -28,6 +25,9 @@ RewriteRule ^cart/([\w\-]+)/([\w\-]+)/?$ ./panier.php?lang=en&step=$1&code=$2 [N
|
||||
RewriteRule ^compte/?([\w\-]+)?/?$ ./compte.php?lang=fr&code=$1 [NC,QSA]
|
||||
RewriteRule ^account/?([\w\-]+)?/?$ ./compte.php?lang=en&code=$1 [NC,QSA]
|
||||
|
||||
RewriteRule ^mobile/?$ ./mobile.php?lang=fr [NC,QSA]
|
||||
RewriteRule ^mobile/en/?$ ./mobile.php?lang=en [NC,QSA]
|
||||
|
||||
RewriteRule ^reserver/([\w\-]+)/(\d+)/?$ ./reserver.php?lang=fr&code=$1&id=$2 [NC,QSA]
|
||||
RewriteRule ^book/([\w\-]+)/(\d+)/?$ ./reserver.php?lang=en&code=$1&id=$2 [NC,QSA]
|
||||
|
||||
@ -36,6 +36,12 @@ RewriteRule ^donate/([\w\-]+)/([\w\-]+)/?/?([\w\-]+)?/?$ ./dons.php?lang=en&code
|
||||
|
||||
RewriteRule ^page/([\w\-]+)/?(fr|en)?/?$ ./index.php?code=$1&lang=$2 [NC,QSA]
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^/(rapports_js|superadm)(/|$) [NC]
|
||||
RewriteRule ^([\w\-]+)/?(fr|en)?/?([\w\-]+)?/?$ ./evenements.php?code=$1&lang=$2&tab=$3 [NC,QSA]
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^/(rapports_js|superadm/.*)$
|
||||
# php -- BEGIN cPanel-generated handler, do not edit
|
||||
# Set the "ea-php81" package as the default "PHP" programming language.
|
||||
<IfModule mime_module>
|
||||
AddHandler application/x-httpd-ea-php81___lsphp .php .php8 .phtml
|
||||
</IfModule>
|
||||
# php -- END cPanel-generated handler, do not edit
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
// Copier vers settings.json (gitignored) et renseigner vos identifiants locaux.
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"mysqlOptions": {
|
||||
@ -11,15 +12,15 @@
|
||||
"port": 3306,
|
||||
"sshOptions": {
|
||||
"port": 7822,
|
||||
"host": "104.254.182.111",
|
||||
"username": "devinscription",
|
||||
"password": "M-G7+[NdA34H"
|
||||
"host": "YOUR_SSH_HOST",
|
||||
"username": "YOUR_SSH_USER",
|
||||
"password": "YOUR_SSH_PASSWORD"
|
||||
},
|
||||
"driver": "MariaDB",
|
||||
"name": "astral(dev) ms1inscription",
|
||||
"username": "devinscription_bd",
|
||||
"password": "x$@_v0-l_I((",
|
||||
"database": "devinscription_preprod"
|
||||
"name": "local dev connection",
|
||||
"username": "YOUR_DB_USER",
|
||||
"password": "YOUR_DB_PASSWORD",
|
||||
"database": "YOUR_DATABASE"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
MSIN-4379-bib_v4-traductions.xlsx
Normal file
1173
ajax_bib_range.php
334
ajax_inscr_gestion.php
Normal file
@ -0,0 +1,334 @@
|
||||
<?php
|
||||
/**
|
||||
* Actions AJAX reservees a la fiche de gestion des inscriptions (v2).
|
||||
* N'altere pas ajax_promoteur.php ni enregistrer.php (legacy promoteur).
|
||||
*/
|
||||
session_start();
|
||||
|
||||
// MSIN-4401 — Traductions toujours au nom de compte.php (jamais ajax_inscr_gestion.php).
|
||||
// Evite les doublons info_prg=ajax_*.php et les libelles manquants selon la page appelante
|
||||
// (hub, compte, /mobile). Meme pattern que ajax_promoteur_hub / ajax_bib_range.
|
||||
define('MS1_INSCR_GESTION_AJAX', true);
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Filet de securite : transforme tout fatal (include manquant, fonction
|
||||
// indefinie, parse error...) en reponse JSON exploitable au lieu d'un 500 muet.
|
||||
$GLOBALS['__inscr_gestion_done'] = false;
|
||||
register_shutdown_function(function () {
|
||||
if (!empty($GLOBALS['__inscr_gestion_done'])) {
|
||||
return;
|
||||
}
|
||||
$err = error_get_last();
|
||||
$strDetail = ($err && in_array($err['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR), true))
|
||||
? ($err['message'] . ' @ ' . $err['file'] . ':' . $err['line'])
|
||||
: 'fatal inconnu';
|
||||
error_log('[ajax_inscr_gestion] ' . $strDetail);
|
||||
if (!headers_sent()) {
|
||||
http_response_code(200);
|
||||
}
|
||||
echo json_encode(array('state' => 'error', 'message' => 'fatal', 'detail' => $strDetail));
|
||||
});
|
||||
|
||||
require_once 'php/inc_fonctions.php';
|
||||
require_once 'php/inc_fx_promoteur.php';
|
||||
require_once 'php/inc_fx_inscriptions_gestion.php';
|
||||
// Necessaire pour fxEveAccesResolveEveIdFromParId() appelee dans le case
|
||||
// 'par_statut_course' : ce fichier n'est sinon charge que paresseusement
|
||||
// (fxInscrGestionLoadEveAcces) APRES ce premier appel -> fatal undefined function.
|
||||
require_once 'php/inc_fx_eve_acces.php';
|
||||
|
||||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||||
$strAction = trim((string)($_REQUEST['a'] ?? ''));
|
||||
$strLangue = trim((string)($_REQUEST['lang'] ?? 'fr'));
|
||||
if ($strLangue === '' || !in_array($strLangue, array('fr', 'en'), true)) {
|
||||
$strLangue = 'fr';
|
||||
}
|
||||
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
fxInscrGestionInitTextes($strLangue);
|
||||
|
||||
if ($intComId <= 0) {
|
||||
$GLOBALS['__inscr_gestion_done'] = true;
|
||||
// MSIN-4401 — code session : le client recharge pour afficher le login.
|
||||
echo json_encode(array('state' => 'error', 'code' => 'session', 'message' => 'session'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$tabRetour = array('state' => 'error');
|
||||
|
||||
try {
|
||||
switch ($strAction) {
|
||||
case 'par_statut_course':
|
||||
$intParId = intval($_REQUEST['par_id'] ?? 0);
|
||||
$strStatut = trim((string)($_REQUEST['par_statut_course'] ?? ''));
|
||||
$intEveId = fxEveAccesResolveEveIdFromParId($intParId);
|
||||
|
||||
if ($intEveId <= 0 || !fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.statut_edit')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'unauthorized');
|
||||
break;
|
||||
}
|
||||
|
||||
$tabRetour = fxSetParStatutCourse($intParId, $strStatut, $strLangue);
|
||||
break;
|
||||
|
||||
case 'refund_form':
|
||||
// Charge le formulaire de remboursement mutualise pour la fiche de gestion (panneau repliable).
|
||||
$strNoPanier = trim((string)($_REQUEST['no_panier'] ?? ''));
|
||||
if ($strNoPanier === '') {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'no_panier_manquant');
|
||||
break;
|
||||
}
|
||||
|
||||
// Meme source que le Super Admin : la commande vient de inscriptions_panier_acheteurs.
|
||||
$sqlCmd = "SELECT *, IF(a.com_id <> 0, (SELECT c.com_login FROM inscriptions_comptes c WHERE a.com_id = c.com_id), '') AS usager"
|
||||
. " FROM inscriptions_panier_acheteurs a, inscriptions_panier_statuts s"
|
||||
. " WHERE s.sta_id = a.sta_id AND a.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "'";
|
||||
$recCommandes = $objDatabase->fxGetRow($sqlCmd);
|
||||
if (!$recCommandes) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'commande_introuvable');
|
||||
break;
|
||||
}
|
||||
|
||||
// L'eve_id de contexte = evenement de la fiche (participant), pas seulement celui du checkout.
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/superadm/php/inc_fx_paypal.php';
|
||||
|
||||
$intEveContext = intval($_REQUEST['eve_id'] ?? 0);
|
||||
if ($intEveContext <= 0) {
|
||||
$intEveContext = intval($recCommandes['eve_id']);
|
||||
}
|
||||
if ($intEveContext <= 0 || !fxInscrGestionCanDo($intComId, $intEveContext, 'inscriptions_gestion.refund')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'unauthorized');
|
||||
break;
|
||||
}
|
||||
if (!fxRefundPanierLinkedToEvent($recCommandes['no_panier'], $intEveContext, intval($recCommandes['eve_id']))) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'panier_evenement_incompatible');
|
||||
break;
|
||||
}
|
||||
|
||||
$strRefundToken = fxRefundEnsureToken();
|
||||
$arrRefundCtx = fxRefundComputeContext($recCommandes, $intEveContext);
|
||||
|
||||
// Auteur = promoteur connecte (alimente l'encart "Ajoute par" du formulaire).
|
||||
$strAuteur = trim((string)($_SESSION['com_info']['com_prenom'] ?? '') . ' '
|
||||
. (string)($_SESSION['com_info']['com_nom'] ?? ''));
|
||||
|
||||
// Markup seul (sans <script>, dernier param false) car injecte via innerHTML :
|
||||
// le comportement (submit AJAX) est gere par le handler delegue dans inc_footer_scripts.php.
|
||||
// NB : le resume de la transaction n'est plus inclus ici (MSIN) ; il est desormais
|
||||
// accessible via le bouton "Afficher la transaction" de la barre Gestion (action transaction_resume).
|
||||
$strDocAside = '';
|
||||
if (function_exists('fxDocRenderRefundPromoteurAside')) {
|
||||
global $vPage;
|
||||
$memPage = $vPage ?? null;
|
||||
$vPage = 'compte.php';
|
||||
$strDocAside = fxDocRenderRefundPromoteurAside();
|
||||
$vPage = $memPage;
|
||||
}
|
||||
$strForm = fxRenderRefundForm($recCommandes, $strRefundToken, $arrRefundCtx, $vDomaine, $strAuteur, null, false, $strDocAside);
|
||||
|
||||
$tabRetour = array('state' => 'ok', 'html' => $strForm);
|
||||
break;
|
||||
|
||||
case 'bib_ocr':
|
||||
// MSIN-4401 — Lecture numero dossard via Google Cloud Vision.
|
||||
// Image : fichier multipart (prefere) ou base64 POST (legacy).
|
||||
if (!fxInscrGestionBibOcrCloudEnabled()) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'ocr_not_configured');
|
||||
break;
|
||||
}
|
||||
$strImage = '';
|
||||
if (!empty($_FILES['image']['tmp_name']) && is_uploaded_file($_FILES['image']['tmp_name'])) {
|
||||
if ((int)$_FILES['image']['error'] === UPLOAD_ERR_OK && (int)$_FILES['image']['size'] > 0
|
||||
&& (int)$_FILES['image']['size'] <= 4 * 1024 * 1024) {
|
||||
$strBin = (string)file_get_contents($_FILES['image']['tmp_name']);
|
||||
$strImage = base64_encode($strBin);
|
||||
} else {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'image_invalid');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$strImage = (string)($_POST['image'] ?? '');
|
||||
}
|
||||
if ($strImage === '') {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'image_invalid');
|
||||
break;
|
||||
}
|
||||
$arrOcr = fxInscrGestionVisionReadBibNumber($strImage);
|
||||
if (!empty($arrOcr['ok']) && !empty($arrOcr['number'])) {
|
||||
$tabRetour = array(
|
||||
'state' => 'ok',
|
||||
'number' => (string)$arrOcr['number'],
|
||||
);
|
||||
break;
|
||||
}
|
||||
$strSeen = '';
|
||||
if (!empty($arrOcr['text'])) {
|
||||
$strSeen = preg_replace('/\D+/', '', (string)$arrOcr['text']);
|
||||
}
|
||||
$tabRetour = array(
|
||||
'state' => 'error',
|
||||
'message' => (string)($arrOcr['message'] ?? 'no_number'),
|
||||
'seen' => $strSeen,
|
||||
'detail' => (string)($arrOcr['detail'] ?? ''),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'bib_duplicate_check':
|
||||
// Controle "dossard en double" dans l'evenement (avant enregistrement legacy).
|
||||
// Ne modifie rien : renvoie qui porte deja ce dossard + la politique a appliquer.
|
||||
$intParId = intval($_REQUEST['par_id'] ?? 0);
|
||||
$strNoBib = trim((string)($_REQUEST['no_bib'] ?? ''));
|
||||
$blnTeam = (trim((string)($_REQUEST['type'] ?? '')) === 'no_equipe');
|
||||
|
||||
$intEveId = fxEveAccesResolveEveIdFromParId($intParId);
|
||||
if ($intEveId <= 0 || !fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.bib_edit')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'unauthorized');
|
||||
break;
|
||||
}
|
||||
|
||||
$strPolicy = fxInscrGestionBibDuplicatePolicy($intEveId);
|
||||
|
||||
if ($strNoBib === '') {
|
||||
$tabRetour = array('state' => 'ok', 'duplicate' => false, 'policy' => $strPolicy, 'who' => array());
|
||||
break;
|
||||
}
|
||||
|
||||
// Commande courante a exclure du controle (un participant ne se "double" pas lui-meme).
|
||||
$intPecOrig = intval($objDatabase->fxGetVar(
|
||||
'SELECT e.pec_id_original FROM resultats_epreuves_commandees e, resultats_participants p'
|
||||
. ' WHERE p.par_id = ' . $intParId . ' AND p.pec_id = e.pec_id_original LIMIT 1'
|
||||
));
|
||||
|
||||
$intCurrentEprId = (int)$objDatabase->fxGetVar(
|
||||
'SELECT epr_id FROM resultats_participants WHERE par_id = ' . $intParId . ' LIMIT 1'
|
||||
);
|
||||
|
||||
$arrDup = fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intPecOrig, $blnTeam, $intCurrentEprId, $intParId);
|
||||
$arrWho = array();
|
||||
foreach ($arrDup as $rowDup) {
|
||||
$arrWho[] = array(
|
||||
'name' => fxInscrGestionEsc(fxUnescape($rowDup['par_nom']) . ', ' . fxUnescape($rowDup['par_prenom'])),
|
||||
'epreuve' => fxInscrGestionEsc(fxInscrGestionEpreuveLabel($rowDup['epr_id'], $strLangue)),
|
||||
);
|
||||
}
|
||||
|
||||
$tabRetour = array(
|
||||
'state' => 'ok',
|
||||
'duplicate' => count($arrWho) > 0,
|
||||
'policy' => $strPolicy,
|
||||
'who' => $arrWho,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'transaction_resume':
|
||||
// Resume de la transaction (meme rendu que facture.php cote promoteur) + detail des
|
||||
// remboursements PayPal, affichable a tout moment via le panneau repliable de la barre Gestion.
|
||||
$strNoPanier = trim((string)($_REQUEST['no_panier'] ?? ''));
|
||||
if ($strNoPanier === '') {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'no_panier_manquant');
|
||||
break;
|
||||
}
|
||||
|
||||
// Commande complete (meme source que refund_form / Super Admin) : alimente fxRefundComputeContext.
|
||||
$sqlCmd = "SELECT *, IF(a.com_id <> 0, (SELECT c.com_login FROM inscriptions_comptes c WHERE a.com_id = c.com_id), '') AS usager"
|
||||
. " FROM inscriptions_panier_acheteurs a, inscriptions_panier_statuts s"
|
||||
. " WHERE s.sta_id = a.sta_id AND a.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "'";
|
||||
$recCommandes = $objDatabase->fxGetRow($sqlCmd);
|
||||
if (!$recCommandes) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'commande_introuvable');
|
||||
break;
|
||||
}
|
||||
|
||||
// L'eve_id fait foi cote serveur (jamais le client). Droit granulaire transaction.
|
||||
$intEveId = intval($recCommandes['eve_id']);
|
||||
if ($intEveId <= 0 || !fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.transaction')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'unauthorized');
|
||||
break;
|
||||
}
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/inc_fx_panier.php';
|
||||
$strResume = fxShowPanier($strNoPanier, 0, 'web', 1, $strLangue);
|
||||
|
||||
// Sommaire des remboursements PayPal (vue compacte, pas la grande table Super Admin).
|
||||
// N'apparait que s'il existe une capture PayPal exploitable ; sinon rien (les cas
|
||||
// gratuits/non-PayPal sont deja couverts par la facture ci-dessus).
|
||||
$strRefunds = '';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/superadm/php/inc_fx_paypal.php';
|
||||
$arrRefundCtx = fxRefundComputeContext($recCommandes);
|
||||
if (!empty($arrRefundCtx['ok'])) {
|
||||
// renderPayPalRefundSummaryHtml() renvoie '' s'il n'y a aucun remboursement :
|
||||
// dans ce cas on n'ajoute meme pas le separateur/encadre (rien a montrer).
|
||||
$strSummary = renderPayPalRefundSummaryHtml($arrRefundCtx['paypaldetails']);
|
||||
if (trim($strSummary) !== '') {
|
||||
$strRefunds = '<div style="margin-top:14px;border-top:1px solid #cfe0ff;padding-top:12px;">'
|
||||
. $strSummary
|
||||
. '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Conteneur encadre (sans bascule interne : le bouton de la barre Gestion fait office de toggle).
|
||||
$strHtml =
|
||||
'<div class="ms1-transaction-resume"'
|
||||
. ' style="border:1px solid #2d6cdf;border-radius:10px;background:#f4f7fc;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.12);">'
|
||||
. '<div style="padding:14px;">' . $strResume . $strRefunds . '</div>'
|
||||
. '</div>';
|
||||
|
||||
$tabRetour = array('state' => 'ok', 'html' => $strHtml);
|
||||
break;
|
||||
|
||||
case 'fiche_audit_history':
|
||||
// MSIN-4464 — derniers changements d'un champ whitelist (popup « ! » fiche).
|
||||
require_once 'php/inc_fx_fiche_audit.php';
|
||||
$intParId = intval($_REQUEST['par_id'] ?? 0);
|
||||
$strField = preg_replace('/[^a-z0-9_]/i', '', (string)($_REQUEST['field'] ?? ''));
|
||||
$intEveId = fxEveAccesResolveEveIdFromParId($intParId);
|
||||
|
||||
if ($intParId <= 0 || $strField === '' || $intEveId <= 0
|
||||
|| !fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.audit_view')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'unauthorized');
|
||||
break;
|
||||
}
|
||||
|
||||
$arrItems = fxFicheAuditGetRecentForField($intParId, $strField);
|
||||
$arrOut = array();
|
||||
if (is_array($arrItems)) {
|
||||
foreach ($arrItems as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$arrOut[] = array(
|
||||
'when' => (string)($row['created_at'] ?? ''),
|
||||
'who' => (string)($row['changed_by_label'] ?? ''),
|
||||
'old' => (string)($row['old_value'] ?? ''),
|
||||
'new' => (string)($row['new_value'] ?? ''),
|
||||
'source' => (string)($row['source'] ?? ''),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$tabRetour = array(
|
||||
'state' => 'success',
|
||||
'field' => $strField,
|
||||
'items' => $arrOut,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
$tabRetour = array('state' => 'error', 'message' => 'invalid_action');
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[ajax_inscr_gestion] ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
$tabRetour = array(
|
||||
'state' => 'error',
|
||||
'message' => 'exception',
|
||||
'detail' => $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine(),
|
||||
);
|
||||
}
|
||||
|
||||
$GLOBALS['__inscr_gestion_done'] = true;
|
||||
echo json_encode($tabRetour);
|
||||
49
ajax_inscr_gestion_qr.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
session_start();
|
||||
|
||||
// MSIN-4401 — Traductions toujours au nom de compte.php (jamais ajax_inscr_gestion_qr.php).
|
||||
define('MS1_INSCR_GESTION_AJAX', true);
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once 'php/inc_fonctions.php';
|
||||
require_once 'php/inc_fx_inscriptions_gestion.php';
|
||||
|
||||
global $vDomaine;
|
||||
|
||||
$strLangue = 'fr';
|
||||
if (!empty($_REQUEST['lang']) && in_array($_REQUEST['lang'], array('fr', 'en'), true)) {
|
||||
$strLangue = $_REQUEST['lang'];
|
||||
}
|
||||
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
fxInscrGestionInitTextes($strLangue);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!isset($_SESSION['com_id'])) {
|
||||
// MSIN-4401 — session morte : pas un faux "QR invalide".
|
||||
echo json_encode(array('state' => 'error', 'code' => 'session', 'message' => 'session'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$intEveId = 0;
|
||||
if (!empty($_REQUEST['promoteur_eve_id'])) {
|
||||
$intEveId = fxInscrGestionParseEveId($_REQUEST['promoteur_eve_id']);
|
||||
}
|
||||
|
||||
if ($intEveId <= 0 || !fxInscrGestionCanScan($_SESSION['com_id'], $intEveId)) {
|
||||
echo json_encode(array('state' => 'error', 'message' => fxInscrGestionT('inscr_gestion_qr_invalid')));
|
||||
exit;
|
||||
}
|
||||
|
||||
$strRaw = '';
|
||||
if (!empty($_REQUEST['qr'])) {
|
||||
$strRaw = $_REQUEST['qr'];
|
||||
} elseif (!empty($_REQUEST['t'])) {
|
||||
$strRaw = $_REQUEST['t'];
|
||||
}
|
||||
|
||||
echo json_encode(fxInscrGestionResolveQrScan($strRaw, $intEveId, $strLangue));
|
||||
260
ajax_jira.php
@ -30,7 +30,7 @@ $strAction = $intEvenement = $intNewNoBib = $intEpreuveCommandee = $intEpreuve =
|
||||
//$_POST['typedemande']="test sl";
|
||||
//$_POST['summary']="sujet";
|
||||
|
||||
$typedemande = (int)($_POST['typedemande'] ?? 4);
|
||||
const JIRA_REQUEST_TYPE_SOUTIEN_TECHNIQUE = '4';
|
||||
|
||||
if (isset($_POST['request_type'] )) {
|
||||
$strAction = $_POST['request_type'];
|
||||
@ -40,82 +40,7 @@ $tabRetour = array();
|
||||
$tabRetour['success']=false;
|
||||
switch($strAction) {
|
||||
case 'check_email':
|
||||
$result=jira_recherche_email($_POST['email']);
|
||||
if($result['success']==false){
|
||||
//cree le user
|
||||
$result=jira_creer_user($_POST['email'],$_POST['nom']);
|
||||
if($result['success']==true){
|
||||
$tabRetour['success']=true;
|
||||
$tabRetour['accountId']=$result['statut']['accountId'] ;
|
||||
}else{
|
||||
$tabRetour['success']=false;
|
||||
$tabRetour['accountId']="";
|
||||
};
|
||||
|
||||
}else{
|
||||
//retourne le id
|
||||
$tabRetour['success']=true;
|
||||
$tabRetour['accountId']= $result['jira']['accountId'];
|
||||
}
|
||||
|
||||
if($tabRetour['success']==true){
|
||||
// creer le jira
|
||||
$data['serviceDeskId'] = 2; // Remplacez par l'ID réel de votre Service Desk
|
||||
$data['requestTypeId'] = (string)$typedemande; // Remplacez par l'ID réel du type de demande
|
||||
$data['summary']=$_POST['summary'];
|
||||
$data['description']=$_POST['description'];
|
||||
$data['typedemande']=$_POST['typedemande'];
|
||||
$data['raiseOnBehalfOf']= $_POST['email'] ;
|
||||
|
||||
$tabRetourtemp=jira_create_ticket($data);
|
||||
$tabRetour['success']=true;
|
||||
}else
|
||||
{
|
||||
$tabRetour['success']=false;
|
||||
$tabRetour['accountId']= "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
break;
|
||||
case 'check_emailold':
|
||||
$result=jira_recherche_email($_POST['email']);
|
||||
if($result['success']==false){
|
||||
//cree le user
|
||||
$result=jira_creer_user($_POST['email'],$_POST['nom']);
|
||||
if($result['success']==true){
|
||||
$tabRetour['success']='success';
|
||||
$tabRetour['accountId']=$result['statut']['accountId'] ;
|
||||
}else{
|
||||
$tabRetour['success']=false;
|
||||
$tabRetour['accountId']="";
|
||||
};
|
||||
|
||||
}else{
|
||||
//retourne le id
|
||||
$tabRetour['success']=true;
|
||||
$tabRetour['accountId']= $result['jira']['accountId'];
|
||||
}
|
||||
if($tabRetour['success']==true){
|
||||
// creer le jira
|
||||
$data['serviceDeskId'] = 2; // Remplacez par l'ID réel de votre Service Desk
|
||||
$data['requestTypeId'] = (string)$typedemande; // Remplacez par l'ID réel du type de demande
|
||||
$data['summary']=utf8_encode($_POST['summary']);
|
||||
$data['description']=utf8_encode($_POST['description']);
|
||||
$data['typedemande']=utf8_encode($_POST['typedemande']);
|
||||
$data['raiseOnBehalfOf']= $_POST['email'] ;
|
||||
|
||||
$tabRetourtemp=jira_create_ticket($data);
|
||||
$tabRetour['success']=true;
|
||||
}else
|
||||
{
|
||||
$tabRetour['success']=false;
|
||||
$tabRetour['accountId']= "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
$tabRetour = jira_submit_contact_ticket($_POST);
|
||||
break;
|
||||
case 'no_equipe':
|
||||
case 'no_bib':
|
||||
@ -129,6 +54,149 @@ header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($tabRetour);
|
||||
exit;
|
||||
|
||||
function jira_get_type_demande_row(array $post)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (empty($post['typedemande']) || !isset($objDatabase)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sqlCol = "SELECT info_texte, info_option2, info_option3 FROM info WHERE info_actif = 1 AND info_clef = 'type_de_demande' AND info_option2 = '" . $objDatabase->fxEscape($post['typedemande']) . "' LIMIT 1";
|
||||
$row = $objDatabase->fxGetResults($sqlCol);
|
||||
|
||||
return !empty($row) ? reset($row) : null;
|
||||
}
|
||||
|
||||
function jira_resolve_request_type_id(array $post)
|
||||
{
|
||||
$row = jira_get_type_demande_row($post);
|
||||
if (!empty($row['info_texte']) && fxJiraIsAutreDemande($row['info_texte'])) {
|
||||
return (string)($post['typedemande'] ?? JIRA_REQUEST_TYPE_SOUTIEN_TECHNIQUE);
|
||||
}
|
||||
|
||||
return JIRA_REQUEST_TYPE_SOUTIEN_TECHNIQUE;
|
||||
}
|
||||
|
||||
function jira_resolve_categorie_interne(array $post)
|
||||
{
|
||||
$strCategorie = '';
|
||||
|
||||
if (isset($post['categorie_interne'])) {
|
||||
$strCategorie = trim((string)$post['categorie_interne']);
|
||||
} else {
|
||||
$row = jira_get_type_demande_row($post);
|
||||
if (!empty($row['info_option3'])) {
|
||||
$strCategorie = trim((string)$row['info_option3']);
|
||||
} elseif (!empty($row['info_texte'])) {
|
||||
$strCategorie = fxJiraMapCategorieInterne($row['info_texte']);
|
||||
}
|
||||
}
|
||||
|
||||
// Seul « Vente » est géré pour l'instant ; les autres choix n'envoient pas de catégorie interne.
|
||||
return ($strCategorie === 'Vente') ? 'Vente' : '';
|
||||
}
|
||||
|
||||
function jira_log_contact_error($context, $detail = '')
|
||||
{
|
||||
$line = 'jira contact [' . $context . ']';
|
||||
if ($detail !== '') {
|
||||
$line .= ': ' . (is_string($detail) ? $detail : json_encode($detail, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
error_log($line);
|
||||
}
|
||||
|
||||
function jira_wait_for_account_id($email, $maxAttempts = 4)
|
||||
{
|
||||
for ($i = 0; $i < $maxAttempts; $i++) {
|
||||
if ($i > 0) {
|
||||
usleep(500000);
|
||||
}
|
||||
$result = jira_recherche_email($email);
|
||||
if (!empty($result['success']) && !empty($result['jira']['accountId'])) {
|
||||
return $result['jira']['accountId'];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function jira_submit_contact_ticket(array $post)
|
||||
{
|
||||
$tabRetour = array('success' => false, 'accountId' => '');
|
||||
$email = trim((string)($post['email'] ?? ''));
|
||||
$nom = trim((string)($post['nom'] ?? ''));
|
||||
$userJustCreated = false;
|
||||
|
||||
$result = jira_recherche_email($email);
|
||||
$accountId = !empty($result['success']) ? ($result['jira']['accountId'] ?? '') : '';
|
||||
|
||||
if ($accountId === '') {
|
||||
$result = jira_creer_user($email, $nom);
|
||||
if (empty($result['success'])) {
|
||||
jira_log_contact_error('user_creation', $result['statut'] ?? '');
|
||||
return $tabRetour;
|
||||
}
|
||||
$userJustCreated = true;
|
||||
if (is_array($result['statut'])) {
|
||||
$accountId = $result['statut']['accountId'] ?? '';
|
||||
}
|
||||
if ($accountId === '') {
|
||||
$accountId = jira_wait_for_account_id($email);
|
||||
}
|
||||
}
|
||||
|
||||
$tabRetour['accountId'] = $accountId;
|
||||
|
||||
$data = array(
|
||||
'serviceDeskId' => 2,
|
||||
'requestTypeId' => jira_resolve_request_type_id($post),
|
||||
'summary' => $post['summary'] ?? '',
|
||||
'description' => $post['description'] ?? '',
|
||||
'categorie_interne' => jira_resolve_categorie_interne($post),
|
||||
'raiseOnBehalfOf' => $accountId !== '' ? $accountId : $email,
|
||||
);
|
||||
|
||||
$ticket = jira_create_ticket($data);
|
||||
if (empty($ticket['success']) && ($userJustCreated || $accountId === '')) {
|
||||
$accountId = jira_wait_for_account_id($email, 3);
|
||||
if ($accountId !== '') {
|
||||
$tabRetour['accountId'] = $accountId;
|
||||
$data['raiseOnBehalfOf'] = $accountId;
|
||||
$ticket = jira_create_ticket($data);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ticket['success'])) {
|
||||
jira_log_contact_error('ticket_creation', $ticket['statut'] ?? '');
|
||||
return $tabRetour;
|
||||
}
|
||||
|
||||
$categorieInterne = $data['categorie_interne'] ?? '';
|
||||
if ($categorieInterne === '') {
|
||||
$tabRetour['success'] = true;
|
||||
return $tabRetour;
|
||||
}
|
||||
|
||||
$issueKey = '';
|
||||
if (is_array($ticket['statut']) && !empty($ticket['statut']['issueKey'])) {
|
||||
$issueKey = $ticket['statut']['issueKey'];
|
||||
}
|
||||
if ($issueKey === '') {
|
||||
jira_log_contact_error('categorie_interne', 'issueKey manquant après création');
|
||||
return $tabRetour;
|
||||
}
|
||||
|
||||
$update = jira_set_categorie_interne($issueKey, $categorieInterne);
|
||||
if (empty($update['success'])) {
|
||||
jira_log_contact_error('categorie_interne', $update['statut'] ?? '');
|
||||
return $tabRetour;
|
||||
}
|
||||
|
||||
$tabRetour['success'] = true;
|
||||
return $tabRetour;
|
||||
}
|
||||
|
||||
function jira_recherche_email($param)
|
||||
{
|
||||
// recherche user
|
||||
@ -263,6 +331,43 @@ function jira_creer_user($email,$displayName)
|
||||
return($sortie);
|
||||
}
|
||||
|
||||
function jira_set_categorie_interne($issueKey, $categorieInterne)
|
||||
{
|
||||
$jiraDomain = 'https://progiwebinc.atlassian.net';
|
||||
$userEmail = 'stephan@progiweb.ca';
|
||||
$apiToken = 'ATATT3xFfGF0Z91qESp8j7uO783qK0E4BWsY8rhPWEmexSa4Rw3c21Spg6hYIkmVgy6Vwzb34RxqFl3sR00Kby_DuY1NdcUJyfPQYZ0_JbeEjAk9ybFgNY8-MBSABMCxzaDk4szTbWupfjxHT8sJxJMvSNxgHPds5FxXaGo2DnZICZu5dXuvWbk=F73E15AE';
|
||||
|
||||
$payload = json_encode(array(
|
||||
'fields' => array(
|
||||
'customfield_11179' => array(
|
||||
'value' => $categorieInterne,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$ch = curl_init($jiraDomain . '/rest/api/3/issue/' . rawurlencode($issueKey));
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||
'Authorization: Basic ' . base64_encode($userEmail . ':' . $apiToken),
|
||||
'Content-Type: application/json',
|
||||
));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$sortie = array('success' => false, 'statut' => '');
|
||||
if ($httpcode === 204) {
|
||||
$sortie['success'] = true;
|
||||
return $sortie;
|
||||
}
|
||||
|
||||
$sortie['statut'] = ($response !== false && $response !== '') ? $response : ('HTTP ' . $httpcode);
|
||||
return $sortie;
|
||||
}
|
||||
|
||||
function jira_create_ticket( $param)
|
||||
{
|
||||
|
||||
@ -278,7 +383,6 @@ function jira_create_ticket( $param)
|
||||
'requestFieldValues' => [
|
||||
'summary' => $param['summary'],
|
||||
'description' => $param['description'],
|
||||
'customfield_11143' => $param['typedemande'],
|
||||
],
|
||||
'raiseOnBehalfOf' => $param['raiseOnBehalfOf'], // Remplacez par l'accountId du client (facultatif)
|
||||
] ;
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// MSIN-4468 — Les libellés AJAX appartiennent toujours au contexte compte.php.
|
||||
define('MS1_PROMOTEUR_AJAX', true);
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
$vPage = 'compte.php';
|
||||
|
||||
require_once "php/inc_fonctions.php";
|
||||
include_once("php/inc_fx_modifierinscriptions.php");
|
||||
require_once "php/inc_fx_promoteur.php";
|
||||
require_once "php/inc_fx_messages.php";
|
||||
require_once "php/inc_fx_eve_acces.php";
|
||||
|
||||
global $vDomaine;
|
||||
$strAction = $intEvenement = $intNewNoBib = $intEpreuveCommandee = $intEpreuve = $intPartipcipant = $intQuantite = $strLangue = '';
|
||||
$strAction = $intEvenement = $intNewNoBib = $intEpreuveCommandee = $intEpreuve = $intPartipcipant = $intQuantite = $strStatutCourse = '';
|
||||
$strLangue = $_SESSION['lang'] ?? 'fr';
|
||||
$strQuantiteTotale = '';
|
||||
|
||||
if (isset($_REQUEST['a'])) {
|
||||
$strAction = $_REQUEST['a'];
|
||||
@ -30,23 +39,72 @@ if (isset($_REQUEST['par_id'])) {
|
||||
if (isset($_REQUEST['qte'])) {
|
||||
$intQuantite = $_REQUEST['qte'];
|
||||
}
|
||||
if (isset($_REQUEST['lang'])) {
|
||||
if (isset($_REQUEST['total'])) {
|
||||
$strQuantiteTotale = trim((string)$_REQUEST['total']);
|
||||
}
|
||||
if (isset($_REQUEST['lang']) && in_array($_REQUEST['lang'], array('fr', 'en'), true)) {
|
||||
$strLangue = $_REQUEST['lang'];
|
||||
}
|
||||
if (isset($_REQUEST['par_statut_course'])) {
|
||||
$strStatutCourse = $_REQUEST['par_statut_course'];
|
||||
}
|
||||
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
|
||||
$tabRetour = array();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
switch($strAction) {
|
||||
case 'qte':
|
||||
$tabRetour = fxUpdateQuantites($intEpreuve, $intQuantite, $strLangue);
|
||||
break;
|
||||
case 'qte_total':
|
||||
// MSIN-4468 — Nouvelle convention V4; l'action legacy qte demeure inchangée.
|
||||
if (!fxBibRequirePromoteurSession()) {
|
||||
$tabRetour = array('state' => 'error', 'message' => fxBibMsg('bib_v4_ajax_session_invalid'));
|
||||
break;
|
||||
}
|
||||
if (!fxBibValidateCsrf($_POST['csrf_token'] ?? '')) {
|
||||
$tabRetour = array('state' => 'error', 'message' => fxBibMsg('bib_v4_ajax_unauthorized'));
|
||||
break;
|
||||
}
|
||||
|
||||
$intEveId = fxBibResolveEveIdFromRequest();
|
||||
$blnCanEditQte = !empty($_SESSION['usa_id'])
|
||||
|| (!empty($_SESSION['com_id'])
|
||||
&& $intEveId > 0
|
||||
&& fxEveAccesHasPermission($_SESSION['com_id'], $intEveId, 'epreuves.edit_qte'));
|
||||
if (!$blnCanEditQte) {
|
||||
$tabRetour = array('state' => 'error', 'message' => fxBibMsg('bib_v4_ajax_unauthorized'));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($strQuantiteTotale === ''
|
||||
|| strlen($strQuantiteTotale) > 7
|
||||
|| !ctype_digit($strQuantiteTotale)) {
|
||||
$tabRetour = array('state' => 'error', 'message' => afficheTexte('promoteur_bad_qte_update', 0));
|
||||
break;
|
||||
}
|
||||
|
||||
$tabRetour = fxUpdateQuantiteTotale($intEpreuve, intval($strQuantiteTotale));
|
||||
if (($tabRetour['state'] ?? 'error') !== 'success') {
|
||||
if (($tabRetour['error_code'] ?? '') === 'total_below_registrations') {
|
||||
$tabRetour['message'] = fxBibMsg(
|
||||
'bib_v5_qte_total_min',
|
||||
intval($tabRetour['qte_inscrits'] ?? 0)
|
||||
);
|
||||
} else {
|
||||
$tabRetour['message'] = afficheTexte('promoteur_bad_qte_update', 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'no_equipe':
|
||||
case 'no_bib':
|
||||
$tabRetour = fxSetBibManuel($strAction, $intEvenement, $intNewNoBib, $intEpreuveCommandee, $intEpreuve, $intPartipcipant, $strLangue);
|
||||
break;
|
||||
case 'par_statut_course':
|
||||
$tabRetour = fxSetParStatutCourse($intPartipcipant, $strStatutCourse, $strLangue);
|
||||
break;
|
||||
case 'teammates':
|
||||
$tabRetour = fxShowTeammates($intEvenement, $intEpreuve, $intEpreuveCommandee);
|
||||
break;
|
||||
|
||||
71
ajax_promoteur_hub.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
session_start();
|
||||
|
||||
// Traductions toujours au nom de compte.php (jamais ajax_promoteur_hub.php).
|
||||
define('MS1_PROMOTEUR_HUB_AJAX', true);
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
require_once __DIR__ . '/php/inc_fx_promoteur_hub.php';
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
$strAction = $_REQUEST['a'] ?? '';
|
||||
$strLangue = $_REQUEST['lang'] ?? ($_SESSION['lang'] ?? 'fr');
|
||||
if (!in_array($strLangue, array('fr', 'en'), true)) {
|
||||
$strLangue = 'fr';
|
||||
}
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
$intEveId = intval($_REQUEST['eve_id'] ?? 0);
|
||||
|
||||
if (empty($_SESSION['com_id'])) {
|
||||
// MSIN-4401 — Le JS doit renvoyer au login, pas afficher un message mort.
|
||||
echo json_encode(array(
|
||||
'ok' => false,
|
||||
'code' => 'session',
|
||||
'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_session', 0),
|
||||
));
|
||||
exit;
|
||||
}
|
||||
|
||||
$intComId = intval($_SESSION['com_id']);
|
||||
|
||||
if (!in_array($strAction, array('event_stats', 'event_links'), true) || $intEveId <= 0) {
|
||||
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_invalid', 0)));
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!fxPromoteurHubComHasEvent($intComId, $intEveId)) {
|
||||
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_denied', 0)));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($strAction === 'event_stats') {
|
||||
if (!fxPromoteurHubCanViewDashboard($intComId, $intEveId)) {
|
||||
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_denied', 0)));
|
||||
exit;
|
||||
}
|
||||
|
||||
$arrBundle = fxPromoteurHubRenderEventStatsBundle($intEveId, $strLangue);
|
||||
|
||||
echo json_encode(array(
|
||||
'ok' => true,
|
||||
'flash' => $arrBundle['flash'],
|
||||
'html' => $arrBundle['html'],
|
||||
));
|
||||
exit;
|
||||
}
|
||||
|
||||
$arrItem = fxPromoteurHubGetEventItemForLinks($intComId, $intEveId);
|
||||
if ($arrItem === null) {
|
||||
echo json_encode(array('ok' => false, 'msg' => fxPromoteurHubTexte('promoteur_hub_ajax_not_found', 0)));
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(array(
|
||||
'ok' => true,
|
||||
'html' => fxPromoteurHubRenderEventPanelContent($arrItem, $strLangue),
|
||||
));
|
||||
1
assets/dossard_templates/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
||||
BIN
assets/dossard_templates/default.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
27
assets/dossard_templates/default.layout.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": 2,
|
||||
"page": "8.5x5.5",
|
||||
"boxes": [
|
||||
{
|
||||
"id": "bib",
|
||||
"field": "no_bib",
|
||||
"align": "C",
|
||||
"font_max": 220,
|
||||
"left": 0.2,
|
||||
"right": 0.78,
|
||||
"top": 0.36,
|
||||
"bottom": 0.66
|
||||
},
|
||||
{
|
||||
"id": "prenom",
|
||||
"field": "par_prenom",
|
||||
"align": "L",
|
||||
"font_max": 28,
|
||||
"left": 0.32,
|
||||
"right": 0.68,
|
||||
"top": 0.72,
|
||||
"bottom": 0.84
|
||||
}
|
||||
],
|
||||
"updated_at": "2026-07-25T00:00:00+00:00"
|
||||
}
|
||||
BIN
assets/dossard_templates/mamie-joe-papi-fan.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
50
auto_chronotrack_sync.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4328 — Cron sync auto ChronoTrack (différentiel).
|
||||
*
|
||||
* Même pattern que auto_liste_attente.php :
|
||||
* /usr/bin/curl -s https://VOTRE-DOMAINE/auto_chronotrack_sync.php > /dev/null 2>&1
|
||||
*
|
||||
* Minute=* Hour=* … → le script décide selon fréquence/fin de chaque event.
|
||||
*/
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/log/php_log.txt');
|
||||
|
||||
@set_time_limit(300);
|
||||
|
||||
$strLockPath = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'ms1_chronotrack_auto_sync.lock';
|
||||
$fpLock = @fopen($strLockPath, 'c');
|
||||
if ($fpLock === false || !flock($fpLock, LOCK_EX | LOCK_NB)) {
|
||||
if (is_resource($fpLock)) {
|
||||
fclose($fpLock);
|
||||
}
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'busy';
|
||||
exit;
|
||||
}
|
||||
|
||||
session_start();
|
||||
|
||||
try {
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
require_once __DIR__ . '/php/chronotrack_api/inc_bootstrap.php';
|
||||
|
||||
$arrResult = fxChronotrackApiSyncRunAutoCron();
|
||||
$strLine = 'auto_chronotrack_sync [' . date('Y-m-d H:i:s') . '] '
|
||||
. ($arrResult['message'] ?? 'done');
|
||||
// Même style que liste d’attente : une ligne dans log/php_log.txt
|
||||
error_log($strLine);
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $strLine . "\n";
|
||||
} catch (Throwable $ex) {
|
||||
$strErr = 'auto_chronotrack_sync ERROR [' . date('Y-m-d H:i:s') . '] ' . $ex->getMessage();
|
||||
error_log($strErr);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $strErr . "\n";
|
||||
}
|
||||
|
||||
flock($fpLock, LOCK_UN);
|
||||
fclose($fpLock);
|
||||
46
bib_dist_print.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// MSIN-4433 — Génération PDF distribution des dossards (POST + session promoteur).
|
||||
|
||||
session_start();
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once('php/inc_fonctions.php');
|
||||
require_once('php/inc_fx_promoteur.php');
|
||||
|
||||
$strLangue = $_SESSION['lang'] ?? 'fr';
|
||||
$reqLang = $_REQUEST['lang'] ?? $_REQUEST['lng'] ?? '';
|
||||
if (!empty($reqLang) && in_array($reqLang, ['fr', 'en'], true)) {
|
||||
$strLangue = $reqLang;
|
||||
}
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit;
|
||||
}
|
||||
|
||||
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
|
||||
$_REQUEST['eve_id'] = $int_eve_id;
|
||||
$_POST['eve_id'] = $int_eve_id;
|
||||
|
||||
fxBibRequireDistPrintAccess();
|
||||
|
||||
$tabSelections = fxBibParseDistPrintSelectionsFromPost($_POST);
|
||||
$tabSave = fxBibSaveDistPrintSelections($int_eve_id, $tabSelections, $strLangue);
|
||||
|
||||
if (!$tabSave['success']) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $tabSave['message'] ?? 'Invalid selection';
|
||||
exit;
|
||||
}
|
||||
|
||||
$strPrintedBy = fxBibGetCurrentPromoteurDisplayName();
|
||||
$intComId = (int)($_SESSION['com_id'] ?? 0);
|
||||
fxBibSaveDistPrintMeta($int_eve_id, $intComId, $strPrintedBy);
|
||||
|
||||
fxBibOutputDistPrintPdf($int_eve_id, $strLangue, $strPrintedBy);
|
||||
exit;
|
||||
83
bib_production_excel.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
// MSIN-4469 — Génération et retéléchargement du fichier officiel de production.
|
||||
|
||||
session_start();
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once('php/inc_fonctions.php');
|
||||
require_once('php/inc_fx_promoteur.php');
|
||||
require_once('php/inc_fx_eve_acces.php');
|
||||
|
||||
$strLangue = $_SESSION['lang'] ?? 'fr';
|
||||
$strReqLang = $_REQUEST['lang'] ?? '';
|
||||
if (in_array($strReqLang, ['fr', 'en'], true)) {
|
||||
$strLangue = $strReqLang;
|
||||
}
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit;
|
||||
}
|
||||
|
||||
$intEveId = (int)($_POST['eve_id'] ?? 0);
|
||||
$strAction = (string)($_POST['action'] ?? '');
|
||||
|
||||
fxBibRequireDistPrintAccess();
|
||||
if (!fxBibProductionUserCanManageEvent($intEveId)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo fxBibMsg('bib_v4_ajax_unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($strAction === 'generate') {
|
||||
$tabResult = fxBibProductionCreateOfficialOrder($intEveId, $strLangue);
|
||||
if (empty($tabResult['success']) || empty($tabResult['order'])) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $tabResult['message'] ?? fxBibMsg('bib_v5_production_generation_error');
|
||||
exit;
|
||||
}
|
||||
$tabOrder = $tabResult['order'];
|
||||
} elseif ($strAction === 'download') {
|
||||
$intOrderId = (int)($_POST['order_id'] ?? 0);
|
||||
$tabOrder = fxBibProductionGetOrder($intOrderId, $intEveId);
|
||||
if (!$tabOrder || (int)($tabOrder['bpo_active_key'] ?? 0) !== 1) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo fxBibMsg('bib_v5_production_file_missing');
|
||||
exit;
|
||||
}
|
||||
} elseif ($strAction === 'development_unlock') {
|
||||
$tabResult = fxBibProductionDevelopmentUnlock($intEveId);
|
||||
if (empty($tabResult['success'])) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $tabResult['message'] ?? fxBibMsg('bib_v4_ajax_unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
$strAccountPage = $strLangue === 'en' ? '/account' : '/compte';
|
||||
header(
|
||||
'Location: ' . $strAccountPage
|
||||
. '/inc_tableau_gestion_epreuves?promoteur_eve_id='
|
||||
. urlencode(base64_encode($intEveId))
|
||||
);
|
||||
exit;
|
||||
} else {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!fxBibProductionStreamOrder($tabOrder)) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo fxBibMsg('bib_v5_production_file_missing');
|
||||
exit;
|
||||
}
|
||||
|
||||
exit;
|
||||
179
compte.php
@ -21,6 +21,10 @@ require_once('php/inc_logs_course.php');
|
||||
require_once('superadm/php/inc_fx_excell.php');
|
||||
|
||||
require_once('php/inc_fx_liste_attente.php');
|
||||
require_once('php/inc_fx_inscriptions_gestion.php');
|
||||
require_once('php/inc_v2_assets.php');
|
||||
require_once('php/inc_fx_eve_acces.php');
|
||||
require_once('php/inc_fx_promoteur_hub.php');
|
||||
global $objDatabase, $vPage, $vDomaine;
|
||||
// vérifier les paramètres
|
||||
|
||||
@ -51,11 +55,34 @@ else
|
||||
if (!empty($_GET['code']))
|
||||
$strCode = 'compte_' . $_GET['code'];
|
||||
else {
|
||||
if (isset($_SESSION['com_id']))
|
||||
$strCode = "compte_accueil";
|
||||
else
|
||||
$strCode = "compte_login";
|
||||
if (isset($_SESSION['com_id'])) {
|
||||
// Compte v2 : atterrissage hub (Mes événements). Sinon accueil participant.
|
||||
if (fxPromoteurHubShouldUse($_SESSION['com_id'])) {
|
||||
$strCode = 'compte_promoteur_hub';
|
||||
} else {
|
||||
$strCode = 'compte_accueil';
|
||||
}
|
||||
} else {
|
||||
$strCode = 'compte_login';
|
||||
}
|
||||
}
|
||||
|
||||
if ($strCode === 'compte_promoteur_hub' && (!isset($_SESSION['com_id']) || !fxPromoteurHubIsAvailable($_SESSION['com_id']))) {
|
||||
header('Location: ' . $vDomaine . '/' . ($strLangue === 'fr' ? 'compte' : 'account'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ancien slug renomme (retrait du nom "mobile") : redirection 301 vers le nouveau slug "gestion".
|
||||
// Conserve les parametres utiles (promoteur_eve_id, pec_id, lng...) pour ne pas casser favoris/QR.
|
||||
if (($_GET['code'] ?? '') === 'inc_tableau_inscriptions_mobile') {
|
||||
$strNewPath = ($strLangue === 'en') ? '/account/inc_tableau_inscriptions_gestion' : '/compte/inc_tableau_inscriptions_gestion';
|
||||
parse_str($_SERVER['QUERY_STRING'] ?? '', $arrOldQs);
|
||||
unset($arrOldQs['code'], $arrOldQs['lang']);
|
||||
$strQsClean = http_build_query($arrOldQs);
|
||||
header('Location: ' . $vDomaine . $strNewPath . ($strQsClean !== '' ? '?' . $strQsClean : ''), true, 301);
|
||||
exit;
|
||||
}
|
||||
|
||||
$recPage = fxGetPage($strCode);
|
||||
|
||||
if (count($recPage) < 1) {
|
||||
@ -72,16 +99,17 @@ if (!empty($_GET['a']) && $_GET['code'] != 'bib_assignment') {
|
||||
$strCode = $_GET['a'];
|
||||
}
|
||||
|
||||
// empêcher d'accéder à une page où il faut être loggé et qu'on ne l'est pas
|
||||
if (($strCode != "compte_login" && $strCode != "compte_motdepasse" && $strCode != "compte_nouveau" && $strCode != "compte_reset" && !isset($_SESSION['com_id'])) || (($strCode == "compte_login" || $strCode == "compte_motdepasse" || $strCode == "compte_nouveau" || $strCode == "compte_reset") && isset($_SESSION['com_id']))) {
|
||||
|
||||
|
||||
|
||||
/// MSIN-4339 Sauvegarder l'URL demandée
|
||||
// Page protégée sans session : sauver la cible puis formulaire de login (MSIN-4339).
|
||||
if ($strCode != 'compte_login' && $strCode != 'compte_motdepasse' && $strCode != 'compte_nouveau' && $strCode != 'compte_reset' && !isset($_SESSION['com_id'])) {
|
||||
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
|
||||
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Déjà connecté sur login / mot de passe / inscription : accueil compte, sans polluer redirect_after_login.
|
||||
// MSIN-4401 — compte_reset exclu : géré dans fxShowFormReset (conflit autre session).
|
||||
if (($strCode == 'compte_login' || $strCode == 'compte_motdepasse' || $strCode == 'compte_nouveau') && isset($_SESSION['com_id'])) {
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -226,7 +254,6 @@ if (!empty($_POST['form_action'])) {
|
||||
default:
|
||||
switch ($strAction) {
|
||||
case 'login':
|
||||
|
||||
fxLoginCompte($_POST, $strLangue);
|
||||
|
||||
|
||||
@ -308,10 +335,26 @@ if (isset($_GET['promoteur_eve_id'])) {
|
||||
|
||||
$intEveId = base64_decode(urldecode($_GET['promoteur_eve_id']));
|
||||
$intEveIdbase = $intEveId;
|
||||
$blnIsPromoteur = fxIsPromoteur( $_SESSION['com_id'],$intEveIdbase );
|
||||
|
||||
|
||||
if (!$blnIsPromoteur) {
|
||||
if ($strCode === 'compte_inc_tableau_inscriptions_gestion') {
|
||||
if (!fxInscrGestionCanAccess($_SESSION['com_id'], $intEveIdbase)) {
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
} elseif ($strCode === 'compte_inc_tableau_gestion_epreuves') {
|
||||
if (empty($_SESSION['usa_id'])
|
||||
&& !fxEveAccesHasAnyPermission($_SESSION['com_id'] ?? 0, $intEveIdbase, array('dossards.manage', 'epreuves.edit_qte'))) {
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
} elseif ($strCode === 'compte_inc_tableau_eve_acces') {
|
||||
require_once('php/inc_fx_eve_acces.php');
|
||||
if (!fxEveAccesCanTeamInvite($_SESSION['com_id'] ?? 0, $intEveIdbase)) {
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
} elseif (empty($_SESSION['usa_id'])
|
||||
&& !fxIsPromoteur($_SESSION['com_id'], $intEveIdbase)
|
||||
&& !fxEveAccesComHasV2ForEvent($_SESSION['com_id'], $intEveIdbase)) {
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
}
|
||||
@ -334,6 +377,69 @@ $strMetaTitle = fxRemoveHtml(fxUnescape($recPage['pag_titre_' . $strLangue]));
|
||||
$strMetaDescription = fxRemoveHtml(fxUnescape($recPage['pag_description_' . $strLangue]));
|
||||
$strMetaKeywords = fxRemoveHtml(fxUnescape($recPage['pag_keywords_' . $strLangue]));
|
||||
$blnBoutonRetour = true;
|
||||
$blnHideCompteTitle = false;
|
||||
|
||||
// MSIN-4401 — POST invite/revoke AVANT le HTML (sinon header Location → page blanche).
|
||||
if ($strCode === 'compte_inc_tableau_eve_acces'
|
||||
&& ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'
|
||||
&& isset($_GET['promoteur_eve_id'])) {
|
||||
require_once('php/inc_fx_eve_acces_promoteur.php');
|
||||
$intEveIdPost = intval(base64_decode(urldecode($_GET['promoteur_eve_id'])));
|
||||
$intActorPost = intval($_SESSION['com_id'] ?? 0);
|
||||
if (!empty($_SESSION['usa_id'])) {
|
||||
$intActorPost = intval($_SESSION['usa_id']);
|
||||
}
|
||||
$strLanguePost = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
$strBasePathPost = ($strLanguePost === 'en') ? '/account' : '/compte';
|
||||
$strSelfUrlPost = $vDomaine . $strBasePathPost . '/inc_tableau_eve_acces?promoteur_eve_id='
|
||||
. urlencode(base64_encode($intEveIdPost)) . '&lng=' . $strLanguePost;
|
||||
|
||||
if ($intEveIdPost > 0 && fxEveAccesCanTeamInvite($intActorPost, $intEveIdPost)) {
|
||||
$strEveAccesAction = (string)($_POST['eve_acces_action'] ?? '');
|
||||
if ($strEveAccesAction === 'invite') {
|
||||
$arrEveAccesResult = fxEveAccesPromoteurInvite($intEveIdPost, $intActorPost, $_POST, $strLanguePost);
|
||||
if (($arrEveAccesResult['state'] ?? '') === 'success') {
|
||||
header('Location: ' . $strSelfUrlPost . '&ok=' . urlencode($arrEveAccesResult['message']));
|
||||
exit;
|
||||
}
|
||||
header('Location: ' . $strSelfUrlPost . '&err=' . urlencode(
|
||||
$arrEveAccesResult['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_err_generic', $strLanguePost)
|
||||
));
|
||||
exit;
|
||||
}
|
||||
if ($strEveAccesAction === 'revoke') {
|
||||
$arrEveAccesResult = fxEveAccesPromoteurRevoke(intval($_POST['ea_id'] ?? 0), $intActorPost, $intEveIdPost);
|
||||
if (($arrEveAccesResult['state'] ?? '') === 'success') {
|
||||
header('Location: ' . $strSelfUrlPost . '&ok=' . urlencode(
|
||||
fxEveAccesPromoteurT('eve_acces_promoteur_ok_revoked', $strLanguePost)
|
||||
));
|
||||
exit;
|
||||
}
|
||||
header('Location: ' . $strSelfUrlPost . '&err=' . urlencode(
|
||||
fxEveAccesPromoteurT('eve_acces_promoteur_err_revoke', $strLanguePost)
|
||||
));
|
||||
exit;
|
||||
}
|
||||
if ($strEveAccesAction === 'resend') {
|
||||
$arrEveAccesResult = fxEveAccesPromoteurResendMail(
|
||||
intval($_POST['ea_id'] ?? 0),
|
||||
$intActorPost,
|
||||
$intEveIdPost,
|
||||
$strLanguePost
|
||||
);
|
||||
if (($arrEveAccesResult['state'] ?? '') === 'success') {
|
||||
header('Location: ' . $strSelfUrlPost . '&ok=' . urlencode(
|
||||
$arrEveAccesResult['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_ok_resent', $strLanguePost)
|
||||
));
|
||||
exit;
|
||||
}
|
||||
header('Location: ' . $strSelfUrlPost . '&err=' . urlencode(
|
||||
$arrEveAccesResult['message'] ?? fxEveAccesPromoteurT('eve_acces_promoteur_err_resend', $strLanguePost)
|
||||
));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once("inc_header.php");
|
||||
|
||||
@ -343,7 +449,7 @@ require_once("inc_header.php");
|
||||
<div class="container bg-white p-3 p-xl-5">
|
||||
|
||||
<?php
|
||||
if (isset($recPage['pag_titre_' . $strLangue]) != '') {
|
||||
if (isset($recPage['pag_titre_' . $strLangue]) != '' && empty($blnHideCompteTitle)) {
|
||||
?>
|
||||
|
||||
|
||||
@ -360,7 +466,7 @@ require_once("inc_header.php");
|
||||
}
|
||||
if (isset($_SESSION['msg'])) {
|
||||
echo fxMessage($_SESSION['msg'], '', $strLangue);
|
||||
} else {
|
||||
} else if (empty($blnHideCompteTitle)) {
|
||||
?>
|
||||
<br>
|
||||
<?php
|
||||
@ -465,9 +571,13 @@ require_once("inc_header.php");
|
||||
case 'compte_annuler_inscription':
|
||||
include_once('annuler_inscription.php');
|
||||
break;
|
||||
case 'compte_promoteur_hub':
|
||||
$blnBoutonRetour = false;
|
||||
echo fxShowPromoteurHub($_SESSION['com_id'], $strLangue);
|
||||
break;
|
||||
case 'compte_accueil':
|
||||
//MSIN-4371
|
||||
if (isset($_SESSION['usa_id'])) { ?>
|
||||
if (fxEveAccesCanQrTest($_SESSION['com_id'])) { ?>
|
||||
|
||||
<a href="qr_test.php" target="_blank" class="btn btn-primary">
|
||||
QR Test
|
||||
@ -688,19 +798,18 @@ require_once("inc_header.php");
|
||||
$footer_script = true;
|
||||
include("inc_tableau_rabais.php");
|
||||
break;
|
||||
// MSIN-3679
|
||||
case 'compte_inc_tableau_remboursement':
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
|
||||
include("inc_tableau_remboursement.php");
|
||||
break;
|
||||
|
||||
case 'compte_inc_tableau_inscriptions':
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
include("inc_tableau_inscriptions.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_inscriptions_gestion':
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
$blnHideCompteTitle = true;
|
||||
fxInscrGestionInitTextes($strLangue);
|
||||
include("inc_tableau_inscriptions_gestion.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_mail_entraineurs':
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
@ -713,14 +822,24 @@ require_once("inc_header.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_dossards':
|
||||
$blnBoutonRetour = false;
|
||||
// include("inc_tableau_dossards.php");
|
||||
// assigmation v2 MSIN-3821
|
||||
include("inc_tableau_dossards_v2.php");
|
||||
|
||||
include("inc_tableau_dossards.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_epreuves':
|
||||
$blnBoutonRetour = false;
|
||||
include("inc_tableau_epreuves.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_gestion_epreuves':
|
||||
$blnBoutonRetour = false;
|
||||
include("inc_tableau_gestion_epreuves.php");
|
||||
break;
|
||||
case 'compte_inc_tableau_eve_acces':
|
||||
// MSIN-4401
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
$blnHideCompteTitle = true;
|
||||
include("inc_tableau_eve_acces.php");
|
||||
break;
|
||||
|
||||
case 'compte_inc_tableau_liste_attente':
|
||||
|
||||
|
||||
324
css/ms1-doc.css
Normal file
@ -0,0 +1,324 @@
|
||||
body.ms1-doc-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* MS1 — Module documentation (overlay paginé).
|
||||
* Le bouton ? réutilise .btn-aide-bib (style.css) — pas de style rond dupliqué ici.
|
||||
*/
|
||||
|
||||
.btn-aide-bib.ms1-doc-trigger {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Overlay plein écran */
|
||||
.ms1-doc-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10050;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ms1-doc-overlay[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ms1-doc-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Panneau central — plus large pour le guide */
|
||||
.ms1-doc-panel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-width: 840px;
|
||||
max-height: 92vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ms1-doc-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.ms1-doc-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ms1-doc-title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.ms1-doc-subtitle {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.ms1-doc-close {
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: #6c757d;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ms1-doc-close:hover {
|
||||
background: #e9ecef;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.ms1-doc-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 20px 22px;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
.ms1-doc-page[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Typographie contenu HTML admin */
|
||||
.ms1-doc-format,
|
||||
.ms1-doc-content {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.ms1-doc-format h2,
|
||||
.ms1-doc-format h3,
|
||||
.ms1-doc-content h2,
|
||||
.ms1-doc-content h3 {
|
||||
margin: 14px 0 8px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.ms1-doc-format h2:first-child,
|
||||
.ms1-doc-format h3:first-child,
|
||||
.ms1-doc-content h2:first-child,
|
||||
.ms1-doc-content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ms1-doc-format p,
|
||||
.ms1-doc-content p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.ms1-doc-format ol,
|
||||
.ms1-doc-format ul,
|
||||
.ms1-doc-content ol,
|
||||
.ms1-doc-content ul {
|
||||
margin: 0 0 10px;
|
||||
padding-left: 1.35rem;
|
||||
}
|
||||
|
||||
.ms1-doc-format li,
|
||||
.ms1-doc-content li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.ms1-doc-format blockquote,
|
||||
.ms1-doc-content blockquote {
|
||||
margin: 12px 0;
|
||||
padding: 10px 12px;
|
||||
border-left: 3px solid #007bff;
|
||||
background: #f8f9fa;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.ms1-doc-format table,
|
||||
.ms1-doc-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.ms1-doc-format th,
|
||||
.ms1-doc-format td,
|
||||
.ms1-doc-content th,
|
||||
.ms1-doc-content td {
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ms1-doc-format th,
|
||||
.ms1-doc-content th {
|
||||
background: #f1f3f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ms1-doc-format strong,
|
||||
.ms1-doc-content strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Bloc pédagogique par bouton / contrôle */
|
||||
.ms1-doc-bouton {
|
||||
margin: 16px 0;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.ms1-doc-bouton h3,
|
||||
.ms1-doc-bouton h4 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
color: #2f5fd0;
|
||||
}
|
||||
|
||||
.ms1-doc-bouton dl {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ms1-doc-bouton dt {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ms1-doc-bouton dt:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ms1-doc-bouton dd {
|
||||
margin: 2px 0 0;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Pied — navigation */
|
||||
.ms1-doc-footer {
|
||||
padding: 10px 14px 12px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.ms1-doc-nav-dots {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ms1-doc-dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #495057;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ms1-doc-dot.is-active,
|
||||
.ms1-doc-dot:hover {
|
||||
border-color: #007bff;
|
||||
background: #e7f1ff;
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.ms1-doc-nav-arrows {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ms1-doc-prev,
|
||||
.ms1-doc-next {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #6c757d;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
color: #495057;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ms1-doc-prev:hover:not(:disabled),
|
||||
.ms1-doc-next:hover:not(:disabled) {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.ms1-doc-prev:disabled,
|
||||
.ms1-doc-next:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ms1-doc-counter {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Métadonnées JSON — jamais affichées */
|
||||
script.ms1-doc-meta {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.ms1-doc-overlay {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ms1-doc-panel {
|
||||
max-height: 95vh;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ms1-doc-dot {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
100
css/ms1-loader.css
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* MS1 — Indicateur de chargement (coureur animé).
|
||||
* Reutilisable en overlay plein ecran (#preloader) ou inline (panneaux AJAX).
|
||||
*/
|
||||
.ms1-loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ms1-loader__runner {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ms1-loader__label {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.ms1-loader--inline {
|
||||
padding: 1.25rem 1rem;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.ms1-loader--inline .ms1-loader__runner {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.ms1-loader--inline .ms1-loader__label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#preloader #spinner.ms1-loader--overlay {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
||||
padding: 1.25rem 1.75rem;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
width: auto;
|
||||
min-width: 200px;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#preloader #spinner.ms1-loader--overlay .ms1-loader__runner {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#preloader #spinner.ms1-loader--overlay #preloader_text,
|
||||
#preloader #spinner.ms1-loader--overlay .ms1-loader__label {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ms1-loader__progress {
|
||||
width: 100%;
|
||||
min-width: 220px;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.ms1-loader__progress-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #e8e8e8;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ms1-loader__progress-fill {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background: linear-gradient(90deg, #2e7d32, #43a047);
|
||||
border-radius: 4px;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.ms1-loader__progress-pct {
|
||||
display: block;
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
5226
css/style.css
49
dossard_print.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4485 — Endpoint PDF dossard (1 participant).
|
||||
* Isolé du tunnel / panier ; droit inscriptions_gestion.dossard_print.
|
||||
*/
|
||||
|
||||
session_start();
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
require_once __DIR__ . '/php/inc_fx_dossard_print.php';
|
||||
|
||||
$strLangue = $_SESSION['lang'] ?? 'fr';
|
||||
$reqLang = $_REQUEST['lang'] ?? $_REQUEST['lng'] ?? '';
|
||||
if (!empty($reqLang) && in_array($reqLang, array('fr', 'en'), true)) {
|
||||
$strLangue = $reqLang;
|
||||
}
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
|
||||
$intParId = intval($_GET['par_id'] ?? $_POST['par_id'] ?? 0);
|
||||
$intEveId = intval($_GET['eve_id'] ?? $_POST['eve_id'] ?? 0);
|
||||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||||
|
||||
if ($intComId <= 0 || $intParId <= 0 || $intEveId <= 0) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!fxDossardPrintCan($intComId, $intEveId)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Droit insuffisant.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$arrRow = fxDossardPrintLoadParticipant($intParId, $intEveId);
|
||||
if ($arrRow === null) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Participant introuvable.';
|
||||
exit;
|
||||
}
|
||||
|
||||
fxDossardPrintOutputPdf($arrRow);
|
||||
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M247.2 17c-22.1-3.1-44.6 .9-64.4 11.4l-74 39.5C89.1 78.4 73.2 94.9 63.4 115L26.7 190.6c-9.8 20.1-13 42.9-9.1 64.9l14.5 82.8c3.9 22.1 14.6 42.3 30.7 57.9l60.3 58.4c16.1 15.6 36.6 25.6 58.7 28.7l83 11.7c22.1 3.1 44.6-.9 64.4-11.4l74-39.5c19.7-10.5 35.6-27 45.4-47.2l36.7-75.5c9.8-20.1 13-42.9 9.1-64.9l-14.6-82.8c-3.9-22.1-14.6-42.3-30.7-57.9L388.9 57.5c-16.1-15.6-36.6-25.6-58.7-28.7L247.2 17zM208 144a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM144 336a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm224-64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="currentColor" d="M247.2 17c-22.1-3.1-44.6 .9-64.4 11.4l-74 39.5C89.1 78.4 73.2 94.9 63.4 115L26.7 190.6c-9.8 20.1-13 42.9-9.1 64.9l14.5 82.8c3.9 22.1 14.6 42.3 30.7 57.9l60.3 58.4c16.1 15.6 36.6 25.6 58.7 28.7l83 11.7c22.1 3.1 44.6-.9 64.4-11.4l74-39.5c19.7-10.5 35.6-27 45.4-47.2l36.7-75.5c9.8-20.1 13-42.9 9.1-64.9l-14.6-82.8c-3.9-22.1-14.6-42.3-30.7-57.9L388.9 57.5c-16.1-15.6-36.6-25.6-58.7-28.7L247.2 17zM208 144a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM144 336a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm224-64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 772 B |
0
images/doc/.gitkeep
Normal file
BIN
images/doc/dossardposition.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
images/ms1-kc-logo.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
images/ms1-runner-loader.gif
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
images/te_sans_inscription_en_ligne.png
Normal file
|
After Width: | Height: | Size: 821 KiB |
262
inc_footer.php
@ -59,7 +59,8 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
|
||||
}
|
||||
}
|
||||
?>
|
||||
<footer>
|
||||
<footer<?php echo (defined('MS1_INSCR_GESTION_ENTRY') && MS1_INSCR_GESTION_ENTRY) ? ' class="inscr-gestion-entry-footer"' : ''; ?>>
|
||||
<?php if (!(defined('MS1_INSCR_GESTION_ENTRY') && MS1_INSCR_GESTION_ENTRY)) { ?>
|
||||
<div class="ms1-footer-top">
|
||||
<div class="container">
|
||||
<div class="row py-4">
|
||||
@ -113,14 +114,20 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
|
||||
<div class="social">
|
||||
<a href="http://www.facebook.com/pages/Ms1timing/387594717971765" target="_blank"><i class="fa fa-facebook-square"></i></a>
|
||||
<a href="https://twitter.com/ms1timing" target="_blank"><i class="fa fa-twitter-square"></i></a>
|
||||
|
||||
<a href="/index.php?cookies" ><img style="border: none;" width="30" src="<?php echo $vDomaine; ?>/images/cookie.png" alt="cookies" /></a>
|
||||
|
||||
<a href="#" class="cookie-manage-link" data-cookie-manage title="<?php echo ($strLangue === 'fr') ? 'Gérer les témoins' : 'Manage cookies'; ?>">
|
||||
<i class="fa cookie-manage-icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" focusable="false">
|
||||
<path d="M247.2 17c-22.1-3.1-44.6 .9-64.4 11.4l-74 39.5C89.1 78.4 73.2 94.9 63.4 115L26.7 190.6c-9.8 20.1-13 42.9-9.1 64.9l14.5 82.8c3.9 22.1 14.6 42.3 30.7 57.9l60.3 58.4c16.1 15.6 36.6 25.6 58.7 28.7l83 11.7c22.1 3.1 44.6-.9 64.4-11.4l74-39.5c19.7-10.5 35.6-27 45.4-47.2l36.7-75.5c9.8-20.1 13-42.9 9.1-64.9l-14.6-82.8c-3.9-22.1-14.6-42.3-30.7-57.9L388.9 57.5c-16.1-15.6-36.6-25.6-58.7-28.7L247.2 17zM208 144a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM144 336a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm224-64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
|
||||
</svg>
|
||||
</i>
|
||||
<span class="sr-only"><?php echo ($strLangue === 'fr') ? 'Gérer les témoins' : 'Manage cookies'; ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="ms1-footer-bottom">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
@ -167,6 +174,48 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
|
||||
</div>
|
||||
<!-- /MSIN-3860 -->
|
||||
<style>
|
||||
footer .social {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
footer .social > a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 10px 0 10px 5px;
|
||||
}
|
||||
|
||||
footer .social > a > i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer .social a.cookie-manage-link {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer .social a.cookie-manage-link:hover {
|
||||
color: #f3954a;
|
||||
}
|
||||
|
||||
footer .social a.cookie-manage-link .cookie-manage-icon {
|
||||
font-size: 30px;
|
||||
line-height: 1;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
footer .social a.cookie-manage-link .cookie-manage-icon svg {
|
||||
display: block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* /MSIN-3860 */
|
||||
.cookie-consent {
|
||||
@ -181,12 +230,40 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
|
||||
}
|
||||
|
||||
.cookie-consent-container {
|
||||
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cookie-consent-container p {
|
||||
flex: 1 1 100%;
|
||||
max-width: 900px;
|
||||
margin: 0 auto 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cookie-consent-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cookie-consent-links {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cookie-consent-links a {
|
||||
color: #0066cc;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#acceptCookies, #rejectCookies {
|
||||
@ -212,117 +289,92 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
|
||||
background-color: #e53935;
|
||||
}
|
||||
|
||||
#cookiePreferencesPanel {
|
||||
max-width: 640px;
|
||||
margin: 12px auto 0;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cookie-pref-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cookie-pref-row label {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cookie-pref-row input[type="checkbox"] {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
#customizeCookies, #saveCookiePreferences {
|
||||
background: #666;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#saveCookiePreferences {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<!-- MSIN-3860 -->
|
||||
|
||||
<div id="cookieConsent" class="cookie-consent">
|
||||
<p><?php echo afficheTexte('experience_cookies', 0, 1, 1); ?></p>
|
||||
|
||||
<div class="cookie-consent-container">
|
||||
|
||||
|
||||
<button id="acceptCookies"><?php echo afficheTexte('bouton_acceper_cookies', 0, 1, 1); ?></button>
|
||||
|
||||
<button id="rejectCookies"><?php echo afficheTexte('bouton_refuser_cookies', 0, 1, 1); ?></button>
|
||||
|
||||
<p class="mb-2 mb-md-0"><?php echo fxCookieConsentText('experience_cookies', $strLangue); ?></p>
|
||||
<div class="cookie-consent-actions">
|
||||
<button type="button" id="acceptCookies"><?php echo fxCookieConsentText('bouton_acceper_cookies', $strLangue); ?></button>
|
||||
<button type="button" id="rejectCookies"><?php echo fxCookieConsentText('bouton_refuser_cookies', $strLangue); ?></button>
|
||||
<button type="button" id="customizeCookies"><?php echo fxCookieConsentText('bouton_personnaliser_cookies', $strLangue); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cookiePreferencesPanel" class="cookie-consent-container d-none">
|
||||
<p><strong><?php echo fxCookieConsentText('cookie_pref_titre', $strLangue); ?></strong></p>
|
||||
<div class="cookie-pref-row">
|
||||
<input type="checkbox" id="cookiePrefEssential" checked disabled>
|
||||
<label for="cookiePrefEssential"><?php echo fxCookieConsentText('cookie_pref_essentiels', $strLangue); ?></label>
|
||||
</div>
|
||||
<div class="cookie-pref-row">
|
||||
<input type="checkbox" id="cookiePrefAnalytics">
|
||||
<label for="cookiePrefAnalytics"><?php echo fxCookieConsentText('cookie_pref_analytique', $strLangue); ?></label>
|
||||
</div>
|
||||
<div class="cookie-pref-row">
|
||||
<input type="checkbox" id="cookiePrefMarketing">
|
||||
<label for="cookiePrefMarketing"><?php echo fxCookieConsentText('cookie_pref_marketing', $strLangue); ?></label>
|
||||
</div>
|
||||
<div class="cookie-consent-actions">
|
||||
<button type="button" id="saveCookiePreferences"><?php echo fxCookieConsentText('bouton_enregistrer_cookies', $strLangue); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookie-consent-links">
|
||||
<a href="<?php echo $vDomaine; ?>/page/cookie/<?php echo $strLangue; ?>"><?php echo fxCookieConsentText('politique_cookie', $strLangue); ?></a>
|
||||
|
|
||||
<a href="<?php echo $vDomaine; ?>/page/confidentialite/<?php echo $strLangue; ?>"><?php echo fxCookieConsentText('politique_confidentialite', $strLangue); ?></a>
|
||||
</div>
|
||||
</br>
|
||||
|
||||
<a href="/page/cookie/<?php if ($strLangue != 'fr') { echo ("/en"); }?> "><?php echo afficheTexte('politique_cookie', 0, 1, 1); ?></a>       
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
// Function to disable all clicks within a specific div
|
||||
function disableClicks(divId) {
|
||||
document.getElementById(divId).addEventListener('click', blockClicks, true);
|
||||
}
|
||||
|
||||
// Function to block the click event
|
||||
function blockClicks(event) {
|
||||
event.stopPropagation(); // Stop the event from reaching its target
|
||||
event.preventDefault(); // Prevent the default action (like navigating a link)
|
||||
}
|
||||
|
||||
// Function to re-enable clicks within a specific div
|
||||
function enableClicks(divId) {
|
||||
document.getElementById(divId).removeEventListener('click', blockClicks, true);
|
||||
}
|
||||
|
||||
//MSIN-3860
|
||||
|
||||
// if (!getCookie('cookiesAccepted') && !getCookie('cookiesRejected')) {
|
||||
|
||||
|
||||
// $('#cookieConsent').fadeIn();
|
||||
// }
|
||||
|
||||
// Handle the acceptance of cookies
|
||||
$('#acceptCookies').click(function() {
|
||||
setCookie('cookiesAccepted', 'true', 365);
|
||||
$('#cookieConsent').fadeOut();
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Handle the rejection of cookies
|
||||
$('#rejectCookies').click(function() {
|
||||
setCookie('cookiesRejected', 'true', 365);
|
||||
$('#cookieConsent').fadeOut();
|
||||
enableClicks('page');
|
||||
|
||||
});
|
||||
|
||||
// Function to set a cookie
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
let expires = "expires="+ d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
|
||||
// Function to get a cookie
|
||||
function getCookie(cname) {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
</script>
|
||||
<?php require_once 'inc_footer_scripts.php'; ?>
|
||||
<?php
|
||||
require_once __DIR__ . '/php/inc_v2_assets.php';
|
||||
fxV2RenderFooterScripts();
|
||||
?>
|
||||
|
||||
if ((isset($_GET['cookies']))){
|
||||
?>
|
||||
<script>
|
||||
setCookie('cookiesAccepted', '', { expires: -1 });
|
||||
setCookie('cookiesRejected', '', { expires: -1 });
|
||||
window.location = window.location.href.split("?")[0];
|
||||
</script>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
|
||||
require_once 'inc_footer_scripts.php'; ?>
|
||||
|
||||
<script src="<?php echo $vDomaine; ?>/js/cookie-consent.js?version=v<?php echo _VERSION_CODE; ?>"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,6 +1,10 @@
|
||||
<?php
|
||||
|
||||
global $vPaypal_devise,$vblnEnvironementDev,$vDomaine, $vClient, $strCode, $strLangue, $vRepertoireFichiers,$vPaypaladv_SANDBOX,$favicon;
|
||||
|
||||
fxAdminTextEditModeHandleRequest();
|
||||
fxAdminPermInspectModeHandleRequest();
|
||||
|
||||
$strQueryString = "";
|
||||
|
||||
if ($_SERVER['QUERY_STRING'] != "") { // sortir les données du query string
|
||||
@ -134,26 +138,38 @@ if (!isset($strOGDescription)) {
|
||||
-->
|
||||
<!-- apres migration des info paypal pour MSIN-4216
|
||||
affaire remplacer echo PAYPAL_CLIENT_ID; -->
|
||||
<?php if (fxPageNeedsPaypal()) { ?>
|
||||
<script src="https://www.paypal.com/sdk/js?components=card-fields,buttons,googlepay&client-id=<?php echo PAYPAL_CLIENT_ID; ?>&disable-funding=paylater,credit¤cy=CAD<?php echo $strPaypalLangue; ?>"></script>
|
||||
<?php } ?>
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="<?php echo $vDomaine; ?>/min/index.php?g=js&version=v<?php echo _VERSION_CODE; ?>"></script>
|
||||
<?php
|
||||
require_once($_SERVER['DOCUMENT_ROOT']."/php/inc_facebook_pixel.php");
|
||||
if (fxCookiesAllowMarketing()) {
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . "/php/inc_facebook_pixel.php");
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<?php include_once('css/theme.php'); ?>
|
||||
<body>
|
||||
<?php
|
||||
$blnInscrGestionShell = defined('MS1_INSCR_GESTION_ENTRY') && MS1_INSCR_GESTION_ENTRY;
|
||||
// MSIN-4436 — Preprod/dev : overlay au chargement des pages lourdes (gestion qtés/dossards v2).
|
||||
$blnPreloaderOnLoad = ($vblnEnvironementDev !== true);
|
||||
if (!$blnPreloaderOnLoad && !empty($strCode) && $strCode === 'compte_inc_tableau_gestion_epreuves') {
|
||||
$blnPreloaderOnLoad = true;
|
||||
}
|
||||
?>
|
||||
<body<?php echo $blnInscrGestionShell ? ' class="inscr-gestion-entry-body"' : ''; ?>>
|
||||
|
||||
<?php if( $vblnEnvironementDev !== true){ ?>
|
||||
<?php if ($blnPreloaderOnLoad) { ?>
|
||||
<div id="preloader">
|
||||
<div id="spinner">
|
||||
<i class="fa fa-spinner fa-pulse"></i>
|
||||
<div id="spinner" class="ms1-loader ms1-loader--overlay">
|
||||
<img class="ms1-loader__runner" src="<?php echo $vDomaine; ?>/images/ms1-runner-loader.gif?v=<?php echo _VERSION_CODE; ?>" alt="" role="presentation" decoding="async">
|
||||
<span id="preloader_text"><?php afficheTexte('preloader_load',1,0,1); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,13 +179,19 @@ if (!isset($strOGDescription)) {
|
||||
</script>
|
||||
<?php
|
||||
}else{
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<div id="preloader" style="display:none;">
|
||||
<div id="spinner" class="ms1-loader ms1-loader--overlay">
|
||||
<img class="ms1-loader__runner" src="<?php echo $vDomaine; ?>/images/ms1-runner-loader.gif?v=<?php echo _VERSION_CODE; ?>" alt="" role="presentation" decoding="async">
|
||||
<span id="preloader_text"><?php afficheTexte('preloader_wait',1,0,1); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
} ?>
|
||||
|
||||
<?php echo $vEnvNotice; ?>
|
||||
<header>
|
||||
<?php fxRenderEnvNoticeBar(); ?>
|
||||
<header<?php echo $blnInscrGestionShell ? ' class="inscr-gestion-entry-header"' : ''; ?>>
|
||||
<?php if (!$blnInscrGestionShell) { ?>
|
||||
<div class="ms1-header-top">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
@ -276,13 +298,18 @@ if (!isset($strOGDescription)) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="ms1-header-bottom py-4">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 text-center text-lg-left">
|
||||
<?php if ($blnInscrGestionShell) { ?>
|
||||
<img class="mb-3 mb-lg-0" src="<?php echo $vDomaine; ?>/images/logoms1inscription.png" alt="<?php echo $vClient; ?>">
|
||||
<?php } else { ?>
|
||||
<a href="<?php echo $vDomaine; ?>">
|
||||
<img class="mb-3 mb-lg-0" src="<?php echo $vDomaine; ?>/images/logoms1inscription.png" alt="<?php echo $vClient; ?>">
|
||||
</a>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
<div class="col-lg-6 text-center text-lg-right">
|
||||
@ -298,6 +325,11 @@ if (!isset($strOGDescription)) {
|
||||
$ref_no_panier = "0";
|
||||
}
|
||||
if (!isset($_SESSION['com_id']) && !isset($_SESSION['affiche_pas_connexion'])) {
|
||||
if ($blnInscrGestionShell) {
|
||||
?>
|
||||
<p class="mb-0 text-muted small"><?php afficheTexte('gauche_placeholder_login', 1, 1, 1); ?></p>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<form action="<?php echo $vDomaine; ?>/<?php if ($strLangue == 'fr') { ?>compte<?php } else { ?>account<?php } ?>" id="frm_login_top" name="frm_login_top" method="post">
|
||||
<input type="hidden" name="form_action" value="<?php echo urlencode(base64_encode('login')); ?>">
|
||||
@ -329,7 +361,40 @@ if (!isset($strOGDescription)) {
|
||||
<?php echo fxShowCartLink($intNbItems, $strEvenementURL, $strLangue); ?>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
if ($blnInscrGestionShell && isset($_SESSION['com_id'])) {
|
||||
if (!function_exists('fxInscrGestionRenderFullAccountLink')) {
|
||||
require_once __DIR__ . '/php/inc_fx_inscriptions_gestion.php';
|
||||
}
|
||||
?>
|
||||
<p class="mb-2"><strong><?php afficheTexte('gauche_bonjour_user',1,1,1); ?> <?php echo $_SESSION['com_info']['com_prenom']; ?></strong></p>
|
||||
<ul class="list-inline mb-0">
|
||||
<?php
|
||||
// MSIN-4401 — Lien hub / Mon compte à côté de Déconnexion (pas dans le contenu).
|
||||
if (function_exists('fxInscrGestionShouldShowFullAccountLink')
|
||||
&& fxInscrGestionShouldShowFullAccountLink(intval($_SESSION['com_id']))) {
|
||||
$strFullUrl = fxInscrGestionFullAccountUrl($strLangue);
|
||||
$blnHubExit = function_exists('fxPromoteurHubIsAvailable')
|
||||
&& fxPromoteurHubIsAvailable(intval($_SESSION['com_id']));
|
||||
$strFullLbl = $blnHubExit
|
||||
? fxInscrGestionT('inscr_gestion_back_full_account')
|
||||
: fxInscrGestionT('inscr_gestion_back_mon_compte');
|
||||
?>
|
||||
<li class="list-inline-item">
|
||||
<i class="fa fa-th-large mr-1" aria-hidden="true"></i>
|
||||
<a href="<?php echo htmlspecialchars($strFullUrl, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($strFullLbl, ENT_QUOTES, 'UTF-8'); ?></a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<li class="list-inline-item">
|
||||
<i class="fa fa-sign-out mr-1"></i>
|
||||
<a class="btn_logout" href="<?php echo $vDomaine; ?>/logout.php?lang=<?php echo $strLangue; ?>&redirect=mobile"><?php afficheTexte('gauche_deconnextion',1,1,1); ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
<?php
|
||||
} elseif (!$blnInscrGestionShell) {
|
||||
?>
|
||||
<p class="mb-2">
|
||||
|
||||
@ -370,6 +435,7 @@ if (!isset($strOGDescription)) {
|
||||
?>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
@ -379,7 +445,7 @@ if (!isset($strOGDescription)) {
|
||||
</div>
|
||||
</header>
|
||||
<?php
|
||||
if (isset($_SESSION['com_id'])) {
|
||||
if (isset($_SESSION['com_id']) && !$blnInscrGestionShell) {
|
||||
$intCompte = $_SESSION['com_id'];
|
||||
// liste attente
|
||||
if (fx_liste_attente_compte_dispo_inscrire($intCompte) > 0) {
|
||||
|
||||
@ -65,9 +65,11 @@ if ($intEveId != 0) {
|
||||
<a class="nav-list" href="<?php echo $vDomaine . "/" . $strCode; ?>bib_assignment?a=show_bib_dupes&promoteur_eve_id=<?php echo urlencode(base64_encode($intEveId)); ?>"> <i class="fa fa-clone" aria-hidden="true"></i> <?php echo afficheTexte('tableau_dossards_doublons', 0) ;?>
|
||||
</a>
|
||||
</li> </ul>
|
||||
|
||||
|
||||
<?php
|
||||
/* MSIN-4379 — Ancienne UI dossards (Tool3). La v4 complète : inc_tableau_gestion_epreuves (superadm). */
|
||||
fxShowBibTool3($strBibCode, $intEveId, $strLangue);
|
||||
|
||||
?>
|
||||
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $vDomaine . $strPage . "/inc_tableau_promoteur?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
|
||||
<?php
|
||||
|
||||
// MSIN-3880 nouveau tableau
|
||||
include_once("php/inc_fx_modifierinscriptions.php");
|
||||
include_once("php/inc_fx_memberships.php");
|
||||
include_once("php/inc_fx_promoteur.php");
|
||||
require_once('superadm/php/inc_manager.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
$intEveId = 0;
|
||||
$blnDon = false;
|
||||
|
||||
if ($strLangue == 'fr') {
|
||||
$strPage = '/compte';
|
||||
} else {
|
||||
$strPage = '/account';
|
||||
}
|
||||
if($strLangue == 'fr')
|
||||
$strCode = 'compte/';
|
||||
else
|
||||
$strCode = 'account/';
|
||||
|
||||
$strBibCode = $strCode;
|
||||
|
||||
if (!empty($_GET['code'])) {
|
||||
$code = $_GET['code'];
|
||||
} else {
|
||||
$code = 'bib_assignment';
|
||||
}
|
||||
|
||||
if (isset($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = base64_decode(urldecode($_GET['promoteur_eve_id']));
|
||||
$arrEvenement = fxGetEvenementsId($intEveId);
|
||||
//$tabEvenement['general'] = $arrEvenement;
|
||||
}
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $vDomaine . $strPage . "/inc_tableau_promoteur?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<br> <br>
|
||||
<?php
|
||||
// recalculer les catégorie
|
||||
if (isset($_REQUEST['resetcategorie'])) {
|
||||
fxregles_niveau_updateall($intEveId);
|
||||
}
|
||||
// Annuler/retablir une inscriptions dans le tableau du promoteur
|
||||
if (isset($_POST['btn_cancel_event_promo'])) {
|
||||
$int_pec_id = $_POST['pec_id_original'];
|
||||
$int_epr_id = $_POST['epr_id'];
|
||||
|
||||
$tabEpreuve = fxGetEpreuve($int_epr_id);
|
||||
fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id);
|
||||
}
|
||||
|
||||
if ($intEveId != 0) {
|
||||
$arrEvenement = fxGetEvenementsId($intEveId);
|
||||
$tabRapports = fxGetMenussuperadm(0, 0, 1, $intEveId);
|
||||
|
||||
|
||||
//$tabEvenement['general'] = $arrEvenement;
|
||||
?> <ul class="nav flex-column ms1-menu-compte">
|
||||
<li class="nav-item">
|
||||
<a class="nav-list" href="<?php echo $vDomaine . "/" . $strCode; ?>bib_assignment?a=show_bib_dupes&promoteur_eve_id=<?php echo urlencode(base64_encode($intEveId)); ?>"> <i class="fa fa-clone" aria-hidden="true"></i> <?php echo afficheTexte('tableau_dossards_doublons', 0) ;?>
|
||||
</a>
|
||||
</li> </ul>
|
||||
V2
|
||||
<?php
|
||||
|
||||
|
||||
fxShowBibTool4($strBibCode, $intEveId, $strLangue);
|
||||
|
||||
?>
|
||||
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $vDomaine . $strPage . "/inc_tableau_promoteur?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<?php
|
||||
} else {
|
||||
if ($strLangue == 'fr')
|
||||
echo "Une erreur quelconque est survenue.";
|
||||
else
|
||||
echo "A generic error occurred.";
|
||||
}
|
||||
56
inc_tableau_eve_acces.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MSIN-4401 — Gestion des accès V2 (promoteur, par événement).
|
||||
* Les POST invite/revoke sont traités dans compte.php AVANT inc_header (évite page blanche).
|
||||
*/
|
||||
|
||||
include_once('php/inc_fx_promoteur.php');
|
||||
require_once('php/inc_fx_eve_acces.php');
|
||||
require_once('php/inc_fx_eve_acces_promoteur.php');
|
||||
require_once('php/inc_v2_assets.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
|
||||
$intEveId = 0;
|
||||
if (isset($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = intval(base64_decode(urldecode($_GET['promoteur_eve_id'])));
|
||||
}
|
||||
|
||||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
$strBasePath = ($strLangue === 'en') ? '/account' : '/compte';
|
||||
// MSIN-4399 — deny : hub + focus si dispo, sinon ancien tableau.
|
||||
if (!function_exists('fxPromoteurHubOrLegacyBackUrl')) {
|
||||
require_once __DIR__ . '/php/inc_fx_promoteur_hub.php';
|
||||
}
|
||||
$strRedirect = ($intEveId > 0)
|
||||
? fxPromoteurHubOrLegacyBackUrl($intEveId, $strLangue)
|
||||
: ($vDomaine . $strBasePath);
|
||||
|
||||
$intActor = intval($_SESSION['com_id'] ?? 0);
|
||||
if (!empty($_SESSION['usa_id'])) {
|
||||
$intActor = intval($_SESSION['usa_id']);
|
||||
}
|
||||
|
||||
if ($intEveId <= 0
|
||||
|| (empty($_SESSION['com_id']) && empty($_SESSION['usa_id']))
|
||||
|| !fxEveAccesCanTeamInvite($intActor, $intEveId)) {
|
||||
// Headers déjà envoyés (include après layout) : message + lien, pas de header Location.
|
||||
echo '<div class="alert alert-warning">';
|
||||
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_access_denied', $strLangue));
|
||||
echo ' <a href="' . htmlspecialchars($strRedirect, ENT_QUOTES, 'UTF-8') . '">';
|
||||
echo fxEveAccesPromoteurEsc(fxEveAccesPromoteurT('eve_acces_promoteur_btn_back', $strLangue));
|
||||
echo '</a></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$strFlash = '';
|
||||
$strError = '';
|
||||
if (!empty($_GET['ok'])) {
|
||||
$strFlash = (string)$_GET['ok'];
|
||||
}
|
||||
if (!empty($_GET['err'])) {
|
||||
$strError = (string)$_GET['err'];
|
||||
}
|
||||
|
||||
fxEveAccesPromoteurRenderPage($intEveId, $strLangue, $strFlash, $strError);
|
||||
76
inc_tableau_gestion_epreuves.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
// MSIN-4379 — Gestion quantités + dossards v4
|
||||
include_once("php/inc_fx_modifierinscriptions.php");
|
||||
include_once("php/inc_fx_memberships.php");
|
||||
include_once("php/inc_fx_promoteur.php");
|
||||
require_once('php/inc_v2_assets.php');
|
||||
require_once('php/inc_fx_eve_acces.php');
|
||||
require_once('superadm/php/inc_manager.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
|
||||
$intEveId = 0;
|
||||
|
||||
if ($strLangue == 'fr') {
|
||||
$strPage = '/compte';
|
||||
} else {
|
||||
$strPage = '/account';
|
||||
}
|
||||
if ($strLangue == 'fr') {
|
||||
$strCode = 'compte/';
|
||||
} else {
|
||||
$strCode = 'account/';
|
||||
}
|
||||
|
||||
$strBibCode = $strCode;
|
||||
|
||||
if (isset($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = base64_decode(urldecode($_GET['promoteur_eve_id']));
|
||||
}
|
||||
|
||||
if ($intEveId <= 0 || (empty($_SESSION['com_id']) && empty($_SESSION['usa_id']))
|
||||
|| (empty($_SESSION['usa_id'])
|
||||
&& !fxEveAccesHasAnyPermission($_SESSION['com_id'] ?? 0, $intEveId, array('dossards.manage', 'epreuves.edit_qte')))) {
|
||||
// MSIN-4399 — même retour que le bouton back (hub + focus si dispo).
|
||||
$strRedirect = fxGetPromoteurBackUrl($intEveId > 0 ? $intEveId : 0, $strPage);
|
||||
header('Location: ' . $strRedirect);
|
||||
exit;
|
||||
}
|
||||
|
||||
$strBackUrl = fxGetPromoteurBackUrl($intEveId, $strPage);
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $strBackUrl; ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<br><br>
|
||||
<?php
|
||||
|
||||
if (isset($_REQUEST['resetcategorie'])) {
|
||||
fxregles_niveau_updateall($intEveId);
|
||||
}
|
||||
|
||||
if (isset($_POST['btn_cancel_event_promo'])) {
|
||||
$int_pec_id = $_POST['pec_id_original'];
|
||||
$int_epr_id = $_POST['epr_id'];
|
||||
$tabEpreuve = fxGetEpreuve($int_epr_id);
|
||||
fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<?php
|
||||
fxV2RegisterScript('bib-assignments');
|
||||
fxShowBibTool4($strBibCode, $intEveId, $strLangue);
|
||||
?>
|
||||
<link rel="stylesheet" href="<?php echo $vDomaine; ?>/libs/tippy.js-6.3.7/tippy.css">
|
||||
<script src="<?php echo $vDomaine; ?>/libs/tippy.js-6.3.7/popper.min.js"></script>
|
||||
<script src="<?php echo $vDomaine; ?>/libs/tippy.js-6.3.7/tippy-bundle.umd.min.js"></script>
|
||||
|
||||
<a class="btn btn-primary rounded-pill mt-3" href="<?php echo $strBackUrl; ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<?php
|
||||
30
inc_tableau_inscriptions_gestion.php
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
<?php
|
||||
|
||||
include_once('php/inc_fx_modifierinscriptions.php');
|
||||
include_once('php/inc_fx_memberships.php');
|
||||
include_once('php/inc_fx_promoteur.php');
|
||||
include_once('php/inc_fx_inscriptions_gestion.php');
|
||||
require_once('php/inc_v2_assets.php');
|
||||
require_once('superadm/php/inc_manager.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
|
||||
$intEveId = 0;
|
||||
if (isset($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = fxInscrGestionParseEveId($_GET['promoteur_eve_id']);
|
||||
}
|
||||
|
||||
if ($intEveId === 0) {
|
||||
if ($strLangue === 'fr') {
|
||||
echo 'Une erreur quelconque est survenue.';
|
||||
} else {
|
||||
echo 'A generic error occurred.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="inscr-gestion-page">';
|
||||
fxV2RegisterScript('inscr-gestion');
|
||||
fxInscrGestionRun($intEveId, $strLangue);
|
||||
echo '</div>';
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once('php/inc_fx_liste_attente.php');
|
||||
include_once('php/inc_fx_promoteur.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
$intEveId = 0;
|
||||
@ -54,6 +55,19 @@ if (isset($_GET['promoteur_eve_id'])) {
|
||||
//$tabEvenement['general'] = $arrEvenement;
|
||||
}
|
||||
|
||||
// Conserver la page d'origine (gestion v4 vs ancien tableau épreuves), y compris après POST.
|
||||
if (isset($_GET['from']) && trim($_GET['from']) !== '') {
|
||||
$_SESSION['liste_attente_from'] = trim($_GET['from']);
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$_SESSION['liste_attente_from'] = 'epreuves';
|
||||
}
|
||||
if (!empty($_SESSION['liste_attente_from'])) {
|
||||
$_GET['from'] = $_SESSION['liste_attente_from'];
|
||||
}
|
||||
|
||||
$strBackUrl = fxGetPromoteurBackUrl($intEveId, $strPage, 'epreuves');
|
||||
$strhomeclientannuler = fxGetPromoteurBackPath($strPage, 'epreuves');
|
||||
|
||||
|
||||
?>
|
||||
<h3> <?php echo $tabEpreuve["epr_type_" . $strLangue] . " / " . $tabEpreuve["epr_nom_" . $strLangue];; ?></h3>
|
||||
@ -61,7 +75,6 @@ if (isset($_GET['promoteur_eve_id'])) {
|
||||
<?php
|
||||
$_GET['id'] = $inteprId;
|
||||
$strhomeclient = "/compte/inc_tableau_liste_attente";
|
||||
$strhomeclientannuler = "/compte/inc_tableau_epreuves";
|
||||
|
||||
fxShowForm("mod", base64_encode(urlencode(250)), $strLangue, $intEveId, $inteprId, 0);
|
||||
|
||||
@ -155,7 +168,7 @@ fxShowList($tabResults, $intmenu, '', $strwhere, '', 1, $strLangue, $inteprId);
|
||||
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back"
|
||||
href="<?php echo $vDomaine . $strPage . "/inc_tableau_epreuves?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
href="<?php echo $strBackUrl; ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
|
||||
@ -218,10 +218,6 @@ if ($intEveId != 0) {
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
$strBibCode = $strPage;
|
||||
// afficher l'outil de no. equipe/no. dossard
|
||||
|
||||
|
||||
$sqlQteLimitee = "SELECT eve_qte_modifiable FROM inscriptions_evenements WHERE eve_id = " . intval($intEveId);
|
||||
$qryQteLimitee = $objDatabase->fxGetVar($sqlQteLimitee);
|
||||
|
||||
@ -236,6 +232,7 @@ if ($intEveId != 0) {
|
||||
</a>
|
||||
</li><?php
|
||||
}
|
||||
// MSIN-4401 — menus V2 (qté/dossards, inscriptions, accès) : hub uniquement, pas le Legacy.
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,294 +0,0 @@
|
||||
|
||||
<?php
|
||||
|
||||
// MSIN-3679 nouveau tableau
|
||||
|
||||
ini_set('display_errors', true);
|
||||
error_reporting(E_ALL);
|
||||
require_once $_SERVER["DOCUMENT_ROOT"] . "/superadm/php/inc_fx_paypal.php";
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
$intEveId = 0;
|
||||
$blnDon = false;
|
||||
|
||||
if ($strLangue == 'fr') {
|
||||
$strPage = '/compte';
|
||||
} else {
|
||||
$strPage = '/account';
|
||||
}
|
||||
if($strLangue == 'fr')
|
||||
$strCode = 'compte/';
|
||||
else
|
||||
$strCode = 'account/';
|
||||
|
||||
$strBibCode = $strCode;
|
||||
|
||||
if (!empty($_GET['code'])) {
|
||||
$code = $_GET['code'];
|
||||
} else {
|
||||
$code = 'bib_assignment';
|
||||
}
|
||||
|
||||
if (isset($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = base64_decode(urldecode($_GET['promoteur_eve_id']));
|
||||
$arrEvenement = fxGetEvenementsId($intEveId);
|
||||
//$tabEvenement['general'] = $arrEvenement;
|
||||
}
|
||||
// a metre dimamique
|
||||
|
||||
$sqlCommandes = "SELECT *, IF(a.com_id <> 0, (SELECT c.com_login FROM inscriptions_comptes c WHERE a.com_id = c.com_id), '') AS usager FROM inscriptions_panier_acheteurs a, inscriptions_panier_statuts s WHERE s.sta_id = a.sta_id AND a.no_panier = '" . $_GET['no_panier']."'";
|
||||
$recCommandes = $objDatabase->fxGetRow($sqlCommandes);
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $vDomaine . $strPage . "/inc_tableau_inscriptions?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<br>
|
||||
|
||||
<h3><img src="images_blue/ico_commandes_big.png" alt="">Détails de la commande</h3>
|
||||
<?php echo fxShowPanier($recCommandes['no_panier'], 0, 'web', 1, 'fr'); ?>
|
||||
<p> </p>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<h3><img src="images_blue/ico_commandes_big.png" alt="">PAYPAL</h3>
|
||||
|
||||
<?php
|
||||
|
||||
// ************************************************ rembourcement
|
||||
|
||||
/****************************************************
|
||||
* Page: remboursement.php
|
||||
* Rôle : affiche le formulaire de remboursement + prépare
|
||||
* les variables serveur nécessaires au front (AJAX).
|
||||
****************************************************/
|
||||
|
||||
// Démarrer la session
|
||||
//session_start();
|
||||
|
||||
// Jeton anti double clic / re-soumission
|
||||
if (empty($_SESSION['refund_token'])) {
|
||||
$_SESSION['refund_token'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
$refundToken = $_SESSION['refund_token'];
|
||||
|
||||
// Récupération des infos commande (présentes dans ton contexte)
|
||||
$CAPTURE_ID = $recCommandes["TransactionID"] ?? null;
|
||||
|
||||
if (!isset($CAPTURE_ID) || !is_string($CAPTURE_ID) || $CAPTURE_ID === '') {
|
||||
//echo '<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">
|
||||
// ⚠️ CAPTURE_ID non fourni côté serveur.
|
||||
// </div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Charger ta classe PayPal
|
||||
require_once $_SERVER["DOCUMENT_ROOT"] . "/paypal_advanced/PaypalCheckout.class.php";
|
||||
$paypal = new PayPalCheckout(); // adapte si nécessaire
|
||||
|
||||
// Préparation
|
||||
$errors = [];
|
||||
$successMsg = null;
|
||||
|
||||
// Devise par défaut si jamais les détails PayPal n’en donnent pas
|
||||
$currencyUI = 'CAD';
|
||||
|
||||
// ====== Calculs initiaux côté serveur ======
|
||||
|
||||
// Solde dispo promoteur (ton helper existant)
|
||||
$solde_dispo_ms1 = statut_payement($recCommandes["eve_id"]);
|
||||
|
||||
// Récupérer les détails PayPal de la capture
|
||||
$paypaldetails = $paypal->paypaldetails($CAPTURE_ID);
|
||||
|
||||
// Harmoniser avec tes remboursements locaux si besoin
|
||||
$paypaldetails = reconcileRefundsWithLocal($paypaldetails);
|
||||
|
||||
// Déterminer le max remboursable PayPal
|
||||
$max_remboursement_paypal = 0.0;
|
||||
|
||||
// Cas habituel : clé remaining
|
||||
if (!empty($paypaldetails['refunds']['totals']['remaining'])) {
|
||||
$max_remboursement_paypal = (float)$paypaldetails['refunds']['totals']['remaining'];
|
||||
}
|
||||
// Fallback : montant total capturé (certains retours utilisent "details", d’autres ont eu une coquille "delails")
|
||||
elseif (!empty($paypaldetails['details']['amount']['value'])) {
|
||||
$max_remboursement_paypal = (float)$paypaldetails['details']['amount']['value'];
|
||||
} elseif (!empty($paypaldetails['delails']['amount']['value'])) { // compat. si ton code historique renvoie "delails"
|
||||
$max_remboursement_paypal = (float)$paypaldetails['delails']['amount']['value'];
|
||||
}
|
||||
|
||||
// Déterminer la devise d’affichage si fournie par PayPal
|
||||
if (!empty($paypaldetails['refunds']['totals']['currency'])) {
|
||||
$currencyUI = (string)$paypaldetails['refunds']['totals']['currency'];
|
||||
} elseif (!empty($paypaldetails['details']['amount']['currency_code'])) {
|
||||
$currencyUI = (string)$paypaldetails['details']['amount']['currency_code'];
|
||||
} elseif (!empty($paypaldetails['delails']['amount']['currency_code'])) {
|
||||
$currencyUI = (string)$paypaldetails['delails']['amount']['currency_code'];
|
||||
}
|
||||
|
||||
// Frais MS1 (selon ta structure)
|
||||
$frais_ms1 = (float)($recCommandes['fra_total'] ?? 0);
|
||||
|
||||
// Max remboursable à l’écran (ta logique actuelle)
|
||||
$maxUI = $max_remboursement_paypal - $frais_ms1;
|
||||
|
||||
// maxblock = limite stricte d’input côté front, selon ton choix actuel
|
||||
if ($max_remboursement_paypal < $solde_dispo_ms1) {
|
||||
$maxblock = $max_remboursement_paypal;
|
||||
} else {
|
||||
$maxblock = 0;
|
||||
$maxUI = 0;
|
||||
}
|
||||
// ******************** variable info
|
||||
//print_rsl($recCommandes);
|
||||
echosl( "no_commande : ".$recCommandes["no_commande"]);
|
||||
// echosl( "rem_ajoute_par : ".$_SESSION["usa_info"]["com_prenom"]." ".$_SESSION["usa_info"]["com_nom"]);
|
||||
echosl( "eve_id : ".$recCommandes["eve_id"]) ;
|
||||
echosl( "Montant remboursable : " . number_format($max_remboursement_paypal, 2) . " " . ($result['refunds']['totals']['currency'] ?? $result['delails']['amount']['currency_code'] ?? ''));
|
||||
|
||||
|
||||
|
||||
echo("solde dispo ms1 :");
|
||||
echosl($solde_dispo_ms1);
|
||||
echo("frais ms1 :");
|
||||
$frais_ms1=$recCommandes['fra_total'];
|
||||
echosl($frais_ms1);
|
||||
echo("max Remboursement :");
|
||||
|
||||
// $maxUI le max Remboursement
|
||||
$maxUI=$max_remboursement_paypal-$frais_ms1;
|
||||
echosl($maxUI);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Remboursement PayPal</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body style="font-family:system-ui,Arial,Helvetica;">
|
||||
|
||||
<div style="max-width:560px;border:1px solid #ddd;border-radius:10px;padding:14px;">
|
||||
<div style="font-weight:700;font-size:16px;margin-bottom:8px;">Remboursement PayPal (Checkout)</div>
|
||||
|
||||
<!-- Zone messages AJAX -->
|
||||
<div id="refund-msg" style="margin-bottom:10px;display:none;padding:10px;border-radius:8px;"></div>
|
||||
|
||||
<?php if ($maxUI > 0): ?>
|
||||
<div style="background:#f9fbff;border:1px solid #dbe7ff;padding:10px;border-radius:8px;margin-bottom:10px;">
|
||||
<div><strong>Capture:</strong> <?= h($CAPTURE_ID) ?></div>
|
||||
<div><strong>Max remboursable (affiché):</strong> <span id="pp_ref_max"><?= h(money2($maxUI)) ?></span> <?= h($currencyUI) ?></div>
|
||||
</div>
|
||||
|
||||
<form id="refund-form" method="post" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||||
<input type="hidden" name="refund_token" value="<?= h($refundToken) ?>">
|
||||
<input type="hidden" name="capture_id" value="<?= h($CAPTURE_ID) ?>">
|
||||
<input type="hidden" name="currency" value="<?= h($currencyUI) ?>">
|
||||
<input type="hidden" name="eve_id" value="<?= h($recCommandes["eve_id"]) ?>">
|
||||
<input type="hidden" name="no_commande" value="<?= h($recCommandes["no_commande"]) ?>">
|
||||
|
||||
<div style="grid-column:1/2;">
|
||||
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">
|
||||
Montant à rembourser (<?= h($currencyUI) ?>)
|
||||
</label>
|
||||
<input id="pp_ref_amount" type="number" name="refund_amount" step="0.01" min="0.01"
|
||||
max="<?= h(money2($maxblock)) ?>"
|
||||
value="<?= h(money2($maxUI)) ?>"
|
||||
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
|
||||
</div>
|
||||
|
||||
<div style="grid-column:2/3;">
|
||||
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">Note (optionnelle)</label>
|
||||
<input type="text" name="refund_note" placeholder="Raison du remboursement"
|
||||
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
|
||||
</div>
|
||||
|
||||
<div style="grid-column:1/3;text-align:right;margin-top:6px;">
|
||||
<button type="submit"
|
||||
style="padding:10px 14px;border:1px solid #2d6cdf;border-radius:8px;background:#2d6cdf;color:#fff;cursor:pointer;">
|
||||
Rembourser
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div style="background:#fff8e6;border:1px solid #ffe1a6;padding:10px;border-radius:8px;">
|
||||
Aucun montant remboursable pour cette capture.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Petit helper visuel
|
||||
function showMsg(text, ok) {
|
||||
const box = document.getElementById('refund-msg');
|
||||
box.style.display = 'block';
|
||||
box.style.background = ok ? '#e9f8ef' : '#feecec';
|
||||
box.style.border = ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5';
|
||||
box.innerText = text;
|
||||
}
|
||||
|
||||
document.getElementById('refund-form')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
showMsg('⏳ Traitement en cours...', false);
|
||||
|
||||
const data = new FormData(e.target);
|
||||
const res = await fetch( '<?php echo $vDomaine; ?>/superadm/ajax_refund.php', { method: 'POST', body: data });
|
||||
|
||||
|
||||
// Défensif
|
||||
if (!res.ok) {
|
||||
showMsg('⚠️ Erreur réseau pendant le remboursement.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
let json;
|
||||
try { json = await res.json(); } catch (_) {
|
||||
showMsg('⚠️ Réponse invalide du serveur.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.success) {
|
||||
showMsg('✅ ' + (json.message || 'Remboursement effectué.'), true);
|
||||
|
||||
// 👉 Ici, tu peux déclencher des actions UI :
|
||||
// - recharger une grille des remboursements
|
||||
// - rafraîchir un solde
|
||||
// - etc.
|
||||
// refreshTableRemboursements();
|
||||
} else {
|
||||
showMsg('⚠️ ' + (json.message || 'Erreur remboursement.'), false);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
<h3><img src="images_blue/ico_commandes_big.png" alt="">Détails PAYPAL</h3>
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
// $sortie=paypaldetails($recCommandes["TransactionID"]);
|
||||
|
||||
|
||||
echo(renderPayPalCaptureHtml($paypaldetails));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back" href="<?php echo $vDomaine . $strPage . "/inc_tableau_promoteur?promoteur_eve_id=" . urlencode(base64_encode($intEveId)); ?>">
|
||||
<i class="fa fa-chevron-left mr-2" aria-hidden="true"></i>
|
||||
<?php afficheTexte('promoteur_back'); ?>
|
||||
</a>
|
||||
<?php
|
||||
15
index.php
@ -58,15 +58,20 @@ require_once("inc_header.php");
|
||||
}
|
||||
}
|
||||
|
||||
echo str_replace('%liste_evenements%', $strListeEvenements, fxUnescape($recPage['pag_texte_' . $strLangue], 1));
|
||||
echo str_replace('%liste_evenements%', $strListeEvenements, fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1)));
|
||||
} else {
|
||||
?>
|
||||
<h1><?php echo fxUnescape($recPage['pag_titre_' . $strLangue], 1); ?></h1>
|
||||
<?php
|
||||
switch ($strCode) {
|
||||
case 'cookie':
|
||||
$strCookieContent = trim((string) $recPage['pag_texte_' . $strLangue]);
|
||||
echo $strCookieContent !== '' ? fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1)) : fxCookiePolicyPageContent($strLangue);
|
||||
break;
|
||||
|
||||
echo fxUnescape($recPage['pag_texte_' . $strLangue], 1);
|
||||
case 'confidentialite':
|
||||
$strPrivacyContent = trim((string) $recPage['pag_texte_' . $strLangue]);
|
||||
echo $strPrivacyContent !== '' ? fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1)) : fxPrivacyPolicyPageContent($strLangue);
|
||||
break;
|
||||
|
||||
case 'evenements':
|
||||
@ -80,7 +85,7 @@ require_once("inc_header.php");
|
||||
}
|
||||
}
|
||||
|
||||
echo str_replace('%liste_evenements%', $strListeEvenements, fxUnescape($recPage['pag_texte_' . $strLangue], 1));
|
||||
echo str_replace('%liste_evenements%', $strListeEvenements, fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1)));
|
||||
break;
|
||||
case 'contact_send':
|
||||
print_r($_POST);
|
||||
@ -88,7 +93,7 @@ require_once("inc_header.php");
|
||||
break;
|
||||
case 'contact':
|
||||
$arrEvenements = fxGetEvenements('', $strLangue, 1, true);
|
||||
$strText = fxUnescape($recPage['pag_texte_' . $strLangue], 1);
|
||||
$strText = fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1));
|
||||
$contact_promoteurs = fxShowListeEvenementsContact($arrEvenements, $strLangue);
|
||||
//$strText = str_replace('%evenements%', $strListeEvenements, $strText);
|
||||
|
||||
@ -147,7 +152,7 @@ require_once("inc_header.php");
|
||||
break;
|
||||
case 'contactsend':
|
||||
$arrEvenements = fxGetEvenements('', $strLangue, 1, true);
|
||||
$strText = fxUnescape($recPage['pag_texte_' . $strLangue], 1);
|
||||
$strText = fxNormalizeCmsPageLinks(fxUnescape($recPage['pag_texte_' . $strLangue], 1));
|
||||
$strListeEvenements = fxShowListeEvenementsContact($arrEvenements, $strLangue);
|
||||
$strText = str_replace('%evenements%', $strListeEvenements, $strText);
|
||||
echo $strText;
|
||||
|
||||
142
js/cookie-consent.js
Normal file
@ -0,0 +1,142 @@
|
||||
(function (window, $) {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_KEY = 'cookiesPreferences';
|
||||
var LEGACY_ACCEPT = 'cookiesAccepted';
|
||||
var LEGACY_REJECT = 'cookiesRejected';
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
var expires = '';
|
||||
|
||||
if (typeof days === 'number') {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = ';expires=' + date.toUTCString();
|
||||
}
|
||||
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + expires + ';path=/;SameSite=Lax';
|
||||
}
|
||||
|
||||
function deleteCookie(name) {
|
||||
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;SameSite=Lax';
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var prefix = name + '=';
|
||||
var parts = decodeURIComponent(document.cookie).split(';');
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
while (part.charAt(0) === ' ') {
|
||||
part = part.substring(1);
|
||||
}
|
||||
|
||||
if (part.indexOf(prefix) === 0) {
|
||||
return part.substring(prefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function readPreferences() {
|
||||
var raw = getCookie(STORAGE_KEY);
|
||||
|
||||
if (raw) {
|
||||
try {
|
||||
var parsed = JSON.parse(raw);
|
||||
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
return {
|
||||
analytics: !!parsed.analytics,
|
||||
marketing: !!parsed.marketing,
|
||||
set: true
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (getCookie(LEGACY_ACCEPT) === 'true') {
|
||||
return { analytics: true, marketing: true, set: true };
|
||||
}
|
||||
|
||||
if (getCookie(LEGACY_REJECT) === 'true') {
|
||||
return { analytics: false, marketing: false, set: true };
|
||||
}
|
||||
|
||||
return { analytics: false, marketing: false, set: false };
|
||||
}
|
||||
|
||||
function writePreferences(prefs, reload) {
|
||||
deleteCookie(LEGACY_ACCEPT);
|
||||
deleteCookie(LEGACY_REJECT);
|
||||
setCookie(STORAGE_KEY, JSON.stringify({
|
||||
analytics: prefs.analytics ? 1 : 0,
|
||||
marketing: prefs.marketing ? 1 : 0,
|
||||
v: 1
|
||||
}), 365);
|
||||
|
||||
if (reload !== false) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function syncPanel(prefs) {
|
||||
$('#cookiePrefAnalytics').prop('checked', !!prefs.analytics);
|
||||
$('#cookiePrefMarketing').prop('checked', !!prefs.marketing);
|
||||
}
|
||||
|
||||
function openBanner() {
|
||||
$('#cookieConsent').stop(true, true).fadeIn(200);
|
||||
}
|
||||
|
||||
function closeBanner() {
|
||||
$('#cookieConsent').stop(true, true).fadeOut(200);
|
||||
$('#cookiePreferencesPanel').addClass('d-none');
|
||||
}
|
||||
|
||||
window.ms1CookieConsent = {
|
||||
getPreferences: readPreferences,
|
||||
openManager: function () {
|
||||
syncPanel(readPreferences());
|
||||
$('#cookiePreferencesPanel').removeClass('d-none');
|
||||
openBanner();
|
||||
}
|
||||
};
|
||||
|
||||
$(function () {
|
||||
var prefs = readPreferences();
|
||||
|
||||
syncPanel(prefs);
|
||||
|
||||
if (!prefs.set) {
|
||||
openBanner();
|
||||
}
|
||||
|
||||
$('#acceptCookies').on('click', function () {
|
||||
writePreferences({ analytics: true, marketing: true });
|
||||
});
|
||||
|
||||
$('#rejectCookies').on('click', function () {
|
||||
writePreferences({ analytics: false, marketing: false });
|
||||
});
|
||||
|
||||
$('#customizeCookies').on('click', function () {
|
||||
$('#cookiePreferencesPanel').removeClass('d-none');
|
||||
});
|
||||
|
||||
$('#saveCookiePreferences').on('click', function () {
|
||||
writePreferences({
|
||||
analytics: $('#cookiePrefAnalytics').is(':checked'),
|
||||
marketing: $('#cookiePrefMarketing').is(':checked')
|
||||
});
|
||||
});
|
||||
|
||||
$('[data-cookie-manage]').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
window.ms1CookieConsent.openManager();
|
||||
});
|
||||
});
|
||||
}(window, jQuery));
|
||||
188
js/ms1-doc.js
Normal file
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* MS1 — Module documentation overlay (navigation + ouverture/fermeture).
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function findOverlayForTrigger(trigger) {
|
||||
var anchor = trigger.getAttribute('data-doc-anchor');
|
||||
if (!anchor) {
|
||||
return null;
|
||||
}
|
||||
var overlays = document.querySelectorAll('.ms1-doc-overlay[data-doc-anchor="' + anchor + '"]');
|
||||
return overlays.length ? overlays[0] : null;
|
||||
}
|
||||
|
||||
function getMeta(overlay) {
|
||||
var anchor = overlay.getAttribute('data-doc-anchor');
|
||||
var script = document.querySelector('script.ms1-doc-meta[data-doc-anchor="' + anchor + '"]');
|
||||
if (!script) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
return JSON.parse(script.textContent || '[]');
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function setPage(overlay, index) {
|
||||
var pages = overlay.querySelectorAll('.ms1-doc-page');
|
||||
var dots = overlay.querySelectorAll('.ms1-doc-dot');
|
||||
var meta = getMeta(overlay);
|
||||
var total = pages.length;
|
||||
|
||||
if (total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
if (index >= total) {
|
||||
index = total - 1;
|
||||
}
|
||||
|
||||
pages.forEach(function (page, i) {
|
||||
if (i === index) {
|
||||
page.removeAttribute('hidden');
|
||||
page.classList.remove('hidden');
|
||||
} else {
|
||||
page.setAttribute('hidden', 'hidden');
|
||||
}
|
||||
});
|
||||
|
||||
dots.forEach(function (dot, i) {
|
||||
if (i === index) {
|
||||
dot.classList.add('is-active');
|
||||
} else {
|
||||
dot.classList.remove('is-active');
|
||||
}
|
||||
});
|
||||
|
||||
var titleEl = overlay.querySelector('.ms1-doc-title');
|
||||
var subtitleEl = overlay.querySelector('.ms1-doc-subtitle');
|
||||
var currentEl = overlay.querySelector('.ms1-doc-counter-current');
|
||||
var prevBtn = overlay.querySelector('.ms1-doc-prev');
|
||||
var nextBtn = overlay.querySelector('.ms1-doc-next');
|
||||
|
||||
if (meta[index]) {
|
||||
if (titleEl) {
|
||||
titleEl.textContent = meta[index].titre || '';
|
||||
}
|
||||
if (subtitleEl) {
|
||||
subtitleEl.textContent = meta[index].sous_titre || '';
|
||||
}
|
||||
}
|
||||
|
||||
if (currentEl) {
|
||||
currentEl.textContent = String(index + 1);
|
||||
}
|
||||
|
||||
if (prevBtn) {
|
||||
prevBtn.disabled = index <= 0;
|
||||
}
|
||||
if (nextBtn) {
|
||||
nextBtn.disabled = index >= total - 1;
|
||||
}
|
||||
|
||||
overlay.setAttribute('data-doc-current-index', String(index));
|
||||
}
|
||||
|
||||
function openOverlay(overlay) {
|
||||
overlay.removeAttribute('hidden');
|
||||
document.body.classList.add('ms1-doc-open');
|
||||
var idx = parseInt(overlay.getAttribute('data-doc-current-index') || '0', 10);
|
||||
setPage(overlay, idx);
|
||||
}
|
||||
|
||||
function closeOverlay(overlay) {
|
||||
overlay.setAttribute('hidden', 'hidden');
|
||||
if (!document.querySelector('.ms1-doc-overlay:not([hidden])')) {
|
||||
document.body.classList.remove('ms1-doc-open');
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentIndex(overlay) {
|
||||
return parseInt(overlay.getAttribute('data-doc-current-index') || '0', 10);
|
||||
}
|
||||
|
||||
function openDocEditPopup(url) {
|
||||
var w = window.open(
|
||||
url,
|
||||
'ms1DocEdit' + Date.now(),
|
||||
'width=1120,height=820,scrollbars=yes,resizable=yes'
|
||||
);
|
||||
if (w) {
|
||||
var timer = setInterval(function () {
|
||||
if (w.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var docEditLink = e.target.closest('a.ms1-doc-edit-link');
|
||||
if (docEditLink && docEditLink.href) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDocEditPopup(docEditLink.href);
|
||||
return;
|
||||
}
|
||||
|
||||
var trigger = e.target.closest('.ms1-doc-trigger, .btn-doc-trigger');
|
||||
if (trigger) {
|
||||
e.preventDefault();
|
||||
var overlay = findOverlayForTrigger(trigger);
|
||||
if (overlay) {
|
||||
openOverlay(overlay);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var closeEl = e.target.closest('[data-ms1-doc-close]');
|
||||
if (closeEl) {
|
||||
var overlayClose = closeEl.closest('.ms1-doc-overlay');
|
||||
if (overlayClose) {
|
||||
closeOverlay(overlayClose);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var dot = e.target.closest('.ms1-doc-dot');
|
||||
if (dot) {
|
||||
var overlayDot = dot.closest('.ms1-doc-overlay');
|
||||
if (overlayDot) {
|
||||
setPage(overlayDot, parseInt(dot.getAttribute('data-doc-page-index') || '0', 10));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.closest('[data-ms1-doc-prev]')) {
|
||||
var overlayPrev = e.target.closest('.ms1-doc-overlay');
|
||||
if (overlayPrev) {
|
||||
setPage(overlayPrev, getCurrentIndex(overlayPrev) - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.closest('[data-ms1-doc-next]')) {
|
||||
var overlayNext = e.target.closest('.ms1-doc-overlay');
|
||||
if (overlayNext) {
|
||||
setPage(overlayNext, getCurrentIndex(overlayNext) + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
var open = document.querySelector('.ms1-doc-overlay:not([hidden])');
|
||||
if (open) {
|
||||
closeOverlay(open);
|
||||
}
|
||||
});
|
||||
})();
|
||||
113
js/ms1-loader.js
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* MS1 — Loader coureur (overlay + inline).
|
||||
* Usage : Ms1Loader.show() / Ms1Loader.hide() / Ms1Loader.inlineHtml(message)
|
||||
* Norme MS1 : tout message d'attente AJAX/panel doit utiliser Ms1Loader.inlineHtml().
|
||||
*/
|
||||
(function (global, $) {
|
||||
'use strict';
|
||||
|
||||
var config = {
|
||||
src: '/images/ms1-runner-loader.gif',
|
||||
labelWait: 'Chargement...'
|
||||
};
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str == null ? '' : str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function runnerImg() {
|
||||
return '<img class="ms1-loader__runner" src="' + escHtml(config.src) + '" alt="" role="presentation" decoding="async">';
|
||||
}
|
||||
|
||||
function init(options) {
|
||||
if (options) {
|
||||
$.extend(config, options);
|
||||
}
|
||||
}
|
||||
|
||||
function inlineHtml(message) {
|
||||
var msg = message == null || message === '' ? config.labelWait : message;
|
||||
return '<div class="ms1-loader ms1-loader--inline" role="status" aria-live="polite" aria-busy="true">'
|
||||
+ runnerImg()
|
||||
+ '<span class="ms1-loader__label">' + escHtml(msg) + '</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
function show(message) {
|
||||
var $preloader = $('#preloader');
|
||||
if (!$preloader.length) {
|
||||
return;
|
||||
}
|
||||
clearProgress();
|
||||
if (message != null && message !== '') {
|
||||
var $text = $('#preloader_text');
|
||||
if ($text.length) {
|
||||
$text.html(message);
|
||||
}
|
||||
}
|
||||
$preloader.show();
|
||||
}
|
||||
|
||||
function clearProgress() {
|
||||
$('#spinner .ms1-loader__progress').remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4433 — Overlay avec coureur + barre de progression (% réel).
|
||||
* @param {number} current
|
||||
* @param {number} total
|
||||
* @param {string} [message]
|
||||
*/
|
||||
function showProgress(current, total, message) {
|
||||
var $preloader = $('#preloader');
|
||||
if (!$preloader.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var intCurrent = Math.max(0, parseInt(current, 10) || 0);
|
||||
var intTotal = Math.max(1, parseInt(total, 10) || 1);
|
||||
var intPct = Math.min(100, Math.round((intCurrent / intTotal) * 100));
|
||||
|
||||
var $spinner = $('#spinner');
|
||||
var $bar = $spinner.find('.ms1-loader__progress');
|
||||
if (!$bar.length) {
|
||||
$bar = $('<div class="ms1-loader__progress" role="progressbar" aria-valuemin="0" aria-valuemax="100">'
|
||||
+ '<div class="ms1-loader__progress-track">'
|
||||
+ '<div class="ms1-loader__progress-fill"></div>'
|
||||
+ '</div>'
|
||||
+ '<span class="ms1-loader__progress-pct"></span>'
|
||||
+ '</div>');
|
||||
$('#preloader_text').after($bar);
|
||||
}
|
||||
|
||||
$bar.attr('aria-valuenow', intPct);
|
||||
$bar.find('.ms1-loader__progress-fill').css('width', intPct + '%');
|
||||
$bar.find('.ms1-loader__progress-pct').text(intPct + '%');
|
||||
|
||||
if (message != null && message !== '') {
|
||||
var $text = $('#preloader_text');
|
||||
if ($text.length) {
|
||||
$text.html(message);
|
||||
}
|
||||
}
|
||||
|
||||
$preloader.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
clearProgress();
|
||||
$('#preloader').fadeOut('slow');
|
||||
}
|
||||
|
||||
global.Ms1Loader = {
|
||||
init: init,
|
||||
inlineHtml: inlineHtml,
|
||||
show: show,
|
||||
showProgress: showProgress,
|
||||
hide: hide
|
||||
};
|
||||
}(window, jQuery));
|
||||
4877
js/v2/bib-assignments.js
Normal file
1560
js/v2/inscr-gestion.js
Normal file
155
js/v2/promoteur-hub.js
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Hub promoteur — chargement AJAX stats + menu fonctionnalités.
|
||||
* Chargé uniquement sur compte_promoteur_hub.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.MS1_V2_PROMOTEUR_HUB || {};
|
||||
var $ = window.jQuery;
|
||||
|
||||
if (!$) {
|
||||
return;
|
||||
}
|
||||
|
||||
var strAjaxUrl = cfg.ajaxUrl || '';
|
||||
var strWaitMsg = cfg.preloaderWait || '';
|
||||
var i18n = cfg.i18n || {};
|
||||
var strAjaxError = i18n.ajaxError || '';
|
||||
var intHubLoaderPending = 0;
|
||||
|
||||
// MSIN-4401 — Session expiree : recharger la page → formulaire de login.
|
||||
// Detection par code serveur (pas egalite de message FR).
|
||||
function fxHubRedirectIfSessionExpired(r) {
|
||||
if (r && r.code === 'session') {
|
||||
window.location.reload();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fxHubShowLoader() {
|
||||
intHubLoaderPending++;
|
||||
if (intHubLoaderPending !== 1) {
|
||||
return;
|
||||
}
|
||||
if (window.Ms1Loader) {
|
||||
Ms1Loader.show(strWaitMsg);
|
||||
return;
|
||||
}
|
||||
var $preloader = $('#preloader');
|
||||
if ($preloader.length) {
|
||||
$('#preloader_text').html(strWaitMsg);
|
||||
$preloader.show();
|
||||
}
|
||||
}
|
||||
|
||||
function fxHubHideLoader() {
|
||||
intHubLoaderPending = Math.max(0, intHubLoaderPending - 1);
|
||||
if (intHubLoaderPending !== 0) {
|
||||
return;
|
||||
}
|
||||
if (window.Ms1Loader) {
|
||||
Ms1Loader.hide();
|
||||
return;
|
||||
}
|
||||
$('#preloader').fadeOut('slow');
|
||||
}
|
||||
|
||||
function fxHubLoadStats($collapse) {
|
||||
if ($collapse.data('hub-loaded') || !strAjaxUrl) {
|
||||
return;
|
||||
}
|
||||
var intEve = $collapse.data('hub-eve-id');
|
||||
var strLang = $collapse.data('hub-lang') || 'fr';
|
||||
|
||||
fxHubShowLoader();
|
||||
$.getJSON(strAjaxUrl, { a: 'event_stats', eve_id: intEve, lang: strLang })
|
||||
.done(function (r) {
|
||||
if (fxHubRedirectIfSessionExpired(r)) {
|
||||
return;
|
||||
}
|
||||
if (!r || !r.ok) {
|
||||
$collapse.find('.promoteur-hub-stats-placeholder').text(r && r.msg ? r.msg : strAjaxError);
|
||||
return;
|
||||
}
|
||||
$collapse.find('.promoteur-hub-stats-placeholder').replaceWith(r.html || '');
|
||||
if (r.flash) {
|
||||
$collapse.closest('.promoteur-hub-card').find('.promoteur-hub-card-flash-wrap').html(r.flash);
|
||||
}
|
||||
$collapse.data('hub-loaded', 1);
|
||||
})
|
||||
.fail(function () {
|
||||
$collapse.find('.promoteur-hub-stats-placeholder').text(strAjaxError);
|
||||
})
|
||||
.always(function () {
|
||||
fxHubHideLoader();
|
||||
});
|
||||
}
|
||||
|
||||
function fxHubLoadLinks($collapse) {
|
||||
if ($collapse.data('hub-links-loaded') || !strAjaxUrl) {
|
||||
return;
|
||||
}
|
||||
var intEve = $collapse.data('hub-eve-id');
|
||||
var strLang = $collapse.data('hub-lang') || 'fr';
|
||||
|
||||
fxHubShowLoader();
|
||||
$.getJSON(strAjaxUrl, { a: 'event_links', eve_id: intEve, lang: strLang })
|
||||
.done(function (r) {
|
||||
if (fxHubRedirectIfSessionExpired(r)) {
|
||||
return;
|
||||
}
|
||||
if (!r || !r.ok) {
|
||||
$collapse.find('.promoteur-hub-links-placeholder').text(r && r.msg ? r.msg : strAjaxError);
|
||||
return;
|
||||
}
|
||||
$collapse.find('.promoteur-hub-links-placeholder').replaceWith(r.html || '');
|
||||
$collapse.data('hub-links-loaded', 1);
|
||||
})
|
||||
.fail(function () {
|
||||
$collapse.find('.promoteur-hub-links-placeholder').text(strAjaxError);
|
||||
})
|
||||
.always(function () {
|
||||
fxHubHideLoader();
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('show.bs.collapse', '.promoteur-hub-epreuves-collapse', function () {
|
||||
var $card = $(this).closest('.promoteur-hub-card');
|
||||
$card.find('.promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show').not(this).collapse('hide');
|
||||
fxHubLoadStats($(this));
|
||||
});
|
||||
|
||||
$(document).on('show.bs.collapse', '.promoteur-hub-functions', function () {
|
||||
var $card = $(this).closest('.promoteur-hub-card');
|
||||
$card.find('.promoteur-hub-epreuves-collapse.show, .promoteur-hub-functions.show').not(this).collapse('hide');
|
||||
fxHubLoadLinks($(this));
|
||||
});
|
||||
|
||||
// MSIN-4399 — Retour depuis une page événement : scroll + ouvrir le menu fonctionnalités.
|
||||
function fxHubFocusEvent(intEve) {
|
||||
intEve = parseInt(intEve, 10) || 0;
|
||||
if (intEve <= 0) {
|
||||
return;
|
||||
}
|
||||
var $card = $('#hub-eve-' + intEve);
|
||||
if (!$card.length) {
|
||||
return;
|
||||
}
|
||||
if ($card[0].scrollIntoView) {
|
||||
$card[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
var $fn = $('#hub-fn-' + intEve);
|
||||
var $epr = $('#hub-epr-' + intEve);
|
||||
if ($fn.length) {
|
||||
$fn.collapse('show');
|
||||
} else if ($epr.length) {
|
||||
$epr.collapse('show');
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
fxHubFocusEvent(cfg.focusEveId);
|
||||
});
|
||||
})();
|
||||
87
kc.php
@ -1,39 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MSIN-3487 — Point d'entrée QR court.
|
||||
*
|
||||
* Modes :
|
||||
* 1) Résultats événement :
|
||||
* /kc.php?t=eve&id={eve_id}
|
||||
* → inscriptions_evenements_extra.eve_resultats_url
|
||||
* → sinon fallback https://ms1.ca
|
||||
*
|
||||
* 2) Key chain (porte-clés / véhicules) — inchangé :
|
||||
* /kc.php?param1={pk_key_chain}
|
||||
* → key_chain.key_chain_http
|
||||
* → sinon insert + fallback https://ms1.ca
|
||||
*/
|
||||
session_start();
|
||||
global $objDatabase, $vDomaine, $strLangue;
|
||||
require_once "php/inc_fonctions.php";
|
||||
require_once 'php/inc_fonctions.php';
|
||||
|
||||
if (!empty($_GET['param1']))
|
||||
$key_chain_id = $_GET['param1'];
|
||||
else
|
||||
$key_chain_id = 0;
|
||||
$defaut_landingpage="https://ms1.ca";
|
||||
$strType = isset($_GET['t']) ? strtolower(trim((string) $_GET['t'])) : '';
|
||||
|
||||
$sqlkey_chain = "SELECT * FROM key_chain WHERE pk_key_chain = " . $key_chain_id ;
|
||||
$infoCompte = $objDatabase->fxGetRow($sqlkey_chain);
|
||||
if (!$infoCompte==null){
|
||||
|
||||
$mem_count=$infoCompte['key_chain_count']+1;
|
||||
$update_plus="";
|
||||
if ($infoCompte['key_chain_com_id']==0 && isset($_SESSION['com_id'])){
|
||||
$update_plus="key_chain_com_id=".$_SESSION['com_id'].",";}
|
||||
|
||||
$sqlUpdate = "UPDATE key_chain SET ".$update_plus."key_chain_adress_ip='". $_SERVER['REMOTE_ADDR'] ."',key_chain_count = ".$mem_count." WHERE pk_key_chain = " . $key_chain_id ;
|
||||
$qryUpdate = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
header("Location: ".$infoCompte['key_chain_http']);
|
||||
}else{
|
||||
if (isset($_SESSION['com_id'])){
|
||||
$mem_com_id=$_SESSION['com_id'];
|
||||
}else{
|
||||
$mem_com_id=0;
|
||||
// --- MSIN-3487 : redirect résultats par eve_id ---
|
||||
if ($strType === 'eve') {
|
||||
$intEveId = 0;
|
||||
if (isset($_GET['id'])) {
|
||||
$intEveId = intval($_GET['id']);
|
||||
} elseif (isset($_GET['eve_id'])) {
|
||||
$intEveId = intval($_GET['eve_id']);
|
||||
}
|
||||
|
||||
header('Location: ' . fxEveExtraResolveResultatsUrl($intEveId));
|
||||
exit;
|
||||
}
|
||||
|
||||
$sqlUpdate = "insert key_chain SET key_chain_com_id=".$mem_com_id.",key_chain_timestamp=now(),key_chain_http='".$defaut_landingpage."',key_chain_adress_ip='". $_SERVER['REMOTE_ADDR'] ."',key_chain_count = 1,pk_key_chain=" . $key_chain_id;
|
||||
// --- Key chain historique ---
|
||||
$key_chain_id = !empty($_GET['param1']) ? intval($_GET['param1']) : 0;
|
||||
$defaut_landingpage = fxEveExtraResultatsFallbackUrl();
|
||||
|
||||
$sqlkey_chain = 'SELECT * FROM key_chain WHERE pk_key_chain = ' . $key_chain_id;
|
||||
$infoCompte = $objDatabase->fxGetRow($sqlkey_chain);
|
||||
if (!$infoCompte == null) {
|
||||
|
||||
$mem_count = $infoCompte['key_chain_count'] + 1;
|
||||
$update_plus = '';
|
||||
if ($infoCompte['key_chain_com_id'] == 0 && isset($_SESSION['com_id'])) {
|
||||
$update_plus = 'key_chain_com_id=' . intval($_SESSION['com_id']) . ',';
|
||||
}
|
||||
|
||||
$sqlUpdate = 'UPDATE key_chain SET ' . $update_plus
|
||||
. "key_chain_adress_ip='" . $objDatabase->fxEscape($_SERVER['REMOTE_ADDR']) . "',"
|
||||
. 'key_chain_count = ' . intval($mem_count)
|
||||
. ' WHERE pk_key_chain = ' . $key_chain_id;
|
||||
$qryUpdate = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
header("Location: $defaut_landingpage" );
|
||||
header('Location: ' . $infoCompte['key_chain_http']);
|
||||
} else {
|
||||
if (isset($_SESSION['com_id'])) {
|
||||
$mem_com_id = intval($_SESSION['com_id']);
|
||||
} else {
|
||||
$mem_com_id = 0;
|
||||
}
|
||||
|
||||
$sqlUpdate = 'INSERT key_chain SET key_chain_com_id=' . $mem_com_id
|
||||
. ',key_chain_timestamp=now(),'
|
||||
. "key_chain_http='" . $objDatabase->fxEscape($defaut_landingpage) . "',"
|
||||
. "key_chain_adress_ip='" . $objDatabase->fxEscape($_SERVER['REMOTE_ADDR']) . "',"
|
||||
. 'key_chain_count = 1,pk_key_chain=' . $key_chain_id;
|
||||
$qryUpdate = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
header('Location: ' . $defaut_landingpage);
|
||||
}
|
||||
exit;
|
||||
|
||||
6
libs/tippy.js-6.3.7/popper.min.js
vendored
Normal file
2
libs/tippy.js-6.3.7/tippy-bundle.umd.min.js
vendored
Normal file
1
libs/tippy.js-6.3.7/tippy.css
Normal file
@ -0,0 +1 @@
|
||||
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
|
||||
@ -25,7 +25,9 @@ return array(
|
||||
'//plugins/kartik-v-bootstrap-popover-x-36c3827/css/bootstrap-popover-x.min.css',
|
||||
'//plugins/select2-4.0.13/dist/css/select2.min.css',
|
||||
'//plugins/select2-bootstrap4-theme-1.5.2/dist/select2-bootstrap4.min.css',
|
||||
'//css/style.css'
|
||||
'//css/style.css',
|
||||
'//css/ms1-doc.css',
|
||||
'//css/ms1-loader.css'
|
||||
),
|
||||
'js' => array(
|
||||
'//libs/jquery/jquery-3.4.1.min.js',
|
||||
@ -43,6 +45,8 @@ return array(
|
||||
'//plugins/flatpickr-4.6.13/dist/flatpickr.min.js',
|
||||
'//plugins/bootbox-5.4/bootbox.min.js',
|
||||
'//plugins/select2-4.0.13/dist/js/select2.full.min.js',
|
||||
'//plugins/kartik-v-bootstrap-popover-x-36c3827/js/bootstrap-popover-x.min.js'
|
||||
'//plugins/kartik-v-bootstrap-popover-x-36c3827/js/bootstrap-popover-x.min.js',
|
||||
'//js/ms1-doc.js',
|
||||
'//js/ms1-loader.js'
|
||||
)
|
||||
);
|
||||
216
mobile.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
define('MS1_INSCR_GESTION_ENTRY', true);
|
||||
|
||||
require_once('php/inc_start_time.php');
|
||||
require_once('php/inc_fonctions.php');
|
||||
require_once('php/inc_fx_messages.php');
|
||||
require_once('php/inc_fx_compte.php');
|
||||
require_once('php/inc_fx_promoteur.php');
|
||||
require_once('php/inc_fx_inscriptions_gestion.php');
|
||||
require_once('php/inc_fx_eve_acces_promoteur.php');
|
||||
require_once('php/inc_v2_assets.php');
|
||||
|
||||
global $objDatabase, $vDomaine, $vPage, $vtexte_page;
|
||||
|
||||
if (!empty($_GET['lang'])) {
|
||||
$strLangue = $_GET['lang'];
|
||||
} else {
|
||||
$strLangue = 'fr';
|
||||
}
|
||||
|
||||
fxInscrGestionInitTextes($strLangue);
|
||||
fxV2RegisterScript('inscr-gestion');
|
||||
|
||||
$vPage = 'mobile.php';
|
||||
$strCode = 'mobile';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['form_action']) && !isset($_SESSION['com_id'])) {
|
||||
$strFormAction = base64_decode(urldecode($_POST['form_action']), true);
|
||||
if ($strFormAction === 'login') {
|
||||
fxLoginCompte($_POST, $strLangue);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['com_id'])) {
|
||||
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
|
||||
$_SESSION['affiche_pas_connexion'] = 'true';
|
||||
|
||||
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
|
||||
$strMetaDescription = '';
|
||||
$strMetaKeywords = '';
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = true;
|
||||
|
||||
require_once('inc_header.php');
|
||||
?>
|
||||
<div id="page">
|
||||
<div id="main">
|
||||
<div class="container bg-white p-3 p-xl-4">
|
||||
<?php
|
||||
if (isset($_SESSION['msg'])) {
|
||||
echo fxMessage($_SESSION['msg'], '', $strLangue);
|
||||
}
|
||||
fxInscrGestionRenderLoginPage($strLangue);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once('inc_footer.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// MSIN-4401 — Lien d'invite destiné à un autre compte : forcer déconnexion.
|
||||
$intInviteCom = intval($_GET['invite_com'] ?? 0);
|
||||
$strInviteSig = (string)($_GET['invite_sig'] ?? '');
|
||||
$intEveForInvite = 0;
|
||||
if (!empty($_GET['promoteur_eve_id'])) {
|
||||
$intEveForInvite = fxInscrGestionParseEveId($_GET['promoteur_eve_id']);
|
||||
}
|
||||
if ($intInviteCom > 0 && $intEveForInvite > 0
|
||||
&& function_exists('fxEveAccesPromoteurInviteSigValid')
|
||||
&& fxEveAccesPromoteurInviteSigValid($intInviteCom, $intEveForInvite, $strInviteSig)
|
||||
&& intval($_SESSION['com_id']) !== $intInviteCom) {
|
||||
|
||||
global $objDatabase;
|
||||
$arrInviteCompte = $objDatabase->fxGetRow(
|
||||
'SELECT com_id, com_prenom, com_nom, com_courriel, com_login FROM inscriptions_comptes WHERE com_id = '
|
||||
. $intInviteCom . ' AND com_actif = 1 LIMIT 1'
|
||||
);
|
||||
|
||||
$strSessionLabel = trim(
|
||||
(string)($_SESSION['com_info']['com_prenom'] ?? $_SESSION['com_prenom'] ?? '')
|
||||
. ' '
|
||||
. (string)($_SESSION['com_info']['com_nom'] ?? '')
|
||||
);
|
||||
if ($strSessionLabel === '') {
|
||||
$strSessionLabel = (string)($_SESSION['com_info']['com_courriel'] ?? ('#' . intval($_SESSION['com_id'])));
|
||||
}
|
||||
$strTargetLabel = $arrInviteCompte
|
||||
? trim(($arrInviteCompte['com_prenom'] ?? '') . ' ' . ($arrInviteCompte['com_nom'] ?? ''))
|
||||
: '';
|
||||
if ($strTargetLabel === '' && $arrInviteCompte) {
|
||||
$strTargetLabel = (string)($arrInviteCompte['com_courriel'] ?? $arrInviteCompte['com_login'] ?? ('#' . $intInviteCom));
|
||||
}
|
||||
if ($strTargetLabel === '') {
|
||||
$strTargetLabel = '#' . $intInviteCom;
|
||||
}
|
||||
$strTargetMail = $arrInviteCompte ? (string)($arrInviteCompte['com_courriel'] ?? '') : '';
|
||||
|
||||
$strReturnPath = (string)(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/mobile');
|
||||
$strQs = (string)(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY) ?: '');
|
||||
if ($strQs !== '') {
|
||||
$strReturnPath .= '?' . $strQs;
|
||||
}
|
||||
$strLogoutUrl = $vDomaine . '/logout.php?lang=' . urlencode($strLangue)
|
||||
. '&return=' . rawurlencode($strReturnPath);
|
||||
$strStayUrl = $vDomaine . ($strLangue === 'en' ? '/account' : '/compte');
|
||||
|
||||
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
|
||||
$strMetaDescription = '';
|
||||
$strMetaKeywords = '';
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = false;
|
||||
|
||||
$strMailPart = ($strTargetMail !== '')
|
||||
? ' (' . htmlspecialchars($strTargetMail, ENT_QUOTES, 'UTF-8') . ')'
|
||||
: '';
|
||||
$strSignedAs = str_replace(
|
||||
'%name%',
|
||||
'<strong>' . htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8') . '</strong>',
|
||||
fxInscrGestionT('mobile_invite_conflict_signed_as')
|
||||
);
|
||||
$strInviteFor = str_replace(
|
||||
array('%name%', '%mail_part%'),
|
||||
array(
|
||||
'<strong>' . htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8') . '</strong>',
|
||||
$strMailPart,
|
||||
),
|
||||
fxInscrGestionT('mobile_invite_conflict_for')
|
||||
);
|
||||
|
||||
require_once('inc_header.php');
|
||||
?>
|
||||
<div id="page">
|
||||
<div id="main">
|
||||
<div class="container bg-white p-3 p-xl-4">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p class="mb-2"><?php echo $strSignedAs; ?></p>
|
||||
<p class="mb-3"><?php echo $strInviteFor; ?></p>
|
||||
<a class="btn btn-primary rounded-0 mr-2" href="<?php echo htmlspecialchars($strLogoutUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo htmlspecialchars(fxInscrGestionT('mobile_invite_conflict_btn_logout'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary rounded-0" href="<?php echo htmlspecialchars($strStayUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo htmlspecialchars(fxInscrGestionT('mobile_invite_conflict_btn_stay'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once('inc_footer.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
unset($_SESSION['affiche_pas_connexion']);
|
||||
|
||||
$intEveId = 0;
|
||||
|
||||
if (!empty($_GET['promoteur_eve_id'])) {
|
||||
$intEveId = fxInscrGestionParseEveId($_GET['promoteur_eve_id']);
|
||||
}
|
||||
|
||||
$tabEveIds = fxInscrGestionGetPromoteurEveIds($_SESSION['com_id']);
|
||||
|
||||
if ($intEveId > 0 && !fxInscrGestionCanAccess($_SESSION['com_id'], $intEveId)) {
|
||||
header('Location: ' . $vDomaine . '/mobile');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($intEveId === 0 && count($tabEveIds) === 1) {
|
||||
header('Location: ' . $vDomaine . '/mobile?promoteur_eve_id=' . urlencode(base64_encode($tabEveIds[0])));
|
||||
exit;
|
||||
}
|
||||
|
||||
$strMetaTitle = fxInscrGestionT('inscr_gestion_mobile_title');
|
||||
$strMetaDescription = '';
|
||||
$strMetaKeywords = '';
|
||||
$blnBoutonRetour = false;
|
||||
$footer_script = ($intEveId > 0);
|
||||
$strSousTitre = '';
|
||||
|
||||
if ($intEveId > 0) {
|
||||
$arrEvenement = fxGetEvenementsId($intEveId);
|
||||
$strSousTitre = '<br><small class="text-muted">' . fxUnescape($arrEvenement['eve_nom_' . $strLangue]) . '</small>';
|
||||
}
|
||||
|
||||
require_once('inc_header.php');
|
||||
|
||||
?>
|
||||
<div id="page">
|
||||
<div id="main">
|
||||
<div class="container bg-white p-3 p-xl-4">
|
||||
<?php
|
||||
if (isset($_SESSION['msg'])) {
|
||||
echo fxMessage($_SESSION['msg'], '', $strLangue);
|
||||
}
|
||||
|
||||
if ($intEveId === 0) {
|
||||
if (count($tabEveIds) === 0) {
|
||||
echo '<div class="alert alert-warning" role="alert">';
|
||||
echo fxInscrGestionEsc(fxInscrGestionT('mobile_no_acces_v2'));
|
||||
echo '</div>';
|
||||
} else {
|
||||
fxInscrGestionRenderEventPicker($tabEveIds, $strLangue, fxInscrGestionBaseUrl($strLangue, true));
|
||||
}
|
||||
} else {
|
||||
include('inc_tableau_inscriptions_gestion.php');
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once('inc_footer.php');
|
||||
134
ms1_kc_qr_batch.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Génération par lot de QR codes key_chain (kc.php?param1=...).
|
||||
*
|
||||
* CLI:
|
||||
* php ms1_kc_qr_batch.php --start=500 --count=100
|
||||
*
|
||||
* Web (protégé par KEYCHAIN_HTTP_EDIT_SECRET):
|
||||
* /ms1_kc_qr_batch.php?k=<secret>&start=500&count=100
|
||||
*/
|
||||
$blnNoMaintenance = true;
|
||||
|
||||
function kc_qr_batch_is_cli()
|
||||
{
|
||||
return (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg');
|
||||
}
|
||||
|
||||
if (kc_qr_batch_is_cli()) {
|
||||
require_once __DIR__ . '/php/inc_settings.php';
|
||||
} else {
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
header('X-Robots-Tag: noindex, nofollow');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/php/inc_fx_kc_qr.php';
|
||||
|
||||
function kc_qr_batch_params()
|
||||
{
|
||||
$source = kc_qr_batch_is_cli() ? kc_qr_batch_cli_args() : $_GET;
|
||||
|
||||
return [
|
||||
'start' => isset($source['start']) ? intval($source['start']) : 500,
|
||||
'count' => isset($source['count']) ? intval($source['count']) : 100,
|
||||
'base_url' => isset($source['base_url']) ? trim((string) $source['base_url']) : 'https://ms1inscription.com',
|
||||
'module_size' => isset($source['module_size']) ? intval($source['module_size']) : 18,
|
||||
'with_logo' => !isset($source['with_logo']) || (string) $source['with_logo'] !== '0',
|
||||
'output_dir' => isset($source['output_dir']) ? trim((string) $source['output_dir']) : (__DIR__ . '/output/kc_qr'),
|
||||
];
|
||||
}
|
||||
|
||||
function kc_qr_batch_cli_args()
|
||||
{
|
||||
global $argv;
|
||||
$params = [];
|
||||
if (!is_array($argv)) {
|
||||
return $params;
|
||||
}
|
||||
foreach (array_slice($argv, 1) as $arg) {
|
||||
if (strpos($arg, '--') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$pair = explode('=', substr($arg, 2), 2);
|
||||
$params[$pair[0]] = $pair[1] ?? '1';
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
function kc_qr_batch_web_authorized()
|
||||
{
|
||||
$strSecret = isset($_GET['k']) ? (string) $_GET['k'] : '';
|
||||
return ($strSecret !== '' && defined('KEYCHAIN_HTTP_EDIT_SECRET') && hash_equals(KEYCHAIN_HTTP_EDIT_SECRET, $strSecret));
|
||||
}
|
||||
|
||||
if (!kc_qr_batch_is_cli()) {
|
||||
if (!kc_qr_batch_web_authorized()) {
|
||||
http_response_code(403);
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$params = kc_qr_batch_params();
|
||||
|
||||
if ($params['count'] < 1 || $params['count'] > 500) {
|
||||
$msg = 'Le paramètre count doit être entre 1 et 500.';
|
||||
if (kc_qr_batch_is_cli()) {
|
||||
fwrite(STDERR, $msg . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
http_response_code(400);
|
||||
echo $msg;
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = fxKcQrGenerateBatch($params);
|
||||
|
||||
if (kc_qr_batch_is_cli()) {
|
||||
echo 'QR codes générés: ' . $result['count'] . PHP_EOL;
|
||||
echo 'Plage param1: ' . $result['start'] . ' → ' . $result['end'] . PHP_EOL;
|
||||
echo 'Dossier: ' . $result['output_dir'] . PHP_EOL;
|
||||
echo 'Manifeste: ' . $result['manifest'] . PHP_EOL;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>QR key_chain — génération</title>
|
||||
<style>
|
||||
body { font-family: "Segoe UI", sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; color: #1a1a2e; }
|
||||
.ok { background: #e6f4ed; border: 1px solid #b8e6d0; padding: 1rem; border-radius: 12px; }
|
||||
code { background: #f3f4f6; padding: 0.15rem 0.35rem; border-radius: 4px; }
|
||||
ul { padding-left: 1.2rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>QR codes générés</h1>
|
||||
<div class="ok">
|
||||
<p><strong><?php echo (int) $result['count']; ?></strong> fichiers créés
|
||||
(param1 <?php echo (int) $result['start']; ?> → <?php echo (int) $result['end']; ?>).</p>
|
||||
<p>Dossier serveur : <code><?php echo htmlspecialchars($result['output_dir'], ENT_QUOTES, 'UTF-8'); ?></code></p>
|
||||
<p>Manifeste CSV : <code><?php echo htmlspecialchars(basename($result['manifest']), ENT_QUOTES, 'UTF-8'); ?></code></p>
|
||||
</div>
|
||||
<h2>Exemples</h2>
|
||||
<ul>
|
||||
<?php
|
||||
$preview = array_slice($result['files'], 0, 6);
|
||||
foreach ($preview as $file) {
|
||||
$name = basename($file);
|
||||
echo '<li><code>' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '</code></li>';
|
||||
}
|
||||
if (count($result['files']) > 6) {
|
||||
echo '<li>… et ' . (count($result['files']) - 6) . ' autres</li>';
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
<p>Relancer avec d’autres paramètres :
|
||||
<code>?k=…&start=<?php echo (int) $params['start']; ?>&count=<?php echo (int) $params['count']; ?></code></p>
|
||||
</body>
|
||||
</html>
|
||||
682
ms1_kc_set.php
Normal file
@ -0,0 +1,682 @@
|
||||
<?php
|
||||
/**
|
||||
* Outil temporaire : modifier key_chain_http pour un pk_key_chain.
|
||||
* Accès 1 : /ms1_kc_set.php?k=<KEYCHAIN_HTTP_EDIT_SECRET>
|
||||
* Accès 2 : compte connecté dont com_id est dans KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS
|
||||
*/
|
||||
$blnNoMaintenance = true;
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
|
||||
header('X-Robots-Tag: noindex, nofollow');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
|
||||
global $objDatabase, $vDomaine;
|
||||
|
||||
function kc_set_allowed_com_ids()
|
||||
{
|
||||
if (!defined('KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS') || KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS === '') {
|
||||
return [];
|
||||
}
|
||||
$arr = array_map('intval', explode(',', KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS));
|
||||
return array_values(array_filter($arr, function ($id) {
|
||||
return $id > 0;
|
||||
}));
|
||||
}
|
||||
|
||||
function kc_set_com_id_authorized()
|
||||
{
|
||||
if (!isset($_SESSION['com_id']) || (int) $_SESSION['com_id'] <= 0) {
|
||||
return false;
|
||||
}
|
||||
return in_array((int) $_SESSION['com_id'], kc_set_allowed_com_ids(), true);
|
||||
}
|
||||
|
||||
$strSecret = isset($_GET['k']) ? (string) $_GET['k'] : '';
|
||||
$blnAuthSecret = ($strSecret !== '' && hash_equals(KEYCHAIN_HTTP_EDIT_SECRET, $strSecret));
|
||||
if ($blnAuthSecret) {
|
||||
$_SESSION['kc_edit_auth_key'] = 1;
|
||||
}
|
||||
$blnAuthUser = kc_set_com_id_authorized();
|
||||
$blnAuth = $blnAuthSecret || $blnAuthUser || !empty($_SESSION['kc_edit_auth_key']);
|
||||
|
||||
if (!$blnAuth) {
|
||||
if (!isset($_SESSION['com_id']) && kc_set_allowed_com_ids() !== []) {
|
||||
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
|
||||
header('Location: ' . $vDomaine . '/compte');
|
||||
exit;
|
||||
}
|
||||
http_response_code(403);
|
||||
echo '<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><title>Accès refusé</title></head><body>';
|
||||
echo '<p>Accès refusé.</p>';
|
||||
if (kc_set_allowed_com_ids() !== []) {
|
||||
echo '<p><a href="' . htmlspecialchars($vDomaine . '/compte', ENT_QUOTES, 'UTF-8') . '">Se connecter</a></p>';
|
||||
}
|
||||
echo '</body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($_SESSION['kc_edit_csrf'])) {
|
||||
$_SESSION['kc_edit_csrf'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
$strCsrf = $_SESSION['kc_edit_csrf'];
|
||||
$strBasePath = $_SERVER['PHP_SELF'];
|
||||
|
||||
function kc_set_page_url(array $params = [])
|
||||
{
|
||||
global $strBasePath;
|
||||
$query = $params;
|
||||
if (!empty($_SESSION['kc_edit_auth_key']) && !isset($query['k'])) {
|
||||
$query = array_merge(['k' => KEYCHAIN_HTTP_EDIT_SECRET], $query);
|
||||
}
|
||||
$qs = http_build_query($query);
|
||||
return $strBasePath . ($qs !== '' ? '?' . $qs : '');
|
||||
}
|
||||
|
||||
$strPageUrl = kc_set_page_url();
|
||||
$strBaseUrlHtml = htmlspecialchars($strPageUrl, ENT_QUOTES, 'UTF-8');
|
||||
$blnCanShareLink = !empty($_SESSION['kc_edit_auth_key']);
|
||||
$strShareUrl = '';
|
||||
if ($blnCanShareLink) {
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$strShareUrl = $scheme . '://' . $_SERVER['HTTP_HOST'] . kc_set_page_url(['k' => KEYCHAIN_HTTP_EDIT_SECRET]);
|
||||
}
|
||||
|
||||
$strUserName = '';
|
||||
if ($blnAuthUser && !empty($_SESSION['com_prenom'])) {
|
||||
$strUserName = trim((string) $_SESSION['com_prenom']);
|
||||
} elseif ($blnAuthUser && !empty($_SESSION['com_info']['com_prenom'])) {
|
||||
$strUserName = trim((string) $_SESSION['com_info']['com_prenom']);
|
||||
}
|
||||
|
||||
$strMessage = '';
|
||||
$strMessageClass = '';
|
||||
$intPk = 0;
|
||||
$strCurrentHttp = '';
|
||||
$blnShowUrlForm = false;
|
||||
$strVehiculeSaisi = '';
|
||||
|
||||
if (!empty($_GET['ok'])) {
|
||||
$strMessage = 'Le lien de votre code QR a été enregistré. Vous pouvez modifier un autre véhicule ci-dessous.';
|
||||
$strMessageClass = 'ok';
|
||||
}
|
||||
|
||||
function kc_set_parse_vehicule_no($input)
|
||||
{
|
||||
$input = trim((string) $input);
|
||||
if ($input === '' || !ctype_digit($input)) {
|
||||
return 0;
|
||||
}
|
||||
$no = intval($input);
|
||||
if ($no <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return $no + KEYCHAIN_HTTP_EDIT_SHIFT;
|
||||
}
|
||||
|
||||
function kc_set_validate_url($url)
|
||||
{
|
||||
$url = trim($url);
|
||||
if ($url === '') {
|
||||
return false;
|
||||
}
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
return false;
|
||||
}
|
||||
$parts = parse_url($url);
|
||||
if (empty($parts['scheme']) || !in_array(strtolower($parts['scheme']), ['http', 'https'], true)) {
|
||||
return false;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
function kc_set_save_lien_for_pk($intPk, $strVehiculeSaisi, $strNewHttp)
|
||||
{
|
||||
global $objDatabase, $strMessage, $strMessageClass, $strCurrentHttp, $blnShowUrlForm;
|
||||
|
||||
if ($strVehiculeSaisi !== '' && kc_set_parse_vehicule_no($strVehiculeSaisi) !== $intPk) {
|
||||
$intPk = 0;
|
||||
}
|
||||
if ($intPk <= 0) {
|
||||
$strMessage = 'Veuillez recommencer avec votre numéro de véhicule.';
|
||||
$strMessageClass = 'err';
|
||||
return false;
|
||||
}
|
||||
$sqlCheck = 'SELECT pk_key_chain FROM key_chain WHERE pk_key_chain = ' . $intPk;
|
||||
if ($objDatabase->fxGetRow($sqlCheck) === null) {
|
||||
$strMessage = 'Code QR introuvable. Vérifiez votre numéro de véhicule.';
|
||||
$strMessageClass = 'err';
|
||||
return false;
|
||||
}
|
||||
$sqlUpdate = "UPDATE key_chain SET key_chain_http = '" . $objDatabase->fxEscape($strNewHttp) . "' WHERE pk_key_chain = " . $intPk;
|
||||
if ($objDatabase->fxQuery($sqlUpdate) === false) {
|
||||
$strMessage = 'Impossible d’enregistrer pour le moment. Réessayez plus tard.';
|
||||
$strMessageClass = 'err';
|
||||
$blnShowUrlForm = true;
|
||||
$strCurrentHttp = $strNewHttp;
|
||||
return false;
|
||||
}
|
||||
header('Location: ' . kc_set_page_url(['ok' => '1']));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$strPostedCsrf = isset($_POST['csrf']) ? (string) $_POST['csrf'] : '';
|
||||
if (!hash_equals($strCsrf, $strPostedCsrf)) {
|
||||
$strMessage = 'Veuillez recharger la page et réessayer.';
|
||||
$strMessageClass = 'err';
|
||||
} else {
|
||||
$strAction = isset($_POST['action']) ? (string) $_POST['action'] : '';
|
||||
|
||||
if ($strAction === 'load') {
|
||||
$strVehiculeSaisi = isset($_POST['vehicule_no']) ? trim((string) $_POST['vehicule_no']) : '';
|
||||
$intPk = kc_set_parse_vehicule_no($strVehiculeSaisi);
|
||||
if ($intPk <= 0) {
|
||||
$strMessage = 'Veuillez entrer un numéro de véhicule valide.';
|
||||
$strMessageClass = 'err';
|
||||
} else {
|
||||
$sql = 'SELECT key_chain_http FROM key_chain WHERE pk_key_chain = ' . $intPk;
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
if ($row === null || !isset($row['key_chain_http'])) {
|
||||
$strMessage = 'Aucun code QR trouvé pour ce numéro de véhicule.';
|
||||
$strMessageClass = 'err';
|
||||
} else {
|
||||
$strCurrentHttp = $row['key_chain_http'];
|
||||
$blnShowUrlForm = true;
|
||||
}
|
||||
}
|
||||
} elseif ($strAction === 'save') {
|
||||
$intPk = intval(isset($_POST['pk']) ? $_POST['pk'] : 0);
|
||||
$strVehiculeSaisi = isset($_POST['vehicule_no']) ? trim((string) $_POST['vehicule_no']) : '';
|
||||
$strLienPost = isset($_POST['lien_qr']) ? trim((string) $_POST['lien_qr']) : '';
|
||||
$strNewHttp = kc_set_validate_url($strLienPost);
|
||||
if ($strNewHttp === false) {
|
||||
$strMessage = 'Veuillez entrer une adresse Web valide (commençant par http:// ou https://).';
|
||||
$strMessageClass = 'err';
|
||||
$blnShowUrlForm = true;
|
||||
$strCurrentHttp = $strLienPost;
|
||||
} else {
|
||||
kc_set_save_lien_for_pk($intPk, $strVehiculeSaisi, $strNewHttp);
|
||||
}
|
||||
} elseif ($strAction === 'save_ms1') {
|
||||
$intPk = intval(isset($_POST['pk']) ? $_POST['pk'] : 0);
|
||||
$strVehiculeSaisi = isset($_POST['vehicule_no']) ? trim((string) $_POST['vehicule_no']) : '';
|
||||
kc_set_save_lien_for_pk($intPk, $strVehiculeSaisi, 'https://ms1.ca');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Code QR — lien de redirection</title>
|
||||
<style>
|
||||
:root {
|
||||
--ms1-orange: #ff8c00;
|
||||
--ms1-orange-dark: #e67a00;
|
||||
--ms1-ink: #1a1a2e;
|
||||
--ms1-card: #ffffff;
|
||||
--ms1-muted: #5c6370;
|
||||
--ms1-ok: #0d7a4a;
|
||||
--ms1-err: #b42318;
|
||||
--radius: 16px;
|
||||
--shadow: 0 12px 40px rgba(26, 26, 46, 0.12);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
color: var(--ms1-ink);
|
||||
background:
|
||||
radial-gradient(ellipse 80% 50% at 50% -10%, rgba(255, 140, 0, 0.25), transparent),
|
||||
linear-gradient(160deg, #f8f4ef 0%, #eef2f7 45%, #e8edf5 100%);
|
||||
padding: 1.5rem 1rem 3rem;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
animation: fadeUp 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.qr-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 0.75rem;
|
||||
background: var(--ms1-ink);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 4px;
|
||||
box-shadow: 0 6px 20px rgba(26, 26, 46, 0.2);
|
||||
}
|
||||
|
||||
.qr-icon span {
|
||||
background: var(--ms1-orange);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.qr-icon span:nth-child(5) { background: #fff; }
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.user-bar {
|
||||
margin-top: 0.5rem;
|
||||
color: var(--ms1-muted);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.intro {
|
||||
text-align: center;
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.6;
|
||||
color: var(--ms1-muted);
|
||||
margin: 0 0 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.steps {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.step {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
background: #dde3eb;
|
||||
color: var(--ms1-muted);
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
background: var(--ms1-orange);
|
||||
color: #fff;
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 4px 14px rgba(255, 140, 0, 0.45);
|
||||
}
|
||||
|
||||
.step.done {
|
||||
background: var(--ms1-ok);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 2rem;
|
||||
height: 3px;
|
||||
align-self: center;
|
||||
background: #dde3eb;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--ms1-card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 1.75rem 1.5rem 1.5rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: 0.6rem;
|
||||
color: var(--ms1-ink);
|
||||
}
|
||||
|
||||
.field-wrap {
|
||||
position: relative;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.input-vehicule {
|
||||
width: 100%;
|
||||
padding: 1rem 1.15rem;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
letter-spacing: 0.08em;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.input-vehicule:focus {
|
||||
outline: none;
|
||||
border-color: var(--ms1-orange);
|
||||
box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2);
|
||||
}
|
||||
|
||||
.input-lien {
|
||||
width: 100%;
|
||||
padding: 1rem 1.15rem;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.45;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.input-lien:focus {
|
||||
outline: none;
|
||||
border-color: var(--ms1-orange);
|
||||
box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2);
|
||||
}
|
||||
|
||||
.input-lien::placeholder {
|
||||
color: #9aa3af;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.75rem 1rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.btn-enter {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.85rem 1.5rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(180deg, var(--ms1-orange) 0%, var(--ms1-orange-dark) 100%);
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 6px 20px rgba(255, 140, 0, 0.4);
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.btn-enter:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 28px rgba(255, 140, 0, 0.5);
|
||||
}
|
||||
|
||||
.btn-enter:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-ms1 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.85rem 1.25rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--ms1-ink);
|
||||
background: #fff;
|
||||
border: 2px solid var(--ms1-ink);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.btn-ms1:hover {
|
||||
background: var(--ms1-ink);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-enter:disabled {
|
||||
opacity: 0.85;
|
||||
cursor: wait;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-enter .key-cap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 1.75rem;
|
||||
padding: 0 0.4rem;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--ms1-muted);
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.msg {
|
||||
margin: 0 0 1.25rem;
|
||||
padding: 1rem 1.15rem;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
animation: fadeUp 0.35s ease-out;
|
||||
}
|
||||
|
||||
.msg.ok {
|
||||
background: #e6f4ed;
|
||||
color: var(--ms1-ok);
|
||||
border: 1px solid #b8e6d0;
|
||||
}
|
||||
|
||||
.msg.err {
|
||||
background: #fdecea;
|
||||
color: var(--ms1-err);
|
||||
border: 1px solid #f5c4c0;
|
||||
}
|
||||
|
||||
.back {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.back a {
|
||||
color: var(--ms1-muted);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
transition: color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.back a:hover {
|
||||
color: var(--ms1-orange-dark);
|
||||
background: rgba(255, 140, 0, 0.1);
|
||||
}
|
||||
|
||||
.share-box {
|
||||
background: #fff8ee;
|
||||
border: 1px dashed var(--ms1-orange);
|
||||
border-radius: 12px;
|
||||
padding: 1rem 1.15rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.share-box strong {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.share-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.share-url {
|
||||
flex: 1 1 12rem;
|
||||
min-width: 0;
|
||||
padding: 0.55rem 0.65rem;
|
||||
font-size: 0.82rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.btn-copy {
|
||||
padding: 0.55rem 1rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: var(--ms1-ink);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-copy:hover { background: #2d2d44; }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.actions { flex-direction: column; align-items: stretch; }
|
||||
.btn-enter { justify-content: center; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="hero">
|
||||
<div class="qr-icon" aria-hidden="true">
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<h1>Code QR</h1>
|
||||
<?php if ($strUserName !== '') { ?>
|
||||
<p class="user-bar">Bonjour <?php echo htmlspecialchars($strUserName, ENT_QUOTES, 'UTF-8'); ?>.</p>
|
||||
<?php } ?>
|
||||
</header>
|
||||
|
||||
<p class="intro">Entrez votre numéro de véhicule. Vous pourrez ainsi changer le lien de votre code QR.</p>
|
||||
|
||||
<?php if ($blnCanShareLink && $strShareUrl !== '') { ?>
|
||||
<div class="share-box">
|
||||
<strong>Lien à envoyer aux autres</strong>
|
||||
<p class="hint" style="margin:0 0 0.65rem;">Chaque personne ouvre ce lien et entre <em>son</em> numéro de véhicule.</p>
|
||||
<div class="share-row">
|
||||
<input type="text" class="share-url" id="share_url" readonly value="<?php echo htmlspecialchars($strShareUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<button type="button" class="btn-copy" id="btn_copy_link">Copier le lien</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="steps" aria-hidden="true">
|
||||
<span class="step <?php echo !$blnShowUrlForm ? 'active' : 'done'; ?>">1</span>
|
||||
<span class="step-line"></span>
|
||||
<span class="step <?php echo $blnShowUrlForm ? 'active' : ''; ?>">2</span>
|
||||
</div>
|
||||
|
||||
<?php if ($strMessage !== '') { ?>
|
||||
<div class="msg <?php echo htmlspecialchars($strMessageClass, ENT_QUOTES, 'UTF-8'); ?>" role="status"><?php
|
||||
echo htmlspecialchars($strMessage, ENT_QUOTES, 'UTF-8');
|
||||
?></div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="card">
|
||||
<?php if (!$blnShowUrlForm) { ?>
|
||||
<form method="post" action="<?php echo $strBaseUrlHtml; ?>" id="frm_load">
|
||||
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($strCsrf, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<input type="hidden" name="action" value="load">
|
||||
<label class="field-label" for="vehicule_no">Numéro de véhicule</label>
|
||||
<div class="field-wrap">
|
||||
<input type="text" inputmode="numeric" pattern="[0-9]+" class="input-vehicule" id="vehicule_no" name="vehicule_no" required autofocus placeholder="ex. 8" value="<?php echo htmlspecialchars($strVehiculeSaisi, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn-enter">
|
||||
<span class="key-cap">↵</span>
|
||||
<span>Continuer</span>
|
||||
</button>
|
||||
<p class="hint">Remplissez le numéro, puis cliquez ou appuyez sur Entrée.</p>
|
||||
</div>
|
||||
</form>
|
||||
<?php } else { ?>
|
||||
<?php if ($strVehiculeSaisi !== '') { ?>
|
||||
<p class="hint" style="margin:0 0 1rem;">Véhicule <strong><?php echo htmlspecialchars($strVehiculeSaisi, ENT_QUOTES, 'UTF-8'); ?></strong></p>
|
||||
<?php } ?>
|
||||
<form method="post" action="<?php echo $strBaseUrlHtml; ?>" id="frm_save">
|
||||
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($strCsrf, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="pk" value="<?php echo (int) $intPk; ?>">
|
||||
<input type="hidden" name="vehicule_no" value="<?php echo htmlspecialchars($strVehiculeSaisi, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<label class="field-label" for="lien_qr">Lien de votre code QR</label>
|
||||
<div class="field-wrap">
|
||||
<input type="url" class="input-lien" id="lien_qr" name="lien_qr" required autofocus placeholder="https://..." value="<?php echo htmlspecialchars($strCurrentHttp, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn-enter">
|
||||
<span class="key-cap">↵</span>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<p class="hint">Ou enregistrer directement le lien par défaut :</p>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" action="<?php echo $strBaseUrlHtml; ?>" style="margin-top:0.75rem;">
|
||||
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($strCsrf, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<input type="hidden" name="action" value="save_ms1">
|
||||
<input type="hidden" name="pk" value="<?php echo (int) $intPk; ?>">
|
||||
<input type="hidden" name="vehicule_no" value="<?php echo htmlspecialchars($strVehiculeSaisi, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<button type="submit" class="btn-ms1">Mettre le lien vers ms1.ca</button>
|
||||
</form>
|
||||
<p class="hint" style="margin-top:0.75rem;">Sinon modifiez l’adresse ci-dessus et appuyez sur Entrée pour enregistrer.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if ($blnShowUrlForm) { ?>
|
||||
<p class="back"><a href="<?php echo $strBaseUrlHtml; ?>">← Autre numéro de véhicule</a></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php if ($blnCanShareLink && $strShareUrl !== '') { ?>
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('btn_copy_link');
|
||||
var input = document.getElementById('share_url');
|
||||
if (!btn || !input) return;
|
||||
btn.addEventListener('click', function () {
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
var ok = false;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(input.value).then(function () { ok = true; });
|
||||
}
|
||||
try { ok = document.execCommand('copy') || ok; } catch (e) {}
|
||||
btn.textContent = ok ? 'Copié !' : 'Sélectionné — Ctrl+C';
|
||||
setTimeout(function () { btn.textContent = 'Copier le lien'; }, 2000);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php } ?>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,526 +0,0 @@
|
||||
<?php
|
||||
// mets les totaux dans un tableau (JSP)
|
||||
// param : id de l'vévénement, no panier, langue
|
||||
// créé : 2021-05-12
|
||||
function fxUpdateFinancesMembership($intEvenement) {
|
||||
global $objDatabase;
|
||||
$strOutput = '';
|
||||
|
||||
// SQL
|
||||
$sqlTypeEvenement = "
|
||||
SELECT
|
||||
te_id
|
||||
FROM
|
||||
inscriptions_evenements
|
||||
WHERE
|
||||
eve_id = " . intval($intEvenement) . "
|
||||
";
|
||||
$intTypeEvenement = $objDatabase->fxGetVar($sqlTypeEvenement);
|
||||
|
||||
if (intval($intTypeEvenement) != 0) {
|
||||
if (intval($intTypeEvenement) == 13) {
|
||||
$sqlAcheteurs = "
|
||||
SELECT
|
||||
a.*, " . intval($intEvenement) . " AS eve_id_membership
|
||||
FROM
|
||||
inscriptions_panier_acheteurs a
|
||||
WHERE
|
||||
a.sta_id = 3
|
||||
AND a.eve_id <> " . intval($intEvenement) . "
|
||||
AND a.no_panier IN(
|
||||
SELECT no_panier FROM inscriptions_panier_produits_new p WHERE p.eve_id = " . intval($intEvenement) . "
|
||||
UNION
|
||||
SELECT no_panier FROM inscriptions_panier_epreuves_commandees ec WHERE ec.eve_id = " . intval($intEvenement) . "
|
||||
)
|
||||
AND a.no_panier NOT IN(
|
||||
SELECT no_panier FROM rapport_finances_membership
|
||||
)
|
||||
";
|
||||
} else {
|
||||
$sqlAcheteurs = "
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inscriptions_panier_acheteurs a
|
||||
JOIN inscriptions_evenements e ON e.eve_id = a.eve_id
|
||||
WHERE
|
||||
a.sta_id = 3
|
||||
AND a.eve_id = " . intval($intEvenement) . "
|
||||
AND a.no_panier IN(
|
||||
SELECT no_panier FROM inscriptions_panier_produits_new p WHERE p.eve_id = e.eve_id_membership
|
||||
UNION
|
||||
SELECT no_panier FROM inscriptions_panier_epreuves_commandees ec WHERE ec.eve_id = e.eve_id_membership
|
||||
)
|
||||
";
|
||||
}
|
||||
$tabAcheteurs = $objDatabase->fxGetResults($sqlAcheteurs);
|
||||
|
||||
if ($tabAcheteurs != null) {
|
||||
foreach ($tabAcheteurs as $acheteur) {
|
||||
//$strOutput .= '<h1>no_commande: ' . $acheteur['no_commande'] . '</h1>';
|
||||
$arrTotalsMembership = fxTotalPanierMembership($acheteur['eve_id'], $acheteur['eve_id_membership'], $acheteur['no_panier']);
|
||||
//$strOutput .= '<pre>' . print_r($arrTotals, true) . '</pre>';
|
||||
// insérer les totaux dans la table de jonction (NOTE: no_commande et no_panier onnt des INDEX UNIQUE donc, aucune chance de doublons)
|
||||
$sqlInsert = "INSERT INTO rapport_finances_membership SET no_commande = '" . $objDatabase->fxEscape($acheteur['no_commande']) . "', no_panier = '" . $objDatabase->fxEscape($acheteur['no_panier']) . "', eve_id = " . intval($acheteur['eve_id']) . ", eve_id_membership = " . intval($acheteur['eve_id_membership']) . ", ach_total = " . floatval($arrTotalsMembership['total']) . ", ach_taxe1 = " . floatval($arrTotalsMembership['taxes'][1]['montant']) . ", ach_taxe2 = " . floatval($arrTotalsMembership['taxes'][2]['montant']) . ", ach_taxe3 = " . floatval($arrTotalsMembership['taxes'][3]['montant']) . ", rabais_total = " . floatval($arrTotalsMembership['rabais_total']);
|
||||
$strOutput = $objDatabase->fxQuery($sqlInsert);
|
||||
// mettre à jour les totaux de la facture originale
|
||||
$arrTotalsOriginal = fxTotalPanier($acheteur['no_panier'], 'fr');
|
||||
$sqlUpdate = "UPDATE inscriptions_panier_acheteurs SET ach_total = " . floatval(floatval($arrTotalsOriginal['total']) - floatval($arrTotalsMembership['total'])) . ", ach_taxe1 = " . floatval(floatval($arrTotalsOriginal['taxes'][1]['montant']) - floatval($arrTotalsMembership['taxes'][1]['montant'])) . ", ach_taxe2 = " . floatval(floatval($arrTotalsOriginal['taxes'][2]['montant']) - floatval($arrTotalsMembership['taxes'][2]['montant'])) . ", ach_taxe3 = " . floatval(floatval($arrTotalsOriginal['taxes'][3]['montant']) - floatval($arrTotalsMembership['taxes'][3]['montant'])) . ", rabais_total = " . floatval(floatval($arrTotalsOriginal['rabais_total']) - floatval($arrTotalsMembership['rabais_total'])) . " WHERE no_panier = '" . $objDatabase->fxEscape($acheteur['no_panier']) . "'";
|
||||
//$strOutput .= $objDatabase->fxQuery($sqlUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $strOutput;
|
||||
}
|
||||
function fxTotalPanierMembership($intEvenement, $intEvenementMembership, $strNoPanier) {
|
||||
global $objDatabase;
|
||||
$fltTotalTaxes = $fltTotalTaxesFrais = $fltMontantTaxable = $fltSousTotal = $fltSousTotal2 = $fltTotal = 0;
|
||||
|
||||
// info événements
|
||||
$sqlEvenements = "SELECT * FROM inscriptions_panier_evenements WHERE eve_id = " . intval($intEvenement);
|
||||
$tabEvenements = $objDatabase->fxGetRow($sqlEvenements);
|
||||
|
||||
// info épreuves
|
||||
$sqlEpreuves = "SELECT pec_id FROM inscriptions_panier_epreuves_commandees WHERE no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . " ORDER BY pec_id";
|
||||
$tabEpreuves = $objDatabase->fxGetResults($sqlEpreuves);
|
||||
|
||||
// Calculer le sous-total
|
||||
$sqlTotal = "
|
||||
SELECT
|
||||
(SELECT SUM(pec_prix) FROM inscriptions_panier_epreuves_commandees WHERE no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS prix_epreuves,
|
||||
(SELECT SUM(e.rab_montant) FROM inscriptions_panier_epreuves_commandees e, inscriptions_panier_rabais r WHERE r.pra_id = e.pra_id AND r.pra_depot = 0 AND e.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS rabais_epreuves,
|
||||
(SELECT SUM(c.pec_prix - c.rab_montant) FROM inscriptions_panier_epreuves_commandees c, inscriptions_panier_epreuves e WHERE c.epr_id = e.epr_id AND e.epr_taxes_incluses = 0 AND c.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS prix_epreuves_taxable,
|
||||
(SELECT SUM(pro_prix) FROM inscriptions_panier_produits_new WHERE no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS prix_produits,
|
||||
(SELECT SUM(p.rab_montant) FROM inscriptions_panier_produits_new p, inscriptions_panier_rabais r WHERE r.pra_id = p.pra_id AND r.pra_depot = 0 AND p.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS rabais_produits,
|
||||
(SELECT SUM(pro_prix - rab_montant) FROM inscriptions_panier_produits_new WHERE pro_taxable = 1 AND no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEvenementMembership) . ") AS prix_produits_taxable,
|
||||
(SELECT SUM(r.rab_montant) FROM inscriptions_panier_rabais r, inscriptions_panier_rabais_acheteurs a WHERE r.pra_id = a.pra_id AND r.pra_depot = 0 AND a.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND r.rab_pourcent = 0 AND r.rab_epreuves = '' AND r.rab_produits = '') AS rabais_panier_montant,
|
||||
(SELECT SUM(r.rab_pourcent) FROM inscriptions_panier_rabais r, inscriptions_panier_rabais_acheteurs a WHERE r.pra_id = a.pra_id AND r.pra_depot = 0 AND a.no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "' AND r.rab_montant = 0 AND r.rab_epreuves = '' AND r.rab_produits = '') AS rabais_panier_pourcent
|
||||
FROM DUAL
|
||||
";
|
||||
$tabTotal = $objDatabase->fxGetRow($sqlTotal);
|
||||
|
||||
$fltRegistrationTotal = $tabTotal['prix_epreuves'] + $tabTotal['prix_produits'];
|
||||
|
||||
// calculer les rabais
|
||||
$fltRabaisMontant = $tabTotal['rabais_panier_montant'] + $tabTotal['rabais_epreuves'] + $tabTotal['rabais_produits'];
|
||||
$intRabaisPourcent = $tabTotal['rabais_panier_pourcent'];
|
||||
|
||||
if ($intRabaisPourcent > 100)
|
||||
$intRabaisPourcent = 100;
|
||||
|
||||
// calculer le rabais %
|
||||
$fltRabaisPourcent = ($fltRegistrationTotal - $fltRabaisMontant) * ($intRabaisPourcent / 100);
|
||||
|
||||
// calculer le total après rabais pourcent
|
||||
$fltRabaisTotal = $fltRabaisMontant + $fltRabaisPourcent;
|
||||
$fltRegistrationTotal -= ($fltRabaisTotal);
|
||||
|
||||
if ($fltRegistrationTotal < 0)
|
||||
$fltRegistrationTotal = 0;
|
||||
|
||||
$fltMontantTaxable = ($tabTotal['prix_epreuves_taxable'] + $tabTotal['prix_produits_taxable']) - ($fltRabaisPourcent + $tabTotal['rabais_panier_montant']);
|
||||
|
||||
if ($fltMontantTaxable < 0)
|
||||
$fltMontantTaxable = 0;
|
||||
|
||||
// calculer le sous total (registration + option)
|
||||
$fltSousTotal = $fltRegistrationTotal;
|
||||
|
||||
// calculer les taxes
|
||||
$tabTaxes[1] = $tabTaxes[2] = $tabTaxes[3] = array('label_fr' => '', 'label_en' => '', 'montant' => 0, 'taux' => 0, 'numero' => '');
|
||||
|
||||
// récupérer les infos des taxes
|
||||
if ($tabEvenements['tt_id'] > 1) {
|
||||
$sqlTaux = "SELECT t.tau_nom_fr, t.tau_nom_en, t.tau_taux, t.tau_numero_client, t.tau_numero_ms1 FROM inscriptions_taux t, inscriptions_taux_types_taux ttt WHERE t.tau_id = ttt.tau_id AND ttt.tt_id = " . intval($tabEvenements['tt_id']) . " ORDER BY ttt.ttt_tri";
|
||||
$tabTaux = $objDatabase->fxGetResults($sqlTaux);
|
||||
|
||||
if ($tabTaux != null) {
|
||||
for ($intCtr = 1; $intCtr <= count($tabTaux); $intCtr++) {
|
||||
$fltMontantTaxes = round($fltMontantTaxable * ($tabTaux[$intCtr]['tau_taux'] / 100), 2);
|
||||
$tabTaxes[$intCtr] = array('label_fr' => $tabTaux[$intCtr]['tau_nom_fr'] . ' (' . $tabTaux[$intCtr]['tau_taux'] . '%)', 'label_en' => $tabTaux[$intCtr]['tau_nom_en'] . ' (' . $tabTaux[$intCtr]['tau_taux'] . '%)', 'montant' => $fltMontantTaxes, 'taux' => $tabTaux[$intCtr]['tau_taux'], 'numero' => $tabTaux[$intCtr]['tau_numero_client']);
|
||||
$fltTotalTaxes += $fltMontantTaxes;
|
||||
}
|
||||
} else {
|
||||
fxcreer_log("Il y a eu une erreur lors de la récupération des taxes:\n" . $sqlTaux);
|
||||
}
|
||||
}
|
||||
// calculer le total après taxes
|
||||
$fltSousTotal2 = $fltSousTotal + $fltTotalTaxes;
|
||||
// calculer le total avec frais d'administration
|
||||
$fltTotal = $fltSousTotal2;
|
||||
|
||||
// affiche le sous-total, les taxes, rabais et le total complet
|
||||
$tabRetour['taxes'] = $tabTaxes;
|
||||
$tabRetour['total_taxes'] = $fltTotalTaxes;
|
||||
$tabRetour['total'] = abs($fltTotal);
|
||||
$tabRetour['stotal'] = $fltSousTotal;
|
||||
$tabRetour['stotal2'] = $fltSousTotal2;
|
||||
$tabRetour['rabais_total'] = $fltRabaisTotal;
|
||||
|
||||
if ($tabEpreuves != null)
|
||||
$tabRetour['pec_id'] = implode(',', array_map(function($tab) { return $tab['pec_id']; }, $tabEpreuves));
|
||||
else
|
||||
$tabRetour['pec_id'] = 0;
|
||||
|
||||
$tabRetour['eve_id'] = $tabEvenements['eve_id'];
|
||||
$tabRetour['eve_id_membership'] = $intEvenementMembership;
|
||||
|
||||
return $tabRetour;
|
||||
}
|
||||
function fxGetTermes($intEvenement, $strNoPanier, $blnMembership, $strLangue) {
|
||||
$strSuffixe = $strTermesText = $strTermesHTML = $strTermesModal = '';
|
||||
$arrRetour = $arrTermesModalButtons = $arr_epreuves_id = array();
|
||||
|
||||
// faire un tableau avec tout les epr_id (sl)
|
||||
$epreuves = fxListPanier($strNoPanier);
|
||||
|
||||
foreach ($epreuves[3] as $key => $value) {
|
||||
$arr_epreuves_id[$key]= $value['epr_id'] ;
|
||||
}
|
||||
|
||||
$tabEvenement = fxGetEvenements(fxGetEvenementsUrl($intEvenement), $strLangue);
|
||||
|
||||
if ($blnMembership) {
|
||||
$strSuffixe = '_mem';
|
||||
$tabEvenement = fxGetEvenements(fxGetEvenementsUrl($tabEvenement['general']['eve_id_membership']), $strLangue);
|
||||
}
|
||||
|
||||
if ($tabEvenement['general']['eve_waiver_' . $strLangue] != '') {
|
||||
$strTermesText .= '<p><strong>' . afficheTexte('terme_waiver', 0, 1, 1) . ' :</strong></p>';
|
||||
$strTermesText .= $tabEvenement['general']['eve_waiver_' . $strLangue] . '<br>';
|
||||
$strTermesHTML = '
|
||||
<div class="card-text">
|
||||
' . afficheTexte('terme_jaccepte', 0, 1, 1) . '
|
||||
<strong><a href="#div_waiver' . $strSuffixe . '" id="link_waiver' . $strSuffixe . '" data-toggle="modal">' . afficheTexte('terme_waiver', 0, 1, 1) .'</a>.</strong>
|
||||
<div class="form-check d-inline-block ml-2">
|
||||
<input class="form-check-input position-static chk_termes" type="checkbox" id="chk_waiver' . $strSuffixe . '" name="chk_waiver' . $strSuffixe . '" value="1" aria-label="..." readonly>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$strTermesModal .= '
|
||||
<div class="modal fade" id="div_waiver' . $strSuffixe . '" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="div_waiver_label' . $strSuffixe . '" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="div_waiver_label' . $strSuffixe . '">' . afficheTexte('terme_waiver_titre', 0, 1, 1) . '</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="' . afficheTexte('btn_terme_close', 0, 1, 1) . '">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
' . $tabEvenement['general']['eve_waiver_' . $strLangue] . '
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary rounded-0" data-dismiss="modal">' . afficheTexte('btn_terme_close', 0, 1, 1) . '</button>
|
||||
<button id="btn_accept_waiver' . $strSuffixe . '" type="button" class="btn btn-primary rounded-0">' . afficheTexte('btn_terme_jaccepte', 0, 1, 1) . '</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$arrTermesModalButtons[] = 'btn_accept_waiver' . $strSuffixe;
|
||||
}
|
||||
|
||||
if ($tabEvenement['general']['eve_policy_' . $strLangue] != '') {
|
||||
$strTermesText .= '<p><strong>' . afficheTexte('terme_politique_achat', 0, 1, 1) . ' :</strong></p>';
|
||||
$strTermesText .= $tabEvenement['general']['eve_policy_' . $strLangue] . '<br>';
|
||||
$strTermesHTML .= '
|
||||
<div class="card-text">
|
||||
' . afficheTexte('terme_jaccepte', 0, 1, 1) . '
|
||||
<strong><a href="#div_policy' . $strSuffixe . '" id="link_policy' . $strSuffixe . '" data-toggle="modal">' . afficheTexte('terme_politique_achat', 0, 1, 1) . '</a>.</strong>
|
||||
<div class="form-check d-inline-block ml-2">
|
||||
<input class="form-check-input position-static chk_termes" type="checkbox" id="chk_policy' . $strSuffixe . '" name="chk_policy' . $strSuffixe . '" value="1" aria-label="..." readonly>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$strTermesModal .= '
|
||||
<div class="modal fade" id="div_policy' . $strSuffixe . '" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="div_policy_label' . $strSuffixe . '" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="div_policy_label' . $strSuffixe . '">' . afficheTexte('terme_policy_titre', 0, 1, 1) . '</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="' . afficheTexte('btn_terme_close', 0, 1, 1) . '">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
' . $tabEvenement['general']['eve_policy_' . $strLangue] . '
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary rounded-0" data-dismiss="modal">' . afficheTexte('btn_terme_close', 0, 1, 1) . '</button>
|
||||
<button id="btn_accept_policy' . $strSuffixe . '" type="button" class="btn btn-primary rounded-0">' . afficheTexte('btn_terme_jaccepte', 0, 1, 1) . '</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$arrTermesModalButtons[] = 'btn_accept_policy' . $strSuffixe;
|
||||
}
|
||||
|
||||
if ($tabEvenement['general']['eve_policy2_' . $strLangue] != '') {
|
||||
$strTermesText .= '<p><strong>' . afficheTexte('terme_location_politique_achat', 0, 1, 1) . ' :</strong></p>';
|
||||
$strTermesText .= $tabEvenement['general']['eve_policy2_' . $strLangue] . '<br>';
|
||||
$strTermesHTML .= '
|
||||
<div class="card-text">
|
||||
' . afficheTexte('terme_jaccepte', 0, 1, 1) . '
|
||||
<strong><a href="#div_location_policy' . $strSuffixe . '" id="link_location_policy' . $strSuffixe . '" data-toggle="modal">' . afficheTexte('terme_location_politique_achat', 0, 1, 1) . '</a>.</strong>
|
||||
<div class="form-check d-inline-block ml-2">
|
||||
<input class="form-check-input position-static chk_termes" type="checkbox" id="chk_location_policy' . $strSuffixe . '" name="chk_location_policy' . $strSuffixe . '" value="1" aria-label="..." readonly>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$strTermesModal .= '
|
||||
<div class="modal fade" id="div_location_policy' . $strSuffixe . '" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="div_location_policy_label' . $strSuffixe . '" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="div_location_policy_label' . $strSuffixe . '">' . afficheTexte('terme_location_policy_titre', 0, 1, 1) . '</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="' . afficheTexte('btn_terme_close', 0, 1, 1) . '">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
' . $tabEvenement['general']['eve_policy2_' . $strLangue] . '
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary rounded-0" data-dismiss="modal">' . afficheTexte('btn_terme_close', 0, 1, 1) . '</button>
|
||||
<button id="btn_accept_location_policy' . $strSuffixe . '" type="button" class="btn btn-primary rounded-0">' . afficheTexte('btn_terme_jaccepte', 0, 1, 1) . '</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$arrTermesModalButtons[] = 'btn_accept_location_policy' . $strSuffixe;
|
||||
}
|
||||
|
||||
// valide les waiver de 0 a 6 (sl)
|
||||
for ($x = 0; $x <= 6; $x++) {
|
||||
$mem_waive="eve_waiver".$x;
|
||||
$array_list = explode(',',$tabEvenement['general'][$mem_waive.'_list_epreuves' ]);
|
||||
|
||||
$result = array_intersect($array_list, $arr_epreuves_id);
|
||||
|
||||
if (count($result) > 0) {
|
||||
$strTermesText .= '<p><strong>' . $tabEvenement['general'][$mem_waive.'_titre_' . $strLangue] . ' :</strong></p>';
|
||||
$strTermesText .= $tabEvenement['general'][$mem_waive . '_' . $strLangue] . '<br>';
|
||||
$strTermesHTML .= '
|
||||
<div class="card-text">
|
||||
' . afficheTexte('terme_jaccepte', 0, 1, 1) . '
|
||||
<strong><a href="#div_' . $mem_waive . $strSuffixe . '" id="link_' . $mem_waive . $strSuffixe . '" data-toggle="modal">' . $tabEvenement['general'][$mem_waive.'_titre_' . $strLangue] . '</a>.</strong>
|
||||
<div class="form-check d-inline-block ml-2">
|
||||
<input class="form-check-input position-static chk_termes" type="checkbox" id="chk_' . $mem_waive . $strSuffixe . '" name="chk_' . $mem_waive . $strSuffixe . '" value="1" aria-label="..." readonly>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$strTermesModal .= '
|
||||
<div class="modal fade" id="div_'.$mem_waive.'' . $strSuffixe . '" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="div_' . $mem_waive . '_label' . $strSuffixe . '" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="div_'.$mem_waive.'_label' . $strSuffixe . '">' .$tabEvenement['general'][$mem_waive.'_titre_' . $strLangue]. '</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="' . afficheTexte('btn_terme_close', 0, 1, 1) . '">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
' . $tabEvenement['general'][$mem_waive.'_' . $strLangue] . '
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary rounded-0" data-dismiss="modal">' . afficheTexte('btn_terme_close', 0, 1, 1) . '</button>
|
||||
<button id="btn_accept_' . $mem_waive . $strSuffixe . '" type="button" class="btn btn-primary rounded-0">' . afficheTexte('btn_terme_jaccepte', 0, 1, 1) . '</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
$arrTermesModalButtons[] = 'btn_accept_' . $mem_waive . $strSuffixe;
|
||||
}
|
||||
}
|
||||
|
||||
$arrRetour['html'] = $strTermesHTML;
|
||||
$arrRetour['modal'] = $strTermesModal;
|
||||
$arrRetour['text'] = $strTermesText;
|
||||
$arrRetour['buttons'] = $arrTermesModalButtons;
|
||||
|
||||
return $arrRetour;
|
||||
}
|
||||
function fxShowBlocPaiement($tabEvenement, $fltMontant, $strLangue) {
|
||||
global $vDomaine, $vblnEnvironementDev;
|
||||
$strCCNumber = ($vblnEnvironementDev) ? ' value="4528038032834793"' : '';
|
||||
$strCVV = ($vblnEnvironementDev) ? ' value="123"' : '';
|
||||
$blnIsPromoteur = $blnAdmin = false;
|
||||
$strOutput = '';
|
||||
|
||||
if (isset($tabEvenement) && intval($tabEvenement['general']['eve_gratuit']) != 1) { // si l'événement n'est pas gratuite, afficher le bloc de paiement
|
||||
$strOutput .= '
|
||||
<div id="paiement" class="card box_participant mb-3">
|
||||
<div class="card-header">
|
||||
<h2 class="box_paiement">' . afficheTexte('mode_paiement', 0) . '</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-lg-3">
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<div class="form-group text-center">
|
||||
';
|
||||
|
||||
if (floatval($fltMontant) > 0) {
|
||||
if (isset($_SESSION['com_info']) && $_SESSION['com_info'] != '') {
|
||||
$blnIsPromoteur = fxIsPromoteur($_SESSION['com_info']['com_id'], $tabEvenement['general']['eve_id']);
|
||||
}
|
||||
|
||||
// si en mode admin, récupérer les mode de paiement réservés aux admin
|
||||
if (isset($_SESSION['usa_id'])) {
|
||||
$blnAdmin = true;
|
||||
}
|
||||
|
||||
$tabPaiements = fxGetPaiements($blnAdmin, $blnIsPromoteur, $strLangue);
|
||||
|
||||
if ($tabPaiements != false) { // afficher le formulaire de la carte de crédit
|
||||
for ($intCtr = 1; $intCtr <= count($tabPaiements); $intCtr++) {
|
||||
if ($tabPaiements[$intCtr]['pai_id'] == 2 || $tabPaiements[$intCtr]['pai_id'] == 6) {
|
||||
$strDetailsPaiement = $strChecked = '';
|
||||
|
||||
if ($tabPaiements[$intCtr]['pai_id'] == 2) {
|
||||
$strChecked = ' checked';
|
||||
}
|
||||
|
||||
if ($tabPaiements[$intCtr]['pai_id'] == 6) {
|
||||
//$strDetailsPaiement = ' <em>' . afficheTexte('paiement_cheque', 0) . '</em>';
|
||||
}
|
||||
|
||||
$strOutput .= '
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="chk_pai_' . $tabPaiements[$intCtr]['pai_id'] . '" name="chk_paiement" value="' . $tabPaiements[$intCtr]['pai_id'] . '"' . $strChecked . '>
|
||||
<label class="form-check-label" for="chk_pai_' . $tabPaiements[$intCtr]['pai_id'] . '">
|
||||
' . $tabPaiements[$intCtr]['pai_nom_' . $strLangue] . '
|
||||
</label>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$strOutput .= '
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="chk_pai_1" name="chk_paiement" value="1" checked>
|
||||
<label class="form-check-label" for="chk_pai_1">
|
||||
' . afficheTexte('paypal', 0) . '
|
||||
</label>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
} else {
|
||||
$strOutput .= '
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="chk_pai_1" name="chk_paiement" value="1" checked>
|
||||
<label class="form-check-label" for="chk_pai_1">
|
||||
' . afficheTexte('gratuit', 0) . '
|
||||
</label>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
if (floatval($fltMontant) > 0) {
|
||||
$strMonth = ($strLangue == 'fr') ? '-- Mois --' : '-- Month --';
|
||||
$strYear = ($strLangue == 'fr') ? '-- Année --' :'-- Year --';
|
||||
|
||||
$strOutput .= '
|
||||
</div>
|
||||
<div id="creditcard">
|
||||
<div class="form-message"></div>
|
||||
<div class="img_cartes text-center">
|
||||
<img id="card_mc" src="' . $vDomaine . '/images/mastercard.gif" width="55" height="34" alt="MasterCard">
|
||||
<img id="card_visa" src="' . $vDomaine . '/images/visa.gif" width="59" height="34" alt="Visa">
|
||||
</div>
|
||||
<br>
|
||||
<input type="hidden" id="card_type" name="card_type" value="">
|
||||
<div class="form-group row justify-content-center">
|
||||
<label for="ccnumber" class="control-label col-12">' . afficheTexte('checkout_ccnumber', 0) . ':</label>
|
||||
<div class="col-11">
|
||||
<input class="form-control" type="text" id="ccnumber" name="ccnumber" size="20" maxlength="16" autocomplete="off" placeholder="1234567890123456"' . $strCCNumber . '>
|
||||
</div>
|
||||
<small class="col text-muted">*</small>
|
||||
</div>
|
||||
<div class="form-group row justify-content-center">
|
||||
<label class="control-label col-12">' . afficheTexte('checkout_ccexp', 0) . ':</label>
|
||||
<div class="col-5">
|
||||
<select class="form-control" id="ccmonth" name="ccmonth">
|
||||
<label class="sr-only" for="ccmonth">' . afficheTexte('month', 0, 1, 1) . '</label>
|
||||
<option value="" selected="selected">' . $strMonth . '</option>
|
||||
';
|
||||
|
||||
for ($intCtr = 1; $intCtr <= 12; $intCtr++) {
|
||||
$strSelected = ($vblnEnvironementDev && $intCtr == 3) ? ' selected' : '';
|
||||
$strOutput .= '<option value="' . fxAddLeadingZero($intCtr) . '"' . $strSelected . '>' . fxMois('2013-' . $intCtr . '-01', $strLangue) . ' (' . $intCtr . ')' . '</option>';
|
||||
}
|
||||
|
||||
$strOutput .= '
|
||||
</select>
|
||||
</div>
|
||||
<small class="col text-muted">*</small>
|
||||
<div class="col-5">
|
||||
<label class="sr-only" for="ccyear">' . afficheTexte('year', 0, 1, 1) . '</label>
|
||||
<select class="form-control" id="ccyear" name="ccyear">
|
||||
<option value="" selected="selected">' . $strYear . '</option>
|
||||
';
|
||||
|
||||
for ($intCtr = date('Y', strtotime(fxGetDate())); $intCtr <= date('Y', strtotime(fxGetDate()))+ 10; $intCtr++) {
|
||||
$strSelected = ($vblnEnvironementDev && $intCtr == 2025) ? ' selected' : '';
|
||||
$strOutput .= '<option value="' . $intCtr . '"' . $strSelected . '>' . $intCtr . '</option>';
|
||||
}
|
||||
|
||||
$strOutput .= '
|
||||
</select>
|
||||
</div>
|
||||
<small class="col text-muted">*</small>
|
||||
</div>
|
||||
<div class="form-group row justify-content-center">
|
||||
<label class="control-label col-12" for="cvv">' . afficheTexte('checkout_cvv', 0) . ':</label>
|
||||
<div class="col-11">
|
||||
<input class="form-control" type="text" id="cvv" name="cvv" size="3" maxlength="3" autocomplete="off" placeholder="123"' . $strCVV . '>
|
||||
</div>
|
||||
<small class="col text-muted">*</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 col-lg-3">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
} else {
|
||||
$strOutput .= '
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
} else { // si événement gratuit
|
||||
$strOutput .= '
|
||||
<input type="hidden" name="chk_paiement" value="1">
|
||||
';
|
||||
}
|
||||
|
||||
return $strOutput;
|
||||
}
|
||||
function fxShowTermesAcceptes($strNoPanier, $strLangue) {
|
||||
$strRetour = '';
|
||||
$tabPanier = fxListPanier($strNoPanier);
|
||||
$tabAcheteur = $tabPanier[0];
|
||||
|
||||
$strRetour .= '
|
||||
<div style="padding: 10px;">
|
||||
<div style="color: #666666; font-size: 0.75rem;">
|
||||
' . ($tabAcheteur['ach_termes_' . $strLangue]) . '
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
return $strRetour;}
|
||||
BIN
pdf/template_dons/recudon1284.docx
Normal file
BIN
pdf/template_dons/recudon1284.pdf
Normal file
2066
php/chronotrack_api/fx_chronotrack_admin.php
Normal file
364
php/chronotrack_api/fx_chronotrack_config.php
Normal file
@ -0,0 +1,364 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — config par eve_id (lien event, statut, mapping épreuves).
|
||||
*/
|
||||
|
||||
function fxChronotrackApiConfigGet($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM api_chronotrack_config WHERE eve_id = " . $intEveId . " LIMIT 1";
|
||||
return $objDatabase->fxGetRow($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiConfigStatusLabel($strStatus) {
|
||||
switch ($strStatus) {
|
||||
case MSIN_API_CHRONOTRACK_SYNC_ACTIF:
|
||||
return 'Actif';
|
||||
case MSIN_API_CHRONOTRACK_SYNC_FERME:
|
||||
return 'Fermé';
|
||||
case MSIN_API_CHRONOTRACK_SYNC_LIE:
|
||||
default:
|
||||
return 'Lié';
|
||||
}
|
||||
}
|
||||
|
||||
function fxChronotrackApiConfigSaveLink($intEveId, $intCtEventId, $strCtEventName, $strCtEventDate, $intUsaId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
$intCtEventId = intval($intCtEventId);
|
||||
if ($intEveId <= 0 || $intCtEventId <= 0) {
|
||||
return array('state' => 'error', 'message' => 'Identifiants invalides');
|
||||
}
|
||||
|
||||
$arrExisting = fxChronotrackApiConfigGet($intEveId);
|
||||
if ($arrExisting !== null && $arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
return array('state' => 'error', 'message' => 'Événement fermé — réouverture non autorisée depuis cette interface.');
|
||||
}
|
||||
|
||||
$strName = $objDatabase->fxEscape(trim($strCtEventName));
|
||||
$strDateSql = 'NULL';
|
||||
if (trim($strCtEventDate) !== '') {
|
||||
$intTs = strtotime($strCtEventDate);
|
||||
if ($intTs !== false) {
|
||||
$strDateSql = "'" . $objDatabase->fxEscape(date('Y-m-d H:i:s', $intTs)) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
if ($arrExisting === null) {
|
||||
$sql = "INSERT INTO api_chronotrack_config SET eve_id = " . $intEveId
|
||||
. ", ct_event_id = " . $intCtEventId
|
||||
. ", ct_event_name = '" . $strName . "'"
|
||||
. ", ct_event_date = " . $strDateSql
|
||||
. ", sync_status = '" . MSIN_API_CHRONOTRACK_SYNC_LIE . "'"
|
||||
. ", linked_at = '" . fxGetDateTime() . "'"
|
||||
. ", linked_by_usa_id = " . intval($intUsaId)
|
||||
. ", updated_at = '" . fxGetDateTime() . "'";
|
||||
} else {
|
||||
$sql = "UPDATE api_chronotrack_config SET ct_event_id = " . $intCtEventId
|
||||
. ", ct_event_name = '" . $strName . "'"
|
||||
. ", ct_event_date = " . $strDateSql
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId
|
||||
. " AND sync_status <> '" . MSIN_API_CHRONOTRACK_SYNC_FERME . "'";
|
||||
}
|
||||
|
||||
if (!$objDatabase->fxQuery($sql)) {
|
||||
return array('state' => 'error', 'message' => 'Erreur SQL enregistrement lien');
|
||||
}
|
||||
|
||||
return array('state' => 'ok');
|
||||
}
|
||||
|
||||
function fxChronotrackApiConfigSetStatus($intEveId, $strStatus, $intUsaId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
$arrExisting = fxChronotrackApiConfigGet($intEveId);
|
||||
if ($arrExisting === null) {
|
||||
return array('state' => 'error', 'message' => 'Aucun lien ChronoTrack pour cet événement');
|
||||
}
|
||||
|
||||
if ($arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
if ($strStatus !== MSIN_API_CHRONOTRACK_SYNC_LIE) {
|
||||
return array('state' => 'error', 'message' => 'Événement fermé — seule la réouverture (Lié) est permise.');
|
||||
}
|
||||
}
|
||||
|
||||
$strStatus = trim($strStatus);
|
||||
if (!in_array($strStatus, array(MSIN_API_CHRONOTRACK_SYNC_LIE, MSIN_API_CHRONOTRACK_SYNC_ACTIF, MSIN_API_CHRONOTRACK_SYNC_FERME), true)) {
|
||||
return array('state' => 'error', 'message' => 'Statut invalide');
|
||||
}
|
||||
|
||||
$strExtra = '';
|
||||
if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_ACTIF) {
|
||||
$strExtra = ", activated_at = '" . fxGetDateTime() . "'";
|
||||
}
|
||||
if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
$strExtra = ", closed_at = '" . fxGetDateTime() . "', closed_by_usa_id = " . intval($intUsaId);
|
||||
}
|
||||
if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_LIE && $arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
$strExtra = ", closed_at = NULL, closed_by_usa_id = NULL";
|
||||
}
|
||||
|
||||
$sql = "UPDATE api_chronotrack_config SET sync_status = '" . $objDatabase->fxEscape($strStatus) . "'"
|
||||
. $strExtra
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId;
|
||||
if (!$objDatabase->fxQuery($sql)) {
|
||||
return array('state' => 'error', 'message' => 'Erreur SQL changement statut');
|
||||
}
|
||||
|
||||
return array('state' => 'ok');
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — mémorise la fin d’un push réussi (référence pour par_maj).
|
||||
*/
|
||||
function fxChronotrackApiConfigSetLastPush($intEveId, $intOkCount) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = "UPDATE api_chronotrack_config SET last_push_at = '" . fxGetDateTime() . "'"
|
||||
. ", last_push_ok_count = " . max(0, intval($intOkCount))
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId;
|
||||
return (bool)$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — fréquences autorisées (minutes), phase A.
|
||||
*/
|
||||
function fxChronotrackApiConfigAutoSyncIntervals() {
|
||||
return array(1, 5, 15, 30, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — enregistre la config sync auto (UI). Le cron (phase B) lira ces champs.
|
||||
*/
|
||||
function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intIntervalMin, $strUntil) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
$arrExisting = fxChronotrackApiConfigGet($intEveId);
|
||||
if ($arrExisting === null) {
|
||||
return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
||||
}
|
||||
if (($arrExisting['sync_status'] ?? '') === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
return array('state' => 'error', 'message' => 'Événement fermé — sync auto impossible.');
|
||||
}
|
||||
|
||||
$blnEnabled = $blnEnabled ? 1 : 0;
|
||||
$intIntervalMin = intval($intIntervalMin);
|
||||
if (!in_array($intIntervalMin, fxChronotrackApiConfigAutoSyncIntervals(), true)) {
|
||||
$intIntervalMin = 15;
|
||||
}
|
||||
|
||||
$strUntilSql = 'NULL';
|
||||
$strUntil = trim((string)$strUntil);
|
||||
if ($strUntil !== '') {
|
||||
// Accepte "YYYY-MM-DDTHH:MM" (input datetime-local) ou "YYYY-MM-DD HH:MM[:SS]"
|
||||
$strUntilNorm = str_replace('T', ' ', $strUntil);
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $strUntilNorm)) {
|
||||
$strUntilNorm .= ':00';
|
||||
}
|
||||
$intTs = strtotime($strUntilNorm);
|
||||
if ($intTs === false) {
|
||||
return array('state' => 'error', 'message' => 'Date / heure de fin invalide');
|
||||
}
|
||||
if ($blnEnabled && $intTs <= time()) {
|
||||
return array('state' => 'error', 'message' => 'La date/heure de fin doit être dans le futur');
|
||||
}
|
||||
$strUntilSql = "'" . $objDatabase->fxEscape(date('Y-m-d H:i:s', $intTs)) . "'";
|
||||
} elseif ($blnEnabled) {
|
||||
return array('state' => 'error', 'message' => 'Indiquez une date et une heure de fin pour activer la sync auto');
|
||||
}
|
||||
|
||||
$sql = "UPDATE api_chronotrack_config SET auto_sync_enabled = " . $blnEnabled
|
||||
. ", auto_sync_interval_min = " . $intIntervalMin
|
||||
. ", auto_sync_until = " . $strUntilSql
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId;
|
||||
if (!$objDatabase->fxQuery($sql)) {
|
||||
return array('state' => 'error', 'message' => 'Erreur SQL enregistrement sync auto');
|
||||
}
|
||||
|
||||
$arrFresh = fxChronotrackApiConfigGet($intEveId);
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'message' => $blnEnabled
|
||||
? ('Sync auto enregistrée (toutes les ' . $intIntervalMin . ' min jusqu’au '
|
||||
. trim((string)($arrFresh['auto_sync_until'] ?? '')) . '). Cron : auto_chronotrack_sync.php')
|
||||
: 'Sync auto désactivée.',
|
||||
'auto_sync' => array(
|
||||
'enabled' => intval($arrFresh['auto_sync_enabled'] ?? 0) === 1,
|
||||
'interval_min' => intval($arrFresh['auto_sync_interval_min'] ?? 15),
|
||||
'until' => ($arrFresh['auto_sync_until'] ?? null),
|
||||
'last_run_at' => ($arrFresh['last_auto_run_at'] ?? null),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — events avec sync auto ON (pas fermés).
|
||||
*/
|
||||
function fxChronotrackApiConfigListAutoSyncEnabled() {
|
||||
global $objDatabase;
|
||||
|
||||
$sql = "SELECT c.*"
|
||||
. " FROM api_chronotrack_config c"
|
||||
. " WHERE IFNULL(c.auto_sync_enabled, 0) = 1"
|
||||
. " AND c.sync_status <> '" . $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_SYNC_FERME) . "'"
|
||||
. " ORDER BY c.eve_id ASC";
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
$tabOut = array();
|
||||
if (!is_array($arrRows)) {
|
||||
return $tabOut;
|
||||
}
|
||||
for ($i = 1; $i <= count($arrRows); $i++) {
|
||||
$tabOut[] = $arrRows[$i];
|
||||
}
|
||||
return $tabOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — horodatage dernier passage cron auto.
|
||||
*/
|
||||
function fxChronotrackApiConfigTouchAutoRun($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
$sql = "UPDATE api_chronotrack_config SET last_auto_run_at = '" . fxGetDateTime() . "'"
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId;
|
||||
return (bool)$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — coupe la sync auto (échéance atteinte).
|
||||
*/
|
||||
function fxChronotrackApiConfigDisableAutoSync($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
$sql = "UPDATE api_chronotrack_config SET auto_sync_enabled = 0"
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId;
|
||||
return (bool)$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiRaceMapGetForEvent($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$sql = "SELECT * FROM api_chronotrack_race_map WHERE eve_id = " . intval($intEveId);
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
$arrMap = array();
|
||||
if ($arrRows !== null) {
|
||||
for ($i = 1; $i <= count($arrRows); $i++) {
|
||||
$arrMap[intval($arrRows[$i]['epr_id'])] = $arrRows[$i];
|
||||
}
|
||||
}
|
||||
return $arrMap;
|
||||
}
|
||||
|
||||
function fxChronotrackApiMs1EpreuvesForEvent($intEveId, $strLangue = 'fr') {
|
||||
global $objDatabase;
|
||||
|
||||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
$sql = "SELECT epr_id, epr_nom_" . $strLangue . " AS epr_nom, epr_type_" . $strLangue . " AS epr_type, epr_date"
|
||||
. " FROM inscriptions_epreuves WHERE eve_id = " . intval($intEveId)
|
||||
. " ORDER BY epr_date, epr_nom_" . $strLangue;
|
||||
return $objDatabase->fxGetResults($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiRaceMapSave($intEveId, $arrMapInput) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
$arrExisting = fxChronotrackApiConfigGet($intEveId);
|
||||
if ($arrExisting === null) {
|
||||
return array('state' => 'error', 'message' => 'Lier d\'abord un événement ChronoTrack');
|
||||
}
|
||||
if ($arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) {
|
||||
return array('state' => 'error', 'message' => 'Événement fermé — mapping en lecture seule');
|
||||
}
|
||||
|
||||
if (!is_array($arrMapInput)) {
|
||||
return array('state' => 'error', 'message' => 'Mapping invalide');
|
||||
}
|
||||
|
||||
foreach ($arrMapInput as $intEprId => $arrRace) {
|
||||
$intEprId = intval($intEprId);
|
||||
$intCtRaceId = intval($arrRace['ct_race_id'] ?? 0);
|
||||
$strCtRaceName = trim((string)($arrRace['ct_race_name'] ?? ''));
|
||||
|
||||
if ($intEprId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($intCtRaceId <= 0) {
|
||||
$sqlDel = "DELETE FROM api_chronotrack_race_map WHERE eve_id = " . $intEveId . " AND epr_id = " . $intEprId;
|
||||
$objDatabase->fxQuery($sqlDel);
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlExists = "SELECT map_id FROM api_chronotrack_race_map WHERE eve_id = " . $intEveId
|
||||
. " AND epr_id = " . $intEprId . " LIMIT 1";
|
||||
if ($objDatabase->fxGetRow($sqlExists) !== null) {
|
||||
$sql = "UPDATE api_chronotrack_race_map SET ct_race_id = " . $intCtRaceId
|
||||
. ", ct_race_name = '" . $objDatabase->fxEscape($strCtRaceName) . "'"
|
||||
. ", updated_at = '" . fxGetDateTime() . "'"
|
||||
. " WHERE eve_id = " . $intEveId . " AND epr_id = " . $intEprId;
|
||||
} else {
|
||||
$sql = "INSERT INTO api_chronotrack_race_map SET eve_id = " . $intEveId
|
||||
. ", epr_id = " . $intEprId
|
||||
. ", ct_race_id = " . $intCtRaceId
|
||||
. ", ct_race_name = '" . $objDatabase->fxEscape($strCtRaceName) . "'"
|
||||
. ", updated_at = '" . fxGetDateTime() . "'";
|
||||
}
|
||||
$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
return array('state' => 'ok');
|
||||
}
|
||||
|
||||
function fxChronotrackApiMs1EventRow($intEveId, $strLangue = 'fr') {
|
||||
global $objDatabase;
|
||||
|
||||
$strLangue = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
$sql = "SELECT eve_id, eve_nom_" . $strLangue . " AS eve_nom, eve_date_debut, eve_date_fin"
|
||||
. " FROM inscriptions_evenements WHERE eve_id = " . intval($intEveId) . " LIMIT 1";
|
||||
return $objDatabase->fxGetRow($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiListActiveConfigs($arrStatuses = null) {
|
||||
global $objDatabase;
|
||||
|
||||
if ($arrStatuses === null) {
|
||||
$arrStatuses = array(MSIN_API_CHRONOTRACK_SYNC_LIE, MSIN_API_CHRONOTRACK_SYNC_ACTIF);
|
||||
}
|
||||
$arrEsc = array();
|
||||
foreach ($arrStatuses as $strStatus) {
|
||||
$arrEsc[] = "'" . $objDatabase->fxEscape($strStatus) . "'";
|
||||
}
|
||||
$sql = "SELECT c.*, e.eve_nom_fr AS eve_nom"
|
||||
. " FROM api_chronotrack_config c"
|
||||
. " INNER JOIN inscriptions_evenements e ON e.eve_id = c.eve_id"
|
||||
. " WHERE c.sync_status IN (" . implode(',', $arrEsc) . ")"
|
||||
. " ORDER BY c.sync_status DESC, c.updated_at DESC";
|
||||
return $objDatabase->fxGetResults($sql);
|
||||
}
|
||||
12
php/chronotrack_api/fx_chronotrack_constants.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — constantes métier du module.
|
||||
*/
|
||||
|
||||
define('MSIN_API_CHRONOTRACK_SYNC_LIE', 'lie');
|
||||
define('MSIN_API_CHRONOTRACK_SYNC_ACTIF', 'actif');
|
||||
define('MSIN_API_CHRONOTRACK_SYNC_FERME', 'ferme');
|
||||
|
||||
if (!defined('MSIN_API_CHRONOTRACK_TOKEN_KEY')) {
|
||||
define('MSIN_API_CHRONOTRACK_TOKEN_KEY', 'prod');
|
||||
}
|
||||
985
php/chronotrack_api/fx_chronotrack_events.php
Normal file
@ -0,0 +1,985 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — lecture / filtrage des ressources CT (event, race).
|
||||
*/
|
||||
|
||||
function fxChronotrackApiNormalizeEntityList($mixPayload, $strEntityKey) {
|
||||
$arrOut = array();
|
||||
|
||||
if (!is_array($mixPayload)) {
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
if (isset($mixPayload[$strEntityKey]) && is_array($mixPayload[$strEntityKey])) {
|
||||
$mixList = $mixPayload[$strEntityKey];
|
||||
if (fxChronotrackApiIsAssocArray($mixList)) {
|
||||
$arrOut[] = $mixList;
|
||||
} else {
|
||||
$arrOut = $mixList;
|
||||
}
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
// Pluriel courant côté CT (ex. "races" pour clé "race")
|
||||
$strPluralKey = $strEntityKey . 's';
|
||||
if ($strEntityKey !== $strPluralKey && isset($mixPayload[$strPluralKey]) && is_array($mixPayload[$strPluralKey])) {
|
||||
$mixList = $mixPayload[$strPluralKey];
|
||||
if (fxChronotrackApiIsAssocArray($mixList)) {
|
||||
$arrOut[] = $mixList;
|
||||
} else {
|
||||
$arrOut = $mixList;
|
||||
}
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
// Clé CT Live pour GET event/{id}/race (ex. event_race, pas race)
|
||||
if ($strEntityKey === 'race') {
|
||||
foreach (array('event_race') as $strAltKey) {
|
||||
if (!isset($mixPayload[$strAltKey]) || !is_array($mixPayload[$strAltKey])) {
|
||||
continue;
|
||||
}
|
||||
$mixList = $mixPayload[$strAltKey];
|
||||
if (fxChronotrackApiIsAssocArray($mixList)) {
|
||||
$arrOut[] = $mixList;
|
||||
} else {
|
||||
$arrOut = $mixList;
|
||||
}
|
||||
return $arrOut;
|
||||
}
|
||||
}
|
||||
|
||||
// MSIN-4328 — clé CT pour reg_choice (souvent event_reg_choice)
|
||||
if ($strEntityKey === 'reg_choice') {
|
||||
foreach (array('event_reg_choice', 'registration_choice') as $strAltKey) {
|
||||
if (!isset($mixPayload[$strAltKey]) || !is_array($mixPayload[$strAltKey])) {
|
||||
continue;
|
||||
}
|
||||
$mixList = $mixPayload[$strAltKey];
|
||||
if (fxChronotrackApiIsAssocArray($mixList)) {
|
||||
$arrOut[] = $mixList;
|
||||
} else {
|
||||
$arrOut = $mixList;
|
||||
}
|
||||
return $arrOut;
|
||||
}
|
||||
}
|
||||
|
||||
// Clé CT Live pour GET event/{id}/entry (ex. event_entry, pas entry)
|
||||
if ($strEntityKey === 'entry') {
|
||||
foreach (array('event_entry', 'entries') as $strAltKey) {
|
||||
if (!isset($mixPayload[$strAltKey]) || !is_array($mixPayload[$strAltKey])) {
|
||||
continue;
|
||||
}
|
||||
$mixList = $mixPayload[$strAltKey];
|
||||
if (fxChronotrackApiIsAssocArray($mixList)) {
|
||||
$arrOut[] = $mixList;
|
||||
} else {
|
||||
$arrOut = $mixList;
|
||||
}
|
||||
return $arrOut;
|
||||
}
|
||||
}
|
||||
|
||||
if (fxChronotrackApiIsAssocArray($mixPayload) && (
|
||||
isset($mixPayload['race_id']) || isset($mixPayload['reg_choice_id'])
|
||||
|| isset($mixPayload['entry_id']) || isset($mixPayload['external_id'])
|
||||
)) {
|
||||
return array($mixPayload);
|
||||
}
|
||||
|
||||
if (fxChronotrackApiIsAssocArray($mixPayload) && isset($mixPayload['id'])
|
||||
&& !isset($mixPayload['event_id']) && !isset($mixPayload['event_name'])
|
||||
) {
|
||||
return array($mixPayload);
|
||||
}
|
||||
|
||||
if (!fxChronotrackApiIsAssocArray($mixPayload)) {
|
||||
return $mixPayload;
|
||||
}
|
||||
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
function fxChronotrackApiIsAssocArray($arr) {
|
||||
if (!is_array($arr) || $arr === array()) {
|
||||
return false;
|
||||
}
|
||||
return array_keys($arr) !== range(0, count($arr) - 1);
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityId($arrEntity) {
|
||||
foreach (array('entry_id', 'reg_choice_id', 'race_id', 'event_id', 'id') as $strKey) {
|
||||
if (isset($arrEntity[$strKey]) && $arrEntity[$strKey] !== '') {
|
||||
return (string)$arrEntity[$strKey];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityExternalId($arrEntity) {
|
||||
foreach (array('external_id', 'entry_external_id', 'ext_id', 'external_entry_id') as $strKey) {
|
||||
if (isset($arrEntity[$strKey]) && $arrEntity[$strKey] !== '') {
|
||||
return (string)$arrEntity[$strKey];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityEntryLabel($arrEntity) {
|
||||
$strFirst = fxChronotrackApiRaceField($arrEntity, array('first_name', 'entry_first_name', 'fname'));
|
||||
$strLast = fxChronotrackApiRaceField($arrEntity, array('last_name', 'entry_last_name', 'lname'));
|
||||
if ($strFirst !== '' || $strLast !== '') {
|
||||
return trim($strFirst . ' ' . $strLast);
|
||||
}
|
||||
|
||||
return fxChronotrackApiEntityName($arrEntity);
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityName($arrEntity) {
|
||||
foreach (array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name',
|
||||
'event_name', 'race_name', 'name', 'title',
|
||||
'entry_name', 'athlete_name',
|
||||
) as $strKey) {
|
||||
if (!empty($arrEntity[$strKey])) {
|
||||
return trim((string)$arrEntity[$strKey]);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiRaceField($arrEntity, $arrKeys) {
|
||||
foreach ($arrKeys as $strKey) {
|
||||
if (!isset($arrEntity[$strKey])) {
|
||||
continue;
|
||||
}
|
||||
$strVal = trim((string)$arrEntity[$strKey]);
|
||||
if ($strVal !== '') {
|
||||
return $strVal;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRaceDistanceLabel($arrEntity) {
|
||||
$strDist = fxChronotrackApiRaceField($arrEntity, array(
|
||||
'race_course_distance', 'course_distance', 'race_distance', 'distance', 'race_length',
|
||||
));
|
||||
if ($strDist === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strUnit = fxChronotrackApiRaceField($arrEntity, array(
|
||||
'race_pref_distance_unit', 'course_distance_type', 'course_distance_unit',
|
||||
'distance_unit', 'race_distance_unit', 'distance_type',
|
||||
));
|
||||
|
||||
if (is_numeric($strDist) && $strUnit !== '') {
|
||||
$fltDist = floatval($strDist);
|
||||
if ($strUnit === 'kilometers' && $fltDist >= 1000) {
|
||||
$strDist = rtrim(rtrim(number_format($fltDist / 1000, 1, '.', ''), '0'), '.') . ' km';
|
||||
return $strDist;
|
||||
}
|
||||
if ($strUnit === 'meters') {
|
||||
$strDist = rtrim(rtrim(number_format($fltDist, 0, '.', ''), '0'), '.') . ' m';
|
||||
return $strDist;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strUnit !== '' && stripos($strDist, $strUnit) === false) {
|
||||
return $strDist . ' ' . $strUnit;
|
||||
}
|
||||
|
||||
return $strDist;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRaceTeamLabel($arrEntity) {
|
||||
foreach (array('team_race', 'is_team_race', 'team') as $strKey) {
|
||||
if (!isset($arrEntity[$strKey])) {
|
||||
continue;
|
||||
}
|
||||
$mixVal = $arrEntity[$strKey];
|
||||
if ($mixVal === true || $mixVal === 1 || $mixVal === '1' || strtolower((string)$mixVal) === 'true' || strtolower((string)$mixVal) === 'yes') {
|
||||
return 'Équipe';
|
||||
}
|
||||
if ($mixVal === false || $mixVal === 0 || $mixVal === '0' || strtolower((string)$mixVal) === 'false' || strtolower((string)$mixVal) === 'no') {
|
||||
return 'Individuel';
|
||||
}
|
||||
}
|
||||
|
||||
$strSubtype = strtolower(fxChronotrackApiRaceField($arrEntity, array('race_subtype', 'subtype', 'participant_type')));
|
||||
if ($strSubtype !== '') {
|
||||
if (strpos($strSubtype, 'team') !== false) {
|
||||
return 'Équipe';
|
||||
}
|
||||
if (strpos($strSubtype, 'individual') !== false || strpos($strSubtype, 'individ') !== false) {
|
||||
return 'Individuel';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRaceRegChoicesLabel($arrEntity) {
|
||||
foreach (array('reg_choice', 'reg_choices', 'registration_choices', 'choices') as $strKey) {
|
||||
if (!isset($arrEntity[$strKey]) || !is_array($arrEntity[$strKey])) {
|
||||
continue;
|
||||
}
|
||||
$arrNames = array();
|
||||
$arrList = $arrEntity[$strKey];
|
||||
if (fxChronotrackApiIsAssocArray($arrList) && (
|
||||
isset($arrList['reg_choice_name']) || isset($arrList['name']) || isset($arrList['label'])
|
||||
)) {
|
||||
$arrList = array($arrList);
|
||||
}
|
||||
foreach ($arrList as $arrChoice) {
|
||||
if (!is_array($arrChoice)) {
|
||||
continue;
|
||||
}
|
||||
$strChoice = fxChronotrackApiRaceField($arrChoice, array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name', 'name', 'label',
|
||||
));
|
||||
if ($strChoice !== '') {
|
||||
$arrNames[] = $strChoice;
|
||||
}
|
||||
}
|
||||
if (count($arrNames) > 0) {
|
||||
return implode(', ', array_values(array_unique($arrNames)));
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRaceLabel($arrEntity) {
|
||||
// MSIN-4328 — nom de race seul ; le reg_choice est ajouté au mapping via ApplyRegChoiceLabels
|
||||
$strName = fxChronotrackApiRaceField($arrEntity, array('race_name', 'display_name', 'name', 'title'));
|
||||
if ($strName !== '') {
|
||||
return $strName;
|
||||
}
|
||||
|
||||
$strTag = fxChronotrackApiRaceField($arrEntity, array('race_tag', 'tag'));
|
||||
$strType = fxChronotrackApiRaceField($arrEntity, array('race_type', 'sport', 'race_course_type'));
|
||||
$strDist = fxChronotrackApiFormatRaceDistanceLabel($arrEntity);
|
||||
$strTeam = fxChronotrackApiFormatRaceTeamLabel($arrEntity);
|
||||
|
||||
$arrExtras = array();
|
||||
if ($strTag !== '') {
|
||||
$arrExtras[] = 'tag ' . $strTag;
|
||||
}
|
||||
if ($strType !== '') {
|
||||
$arrExtras[] = $strType;
|
||||
}
|
||||
if ($strDist !== '') {
|
||||
$arrExtras[] = $strDist;
|
||||
}
|
||||
if ($strTeam !== '') {
|
||||
$arrExtras[] = $strTeam;
|
||||
}
|
||||
|
||||
$arrExtras = array_values(array_unique($arrExtras));
|
||||
$strName = $strDist !== '' ? $strDist : ('Race ' . fxChronotrackApiEntityId($arrEntity));
|
||||
|
||||
if (count($arrExtras) === 0) {
|
||||
return $strName;
|
||||
}
|
||||
|
||||
return $strName . ' — ' . implode(' · ', $arrExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — Fusionne deux index race_id → noms reg_choice.
|
||||
*
|
||||
* @param array<int, string[]> $arrA
|
||||
* @param array<int, string[]> $arrB
|
||||
* @return array<int, string[]>
|
||||
*/
|
||||
function fxChronotrackApiMergeRegChoiceNamesByRaceId($arrA, $arrB) {
|
||||
if (!is_array($arrA)) {
|
||||
$arrA = array();
|
||||
}
|
||||
if (!is_array($arrB) || count($arrB) === 0) {
|
||||
return $arrA;
|
||||
}
|
||||
foreach ($arrB as $intRaceId => $arrNames) {
|
||||
$intRaceId = intval($intRaceId);
|
||||
if ($intRaceId <= 0 || !is_array($arrNames)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($arrA[$intRaceId])) {
|
||||
$arrA[$intRaceId] = array();
|
||||
}
|
||||
foreach ($arrNames as $strName) {
|
||||
$strName = trim((string)$strName);
|
||||
if ($strName === '') {
|
||||
continue;
|
||||
}
|
||||
$arrA[$intRaceId][] = $strName;
|
||||
}
|
||||
$arrA[$intRaceId] = array_values(array_unique($arrA[$intRaceId]));
|
||||
}
|
||||
return $arrA;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — Indexe les noms de reg_choice par race_id parent (payload event/race).
|
||||
*
|
||||
* @return array<int, string[]>
|
||||
*/
|
||||
function fxChronotrackApiRegChoiceNamesByRaceId($mixPayload) {
|
||||
$arrByRace = array();
|
||||
foreach (fxChronotrackApiCollectRegChoiceEntities($mixPayload) as $arrChoice) {
|
||||
if (!is_array($arrChoice)) {
|
||||
continue;
|
||||
}
|
||||
$intParent = intval(fxChronotrackApiRaceField($arrChoice, array('race_id', 'event_race_id')));
|
||||
if ($intParent <= 0 && isset($arrChoice['race']) && is_array($arrChoice['race'])) {
|
||||
$intParent = intval(fxChronotrackApiEntityId($arrChoice['race']));
|
||||
}
|
||||
if ($intParent <= 0) {
|
||||
continue;
|
||||
}
|
||||
$strName = fxChronotrackApiRaceField($arrChoice, array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name', 'display_name', 'name', 'title',
|
||||
));
|
||||
if ($strName === '' && isset($arrChoice['reg_choice_id']) && $arrChoice['reg_choice_id'] !== '') {
|
||||
$strName = 'reg_choice ' . $arrChoice['reg_choice_id'];
|
||||
}
|
||||
if ($strName === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($arrByRace[$intParent])) {
|
||||
$arrByRace[$intParent] = array();
|
||||
}
|
||||
$arrByRace[$intParent][] = $strName;
|
||||
}
|
||||
foreach ($arrByRace as $intRaceId => $arrNames) {
|
||||
$arrByRace[$intRaceId] = array_values(array_unique($arrNames));
|
||||
}
|
||||
return $arrByRace;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — Filet : déduit race_id → reg_choice_name depuis les entries CT.
|
||||
*
|
||||
* @return array<int, string[]>
|
||||
*/
|
||||
function fxChronotrackApiRegChoiceNamesFromEntriesPayload($mixPayload) {
|
||||
$arrByRace = array();
|
||||
$arrEntries = fxChronotrackApiNormalizeEntityList($mixPayload, 'entry');
|
||||
if (count($arrEntries) === 0 && is_array($mixPayload) && isset($mixPayload['event']) && is_array($mixPayload['event'])) {
|
||||
$arrEntries = fxChronotrackApiNormalizeEntityList($mixPayload['event'], 'entry');
|
||||
}
|
||||
foreach ($arrEntries as $arrEntry) {
|
||||
if (!is_array($arrEntry)) {
|
||||
continue;
|
||||
}
|
||||
$intRaceId = intval(fxChronotrackApiRaceField($arrEntry, array('race_id', 'event_race_id')));
|
||||
if ($intRaceId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$strName = fxChronotrackApiRaceField($arrEntry, array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name',
|
||||
));
|
||||
if ($strName === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($arrByRace[$intRaceId])) {
|
||||
$arrByRace[$intRaceId] = array();
|
||||
}
|
||||
$arrByRace[$intRaceId][] = $strName;
|
||||
}
|
||||
foreach ($arrByRace as $intRaceId => $arrNames) {
|
||||
$arrByRace[$intRaceId] = array_values(array_unique($arrNames));
|
||||
}
|
||||
return $arrByRace;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — Ajoute « (reg_choice) » au label de chaque course du mapping.
|
||||
*/
|
||||
function fxChronotrackApiApplyRegChoiceLabelsToRaceRows(&$arrRaceRows, $arrNamesByRaceId) {
|
||||
if (!is_array($arrRaceRows) || !is_array($arrNamesByRaceId)) {
|
||||
return;
|
||||
}
|
||||
foreach ($arrRaceRows as &$arrRow) {
|
||||
if (!is_array($arrRow)) {
|
||||
continue;
|
||||
}
|
||||
$intId = intval($arrRow['ct_race_id'] ?? 0);
|
||||
$strRc = '';
|
||||
if ($intId > 0 && !empty($arrNamesByRaceId[$intId]) && is_array($arrNamesByRaceId[$intId])) {
|
||||
$strRc = implode(', ', $arrNamesByRaceId[$intId]);
|
||||
}
|
||||
if ($strRc === '' && isset($arrRow['raw']) && is_array($arrRow['raw'])) {
|
||||
$strRc = fxChronotrackApiFormatRaceRegChoicesLabel($arrRow['raw']);
|
||||
if ($strRc === '') {
|
||||
$strRc = fxChronotrackApiRaceField($arrRow['raw'], array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name', 'reg_choice_label',
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($strRc === '') {
|
||||
continue;
|
||||
}
|
||||
$arrRow['reg_choice_label'] = $strRc;
|
||||
$strBase = isset($arrRow['name']) && $arrRow['name'] !== ''
|
||||
? $arrRow['name']
|
||||
: (isset($arrRow['label']) ? $arrRow['label'] : 'Course');
|
||||
// Évite double parenthèses si FormatRaceLabel a déjà collé le même texte
|
||||
$strExpected = $strBase . ' (' . $strRc . ')';
|
||||
if (!isset($arrRow['label']) || $arrRow['label'] !== $strExpected) {
|
||||
$arrRow['label'] = $strExpected;
|
||||
}
|
||||
}
|
||||
unset($arrRow);
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityStartEpoch($arrEntity) {
|
||||
foreach (array(
|
||||
'race_planned_start_time', 'race_start_time', 'event_start_time',
|
||||
'start_time', 'start_date', 'event_date',
|
||||
) as $strKey) {
|
||||
if (!isset($arrEntity[$strKey]) || $arrEntity[$strKey] === '' || $arrEntity[$strKey] === null) {
|
||||
continue;
|
||||
}
|
||||
$mixVal = $arrEntity[$strKey];
|
||||
if (is_numeric($mixVal)) {
|
||||
return intval($mixVal);
|
||||
}
|
||||
$intTs = strtotime((string)$mixVal);
|
||||
if ($intTs !== false) {
|
||||
return $intTs;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatEventRow($arrEntity) {
|
||||
$intStart = fxChronotrackApiEntityStartEpoch($arrEntity);
|
||||
return array(
|
||||
'ct_event_id' => fxChronotrackApiEntityId($arrEntity),
|
||||
'name' => fxChronotrackApiEntityName($arrEntity),
|
||||
'start_epoch' => $intStart,
|
||||
'start_label' => $intStart > 0 ? date('Y-m-d H:i', $intStart) : '',
|
||||
'is_past' => ($intStart > 0 && $intStart < strtotime('today')),
|
||||
'raw' => $arrEntity,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiFetchEventsRaw() {
|
||||
$arrApi = fxChronotrackApiOAuthApiGet('event');
|
||||
if ($arrApi['state'] !== 'ok') {
|
||||
return $arrApi;
|
||||
}
|
||||
|
||||
$arrList = fxChronotrackApiNormalizeEntityList($arrApi['json'], 'event');
|
||||
$arrRows = array();
|
||||
foreach ($arrList as $arrEntity) {
|
||||
if (!is_array($arrEntity)) {
|
||||
continue;
|
||||
}
|
||||
$arrRow = fxChronotrackApiFormatEventRow($arrEntity);
|
||||
if ($arrRow['ct_event_id'] === '') {
|
||||
continue;
|
||||
}
|
||||
$arrRows[] = $arrRow;
|
||||
}
|
||||
|
||||
return array('state' => 'ok', 'events' => $arrRows);
|
||||
}
|
||||
|
||||
function fxChronotrackApiFilterEvents($arrEvents, $strSearch = '', $blnShowPast = false) {
|
||||
$strSearch = mb_strtolower(trim($strSearch), 'UTF-8');
|
||||
$arrFiltered = array();
|
||||
|
||||
foreach ($arrEvents as $arrEvent) {
|
||||
if (!$blnShowPast && !empty($arrEvent['is_past'])) {
|
||||
continue;
|
||||
}
|
||||
if ($strSearch !== '') {
|
||||
$strHay = mb_strtolower($arrEvent['name'] . ' ' . $arrEvent['ct_event_id'], 'UTF-8');
|
||||
if (mb_strpos($strHay, $strSearch, 0, 'UTF-8') === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$arrFiltered[] = $arrEvent;
|
||||
}
|
||||
|
||||
usort($arrFiltered, function ($a, $b) {
|
||||
$intA = intval($a['start_epoch']);
|
||||
$intB = intval($b['start_epoch']);
|
||||
if ($intA === $intB) {
|
||||
return strcasecmp($a['name'], $b['name']);
|
||||
}
|
||||
if ($intA <= 0) {
|
||||
return 1;
|
||||
}
|
||||
if ($intB <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return ($intA < $intB) ? -1 : 1;
|
||||
});
|
||||
|
||||
return $arrFiltered;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFetchEventsFiltered($strSearch = '', $blnShowPast = false) {
|
||||
$arrRaw = fxChronotrackApiFetchEventsRaw();
|
||||
if ($arrRaw['state'] !== 'ok') {
|
||||
return $arrRaw;
|
||||
}
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'events' => fxChronotrackApiFilterEvents($arrRaw['events'], $strSearch, $blnShowPast),
|
||||
'total' => count($arrRaw['events']),
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiEntityBelongsToEvent($arrEntity, $intCtEventId) {
|
||||
if ($intCtEventId <= 0) {
|
||||
return true;
|
||||
}
|
||||
foreach (array('event_id', 'event') as $strKey) {
|
||||
if (!isset($arrEntity[$strKey])) {
|
||||
continue;
|
||||
}
|
||||
$mixVal = $arrEntity[$strKey];
|
||||
if (is_array($mixVal)) {
|
||||
$mixVal = $mixVal['event_id'] ?? $mixVal['id'] ?? null;
|
||||
}
|
||||
if ($mixVal !== null && $mixVal !== '' && intval($mixVal) !== $intCtEventId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRegChoiceRow($arrEntity) {
|
||||
$strName = fxChronotrackApiRaceField($arrEntity, array(
|
||||
'reg_choice_name', 'registration_choice_name', 'choice_name', 'display_name', 'name', 'title',
|
||||
));
|
||||
$intParentRaceId = intval(fxChronotrackApiRaceField($arrEntity, array('race_id')));
|
||||
|
||||
$arrExtras = array();
|
||||
if (isset($arrEntity['race']) && is_array($arrEntity['race'])) {
|
||||
$strRaceName = fxChronotrackApiEntityName($arrEntity['race']);
|
||||
$strDist = fxChronotrackApiFormatRaceDistanceLabel($arrEntity['race']);
|
||||
if ($strRaceName !== '' && strcasecmp($strRaceName, $strName) !== 0) {
|
||||
$arrExtras[] = $strRaceName;
|
||||
} elseif ($strDist !== '' && stripos($strName, $strDist) === false) {
|
||||
$arrExtras[] = $strDist;
|
||||
}
|
||||
$strTeam = fxChronotrackApiFormatRaceTeamLabel($arrEntity['race']);
|
||||
if ($strTeam !== '') {
|
||||
$arrExtras[] = $strTeam;
|
||||
}
|
||||
}
|
||||
|
||||
$strLabel = $strName;
|
||||
if ($strLabel === '') {
|
||||
$strLabel = 'Choix d\'inscription ' . fxChronotrackApiEntityId($arrEntity);
|
||||
} elseif (count($arrExtras) > 0) {
|
||||
$strLabel .= ' · ' . implode(' · ', array_values(array_unique($arrExtras)));
|
||||
}
|
||||
|
||||
return array(
|
||||
'ct_race_id' => fxChronotrackApiEntityId($arrEntity),
|
||||
'kind' => 'reg_choice',
|
||||
'name' => $strName !== '' ? $strName : $strLabel,
|
||||
'label' => $strLabel,
|
||||
'parent_race_id' => $intParentRaceId > 0 ? $intParentRaceId : null,
|
||||
'start_label' => '',
|
||||
'raw' => $arrEntity,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiCollectRegChoiceEntities($mixPayload) {
|
||||
$arrById = array();
|
||||
if (!is_array($mixPayload)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$arrScopes = array($mixPayload);
|
||||
if (isset($mixPayload['event']) && is_array($mixPayload['event'])) {
|
||||
$arrScopes[] = $mixPayload['event'];
|
||||
}
|
||||
|
||||
foreach ($arrScopes as $arrScope) {
|
||||
if (!is_array($arrScope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrList = fxChronotrackApiNormalizeEntityList($arrScope, 'reg_choice');
|
||||
foreach ($arrList as $arrEntity) {
|
||||
if (!is_array($arrEntity) || !isset($arrEntity['reg_choice_id'])) {
|
||||
continue;
|
||||
}
|
||||
$strId = (string)$arrEntity['reg_choice_id'];
|
||||
$arrById[$strId] = $arrEntity;
|
||||
}
|
||||
|
||||
$arrRaces = fxChronotrackApiNormalizeEntityList($arrScope, 'race');
|
||||
foreach ($arrRaces as $arrRace) {
|
||||
if (!is_array($arrRace) || !isset($arrRace['race_id'])) {
|
||||
continue;
|
||||
}
|
||||
$arrChoices = fxChronotrackApiNormalizeEntityList($arrRace, 'reg_choice');
|
||||
foreach ($arrChoices as $arrChoice) {
|
||||
if (!is_array($arrChoice) || !isset($arrChoice['reg_choice_id'])) {
|
||||
continue;
|
||||
}
|
||||
$strId = (string)$arrChoice['reg_choice_id'];
|
||||
if (!isset($arrChoice['race']) || !is_array($arrChoice['race'])) {
|
||||
$arrChoice['race'] = $arrRace;
|
||||
}
|
||||
$arrById[$strId] = $arrChoice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($arrById);
|
||||
}
|
||||
|
||||
function fxChronotrackApiCollectRaceEntities($mixPayload) {
|
||||
$arrById = array();
|
||||
if (!is_array($mixPayload)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$arrScopes = array($mixPayload);
|
||||
if (isset($mixPayload['event']) && is_array($mixPayload['event'])) {
|
||||
$arrScopes[] = $mixPayload['event'];
|
||||
}
|
||||
|
||||
foreach ($arrScopes as $arrScope) {
|
||||
if (!is_array($arrScope)) {
|
||||
continue;
|
||||
}
|
||||
$arrList = fxChronotrackApiNormalizeEntityList($arrScope, 'race');
|
||||
foreach ($arrList as $arrEntity) {
|
||||
if (!is_array($arrEntity) || !isset($arrEntity['race_id'])) {
|
||||
continue;
|
||||
}
|
||||
$strId = (string)$arrEntity['race_id'];
|
||||
$arrById[$strId] = $arrEntity;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($arrById);
|
||||
}
|
||||
|
||||
function fxChronotrackApiParseRegChoiceRowsFromPayload($mixPayload, $intCtEventId = 0) {
|
||||
$arrRows = array();
|
||||
$arrEntities = fxChronotrackApiCollectRegChoiceEntities($mixPayload);
|
||||
|
||||
foreach ($arrEntities as $arrEntity) {
|
||||
if ($intCtEventId > 0 && !fxChronotrackApiEntityBelongsToEvent($arrEntity, $intCtEventId)) {
|
||||
continue;
|
||||
}
|
||||
$arrRow = fxChronotrackApiFormatRegChoiceRow($arrEntity);
|
||||
if ($arrRow['ct_race_id'] === '') {
|
||||
continue;
|
||||
}
|
||||
$arrRows[$arrRow['ct_race_id']] = $arrRow;
|
||||
}
|
||||
|
||||
$arrRows = array_values($arrRows);
|
||||
usort($arrRows, function ($a, $b) {
|
||||
return strcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
return $arrRows;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFetchRegChoicesForEvent($intCtEventId) {
|
||||
$intCtEventId = intval($intCtEventId);
|
||||
if ($intCtEventId <= 0) {
|
||||
return array('state' => 'error', 'message' => 'event_id CT invalide');
|
||||
}
|
||||
|
||||
$arrApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId);
|
||||
if ($arrApi['state'] !== 'ok') {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => $arrApi['message'] ?? 'Impossible de lire l\'événement ChronoTrack',
|
||||
);
|
||||
}
|
||||
|
||||
$arrRows = fxChronotrackApiParseRegChoiceRowsFromPayload($arrApi['json'], $intCtEventId);
|
||||
if (count($arrRows) > 0) {
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'races' => $arrRows,
|
||||
'source' => 'event/' . $intCtEventId,
|
||||
'kind' => 'reg_choice',
|
||||
);
|
||||
}
|
||||
|
||||
return array('state' => 'ok', 'races' => array(), 'kind' => 'reg_choice');
|
||||
}
|
||||
|
||||
function fxChronotrackApiFormatRaceRow($arrEntity) {
|
||||
$intStart = fxChronotrackApiEntityStartEpoch($arrEntity);
|
||||
$strLabel = fxChronotrackApiFormatRaceLabel($arrEntity);
|
||||
return array(
|
||||
'ct_race_id' => fxChronotrackApiEntityId($arrEntity),
|
||||
'kind' => 'race',
|
||||
'name' => fxChronotrackApiEntityName($arrEntity),
|
||||
'label' => $strLabel,
|
||||
'race_tag' => fxChronotrackApiRaceField($arrEntity, array('race_tag', 'tag')),
|
||||
'race_type' => fxChronotrackApiRaceField($arrEntity, array('race_type', 'sport')),
|
||||
'distance' => fxChronotrackApiFormatRaceDistanceLabel($arrEntity),
|
||||
'team_mode' => fxChronotrackApiFormatRaceTeamLabel($arrEntity),
|
||||
'start_label' => $intStart > 0 ? date('Y-m-d H:i', $intStart) : '',
|
||||
'raw' => $arrEntity,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiParseRaceRowsFromPayload($mixPayload, $intCtEventId = 0) {
|
||||
$arrRows = array();
|
||||
$arrEntities = fxChronotrackApiCollectRaceEntities($mixPayload);
|
||||
|
||||
foreach ($arrEntities as $arrEntity) {
|
||||
if ($intCtEventId > 0 && !fxChronotrackApiEntityBelongsToEvent($arrEntity, $intCtEventId)) {
|
||||
continue;
|
||||
}
|
||||
$arrRow = fxChronotrackApiFormatRaceRow($arrEntity);
|
||||
if ($arrRow['ct_race_id'] === '') {
|
||||
continue;
|
||||
}
|
||||
$arrRows[$arrRow['ct_race_id']] = $arrRow;
|
||||
}
|
||||
|
||||
$arrRows = array_values($arrRows);
|
||||
usort($arrRows, function ($a, $b) {
|
||||
return strcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
return $arrRows;
|
||||
}
|
||||
|
||||
function fxChronotrackApiFetchRacesForEvent($intCtEventId) {
|
||||
$intCtEventId = intval($intCtEventId);
|
||||
if ($intCtEventId <= 0) {
|
||||
return array('state' => 'error', 'message' => 'event_id CT invalide');
|
||||
}
|
||||
|
||||
if (!fxChronotrackApiSettingsConfigured()) {
|
||||
return array('state' => 'error', 'message' => fxChronotrackApiSettingsErrorMessage());
|
||||
}
|
||||
|
||||
$strRacePath = 'event/' . $intCtEventId . '/race';
|
||||
$arrApi = fxChronotrackApiOAuthApiGet($strRacePath);
|
||||
if ($arrApi['state'] !== 'ok') {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => $arrApi['message'] ?? 'Impossible de lire les courses ChronoTrack',
|
||||
);
|
||||
}
|
||||
|
||||
$arrRaceRows = fxChronotrackApiParseRaceRowsFromPayload($arrApi['json'], $intCtEventId);
|
||||
|
||||
// MSIN-4328 — enrichir libellés dropdown CT : race_name (reg_choice)
|
||||
$arrNamesByRace = fxChronotrackApiRegChoiceNamesByRaceId($arrApi['json']);
|
||||
if (count($arrRaceRows) > 0) {
|
||||
$arrEventApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId);
|
||||
if (($arrEventApi['state'] ?? '') === 'ok') {
|
||||
$arrNamesByRace = fxChronotrackApiMergeRegChoiceNamesByRaceId(
|
||||
$arrNamesByRace,
|
||||
fxChronotrackApiRegChoiceNamesByRaceId($arrEventApi['json'] ?? null)
|
||||
);
|
||||
}
|
||||
// Filet : noms visibles CT souvent présents sur les entries
|
||||
if (count($arrNamesByRace) === 0) {
|
||||
$arrEntryApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/entry');
|
||||
if (($arrEntryApi['state'] ?? '') === 'ok') {
|
||||
$arrNamesByRace = fxChronotrackApiRegChoiceNamesFromEntriesPayload(
|
||||
$arrEntryApi['json'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fxChronotrackApiApplyRegChoiceLabelsToRaceRows($arrRaceRows, $arrNamesByRace);
|
||||
|
||||
if (count($arrRaceRows) > 0) {
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'races' => $arrRaceRows,
|
||||
'source' => $strRacePath,
|
||||
'kind' => 'race',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'races' => array(),
|
||||
'kind' => 'race',
|
||||
'message' => 'Aucune course dans GET ' . $strRacePath
|
||||
. ' (clé attendue : event_race).',
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiDiagnosticEntitySamples($mixPayload, $strEntityKey, $intMax = 8) {
|
||||
if ($strEntityKey === 'reg_choice') {
|
||||
$arrEntities = fxChronotrackApiCollectRegChoiceEntities($mixPayload);
|
||||
} elseif ($strEntityKey === 'race') {
|
||||
$arrEntities = fxChronotrackApiCollectRaceEntities($mixPayload);
|
||||
} elseif ($strEntityKey === 'entry') {
|
||||
$arrEntities = fxChronotrackApiNormalizeEntityList($mixPayload, 'entry');
|
||||
} else {
|
||||
$arrEntities = fxChronotrackApiNormalizeEntityList($mixPayload, $strEntityKey);
|
||||
}
|
||||
|
||||
$arrSamples = array();
|
||||
foreach ($arrEntities as $arrEntity) {
|
||||
if (!is_array($arrEntity)) {
|
||||
continue;
|
||||
}
|
||||
if ($strEntityKey === 'entry') {
|
||||
$strId = fxChronotrackApiEntityId($arrEntity);
|
||||
if ($strId === '') {
|
||||
$strId = fxChronotrackApiEntityExternalId($arrEntity);
|
||||
}
|
||||
if ($strId === '') {
|
||||
continue;
|
||||
}
|
||||
$arrSamples[$strId] = array(
|
||||
'id' => $strId,
|
||||
'external_id' => fxChronotrackApiEntityExternalId($arrEntity),
|
||||
'name' => fxChronotrackApiEntityEntryLabel($arrEntity),
|
||||
'bib' => fxChronotrackApiRaceField($arrEntity, array('bib', 'bib_number', 'entry_bib')),
|
||||
'entry_status'=> fxChronotrackApiRaceField($arrEntity, array('entry_status', 'status')),
|
||||
'race_id' => fxChronotrackApiRaceField($arrEntity, array('race_id', 'event_race_id')),
|
||||
'country_code'=> fxChronotrackApiRaceField($arrEntity, array('country_code', 'country', 'country_iso')),
|
||||
'state_code' => fxChronotrackApiRaceField($arrEntity, array('state_code', 'state', 'province_code')),
|
||||
'keys' => array_slice(array_keys($arrEntity), 0, 32),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$strId = fxChronotrackApiEntityId($arrEntity);
|
||||
if ($strId === '') {
|
||||
continue;
|
||||
}
|
||||
$arrSamples[$strId] = array(
|
||||
'id' => $strId,
|
||||
'name' => fxChronotrackApiEntityName($arrEntity),
|
||||
'keys' => array_slice(array_keys($arrEntity), 0, 24),
|
||||
);
|
||||
}
|
||||
|
||||
$arrSamples = array_values($arrSamples);
|
||||
return array(
|
||||
'count' => count($arrSamples),
|
||||
'samples' => array_slice($arrSamples, 0, $intMax),
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiDiagnosticSummarizeEntryPayload($mixJson) {
|
||||
if (!is_array($mixJson)) {
|
||||
return array(
|
||||
'top_keys' => array(),
|
||||
'entry' => array('count' => 0, 'samples' => array(), 'field_keys' => array()),
|
||||
);
|
||||
}
|
||||
|
||||
$arrEntry = fxChronotrackApiDiagnosticEntitySamples($mixJson, 'entry', 5);
|
||||
$arrFieldKeys = array();
|
||||
if (!empty($arrEntry['samples'][0]['keys'])) {
|
||||
$arrFieldKeys = $arrEntry['samples'][0]['keys'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'top_keys' => array_slice(array_keys($mixJson), 0, 40),
|
||||
'entry' => $arrEntry,
|
||||
'field_keys' => $arrFieldKeys,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiDiagnosticSummarizePayload($mixJson) {
|
||||
if (!is_array($mixJson)) {
|
||||
return array(
|
||||
'top_keys' => array(),
|
||||
'reg_choice' => array('count' => 0, 'samples' => array()),
|
||||
'race' => array('count' => 0, 'samples' => array()),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'top_keys' => array_slice(array_keys($mixJson), 0, 40),
|
||||
'event_race' => fxChronotrackApiDiagnosticEntitySamples(
|
||||
is_array($mixJson) && isset($mixJson['event_race']) ? array('event_race' => $mixJson['event_race']) : $mixJson,
|
||||
'race'
|
||||
),
|
||||
'reg_choice' => fxChronotrackApiDiagnosticEntitySamples($mixJson, 'reg_choice'),
|
||||
'race' => fxChronotrackApiDiagnosticEntitySamples($mixJson, 'race'),
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiDiagnosticEncodeJson($mixJson) {
|
||||
$strRaw = json_encode($mixJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
if (!is_string($strRaw)) {
|
||||
return '(json_encode a échoué)';
|
||||
}
|
||||
if (strlen($strRaw) > 48000) {
|
||||
return substr($strRaw, 0, 48000) . "\n\n... [tronqué — " . strlen($strRaw) . ' octets au total]';
|
||||
}
|
||||
return $strRaw;
|
||||
}
|
||||
|
||||
function fxChronotrackApiRunEventDiagnostic($intCtEventId) {
|
||||
$intCtEventId = intval($intCtEventId);
|
||||
if ($intCtEventId <= 0) {
|
||||
return array('state' => 'error', 'message' => 'ct_event_id manquant');
|
||||
}
|
||||
if (!fxChronotrackApiSettingsConfigured()) {
|
||||
return array('state' => 'error', 'message' => fxChronotrackApiSettingsErrorMessage());
|
||||
}
|
||||
|
||||
$arrToken = fxChronotrackApiOAuthFetchToken(false);
|
||||
if ($arrToken['state'] !== 'ok') {
|
||||
return array('state' => 'error', 'message' => 'OAuth : ' . ($arrToken['message'] ?? 'échec'));
|
||||
}
|
||||
|
||||
$arrPaths = array(
|
||||
array('path' => 'event/' . $intCtEventId . '/race'),
|
||||
array('path' => 'event/' . $intCtEventId . '/entry', 'query' => array('page' => 1, 'page_size' => 5)),
|
||||
array('path' => 'event/' . $intCtEventId),
|
||||
array('path' => 'event/' . $intCtEventId . '/reg_choice'),
|
||||
);
|
||||
|
||||
$arrProbes = array();
|
||||
foreach ($arrPaths as $arrPathDef) {
|
||||
$strPath = $arrPathDef['path'];
|
||||
$arrQuery = isset($arrPathDef['query']) ? $arrPathDef['query'] : array();
|
||||
$arrApi = fxChronotrackApiOAuthApiGet($strPath, $arrQuery);
|
||||
$arrProbe = array(
|
||||
'path' => $strPath,
|
||||
'query' => $arrQuery,
|
||||
'state' => $arrApi['state'] ?? 'error',
|
||||
'http_code' => intval($arrApi['http_code'] ?? $arrApi['http']['http_code'] ?? 0),
|
||||
'message' => $arrApi['message'] ?? '',
|
||||
'summary' => array(),
|
||||
'raw_json' => '',
|
||||
);
|
||||
if ($arrApi['state'] === 'ok') {
|
||||
if (strpos($strPath, '/entry') !== false) {
|
||||
$arrProbe['summary'] = fxChronotrackApiDiagnosticSummarizeEntryPayload($arrApi['json']);
|
||||
} else {
|
||||
$arrProbe['summary'] = fxChronotrackApiDiagnosticSummarizePayload($arrApi['json']);
|
||||
}
|
||||
$arrProbe['raw_json'] = fxChronotrackApiDiagnosticEncodeJson($arrApi['json']);
|
||||
} elseif (isset($arrApi['http']['body']) && trim((string)$arrApi['http']['body']) !== '') {
|
||||
$arrProbe['raw_json'] = substr(trim((string)$arrApi['http']['body']), 0, 8000);
|
||||
}
|
||||
$arrProbes[] = $arrProbe;
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'ct_event_id' => $intCtEventId,
|
||||
'oauth' => !empty($arrToken['from_cache']) ? 'token en cache' : 'token renouvelé',
|
||||
'probes' => $arrProbes,
|
||||
'generated_at'=> date('c'),
|
||||
);
|
||||
}
|
||||
111
php/chronotrack_api/fx_chronotrack_http.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — client HTTP bas niveau (cURL).
|
||||
*/
|
||||
|
||||
function fxChronotrackApiHttpRequest($strMethod, $strUrl, $arrOptions = array()) {
|
||||
$arrResult = array(
|
||||
'state' => 'error',
|
||||
'http_code' => 0,
|
||||
'body' => '',
|
||||
'json' => null,
|
||||
'curl_error' => '',
|
||||
);
|
||||
|
||||
if (!function_exists('curl_init')) {
|
||||
$arrResult['curl_error'] = 'cURL indisponible';
|
||||
return $arrResult;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
$arrHeaders = isset($arrOptions['headers']) && is_array($arrOptions['headers']) ? $arrOptions['headers'] : array();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $strUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 180);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $arrHeaders);
|
||||
|
||||
$strMethod = strtoupper(trim($strMethod));
|
||||
if ($strMethod === 'GET') {
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $strMethod);
|
||||
if (isset($arrOptions['body'])) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $arrOptions['body']);
|
||||
} elseif ($strMethod === 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($arrOptions['basic_user']) || !empty($arrOptions['basic_pass'])) {
|
||||
curl_setopt($ch, CURLOPT_USERPWD, $arrOptions['basic_user'] . ':' . $arrOptions['basic_pass']);
|
||||
}
|
||||
|
||||
$strBody = curl_exec($ch);
|
||||
$arrResult['http_code'] = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($strBody === false) {
|
||||
$arrResult['curl_error'] = curl_error($ch);
|
||||
} else {
|
||||
$arrResult['body'] = $strBody;
|
||||
$tabJson = json_decode($strBody, true);
|
||||
if (is_array($tabJson)) {
|
||||
$arrResult['json'] = $tabJson;
|
||||
}
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($arrResult['curl_error'] !== '') {
|
||||
return $arrResult;
|
||||
}
|
||||
|
||||
if ($arrResult['http_code'] >= 200 && $arrResult['http_code'] < 300) {
|
||||
$arrResult['state'] = 'ok';
|
||||
}
|
||||
|
||||
return $arrResult;
|
||||
}
|
||||
|
||||
function fxChronotrackApiHttpGetJsonErrorMessage($arrHttp) {
|
||||
if (!empty($arrHttp['curl_error'])) {
|
||||
return $arrHttp['curl_error'];
|
||||
}
|
||||
|
||||
$arrParts = array();
|
||||
if (is_array($arrHttp['json'])) {
|
||||
foreach (array('errorMessage', 'message', 'error_description', 'errorCode') as $strKey) {
|
||||
if (!empty($arrHttp['json'][$strKey])) {
|
||||
$arrParts[] = (string)$arrHttp['json'][$strKey];
|
||||
}
|
||||
}
|
||||
if (!empty($arrHttp['json']['error'])) {
|
||||
if (is_array($arrHttp['json']['error'])) {
|
||||
foreach ($arrHttp['json']['error'] as $mixErr) {
|
||||
if (is_string($mixErr) && trim($mixErr) !== '') {
|
||||
$arrParts[] = $mixErr;
|
||||
} elseif (is_array($mixErr)) {
|
||||
if (!empty($mixErr['errorMessage'])) {
|
||||
$arrParts[] = (string)$mixErr['errorMessage'];
|
||||
} elseif (!empty($mixErr['message'])) {
|
||||
$arrParts[] = (string)$mixErr['message'];
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (is_string($arrHttp['json']['error'])) {
|
||||
$arrParts[] = $arrHttp['json']['error'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($arrParts) > 0) {
|
||||
return 'HTTP ' . intval($arrHttp['http_code']) . ' — ' . implode(' | ', array_unique($arrParts));
|
||||
}
|
||||
|
||||
$strBody = trim((string)($arrHttp['body'] ?? ''));
|
||||
if ($strBody !== '') {
|
||||
return 'HTTP ' . intval($arrHttp['http_code']) . ' — ' . substr($strBody, 0, 400);
|
||||
}
|
||||
|
||||
return 'HTTP ' . intval($arrHttp['http_code']);
|
||||
}
|
||||
221
php/chronotrack_api/fx_chronotrack_oauth.php
Normal file
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — OAuth2 Password Flow + cache token.
|
||||
*/
|
||||
|
||||
function fxChronotrackApiOAuthGetTokenRow() {
|
||||
global $objDatabase;
|
||||
|
||||
$sql = "SELECT * FROM api_chronotrack_token WHERE token_key = '"
|
||||
. $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_TOKEN_KEY) . "' LIMIT 1";
|
||||
return $objDatabase->fxGetRow($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthSaveToken($strAccessToken, $strRefreshToken, $intExpiresEpoch) {
|
||||
global $objDatabase;
|
||||
|
||||
$strExpires = '';
|
||||
if ($intExpiresEpoch > 0) {
|
||||
$strExpires = date('Y-m-d H:i:s', $intExpiresEpoch);
|
||||
}
|
||||
|
||||
$sqlExists = "SELECT token_key FROM api_chronotrack_token WHERE token_key = '"
|
||||
. $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_TOKEN_KEY) . "' LIMIT 1";
|
||||
if ($objDatabase->fxGetRow($sqlExists) !== null) {
|
||||
$sql = "UPDATE api_chronotrack_token SET access_token = '"
|
||||
. $objDatabase->fxEscape($strAccessToken) . "', refresh_token = '"
|
||||
. $objDatabase->fxEscape($strRefreshToken) . "', expires_at = "
|
||||
. ($strExpires !== '' ? "'" . $objDatabase->fxEscape($strExpires) . "'" : 'NULL')
|
||||
. ", updated_at = '" . fxGetDateTime() . "' WHERE token_key = '"
|
||||
. $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_TOKEN_KEY) . "'";
|
||||
} else {
|
||||
$sql = "INSERT INTO api_chronotrack_token SET token_key = '"
|
||||
. $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_TOKEN_KEY) . "', access_token = '"
|
||||
. $objDatabase->fxEscape($strAccessToken) . "', refresh_token = '"
|
||||
. $objDatabase->fxEscape($strRefreshToken) . "', expires_at = "
|
||||
. ($strExpires !== '' ? "'" . $objDatabase->fxEscape($strExpires) . "'" : 'NULL')
|
||||
. ", updated_at = '" . fxGetDateTime() . "'";
|
||||
}
|
||||
$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthTokenValid($arrRow) {
|
||||
if ($arrRow === null || trim((string)($arrRow['access_token'] ?? '')) === '') {
|
||||
return false;
|
||||
}
|
||||
if (empty($arrRow['expires_at'])) {
|
||||
return true;
|
||||
}
|
||||
$intExpires = strtotime($arrRow['expires_at']);
|
||||
if ($intExpires === false) {
|
||||
return true;
|
||||
}
|
||||
return ($intExpires > (time() + 120));
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthFetchToken($blnForceRefresh = false) {
|
||||
if (!fxChronotrackApiSettingsConfigured()) {
|
||||
return array('state' => 'error', 'message' => fxChronotrackApiSettingsErrorMessage());
|
||||
}
|
||||
|
||||
if (!$blnForceRefresh) {
|
||||
$arrCached = fxChronotrackApiOAuthGetTokenRow();
|
||||
if (fxChronotrackApiOAuthTokenValid($arrCached)) {
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'access_token' => $arrCached['access_token'],
|
||||
'from_cache' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$strBase = fxChronotrackApiGetBaseUrl();
|
||||
$strQuery = http_build_query(array(
|
||||
'grant_type' => 'password',
|
||||
'username' => fxChronotrackApiGetServiceUser(),
|
||||
'password' => fxChronotrackApiGetServicePass(),
|
||||
));
|
||||
$strUrl = $strBase . '/oauth2/token?' . $strQuery;
|
||||
|
||||
$arrHttp = fxChronotrackApiHttpRequest('GET', $strUrl, array(
|
||||
'basic_user' => fxChronotrackApiGetClientId(),
|
||||
'basic_pass' => fxChronotrackApiGetClientSecret(),
|
||||
));
|
||||
|
||||
if ($arrHttp['state'] !== 'ok' || !is_array($arrHttp['json'])) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
);
|
||||
}
|
||||
|
||||
$arrJson = $arrHttp['json'];
|
||||
if (!empty($arrJson['errorCode']) || !empty($arrJson['error'])) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
);
|
||||
}
|
||||
|
||||
$strToken = trim((string)($arrJson['access_token'] ?? ''));
|
||||
if ($strToken === '') {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => 'access_token absent — ' . fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
);
|
||||
}
|
||||
|
||||
$strRefresh = trim((string)($arrJson['refresh_token'] ?? ''));
|
||||
$intExpires = intval($arrJson['expires'] ?? 0);
|
||||
if ($intExpires <= 0 && !empty($arrJson['expires_in'])) {
|
||||
$intExpires = time() + intval($arrJson['expires_in']);
|
||||
}
|
||||
fxChronotrackApiOAuthSaveToken($strToken, $strRefresh, $intExpires);
|
||||
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'access_token' => $strToken,
|
||||
'from_cache' => false,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthApiGet($strPath, $arrQuery = array()) {
|
||||
$arrToken = fxChronotrackApiOAuthFetchToken(false);
|
||||
if ($arrToken['state'] !== 'ok') {
|
||||
return $arrToken;
|
||||
}
|
||||
|
||||
$arrQuery['client_id'] = fxChronotrackApiGetClientId();
|
||||
$strUrl = fxChronotrackApiGetApiBaseUrl() . '/' . ltrim($strPath, '/');
|
||||
if (!empty($arrQuery)) {
|
||||
$strUrl .= '?' . http_build_query($arrQuery);
|
||||
}
|
||||
|
||||
$arrHttp = fxChronotrackApiHttpRequest('GET', $strUrl, array(
|
||||
'headers' => array('Authorization: Bearer ' . $arrToken['access_token']),
|
||||
));
|
||||
|
||||
if ($arrHttp['state'] === 'ok') {
|
||||
return array('state' => 'ok', 'json' => $arrHttp['json'], 'http' => $arrHttp);
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
'http' => $arrHttp,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthApiPost($strPath, $mixBody, $arrQuery = array()) {
|
||||
$arrToken = fxChronotrackApiOAuthFetchToken(false);
|
||||
if ($arrToken['state'] !== 'ok') {
|
||||
return $arrToken;
|
||||
}
|
||||
|
||||
$arrQuery['client_id'] = fxChronotrackApiGetClientId();
|
||||
$strUrl = fxChronotrackApiGetApiBaseUrl() . '/' . ltrim($strPath, '/');
|
||||
if (!empty($arrQuery)) {
|
||||
$strUrl .= '?' . http_build_query($arrQuery);
|
||||
}
|
||||
|
||||
$strBody = json_encode($mixBody, JSON_UNESCAPED_UNICODE);
|
||||
if (!is_string($strBody)) {
|
||||
return array('state' => 'error', 'message' => 'json_encode a échoué');
|
||||
}
|
||||
|
||||
$arrHttp = fxChronotrackApiHttpRequest('POST', $strUrl, array(
|
||||
'headers' => array(
|
||||
'Authorization: Bearer ' . $arrToken['access_token'],
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
),
|
||||
'body' => $strBody,
|
||||
));
|
||||
|
||||
if ($arrHttp['state'] === 'ok') {
|
||||
return array('state' => 'ok', 'json' => $arrHttp['json'], 'http' => $arrHttp);
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
'http' => $arrHttp,
|
||||
);
|
||||
}
|
||||
|
||||
function fxChronotrackApiOAuthApiPut($strPath, $mixBody, $arrQuery = array()) {
|
||||
$arrToken = fxChronotrackApiOAuthFetchToken(false);
|
||||
if ($arrToken['state'] !== 'ok') {
|
||||
return $arrToken;
|
||||
}
|
||||
|
||||
$arrQuery['client_id'] = fxChronotrackApiGetClientId();
|
||||
$strUrl = fxChronotrackApiGetApiBaseUrl() . '/' . ltrim($strPath, '/');
|
||||
if (!empty($arrQuery)) {
|
||||
$strUrl .= '?' . http_build_query($arrQuery);
|
||||
}
|
||||
|
||||
$strBody = json_encode($mixBody, JSON_UNESCAPED_UNICODE);
|
||||
if (!is_string($strBody)) {
|
||||
return array('state' => 'error', 'message' => 'json_encode a échoué');
|
||||
}
|
||||
|
||||
$arrHttp = fxChronotrackApiHttpRequest('PUT', $strUrl, array(
|
||||
'headers' => array(
|
||||
'Authorization: Bearer ' . $arrToken['access_token'],
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
),
|
||||
'body' => $strBody,
|
||||
));
|
||||
|
||||
if ($arrHttp['state'] === 'ok') {
|
||||
return array('state' => 'ok', 'json' => $arrHttp['json'], 'http' => $arrHttp);
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => fxChronotrackApiHttpGetJsonErrorMessage($arrHttp),
|
||||
'http' => $arrHttp,
|
||||
);
|
||||
}
|
||||
51
php/chronotrack_api/fx_chronotrack_settings.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — lecture des credentials (inc_settings.php).
|
||||
*/
|
||||
|
||||
function fxChronotrackApiSettingsConfigured() {
|
||||
if (!defined('MSIN_API_CHRONOTRACK_CLIENT_ID') || !defined('MSIN_API_CHRONOTRACK_CLIENT_SECRET')) {
|
||||
return false;
|
||||
}
|
||||
if (trim(MSIN_API_CHRONOTRACK_CLIENT_ID) === '' || trim(MSIN_API_CHRONOTRACK_CLIENT_SECRET) === '') {
|
||||
return false;
|
||||
}
|
||||
if (!defined('MSIN_API_CHRONOTRACK_SERVICE_USER') || !defined('MSIN_API_CHRONOTRACK_SERVICE_PASS')) {
|
||||
return false;
|
||||
}
|
||||
if (trim(MSIN_API_CHRONOTRACK_SERVICE_USER) === '' || trim(MSIN_API_CHRONOTRACK_SERVICE_PASS) === '') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetBaseUrl() {
|
||||
if (defined('MSIN_API_CHRONOTRACK_BASE_URL') && trim(MSIN_API_CHRONOTRACK_BASE_URL) !== '') {
|
||||
return rtrim(MSIN_API_CHRONOTRACK_BASE_URL, '/');
|
||||
}
|
||||
return 'https://api.chronotrack.com';
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetApiBaseUrl() {
|
||||
return fxChronotrackApiGetBaseUrl() . '/api';
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetClientId() {
|
||||
return defined('MSIN_API_CHRONOTRACK_CLIENT_ID') ? trim(MSIN_API_CHRONOTRACK_CLIENT_ID) : '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetClientSecret() {
|
||||
return defined('MSIN_API_CHRONOTRACK_CLIENT_SECRET') ? MSIN_API_CHRONOTRACK_CLIENT_SECRET : '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetServiceUser() {
|
||||
return defined('MSIN_API_CHRONOTRACK_SERVICE_USER') ? trim(MSIN_API_CHRONOTRACK_SERVICE_USER) : '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiGetServicePass() {
|
||||
return defined('MSIN_API_CHRONOTRACK_SERVICE_PASS') ? MSIN_API_CHRONOTRACK_SERVICE_PASS : '';
|
||||
}
|
||||
|
||||
function fxChronotrackApiSettingsErrorMessage() {
|
||||
return 'Credentials MSIN API ChronoTrack incomplets dans php/inc_settings.php (client_id, client_secret, service_user, service_pass).';
|
||||
}
|
||||
2910
php/chronotrack_api/fx_chronotrack_sync.php
Normal file
17
php/chronotrack_api/inc_bootstrap.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN API ChronoTrack — chargement du module (parallèle au site legacy).
|
||||
*/
|
||||
|
||||
if (!defined('MSIN_API_CHRONOTRACK_LOADED')) {
|
||||
define('MSIN_API_CHRONOTRACK_LOADED', true);
|
||||
|
||||
require_once __DIR__ . '/fx_chronotrack_constants.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_settings.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_http.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_oauth.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_events.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_config.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_sync.php';
|
||||
require_once __DIR__ . '/fx_chronotrack_admin.php';
|
||||
}
|
||||
466
php/doc_edition.php
Normal file
@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Éditeur documentation MS1 (pop-up) — module doc_page FR/EN, toutes les pages d'un module.
|
||||
*/
|
||||
|
||||
require_once('inc_start_time.php');
|
||||
require_once('inc_fonctions.php');
|
||||
require_once('inc_fx_messages.php');
|
||||
|
||||
if (empty($_SESSION['usa_id'])) {
|
||||
fxAdminRequireSuperadmLogin();
|
||||
}
|
||||
|
||||
if (!fxAdminCanUseTextEditTools()) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
|
||||
function docEdEsc($str) {
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function docEdFieldAttrs($strChamp, $strRowKey, $strLang) {
|
||||
$id = 'doc_' . preg_replace('/[^a-z0-9_]/i', '_', $strRowKey) . '_' . $strLang . '_' . $strChamp;
|
||||
return 'id="' . docEdEsc($id) . '" name="pages[' . docEdEsc($strRowKey) . '][' . docEdEsc($strLang) . '][' . docEdEsc($strChamp) . ']"';
|
||||
}
|
||||
|
||||
function docEdLoadModulePages($modClef, $prg) {
|
||||
global $objDatabase;
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM doc_page
|
||||
WHERE doc_mod_clef = '" . $objDatabase->fxEscape($modClef) . "'
|
||||
AND doc_prg IN ('" . $objDatabase->fxEscape($prg) . "', 'global')
|
||||
ORDER BY doc_tri, doc_page_clef, doc_langue DESC";
|
||||
$rows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
$grouped = array();
|
||||
if (!is_array($rows)) {
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= count($rows); $i++) {
|
||||
$row = $rows[$i];
|
||||
$pageClef = $row['doc_page_clef'] ?? '';
|
||||
if ($pageClef === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($grouped[$pageClef])) {
|
||||
$grouped[$pageClef] = array(
|
||||
'doc_page_clef' => $pageClef,
|
||||
'doc_tri' => (int)($row['doc_tri'] ?? 0),
|
||||
'fr' => null,
|
||||
'en' => null,
|
||||
);
|
||||
}
|
||||
$lang = $row['doc_langue'] ?? 'fr';
|
||||
if ($lang === 'fr' || $lang === 'en') {
|
||||
$grouped[$pageClef][$lang] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
uasort($grouped, function ($a, $b) {
|
||||
if ($a['doc_tri'] === $b['doc_tri']) {
|
||||
return strcmp($a['doc_page_clef'], $b['doc_page_clef']);
|
||||
}
|
||||
return $a['doc_tri'] <=> $b['doc_tri'];
|
||||
});
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
$anchor = trim($_GET['anchor'] ?? $_POST['anchor'] ?? '');
|
||||
$modClef = trim($_GET['mod'] ?? $_POST['mod'] ?? '');
|
||||
$prg = trim($_GET['prg'] ?? $_POST['prg'] ?? 'compte.php');
|
||||
$page = trim($_GET['page'] ?? $_POST['page'] ?? '');
|
||||
$bd = trim(strtolower($_GET['bd'] ?? $_POST['bd'] ?? 'client'));
|
||||
$blnSaved = false;
|
||||
|
||||
if ($modClef === '' && $anchor !== '') {
|
||||
$modClef = fxDocResolveModClef($anchor, 0);
|
||||
}
|
||||
if ($anchor === '' && $modClef !== '') {
|
||||
$anchor = $modClef;
|
||||
}
|
||||
|
||||
if (isset($_POST['btn_mod']) && !empty($_POST['pages']) && is_array($_POST['pages'])) {
|
||||
foreach ($_POST['pages'] as $pageClef => $langs) {
|
||||
if (!is_array($langs)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($langs as $lang => $tabPosted) {
|
||||
if (!is_array($tabPosted) || empty($tabPosted['doc_langue'])) {
|
||||
continue;
|
||||
}
|
||||
$tabPosted['doc_page_clef'] = $pageClef;
|
||||
$tabPosted['doc_mod_clef'] = $modClef;
|
||||
$tabPosted['doc_prg'] = $prg;
|
||||
if (!empty($tabPosted['pk_doc_page'])) {
|
||||
fxSetDocPage('mod', $tabPosted, $tabPosted['pk_doc_page']);
|
||||
} else {
|
||||
fxSetDocPage('add', $tabPosted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_POST['doc_mod']) && is_array($_POST['doc_mod']) && !empty($_POST['doc_mod']['pk_doc_mod'])) {
|
||||
fxSetDocMod('mod', $_POST['doc_mod'], $_POST['doc_mod']['pk_doc_mod']);
|
||||
}
|
||||
|
||||
$blnSaved = true;
|
||||
}
|
||||
|
||||
$sqlMod = "SELECT * FROM doc_mod
|
||||
WHERE doc_mod_clef = '" . $objDatabase->fxEscape($modClef) . "'
|
||||
AND doc_mod_prg IN ('" . $objDatabase->fxEscape($prg) . "', 'global')
|
||||
ORDER BY FIELD(doc_mod_prg, '" . $objDatabase->fxEscape($prg) . "', 'global')
|
||||
LIMIT 1";
|
||||
$tabMod = $objDatabase->fxGetRow($sqlMod);
|
||||
|
||||
$tabPages = docEdLoadModulePages($modClef, $prg);
|
||||
|
||||
$tabLangMeta = array(
|
||||
'fr' => 'Français',
|
||||
'en' => 'English',
|
||||
);
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Documentation — <?php echo docEdEsc($modClef); ?></title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
color: #212529;
|
||||
background: #f1f3f5;
|
||||
}
|
||||
.doced-page {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px 32px;
|
||||
}
|
||||
.doced-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.doced-card-header {
|
||||
padding: 14px 18px;
|
||||
background: #2f5fd0;
|
||||
color: #fff;
|
||||
}
|
||||
.doced-card-header h1 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.doced-card-header .doced-key {
|
||||
font-size: 12px;
|
||||
opacity: .9;
|
||||
word-break: break-all;
|
||||
}
|
||||
.doced-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
padding: 12px 18px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
.doced-meta dt {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
color: #6c757d;
|
||||
}
|
||||
.doced-meta dd {
|
||||
margin: 2px 0 0;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
.doced-alert {
|
||||
margin: 14px 18px 0;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
background: #d1e7dd;
|
||||
color: #0f5132;
|
||||
border: 1px solid #badbcc;
|
||||
}
|
||||
.doced-mod-field {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
.doced-mod-field label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.doced-mod-field input {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
}
|
||||
.doced-page-block {
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.doced-page-block:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
.doced-page-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.doced-page-head h2 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #2f5fd0;
|
||||
}
|
||||
.doced-page-head .doced-page-clef {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
}
|
||||
.doced-page-head input[type="number"] {
|
||||
width: 72px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.doced-lang-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.doced-lang-grid { grid-template-columns: 1fr; }
|
||||
.doced-meta { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
.doced-lang-panel {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
.doced-lang-panel h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.doced-field {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.doced-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.doced-field label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
.doced-field .doced-hint {
|
||||
font-weight: 400;
|
||||
color: #868e96;
|
||||
}
|
||||
.doced-field input[type="text"],
|
||||
.doced-field textarea {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
color: #212529;
|
||||
}
|
||||
.doced-field textarea.doced-html {
|
||||
min-height: 200px;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.doced-actions {
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.doced-btn-save {
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #28a745;
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.doced-btn-save:hover {
|
||||
background: #218838;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doced-page">
|
||||
<form action="<?php echo docEdEsc($vDomaine); ?>/php/doc_edition.php" method="post" id="frm_doc_edition">
|
||||
<input type="hidden" name="anchor" value="<?php echo docEdEsc($anchor); ?>">
|
||||
<input type="hidden" name="mod" value="<?php echo docEdEsc($modClef); ?>">
|
||||
<input type="hidden" name="prg" value="<?php echo docEdEsc($prg); ?>">
|
||||
<input type="hidden" name="page" value="<?php echo docEdEsc($page); ?>">
|
||||
<input type="hidden" name="bd" value="<?php echo docEdEsc($bd); ?>">
|
||||
|
||||
<div class="doced-card">
|
||||
<div class="doced-card-header">
|
||||
<h1>Guide documentation</h1>
|
||||
<div class="doced-key">Module <?php echo docEdEsc($modClef); ?> · ancre <?php echo docEdEsc($anchor); ?></div>
|
||||
</div>
|
||||
|
||||
<dl class="doced-meta">
|
||||
<div>
|
||||
<dt>Programme</dt>
|
||||
<dd><?php echo docEdEsc($prg); ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Ancre UI</dt>
|
||||
<dd><?php echo docEdEsc($anchor); ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Pages</dt>
|
||||
<dd><?php echo count($tabPages); ?></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Base</dt>
|
||||
<dd><?php echo docEdEsc($bd); ?></dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<?php if ($blnSaved) { ?>
|
||||
<div class="doced-alert">Enregistré. Fermez cette fenêtre pour recharger l'écran principal.</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (is_array($tabMod) && !empty($tabMod['pk_doc_mod'])) { ?>
|
||||
<div class="doced-mod-field">
|
||||
<label for="doc_mod_titre">Titre du module (doc_mod)</label>
|
||||
<input type="hidden" name="doc_mod[pk_doc_mod]" value="<?php echo (int)$tabMod['pk_doc_mod']; ?>">
|
||||
<input type="hidden" name="doc_mod[doc_mod_clef]" value="<?php echo docEdEsc($tabMod['doc_mod_clef']); ?>">
|
||||
<input type="hidden" name="doc_mod[doc_mod_prg]" value="<?php echo docEdEsc($tabMod['doc_mod_prg']); ?>">
|
||||
<input type="text" id="doc_mod_titre" name="doc_mod[doc_mod_titre]"
|
||||
value="<?php echo docEdEsc($tabMod['doc_mod_titre'] ?? ''); ?>" maxlength="255">
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
$intPageNum = 0;
|
||||
foreach ($tabPages as $pageClef => $tabPageGroup) {
|
||||
$intPageNum++;
|
||||
$tri = (int)$tabPageGroup['doc_tri'];
|
||||
?>
|
||||
<section class="doced-page-block">
|
||||
<div class="doced-page-head">
|
||||
<h2>Page <?php echo (int)$intPageNum; ?></h2>
|
||||
<span class="doced-page-clef"><?php echo docEdEsc($pageClef); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="doced-lang-grid">
|
||||
<?php foreach ($tabLangMeta as $strLang => $strLangLabel) {
|
||||
$tabRow = $tabPageGroup[$strLang];
|
||||
$isMod = is_array($tabRow) && !empty($tabRow['pk_doc_page']);
|
||||
?>
|
||||
<div class="doced-lang-panel">
|
||||
<h3><?php echo docEdEsc($strLangLabel); ?></h3>
|
||||
|
||||
<input type="hidden" <?php echo docEdFieldAttrs('pk_doc_page', $pageClef, $strLang); ?>
|
||||
value="<?php echo $isMod ? (int)$tabRow['pk_doc_page'] : ''; ?>">
|
||||
<input type="hidden" <?php echo docEdFieldAttrs('doc_langue', $pageClef, $strLang); ?>
|
||||
value="<?php echo docEdEsc($strLang); ?>">
|
||||
<input type="hidden" <?php echo docEdFieldAttrs('doc_tri', $pageClef, $strLang); ?>
|
||||
value="<?php echo $isMod ? (int)$tabRow['doc_tri'] : $tri; ?>">
|
||||
<input type="hidden" <?php echo docEdFieldAttrs('doc_actif', $pageClef, $strLang); ?>
|
||||
value="1">
|
||||
|
||||
<div class="doced-field">
|
||||
<label>Titre overlay</label>
|
||||
<input type="text" maxlength="255"
|
||||
<?php echo docEdFieldAttrs('doc_titre', $pageClef, $strLang); ?>
|
||||
value="<?php echo docEdEsc($isMod ? ($tabRow['doc_titre'] ?? '') : ''); ?>">
|
||||
</div>
|
||||
<div class="doced-field">
|
||||
<label>Sous-titre</label>
|
||||
<input type="text" maxlength="255"
|
||||
<?php echo docEdFieldAttrs('doc_sous_titre', $pageClef, $strLang); ?>
|
||||
value="<?php echo docEdEsc($isMod ? ($tabRow['doc_sous_titre'] ?? '') : ''); ?>">
|
||||
</div>
|
||||
<div class="doced-field">
|
||||
<label>Contenu HTML <span class="doced-hint">(doc_html)</span></label>
|
||||
<textarea class="doced-html" <?php echo docEdFieldAttrs('doc_html', $pageClef, $strLang); ?>><?php
|
||||
echo docEdEsc($isMod ? ($tabRow['doc_html'] ?? '') : '');
|
||||
?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (empty($tabPages)) { ?>
|
||||
<div class="doced-page-block">
|
||||
<p>Aucune page pour ce module. Vérifiez que <code>doc_page</code> est peuplé pour
|
||||
<strong><?php echo docEdEsc($modClef); ?></strong>.</p>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="doced-actions">
|
||||
<button type="submit" name="btn_mod" value="save" class="doced-btn-save">Enregistrer tout le guide</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Editeur WYSIWYG Trumbowyg — UNIQUEMENT cette page (doc_edition.php).
|
||||
N'affecte pas le TinyMCE/CKEditor utilises ailleurs sur le site.
|
||||
-->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trumbowyg@2.27.3/dist/ui/trumbowyg.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trumbowyg@2.27.3/dist/plugins/table/ui/trumbowyg.table.min.css">
|
||||
<style>
|
||||
.trumbowyg-box { margin: 0; border-radius: 4px; }
|
||||
.trumbowyg-editor { min-height: 220px; font-family: inherit; font-size: 14px; }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/trumbowyg@2.27.3/dist/trumbowyg.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/trumbowyg@2.27.3/dist/plugins/table/trumbowyg.table.min.js"></script>
|
||||
<script>
|
||||
jQuery(function ($) {
|
||||
$('textarea.doced-html').trumbowyg({
|
||||
semantic: true,
|
||||
autogrow: true,
|
||||
resetCss: true,
|
||||
removeformatPasted: true,
|
||||
btns: [
|
||||
['viewHTML'],
|
||||
['formatting'],
|
||||
['strong', 'em'],
|
||||
['link'],
|
||||
['unorderedList', 'orderedList'],
|
||||
['table'],
|
||||
['horizontalRule'],
|
||||
['removeformat']
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
677
php/inc_cookie_consent.php
Normal file
@ -0,0 +1,677 @@
|
||||
<?php
|
||||
|
||||
function fxCookiesPreferences(): array
|
||||
{
|
||||
$defaults = ['analytics' => false, 'marketing' => false, 'set' => false];
|
||||
|
||||
if (!empty($_COOKIE['cookiesPreferences'])) {
|
||||
$prefs = json_decode(stripslashes((string) $_COOKIE['cookiesPreferences']), true);
|
||||
|
||||
if (is_array($prefs)) {
|
||||
return [
|
||||
'analytics' => !empty($prefs['analytics']),
|
||||
'marketing' => !empty($prefs['marketing']),
|
||||
'set' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_COOKIE['cookiesAccepted']) && $_COOKIE['cookiesAccepted'] === 'true') {
|
||||
return ['analytics' => true, 'marketing' => true, 'set' => true];
|
||||
}
|
||||
|
||||
if (isset($_COOKIE['cookiesRejected']) && $_COOKIE['cookiesRejected'] === 'true') {
|
||||
return ['analytics' => false, 'marketing' => false, 'set' => true];
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
function fxCookiesConsentAccepted(): bool
|
||||
{
|
||||
$prefs = fxCookiesPreferences();
|
||||
|
||||
return $prefs['set'] && ($prefs['analytics'] || $prefs['marketing']);
|
||||
}
|
||||
|
||||
function fxCookiesConsentRejected(): bool
|
||||
{
|
||||
$prefs = fxCookiesPreferences();
|
||||
|
||||
return $prefs['set'] && !$prefs['analytics'] && !$prefs['marketing'];
|
||||
}
|
||||
|
||||
function fxCookiesConsentPending(): bool
|
||||
{
|
||||
return !fxCookiesPreferences()['set'];
|
||||
}
|
||||
|
||||
function fxCookiesAllowAnalytics(): bool
|
||||
{
|
||||
return fxCookiesPreferences()['analytics'];
|
||||
}
|
||||
|
||||
function fxCookiesAllowMarketing(): bool
|
||||
{
|
||||
return fxCookiesPreferences()['marketing'];
|
||||
}
|
||||
|
||||
function fxCookiesConsentResetIfRequested(): void
|
||||
{
|
||||
if (!isset($_GET['cookies'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$past = time() - 3600;
|
||||
$names = ['cookiesAccepted', 'cookiesRejected', 'cookiesPreferences'];
|
||||
|
||||
foreach ($names as $name) {
|
||||
setcookie($name, '', $past, '/');
|
||||
unset($_COOKIE[$name]);
|
||||
}
|
||||
|
||||
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/';
|
||||
$query = [];
|
||||
|
||||
if (!empty($_SERVER['QUERY_STRING'])) {
|
||||
parse_str($_SERVER['QUERY_STRING'], $query);
|
||||
}
|
||||
|
||||
unset($query['cookies']);
|
||||
$location = $path;
|
||||
|
||||
if ($query) {
|
||||
$location .= '?' . http_build_query($query);
|
||||
}
|
||||
|
||||
header('Location: ' . $location);
|
||||
exit;
|
||||
}
|
||||
|
||||
function fxPageNeedsPaypal(): bool
|
||||
{
|
||||
global $strPage;
|
||||
|
||||
if (empty($strPage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $paypalPages = [
|
||||
'panier.php',
|
||||
'dons.php',
|
||||
'paiement_redirect.php',
|
||||
'panier',
|
||||
'cart',
|
||||
'don',
|
||||
'donate',
|
||||
];
|
||||
|
||||
return in_array($strPage, $paypalPages, true);
|
||||
}
|
||||
|
||||
function fxCookieConsentDefaults(): array
|
||||
{
|
||||
return [
|
||||
'experience_cookies' => [
|
||||
'fr' => 'Nous utilisons des témoins (cookies) pour assurer le bon fonctionnement du site, sécuriser vos inscriptions et mémoriser votre panier. Avec votre consentement, nous utilisons aussi des outils de mesure d\'audience et de marketing. Vous pouvez accepter, refuser ou personnaliser vos choix à tout moment. Votre décision n\'empêche pas de vous inscrire.',
|
||||
'en' => 'We use cookies to ensure the site works properly, secure your registration and remember your cart. With your consent, we also use analytics and marketing tools. You may accept, refuse or customize your choices at any time. Your decision does not prevent you from registering.',
|
||||
],
|
||||
'bouton_acceper_cookies' => [
|
||||
'fr' => 'Tout accepter',
|
||||
'en' => 'Accept all',
|
||||
],
|
||||
'bouton_refuser_cookies' => [
|
||||
'fr' => 'Tout refuser',
|
||||
'en' => 'Refuse all',
|
||||
],
|
||||
'bouton_personnaliser_cookies' => [
|
||||
'fr' => 'Personnaliser',
|
||||
'en' => 'Customize',
|
||||
],
|
||||
'bouton_enregistrer_cookies' => [
|
||||
'fr' => 'Enregistrer mes choix',
|
||||
'en' => 'Save my choices',
|
||||
],
|
||||
'cookie_pref_titre' => [
|
||||
'fr' => 'Gérer mes préférences de témoins',
|
||||
'en' => 'Manage my cookie preferences',
|
||||
],
|
||||
'cookie_pref_essentiels' => [
|
||||
'fr' => 'Témoins essentiels — nécessaires au panier, à la session, à la connexion et à la sécurité du site. Toujours actifs.',
|
||||
'en' => 'Essential cookies — required for cart, session, login and site security. Always active.',
|
||||
],
|
||||
'cookie_pref_analytique' => [
|
||||
'fr' => 'Mesure d\'audience — statistiques de fréquentation du site via Google Analytics.',
|
||||
'en' => 'Analytics — site traffic statistics via Google Analytics.',
|
||||
],
|
||||
'cookie_pref_marketing' => [
|
||||
'fr' => 'Marketing — mesure des conversions et publicité via Meta (Facebook) et scripts ajoutés par l\'organisateur.',
|
||||
'en' => 'Marketing — conversion tracking and advertising via Meta (Facebook) and scripts added by the organizer.',
|
||||
],
|
||||
'politique_cookie' => [
|
||||
'fr' => 'Politique de témoins',
|
||||
'en' => 'Cookie policy',
|
||||
],
|
||||
'politique_confidentialite' => [
|
||||
'fr' => 'Politique de confidentialité',
|
||||
'en' => 'Privacy policy',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function fxCookieConsentTextIsDraft(string $clef, string $text): bool
|
||||
{
|
||||
$text = trim($text);
|
||||
|
||||
if ($text === '' || $text === $clef || $text === '*' . $clef . '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stripos($text, $clef) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function fxEnsureCookieConsentTexts(): void
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $done = false;
|
||||
|
||||
if ($done) {
|
||||
return;
|
||||
}
|
||||
|
||||
$done = true;
|
||||
|
||||
foreach (fxCookieConsentDefaults() as $clef => $texts) {
|
||||
foreach (['fr', 'en'] as $lang) {
|
||||
if (!isset($texts[$lang])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sql = "SELECT pk_info, info_texte FROM info
|
||||
WHERE info_clef = '" . $objDatabase->fxEscape($clef) . "'
|
||||
AND info_langue = '" . $objDatabase->fxEscape($lang) . "'
|
||||
AND info_prg = 'global'
|
||||
LIMIT 1";
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
|
||||
if (!$row) {
|
||||
if (function_exists('fxSetInfo')) {
|
||||
fxSetInfo('add', [
|
||||
'info_clef' => $clef,
|
||||
'info_langue' => $lang,
|
||||
'info_texte' => $texts[$lang],
|
||||
'info_aide' => '',
|
||||
'info_prg' => 'global',
|
||||
'info_actif' => 1,
|
||||
], $lang);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fxCookieConsentTextIsDraft($clef, (string) $row['info_texte'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetText = fxCookieConsentDefaults()[$clef][$lang] ?? $texts[$lang];
|
||||
|
||||
$objDatabase->fxQuery(
|
||||
"UPDATE info SET info_texte = '" . $objDatabase->fxEscape($targetText) . "', info_maj = NOW()
|
||||
WHERE pk_info = " . intval($row['pk_info'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fxCookieConsentText(string $clef, string $strLangue): string
|
||||
{
|
||||
$defaults = fxCookieConsentDefaults();
|
||||
$lang = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
|
||||
return $defaults[$clef][$lang] ?? $clef;
|
||||
}
|
||||
|
||||
function fxCookieLegalPlaceholders(string $html): string
|
||||
{
|
||||
global $vClient, $vDomaine;
|
||||
|
||||
$org = htmlspecialchars((string) $vClient, ENT_QUOTES, 'UTF-8');
|
||||
$domain = htmlspecialchars((string) $vDomaine, ENT_QUOTES, 'UTF-8');
|
||||
$email = 'confidentialite@' . preg_replace('/^https?:\/\//', '', (string) parse_url($vDomaine, PHP_URL_HOST));
|
||||
$date = date('Y-m-d');
|
||||
|
||||
$replacements = [
|
||||
'[ORGANISATION]' => $org,
|
||||
'[ORGANIZATION]' => $org,
|
||||
'[SITE]' => $domain,
|
||||
'[DATE]' => $date,
|
||||
'[COURRIEL]' => $email,
|
||||
'[EMAIL]' => $email,
|
||||
];
|
||||
|
||||
return str_replace(array_keys($replacements), array_values($replacements), $html);
|
||||
}
|
||||
|
||||
function fxCookiePolicyHtml(string $strLangue): string
|
||||
{
|
||||
if ($strLangue === 'en') {
|
||||
return fxCookieLegalPlaceholders(<<<'HTML'
|
||||
<p><em>Last updated: [DATE]</em></p>
|
||||
|
||||
<p>This cookie policy explains how <strong>[ORGANIZATION]</strong> uses cookies and similar technologies on the registration website <strong>[SITE]</strong>, operated on the MS1 Inscription platform (Progiweb inc.).</p>
|
||||
|
||||
<h3>What is a cookie?</h3>
|
||||
<p>A cookie is a small file stored on your device when you visit a website. It helps the site remember information such as your language, session or cart contents.</p>
|
||||
|
||||
<h3>How to manage your choices</h3>
|
||||
<p>When you first visit the site, a banner lets you <strong>accept all</strong>, <strong>refuse all</strong> or <strong>customize</strong> optional cookies. You can change your choices at any time by clicking the cookie icon in the footer.</p>
|
||||
|
||||
<h3>1. Essential cookies (always active)</h3>
|
||||
<p>These cookies are necessary for the site to function. They do not require your consent.</p>
|
||||
<ul>
|
||||
<li><strong>Session cookie (PHPSESSID)</strong> — keeps you logged in, maintains your cart and forms. Duration: session, approximately 3 hours of inactivity.</li>
|
||||
<li><strong>no_panier</strong> — identifies your registration cart. Duration: 10 hours.</li>
|
||||
<li><strong>cookiesPreferences</strong> — remembers your cookie choices. Duration: 12 months.</li>
|
||||
<li><strong>reCAPTCHA (Google)</strong> — protects forms against spam and abuse.</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. Analytics cookies (optional)</h3>
|
||||
<p>Active only if you enable <em>Analytics</em> in the banner.</p>
|
||||
<ul>
|
||||
<li><strong>Google Analytics</strong> (_ga, _ga_*, _gid) — helps us understand how the site is used. Provider: Google LLC. Duration: up to 13 months.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Marketing cookies (optional)</h3>
|
||||
<p>Active only if you enable <em>Marketing</em> in the banner and if enabled for the event.</p>
|
||||
<ul>
|
||||
<li><strong>Meta Pixel</strong> (_fbp, _fbc) — measures advertising effectiveness and conversions. Provider: Meta Platforms Inc. Duration: up to 90 days.</li>
|
||||
<li><strong>Custom scripts</strong> — the event organizer may add additional tools for a specific event.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Payment cookies</h3>
|
||||
<p>When you proceed to payment, <strong>PayPal</strong> may place cookies required to process your transaction securely. These cookies are used only during checkout.</p>
|
||||
|
||||
<h3>5. Similar technologies</h3>
|
||||
<p>We may use local storage on your device to control how often certain informational messages are displayed. This data remains on your device.</p>
|
||||
|
||||
<h3>6. Cross-border transfers</h3>
|
||||
<p>Some providers (Google, Meta, PayPal) may process data outside Quebec, including in the United States. Contractual and technical safeguards are in place.</p>
|
||||
|
||||
<h3>7. Your rights</h3>
|
||||
<p>Under Quebec Law 25, you may withdraw your consent for optional cookies at any time. For any questions: <a href="mailto:[EMAIL]">[EMAIL]</a>.</p>
|
||||
<p>See also our <a href="[SITE]/page/confidentialite/en">privacy policy</a>.</p>
|
||||
HTML
|
||||
);
|
||||
}
|
||||
|
||||
return fxCookieLegalPlaceholders(<<<'HTML'
|
||||
<p><em>Dernière mise à jour : [DATE]</em></p>
|
||||
|
||||
<p>La présente politique explique comment <strong>[ORGANISATION]</strong> utilise des témoins (cookies) et technologies similaires sur le site d'inscription <strong>[SITE]</strong>, exploité via la plateforme MS1 Inscription (Progiweb inc.).</p>
|
||||
|
||||
<h3>Qu'est-ce qu'un témoin?</h3>
|
||||
<p>Un témoin est un petit fichier déposé sur votre appareil lors d'une visite. Il permet au site de mémoriser certaines informations, comme votre langue, votre session ou le contenu de votre panier.</p>
|
||||
|
||||
<h3>Comment gérer vos choix</h3>
|
||||
<p>Lors de votre première visite, un bandeau vous permet d'<strong>accepter tout</strong>, de <strong>tout refuser</strong> ou de <strong>personnaliser</strong> les témoins optionnels. Vous pouvez modifier vos choix en tout temps en cliquant sur l'icône témoins dans le pied de page.</p>
|
||||
|
||||
<h3>1. Témoins essentiels (toujours actifs)</h3>
|
||||
<p>Ces témoins sont nécessaires au fonctionnement du site. Ils ne requièrent pas votre consentement.</p>
|
||||
<ul>
|
||||
<li><strong>Témoin de session (PHPSESSID)</strong> — maintient votre connexion, votre panier et vos formulaires. Durée : session, environ 3 heures d'inactivité.</li>
|
||||
<li><strong>no_panier</strong> — identifie votre panier d'inscription. Durée : 10 heures.</li>
|
||||
<li><strong>cookiesPreferences</strong> — mémorise vos choix de témoins. Durée : 12 mois.</li>
|
||||
<li><strong>reCAPTCHA (Google)</strong> — protège les formulaires contre le pourriel et les abus.</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. Témoins analytiques (optionnels)</h3>
|
||||
<p>Actifs seulement si vous activez la <em>mesure d'audience</em> dans le bandeau.</p>
|
||||
<ul>
|
||||
<li><strong>Google Analytics</strong> (_ga, _ga_*, _gid) — nous aide à comprendre l'utilisation du site. Fournisseur : Google LLC. Durée : jusqu'à 13 mois.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Témoins marketing (optionnels)</h3>
|
||||
<p>Actifs seulement si vous activez le <em>marketing</em> dans le bandeau et si l'organisateur les a activés pour l'événement.</p>
|
||||
<ul>
|
||||
<li><strong>Pixel Meta</strong> (_fbp, _fbc) — mesure l'efficacité publicitaire et les conversions. Fournisseur : Meta Platforms inc. Durée : jusqu'à 90 jours.</li>
|
||||
<li><strong>Scripts personnalisés</strong> — l'organisateur de l'événement peut ajouter d'autres outils pour un événement précis.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Témoins de paiement</h3>
|
||||
<p>Lors du paiement, <strong>PayPal</strong> peut déposer des témoins nécessaires au traitement sécurisé de la transaction. Ces témoins sont utilisés uniquement lors du passage à la caisse.</p>
|
||||
|
||||
<h3>5. Technologies similaires</h3>
|
||||
<p>Nous pouvons utiliser le stockage local de votre navigateur pour contrôler la fréquence d'affichage de certains messages informatifs. Ces données restent sur votre appareil.</p>
|
||||
|
||||
<h3>6. Transferts hors Québec</h3>
|
||||
<p>Certains fournisseurs (Google, Meta, PayPal) peuvent traiter des données à l'extérieur du Québec, notamment aux États-Unis. Des mesures contractuelles et techniques sont en place.</p>
|
||||
|
||||
<h3>7. Vos droits</h3>
|
||||
<p>Conformément à la Loi 25, vous pouvez retirer votre consentement pour les témoins optionnels en tout temps. Pour toute question : <a href="mailto:[COURRIEL]">[COURRIEL]</a>.</p>
|
||||
<p>Consultez aussi notre <a href="[SITE]/page/confidentialite/fr">politique de confidentialité</a>.</p>
|
||||
HTML
|
||||
);
|
||||
}
|
||||
|
||||
function fxPrivacyPolicyHtml(string $strLangue): string
|
||||
{
|
||||
if ($strLangue === 'en') {
|
||||
return fxCookieLegalPlaceholders(<<<'HTML'
|
||||
<p><em>Last updated: [DATE]</em></p>
|
||||
|
||||
<p><strong>[ORGANIZATION]</strong> (the "Organizer") respects your privacy. This policy describes how we collect, use and protect personal information when you use our registration website <strong>[SITE]</strong>, in accordance with Quebec's Act respecting the protection of personal information in the private sector (Law 25).</p>
|
||||
|
||||
<p>The site is operated on the MS1 Inscription platform by Progiweb inc., which acts as a technical service provider for the Organizer.</p>
|
||||
|
||||
<h3>1. Person responsible for the protection of personal information</h3>
|
||||
<p>For any privacy-related request:</p>
|
||||
<p><strong>Email:</strong> <a href="mailto:[EMAIL]">[EMAIL]</a></p>
|
||||
|
||||
<h3>2. Personal information we collect</h3>
|
||||
<p>Depending on your registration, we may collect:</p>
|
||||
<ul>
|
||||
<li><strong>Identity and contact:</strong> name, address, city, province, postal code, email, phone, date of birth, sex;</li>
|
||||
<li><strong>Registration:</strong> category, race, team, bib number, answers to organizer questions;</li>
|
||||
<li><strong>Account:</strong> login credentials if you create an account;</li>
|
||||
<li><strong>Payment:</strong> amount, payment method, transaction ID (card numbers are processed by PayPal — we do not store them);</li>
|
||||
<li><strong>Technical data:</strong> IP address, browser type, pages visited, cookies (see our <a href="[SITE]/page/cookie/en">cookie policy</a>).</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Purposes of collection</h3>
|
||||
<ul>
|
||||
<li>Process and confirm your event registration;</li>
|
||||
<li>Manage payments, refunds and order-related communications;</li>
|
||||
<li>Send confirmations, reminders and practical event information;</li>
|
||||
<li>Ensure site security and prevent fraud;</li>
|
||||
<li>Produce visit statistics and measure advertising campaigns, <strong>only with your cookie consent</strong>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Legal basis</h3>
|
||||
<ul>
|
||||
<li><strong>Contract:</strong> registration and payment processing;</li>
|
||||
<li><strong>Legal obligation:</strong> retention of certain accounting records;</li>
|
||||
<li><strong>Legitimate interest:</strong> site security and fraud prevention;</li>
|
||||
<li><strong>Consent:</strong> non-essential cookies and marketing communications, where applicable.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Third parties and service providers</h3>
|
||||
<p>Your information may be shared with:</p>
|
||||
<ul>
|
||||
<li><strong>Progiweb inc. (MS1 Inscription)</strong> — hosting and platform operation;</li>
|
||||
<li><strong>PayPal</strong> — payment processing;</li>
|
||||
<li><strong>Google</strong> — reCAPTCHA (security); Google Analytics (if consented);</li>
|
||||
<li><strong>Meta (Facebook)</strong> — advertising pixel (if consented);</li>
|
||||
<li><strong>Event organizer and partners</strong> — race management, timing, bib pickup.</li>
|
||||
</ul>
|
||||
<p>We do not sell your personal information.</p>
|
||||
|
||||
<h3>6. Cross-border transfers</h3>
|
||||
<p>Some service providers may process data outside Quebec. We require appropriate contractual and security measures in such cases.</p>
|
||||
|
||||
<h3>7. Retention period</h3>
|
||||
<ul>
|
||||
<li>Confirmed registration data: retained for the period required by the Organizer and applicable laws;</li>
|
||||
<li>Uncompleted cart: up to 10 hours;</li>
|
||||
<li>Session: approximately 3 hours of inactivity;</li>
|
||||
<li>Accounting and transaction records: minimum 6 years where required by law.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Security</h3>
|
||||
<p>We apply reasonable measures including HTTPS encryption, access controls and backups. No method is completely risk-free.</p>
|
||||
|
||||
<h3>9. Your rights</h3>
|
||||
<p>You may request access to, correction or deletion of your personal information, or withdraw your consent where applicable. Contact: <a href="mailto:[EMAIL]">[EMAIL]</a>. We will respond within a reasonable time, generally within 30 days.</p>
|
||||
<p>You may also file a complaint with the Commission d'accès à l'information du Québec (CAI): <a href="https://www.cai.gouv.qc.ca" target="_blank" rel="noopener">www.cai.gouv.qc.ca</a>.</p>
|
||||
|
||||
<h3>10. Minors</h3>
|
||||
<p>Some events involve minor participants. We collect only information necessary for registration and event safety. Parental consent may be required depending on the event.</p>
|
||||
|
||||
<h3>11. Privacy incidents</h3>
|
||||
<p>In the event of an incident presenting a risk of serious harm, we will notify the CAI and affected individuals as required by law.</p>
|
||||
|
||||
<h3>12. Changes</h3>
|
||||
<p>We may update this policy. The date above indicates the latest revision. Material changes may be communicated by email or site notice.</p>
|
||||
HTML
|
||||
);
|
||||
}
|
||||
|
||||
return fxCookieLegalPlaceholders(<<<'HTML'
|
||||
<p><em>Dernière mise à jour : [DATE]</em></p>
|
||||
|
||||
<p><strong>[ORGANISATION]</strong> (l'« Organisateur ») respecte votre vie privée. La présente politique décrit comment nous collectons, utilisons et protégeons vos renseignements personnels lorsque vous utilisez notre site d'inscription <strong>[SITE]</strong>, conformément à la <em>Loi sur la protection des renseignements personnels dans le secteur privé</em> (Loi 25).</p>
|
||||
|
||||
<p>Le site est exploité sur la plateforme MS1 Inscription par Progiweb inc., qui agit à titre de mandataire technique pour l'Organisateur.</p>
|
||||
|
||||
<h3>1. Responsable de la protection des renseignements personnels</h3>
|
||||
<p>Pour toute demande relative à la confidentialité :</p>
|
||||
<p><strong>Courriel :</strong> <a href="mailto:[COURRIEL]">[COURRIEL]</a></p>
|
||||
|
||||
<h3>2. Renseignements personnels collectés</h3>
|
||||
<p>Selon votre inscription, nous pouvons collecter :</p>
|
||||
<ul>
|
||||
<li><strong>Identité et coordonnées :</strong> nom, adresse, ville, province, code postal, courriel, téléphone, date de naissance, sexe;</li>
|
||||
<li><strong>Inscription :</strong> catégorie, épreuve, équipe, numéro de dossard, réponses aux questions de l'organisateur;</li>
|
||||
<li><strong>Compte :</strong> identifiants de connexion si vous créez un compte;</li>
|
||||
<li><strong>Paiement :</strong> montant, mode de paiement, identifiant de transaction (les numéros de carte sont traités par PayPal — nous ne les conservons pas);</li>
|
||||
<li><strong>Données techniques :</strong> adresse IP, type de navigateur, pages visitées, témoins (voir notre <a href="[SITE]/page/cookie/fr">politique de témoins</a>).</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Finalités de la collecte</h3>
|
||||
<ul>
|
||||
<li>Traiter et confirmer votre inscription à l'événement;</li>
|
||||
<li>Gérer les paiements, remboursements et communications liées à votre commande;</li>
|
||||
<li>Envoyer confirmations, rappels et informations pratiques sur l'événement;</li>
|
||||
<li>Assurer la sécurité du site et prévenir la fraude;</li>
|
||||
<li>Produire des statistiques de visite et mesurer des campagnes publicitaires, <strong>seulement avec votre consentement aux témoins</strong>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Fondements</h3>
|
||||
<ul>
|
||||
<li><strong>Exécution du contrat :</strong> inscription et traitement du paiement;</li>
|
||||
<li><strong>Obligation légale :</strong> conservation de certains documents comptables;</li>
|
||||
<li><strong>Intérêt légitime :</strong> sécurité du site et prévention de la fraude;</li>
|
||||
<li><strong>Consentement :</strong> témoins non essentiels et communications marketing, le cas échéant.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Tiers et fournisseurs de services</h3>
|
||||
<p>Vos renseignements peuvent être communiqués à :</p>
|
||||
<ul>
|
||||
<li><strong>Progiweb inc. (MS1 Inscription)</strong> — hébergement et exploitation de la plateforme;</li>
|
||||
<li><strong>PayPal</strong> — traitement des paiements;</li>
|
||||
<li><strong>Google</strong> — reCAPTCHA (sécurité); Google Analytics (si consenti);</li>
|
||||
<li><strong>Meta (Facebook)</strong> — pixel publicitaire (si consenti);</li>
|
||||
<li><strong>Organisateur et partenaires de l'événement</strong> — gestion de la course, chronométrage, remise des dossards.</li>
|
||||
</ul>
|
||||
<p>Nous ne vendons pas vos renseignements personnels.</p>
|
||||
|
||||
<h3>6. Transferts hors Québec</h3>
|
||||
<p>Certains mandataires peuvent traiter des renseignements à l'extérieur du Québec. Nous exigeons des mesures contractuelles et de sécurité appropriées.</p>
|
||||
|
||||
<h3>7. Durée de conservation</h3>
|
||||
<ul>
|
||||
<li>Données d'inscription confirmée : conservées pour la période requise par l'Organisateur et les lois applicables;</li>
|
||||
<li>Panier non finalisé : jusqu'à 10 heures;</li>
|
||||
<li>Session : environ 3 heures d'inactivité;</li>
|
||||
<li>Documents comptables et transactions : minimum 6 ans lorsque exigé par la loi.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Sécurité</h3>
|
||||
<p>Nous appliquons des mesures raisonnables incluant le chiffrement HTTPS, des contrôles d'accès et des sauvegardes. Aucune méthode n'est toutefois sans risque.</p>
|
||||
|
||||
<h3>9. Vos droits</h3>
|
||||
<p>Vous pouvez demander l'accès à vos renseignements, leur rectification ou leur suppression, ou retirer votre consentement lorsque applicable. Contact : <a href="mailto:[COURRIEL]">[COURRIEL]</a>. Nous répondrons dans un délai raisonnable, généralement sous 30 jours.</p>
|
||||
<p>Vous pouvez aussi déposer une plainte auprès de la Commission d'accès à l'information du Québec (CAI) : <a href="https://www.cai.gouv.qc.ca" target="_blank" rel="noopener">www.cai.gouv.qc.ca</a>.</p>
|
||||
|
||||
<h3>10. Mineurs</h3>
|
||||
<p>Certains événements concernent des participants mineurs. Nous collectons seulement les renseignements nécessaires à l'inscription et à la sécurité. Un consentement parental peut être requis selon l'événement.</p>
|
||||
|
||||
<h3>11. Incidents de confidentialité</h3>
|
||||
<p>En cas d'incident présentant un risque de préjudice grave, nous aviserons la CAI et les personnes concernées conformément à la loi.</p>
|
||||
|
||||
<h3>12. Modifications</h3>
|
||||
<p>Nous pouvons mettre à jour cette politique. La date ci-dessus indique la dernière révision. Les changements importants pourront être communiqués par courriel ou avis sur le site.</p>
|
||||
HTML
|
||||
);
|
||||
}
|
||||
|
||||
function fxLegalPageNeedsSeed(array $row): bool
|
||||
{
|
||||
if (trim((string) ($row['pag_texte_fr'] ?? '')) === '' || trim((string) ($row['pag_texte_en'] ?? '')) === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$plainFr = trim(strip_tags((string) $row['pag_texte_fr']));
|
||||
|
||||
return strlen($plainFr) < 500;
|
||||
}
|
||||
|
||||
function fxLegalLabelIsDraft(string $label): bool
|
||||
{
|
||||
$label = trim($label);
|
||||
|
||||
if ($label === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Un libellé qui contient un underscore ressemble à une clé technique
|
||||
// (ex. "politique_cookie") plutôt qu'à un vrai libellé.
|
||||
if (strpos($label, '_') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function fxEnsureLegalPage(string $code, string $labelFr, string $labelEn, string $titleFr, string $titleEn, callable $bodyFr, callable $bodyEn, int $sort = 90): void
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM inscriptions_pages WHERE pag_code = '" . $objDatabase->fxEscape($code) . "' LIMIT 1";
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
|
||||
$textFr = $bodyFr();
|
||||
$textEn = $bodyEn();
|
||||
|
||||
if (!$row) {
|
||||
$sqlInsert = "INSERT INTO inscriptions_pages
|
||||
(pag_code, pag_label_fr, pag_label_en, pag_titre_fr, pag_titre_en, pag_texte_fr, pag_texte_en, pag_tri, pag_maj, pag_menu, pag_actif)
|
||||
VALUES (
|
||||
'" . $objDatabase->fxEscape($code) . "',
|
||||
'" . $objDatabase->fxEscape($labelFr) . "',
|
||||
'" . $objDatabase->fxEscape($labelEn) . "',
|
||||
'" . $objDatabase->fxEscape($titleFr) . "',
|
||||
'" . $objDatabase->fxEscape($titleEn) . "',
|
||||
'" . $objDatabase->fxEscape($textFr) . "',
|
||||
'" . $objDatabase->fxEscape($textEn) . "',
|
||||
" . intval($sort) . ",
|
||||
NOW(),
|
||||
1,
|
||||
1
|
||||
)";
|
||||
$objDatabase->fxQuery($sqlInsert);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
|
||||
if (fxLegalPageNeedsSeed($row)) {
|
||||
$updates[] = "pag_texte_fr = '" . $objDatabase->fxEscape($textFr) . "'";
|
||||
$updates[] = "pag_texte_en = '" . $objDatabase->fxEscape($textEn) . "'";
|
||||
}
|
||||
|
||||
if (fxLegalLabelIsDraft((string) $row['pag_label_fr'])) {
|
||||
$updates[] = "pag_label_fr = '" . $objDatabase->fxEscape($labelFr) . "'";
|
||||
}
|
||||
|
||||
if (fxLegalLabelIsDraft((string) $row['pag_label_en'])) {
|
||||
$updates[] = "pag_label_en = '" . $objDatabase->fxEscape($labelEn) . "'";
|
||||
}
|
||||
|
||||
if (fxLegalLabelIsDraft((string) $row['pag_titre_fr'])) {
|
||||
$updates[] = "pag_titre_fr = '" . $objDatabase->fxEscape($titleFr) . "'";
|
||||
}
|
||||
|
||||
if (fxLegalLabelIsDraft((string) $row['pag_titre_en'])) {
|
||||
$updates[] = "pag_titre_en = '" . $objDatabase->fxEscape($titleEn) . "'";
|
||||
}
|
||||
|
||||
if ((int) $row['pag_menu'] !== 1) {
|
||||
$updates[] = 'pag_menu = 1';
|
||||
}
|
||||
|
||||
if ((int) $row['pag_actif'] !== 1) {
|
||||
$updates[] = 'pag_actif = 1';
|
||||
}
|
||||
|
||||
if ($updates) {
|
||||
$updates[] = 'pag_maj = NOW()';
|
||||
$objDatabase->fxQuery('UPDATE inscriptions_pages SET ' . implode(', ', $updates) . " WHERE pag_id = " . intval($row['pag_id']));
|
||||
}
|
||||
}
|
||||
|
||||
function fxEnsureLegalPages(): void
|
||||
{
|
||||
static $done = false;
|
||||
|
||||
if ($done) {
|
||||
return;
|
||||
}
|
||||
|
||||
$done = true;
|
||||
|
||||
fxEnsureCookieConsentTexts();
|
||||
|
||||
fxEnsureLegalPage(
|
||||
'cookie',
|
||||
'Témoins',
|
||||
'Cookies',
|
||||
'Politique de témoins',
|
||||
'Cookie policy',
|
||||
static function () {
|
||||
return fxCookiePolicyHtml('fr');
|
||||
},
|
||||
static function () {
|
||||
return fxCookiePolicyHtml('en');
|
||||
},
|
||||
98
|
||||
);
|
||||
|
||||
fxEnsureLegalPage(
|
||||
'confidentialite',
|
||||
'Confidentialité',
|
||||
'Privacy',
|
||||
'Politique de confidentialité',
|
||||
'Privacy policy',
|
||||
static function () {
|
||||
return fxPrivacyPolicyHtml('fr');
|
||||
},
|
||||
static function () {
|
||||
return fxPrivacyPolicyHtml('en');
|
||||
},
|
||||
99
|
||||
);
|
||||
}
|
||||
|
||||
function fxCookiePolicyPageContent(string $strLangue): string
|
||||
{
|
||||
return fxCookiePolicyHtml($strLangue);
|
||||
}
|
||||
|
||||
function fxPrivacyPolicyPageContent(string $strLangue): string
|
||||
{
|
||||
return fxPrivacyPolicyHtml($strLangue);
|
||||
}
|
||||
@ -13,6 +13,10 @@
|
||||
//
|
||||
// pixcel facebook
|
||||
|
||||
if (!fxCookiesAllowMarketing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Facebook Pixel Code
|
||||
$mem_facebook_pixel=0;
|
||||
//echo $tabEvenement['general']['eve_facebook_pixel'];
|
||||
|
||||
@ -5,11 +5,14 @@ define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..') . '/');
|
||||
|
||||
// initialiser la variable $binEnvironementDev en utiliant le nom de domaine
|
||||
include_once APPLICATION_PATH . 'php/inc_fonctions_sl.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_fx_doc.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_settings.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_cookie_consent.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_mysql.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_dates.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_mail.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_fx_memberships.php';
|
||||
include_once APPLICATION_PATH . 'php/inc_fx_eve_extra.php'; // MSIN-3487 — extra événement (lien résultats, etc.)
|
||||
include_once APPLICATION_PATH . 'php/inc_fx_liste_attente.php';// inclure les fonctions des abonemennts
|
||||
include_once APPLICATION_PATH . 'php/recaptcha.class.php';
|
||||
|
||||
@ -22,6 +25,8 @@ $objDatabase = new clsMysql($arrConDatabaseMain);
|
||||
$objDatabaseProd= new clsMysql($arrConDatabaseProd);
|
||||
$objDatabasePreProd= new clsMysql($arrConDatabasePreProd);
|
||||
|
||||
fxEnsureLegalPages();
|
||||
|
||||
//MSIN-3999
|
||||
if (isset($_GET['preprod'])) {
|
||||
$preprod = $_GET['preprod'];
|
||||
@ -155,6 +160,10 @@ if (isset($_GET['reloadlogin'])) {
|
||||
|
||||
function fxsessionpixelfacebook($memarray)
|
||||
{
|
||||
if (!fxCookiesAllowMarketing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION['pixcel'][] = $memarray;
|
||||
}
|
||||
function fxcount($memarray)
|
||||
@ -166,6 +175,42 @@ function fxcount($memarray)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4405 — Determine, en LECTURE SEULE, pourquoi une inscription annulee (is_cancelled = 1)
|
||||
* porte ce statut. Aucune ecriture : on ne touche ni is_cancelled, ni pec_actif, ni la base.
|
||||
*
|
||||
* Regles (cf. fxMajEpreuvesCommandees / annuler_inscription.php) :
|
||||
* - vraie annulation : l'ancien pec reste actif (pec_actif = 1), ou defaut
|
||||
* - transfert complete: ligne dans inscriptions_transferts (ts_id = 2) pour ce pec
|
||||
*
|
||||
* @param int $intPecId pec_id_original de l'inscription
|
||||
* @param int|null $intPecActif valeur de pec_actif si deja connue (evite une requete)
|
||||
* @return string 'annule' | 'transfert'
|
||||
*/
|
||||
function fxGetAnnulationCause($intPecId, $intPecActif = null) {
|
||||
global $objDatabase;
|
||||
|
||||
$intPecId = intval($intPecId);
|
||||
if ($intPecId <= 0) {
|
||||
return 'annule';
|
||||
}
|
||||
|
||||
// Une vraie annulation laisse l'inscription active (pec_actif = 1).
|
||||
if ($intPecActif !== null && intval($intPecActif) === 1) {
|
||||
return 'annule';
|
||||
}
|
||||
|
||||
// Transfert complete vers une autre personne.
|
||||
$varTransfert = $objDatabase->fxGetVar(
|
||||
"SELECT 1 FROM inscriptions_transferts WHERE pec_id = " . $intPecId . " AND ts_id = 2 LIMIT 1"
|
||||
);
|
||||
if ($varTransfert) {
|
||||
return 'transfert';
|
||||
}
|
||||
|
||||
return 'annule';
|
||||
}
|
||||
|
||||
|
||||
// fonction qui retourne les infos des champs de la table (SL / JSP)
|
||||
// param : nom de la table, nom de la base de données
|
||||
@ -241,7 +286,7 @@ function fxListFieldsValues($tabData, $strKey, $strTable, $strBdd)
|
||||
// créé : 2012-09-18
|
||||
function tcheckevents($category, $action, $label)
|
||||
{
|
||||
if ($category == "")
|
||||
if ($category == "" || !fxCookiesAllowMarketing())
|
||||
return "";
|
||||
else {
|
||||
$param = "'_trackEvent', '" . $category . "', '" . $action . "', '" . $label . "', 1";
|
||||
@ -314,12 +359,54 @@ function fxGetPage($code)
|
||||
$sqlPage = "SELECT * FROM inscriptions_pages WHERE pag_actif = 1 AND pag_code = '" . $objDatabase->fxEscape($code) . "'";
|
||||
$recPage = $objDatabase->fxGetRow($sqlPage);
|
||||
|
||||
if (!$recPage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$recPage['pag_description_fr'] = fxGetNbWords($recPage['pag_texte_fr'], 50);
|
||||
$recPage['pag_description_en'] = fxGetNbWords($recPage['pag_texte_en'], 50);
|
||||
|
||||
return $recPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrige les liens internes CMS issus de TinyMCE (relative_urls).
|
||||
* Ex. href="page/cookie/fr" ou href="page/confidentialite/page/cookie/fr."
|
||||
* → /page/cookie/fr. Ne touche pas http(s), mailto, #, ni les /page/ déjà corrects.
|
||||
*/
|
||||
function fxNormalizeCmsPageLinks($strHtml)
|
||||
{
|
||||
if ($strHtml === null || $strHtml === '') {
|
||||
return $strHtml;
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'#\bhref=(["\'])([^"\']*)\1#i',
|
||||
function ($arrMatch) {
|
||||
$strQuote = $arrMatch[1];
|
||||
$strUrl = rtrim($arrMatch[2], '.');
|
||||
|
||||
if ($strUrl === ''
|
||||
|| $strUrl[0] === '#'
|
||||
|| stripos($strUrl, 'mailto:') === 0
|
||||
|| preg_match('#^(?:https?:)?//#i', $strUrl)
|
||||
) {
|
||||
return $arrMatch[0];
|
||||
}
|
||||
|
||||
// Dernier segment page/{code}/{fr|en} (y compris chemins imbriqués TinyMCE)
|
||||
if (preg_match('#(?:^|/)page/([\w\-]+)/(fr|en)/?$#i', $strUrl, $arrPage)
|
||||
&& preg_match('#(^|/)page/#i', $strUrl)
|
||||
) {
|
||||
return 'href=' . $strQuote . '/page/' . $arrPage[1] . '/' . strtolower($arrPage[2]) . $strQuote;
|
||||
}
|
||||
|
||||
return $arrMatch[0];
|
||||
},
|
||||
$strHtml
|
||||
);
|
||||
}
|
||||
|
||||
// fonction qui détermine le url de retour (JSP)
|
||||
// créé : 2012-07-02
|
||||
function fxGetReferer()
|
||||
@ -338,6 +425,57 @@ function fxGetReferer()
|
||||
return $url_retour;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4474 — URL de retour après modif inscription (même hôte uniquement).
|
||||
* Préserve la liste d'origine (ex. inscriptions_gestion) via GET/POST return_url.
|
||||
*/
|
||||
function fxSafeReturnUrl($strUrl, $strFallback = '')
|
||||
{
|
||||
global $vDomaine;
|
||||
|
||||
$strUrl = trim((string)$strUrl);
|
||||
$strFallback = trim((string)$strFallback);
|
||||
|
||||
if ($strUrl === '') {
|
||||
return $strFallback;
|
||||
}
|
||||
|
||||
$arrDom = parse_url($vDomaine);
|
||||
$arrUrl = parse_url($strUrl);
|
||||
|
||||
if ($arrUrl === false || empty($arrUrl['host']) || empty($arrDom['host'])) {
|
||||
return $strFallback;
|
||||
}
|
||||
|
||||
if (stripos($arrUrl['host'], $arrDom['host']) === false) {
|
||||
return $strFallback;
|
||||
}
|
||||
|
||||
return $strUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4474 — Résout l'URL de retour pour le formulaire modifier (promoteur/client).
|
||||
* Ignore le referer s'il pointe déjà vers reserver/book (après Suivant / switch).
|
||||
*/
|
||||
function fxModInscriptionReturnUrl($strFallback = '')
|
||||
{
|
||||
$strCandidate = '';
|
||||
|
||||
if (!empty($_POST['return_url'])) {
|
||||
$strCandidate = $_POST['return_url'];
|
||||
} elseif (!empty($_GET['return_url'])) {
|
||||
$strCandidate = $_GET['return_url'];
|
||||
} elseif (!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$strRef = (string)$_SERVER['HTTP_REFERER'];
|
||||
if (!preg_match('#/(reserver|book)(/|\\?|$)#i', $strRef)) {
|
||||
$strCandidate = $strRef;
|
||||
}
|
||||
}
|
||||
|
||||
return fxSafeReturnUrl($strCandidate, $strFallback);
|
||||
}
|
||||
|
||||
// fonction pour un message via la page contact (JSP)
|
||||
// param : données post, langue
|
||||
// créé : 2012-07-03
|
||||
@ -814,6 +952,49 @@ function fxGetPaiements($blnAdmin = false, $blnIsPromoteur = false, $strLangue =
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Compte connecté au panier (com_info ou com_id en session). */
|
||||
function fxPaiementSessionComId()
|
||||
{
|
||||
if (!empty($_SESSION['com_info']['com_id'])) {
|
||||
return intval($_SESSION['com_info']['com_id']);
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['com_id'])) {
|
||||
return intval($_SESSION['com_id']);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Modes paiement réservés promoteur (chèque, etc.) — aligné hub v2 + legacy. */
|
||||
function fxPaiementCanUseModesPromoteur($intComId, $intEveId)
|
||||
{
|
||||
$intComId = intval($intComId);
|
||||
$intEveId = intval($intEveId);
|
||||
|
||||
if ($intComId <= 0 || $intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!function_exists('fxPromoteurHubCanAccessPromoteurWeb')) {
|
||||
require_once __DIR__ . '/inc_fx_promoteur_hub.php';
|
||||
}
|
||||
|
||||
$arrLegacyFlip = array();
|
||||
|
||||
if (function_exists('fxPromoteurHubGetLegacyEventIds')) {
|
||||
$arrLegacyFlip = array_flip(array_map('intval', fxPromoteurHubGetLegacyEventIds($intComId)));
|
||||
}
|
||||
|
||||
return fxPromoteurHubCanAccessPromoteurWeb($intComId, $intEveId, $arrLegacyFlip);
|
||||
}
|
||||
|
||||
/** Modes paiement réservés admin (canal superadm / usa_id). */
|
||||
function fxPaiementCanUseModesAdmin()
|
||||
{
|
||||
return !empty($_SESSION['usa_id']);
|
||||
}
|
||||
|
||||
// fonction qui retire/transforme le HTML présent dans les termes et conditions à accepter
|
||||
// param : texteà transformer
|
||||
// créé : 2013-04-26
|
||||
@ -1048,6 +1229,121 @@ function fxcreer_log($strDescription, $intType = 10, $int_Email = 0, $int_eve_id
|
||||
return (true);
|
||||
}
|
||||
|
||||
/** MSIN-4379 — Table inscriptions_evt_erreur disponible ? */
|
||||
function fxEvtErreurTableReady() {
|
||||
static $blnReady = null;
|
||||
|
||||
if ($blnReady !== null) {
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
global $objDatabase;
|
||||
|
||||
$tabRow = $objDatabase->fxGetRow("SHOW TABLES LIKE 'inscriptions_evt_erreur'");
|
||||
$blnReady = is_array($tabRow) && !empty($tabRow);
|
||||
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4379 — Ouvre une erreur actionnable (une seule ligne ouverte par type + epr + par).
|
||||
* @param string $strType ex. bib_auto_miss
|
||||
* @param array<string, mixed> $tabCtx eve_id, epr_id, par_id, pec_id, no_commande, no_panier, com_id, detail
|
||||
*/
|
||||
function fxEvtErreurOuvrir($strType, array $tabCtx = []) {
|
||||
global $objDatabase;
|
||||
|
||||
if (!fxEvtErreurTableReady()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strType = trim((string)$strType);
|
||||
if ($strType === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intEveId = (int)($tabCtx['eve_id'] ?? 0);
|
||||
$intEprId = (int)($tabCtx['epr_id'] ?? 0);
|
||||
$intParId = (int)($tabCtx['par_id'] ?? 0);
|
||||
|
||||
if ($intEveId <= 0 || $intEprId <= 0 || $intParId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sqlExists = "
|
||||
SELECT evt_err_id
|
||||
FROM inscriptions_evt_erreur
|
||||
WHERE evt_err_type = '" . $objDatabase->fxEscape($strType) . "'
|
||||
AND epr_id = $intEprId
|
||||
AND par_id = $intParId
|
||||
AND evt_err_statut = 'ouvert'
|
||||
LIMIT 1
|
||||
";
|
||||
if ((int)$objDatabase->fxGetVar($sqlExists) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$intPecId = (int)($tabCtx['pec_id'] ?? 0);
|
||||
$strNoCommande = trim((string)($tabCtx['no_commande'] ?? ''));
|
||||
$strNoPanier = trim((string)($tabCtx['no_panier'] ?? ''));
|
||||
$intComId = (int)($tabCtx['com_id'] ?? 0);
|
||||
$strDetail = trim((string)($tabCtx['detail'] ?? ''));
|
||||
|
||||
$sqlInsert = "
|
||||
INSERT INTO inscriptions_evt_erreur SET
|
||||
evt_err_type = '" . $objDatabase->fxEscape($strType) . "',
|
||||
evt_err_statut = 'ouvert',
|
||||
eve_id = $intEveId,
|
||||
epr_id = $intEprId,
|
||||
par_id = $intParId,
|
||||
pec_id = $intPecId,
|
||||
no_commande = '" . $objDatabase->fxEscape($strNoCommande) . "',
|
||||
no_panier = '" . $objDatabase->fxEscape($strNoPanier) . "',
|
||||
com_id = $intComId,
|
||||
evt_err_detail = " . ($strDetail !== '' ? "'" . $objDatabase->fxEscape($strDetail) . "'" : 'NULL') . ",
|
||||
evt_err_created = NOW()
|
||||
";
|
||||
|
||||
return $objDatabase->fxQuery($sqlInsert) !== false;
|
||||
}
|
||||
|
||||
/** MSIN-4379 — Marque résolues les erreurs ouvertes pour un participant / épreuve. */
|
||||
function fxEvtErreurResoudre($strType, $int_epr_id, $int_par_id, $int_resolved_by = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
if (!fxEvtErreurTableReady()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strType = trim((string)$strType);
|
||||
$int_epr_id = (int)$int_epr_id;
|
||||
$int_par_id = (int)$int_par_id;
|
||||
$int_resolved_by = (int)$int_resolved_by;
|
||||
|
||||
if ($strType === '' || $int_epr_id <= 0 || $int_par_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($int_resolved_by <= 0 && isset($_SESSION['com_id'])) {
|
||||
$int_resolved_by = (int)$_SESSION['com_id'];
|
||||
}
|
||||
|
||||
$sqlUpdate = "
|
||||
UPDATE inscriptions_evt_erreur
|
||||
SET evt_err_statut = 'resolu',
|
||||
evt_err_resolved = NOW(),
|
||||
evt_err_resolved_by = $int_resolved_by
|
||||
WHERE evt_err_type = '" . $objDatabase->fxEscape($strType) . "'
|
||||
AND epr_id = $int_epr_id
|
||||
AND par_id = $int_par_id
|
||||
AND evt_err_statut = 'ouvert'
|
||||
";
|
||||
|
||||
$objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function fxIsRegistered($strPartId, $strChamp, $strWhere, $strTable, $strLangue)
|
||||
{
|
||||
global $objDatabase;
|
||||
@ -1400,17 +1696,38 @@ function fxGetRoleTab($int_rol_id)
|
||||
|
||||
function fxIsPromoteur($intComId, $intEveId)
|
||||
{
|
||||
global $objDatabase;
|
||||
if (!fxPromoteurLegacySiteEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = "select * from inscriptions_comptes where FIND_IN_SET('" . intval($intEveId) . "',com_eve_promoteur) AND com_id = " . intval($intComId);
|
||||
$tab = $objDatabase->fxGetResults($sql);
|
||||
static $arrLegacyByCom = array();
|
||||
$intComId = intval($intComId);
|
||||
$intEveId = intval($intEveId);
|
||||
|
||||
if ($tab != null)
|
||||
$return = true;
|
||||
else
|
||||
$return = false;
|
||||
if (!isset($arrLegacyByCom[$intComId])) {
|
||||
$strCsv = '';
|
||||
|
||||
return $return;
|
||||
if (!empty($_SESSION['com_info']['com_id'])
|
||||
&& intval($_SESSION['com_info']['com_id']) === $intComId
|
||||
&& isset($_SESSION['com_info']['com_eve_promoteur'])) {
|
||||
$strCsv = (string)$_SESSION['com_info']['com_eve_promoteur'];
|
||||
} else {
|
||||
global $objDatabase;
|
||||
$row = $objDatabase->fxGetRow(
|
||||
'SELECT com_eve_promoteur FROM inscriptions_comptes WHERE com_id = ' . $intComId
|
||||
);
|
||||
$strCsv = ($row !== null) ? (string)$row['com_eve_promoteur'] : '';
|
||||
}
|
||||
|
||||
$arrLegacyByCom[$intComId] = array();
|
||||
foreach (array_map('trim', explode(',', $strCsv)) as $strId) {
|
||||
if ($strId !== '') {
|
||||
$arrLegacyByCom[$intComId][intval($strId)] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($arrLegacyByCom[$intComId][$intEveId]);
|
||||
}
|
||||
|
||||
// nouvelle fonction 2026
|
||||
@ -1869,6 +2186,31 @@ function fxGetVariable($strVarNom)
|
||||
return $strVarValeur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupteur site : promoteur legacy (com_eve_promoteur / fxIsPromoteur) actif ?
|
||||
* Defaut : oui (1). Super admin : bouton dans inc_droite (variables.promoteur_legacy_actif).
|
||||
*/
|
||||
function fxPromoteurLegacySiteEnabled()
|
||||
{
|
||||
static $blnEnabled = null;
|
||||
|
||||
if ($blnEnabled !== null) {
|
||||
return $blnEnabled;
|
||||
}
|
||||
|
||||
global $objDatabase;
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
$blnEnabled = true;
|
||||
return $blnEnabled;
|
||||
}
|
||||
|
||||
$strVal = fxGetVariable('promoteur_legacy_actif');
|
||||
$blnEnabled = ($strVal === null || $strVal === '' || intval($strVal) === 1);
|
||||
|
||||
return $blnEnabled;
|
||||
}
|
||||
|
||||
function fxGetMailHeader($blnTransaction = false, $strLangue = 'fr')
|
||||
{
|
||||
if ($blnTransaction) {
|
||||
@ -2400,6 +2742,31 @@ function fxShowListeEvenementsContact($arrEvenements, $strLangue)
|
||||
return $strHTML;
|
||||
}
|
||||
|
||||
function fxJiraIsAutreDemande($strLibelle)
|
||||
{
|
||||
$strNorm = mb_strtolower(trim($strLibelle));
|
||||
|
||||
return ($strNorm === 'autre' || $strNorm === 'autres' || strpos($strNorm, 'autre') !== false);
|
||||
}
|
||||
|
||||
function fxJiraMapCategorieInterne($strLibelle)
|
||||
{
|
||||
if (fxJiraIsAutreDemande($strLibelle)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strNorm = mb_strtolower(trim($strLibelle));
|
||||
if (strpos($strNorm, 'organisateur') !== false) {
|
||||
return 'Vente';
|
||||
}
|
||||
if (strpos($strNorm, 'participant') !== false
|
||||
&& (strpos($strNorm, 'course') !== false || strpos($strNorm, 'épreuve') !== false || strpos($strNorm, 'epreuve') !== false)) {
|
||||
return 'Questions Résultats course';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function fxShowFormcontactjira($strLangue)
|
||||
{ global $objDatabase;
|
||||
$mem_nom="";
|
||||
@ -2480,7 +2847,13 @@ function fxShowFormcontactjira($strLangue)
|
||||
<?php
|
||||
foreach ($tabCol as $key => $value ){
|
||||
?>
|
||||
<option value="<?php echo($tabCol[$key]["info_option2"]); ?>"><?php echo($tabCol[$key]["info_texte"]); ?></option>
|
||||
<?php
|
||||
$strCategorieInterne = trim((string)($tabCol[$key]['info_option3'] ?? ''));
|
||||
if ($strCategorieInterne === '') {
|
||||
$strCategorieInterne = fxJiraMapCategorieInterne($tabCol[$key]['info_texte']);
|
||||
}
|
||||
?>
|
||||
<option value="<?php echo($tabCol[$key]["info_option2"]); ?>" data-categorie="<?php echo htmlspecialchars($strCategorieInterne, ENT_QUOTES, 'UTF-8'); ?>"><?php echo($tabCol[$key]["info_texte"]); ?></option>
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
@ -23,17 +23,68 @@ function afficheTexte($clef, $mem_echo = 1, $mem_trduction = 1, $mem_global = 0)
|
||||
$mem_page = "global";
|
||||
|
||||
// creation dans la bd info
|
||||
$info1 = array("info_clef" => $clef, "info_langue" => "fr", "info_texte" => $clef, "info_prg" => $mem_page, "info_actif" => 1);
|
||||
$info1 = array("info_clef" => $clef, "info_langue" => "fr", "info_texte" => $clef, "info_aide" => '', "info_prg" => $mem_page, "info_actif" => 1);
|
||||
fxSetInfo('add', $info1, '', $mem_id);
|
||||
|
||||
$info1 = array("info_clef" => $clef, "info_langue" => "en", "info_texte" => $clef, "info_prg" => $mem_page, "info_actif" => 1);
|
||||
$info1 = array("info_clef" => $clef, "info_langue" => "en", "info_texte" => $clef, "info_aide" => '', "info_prg" => $mem_page, "info_actif" => 1);
|
||||
fxSetInfo('add', $info1, '', $mem_id);
|
||||
}
|
||||
|
||||
if ($mem_echo == 1)
|
||||
echo $mem_texte;
|
||||
|
||||
return $mem_texte;
|
||||
$strOut = $mem_texte . fxAdminTradMarker($clef, $mem_global);
|
||||
|
||||
if ($mem_echo == 1) {
|
||||
echo $strOut;
|
||||
}
|
||||
|
||||
return $strOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Libellé info_texte en contexte HTML (crayon via afficheTexte en mode Textes).
|
||||
*/
|
||||
function afficheTextePage($clef, $mem_global = 0) {
|
||||
afficheTexte($clef, 1, 0, $mem_global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Texte d'aide long (pop-up Tippy sur le bouton ?) — colonne info_aide.
|
||||
* info_texte = libellé / tip court ; info_aide = contenu long éditable via traduction.php.
|
||||
* Créateur : stephan (refonte dossards v4)
|
||||
*/
|
||||
function afficheAide($clef, $mem_echo = 1, $mem_global = 0) {
|
||||
global $vtexte_page, $vPage;
|
||||
$mem_id = "";
|
||||
|
||||
if (isset($vtexte_page[$clef])) {
|
||||
$mem_aide = isset($vtexte_page[$clef]['info_aide']) ? $vtexte_page[$clef]['info_aide'] : '';
|
||||
} else {
|
||||
$mem_aide = '';
|
||||
|
||||
if ($mem_global == 0) {
|
||||
$mem_page = $vPage;
|
||||
} else {
|
||||
$mem_page = "global";
|
||||
}
|
||||
|
||||
$info1 = array(
|
||||
"info_clef" => $clef,
|
||||
"info_langue" => "fr",
|
||||
"info_texte" => $clef,
|
||||
"info_aide" => '',
|
||||
"info_prg" => $mem_page,
|
||||
"info_actif" => 1
|
||||
);
|
||||
fxSetInfo('add', $info1, '', $mem_id);
|
||||
|
||||
$info1['info_langue'] = 'en';
|
||||
fxSetInfo('add', $info1, '', $mem_id);
|
||||
}
|
||||
|
||||
if ($mem_echo == 1) {
|
||||
echo $mem_aide;
|
||||
}
|
||||
|
||||
return $mem_aide;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,20 +114,21 @@ function obtenirTextepage() {
|
||||
|
||||
switch ($centrale) {
|
||||
case 3:
|
||||
$sql_info = "SELECT info_clef,info_texte,pk_info,info_prg,'centrale' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$sql_info = "SELECT info_clef,info_texte,info_aide,pk_info,info_prg,'centrale' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$info = $GLOBALS['dbcentrale']->fxFetchArray(3, $sql_info);
|
||||
break;
|
||||
case 2:
|
||||
$sql_info = "SELECT info_clef,info_texte,pk_info,info_prg,'client' as bd FROM info where (info_prg='" . $page . "' or info_prg='global') and info_langue='" . $Langue . "'";
|
||||
/* MSIN-4379 — info_aide : texte long pop-up bib v4 (Tippy). */
|
||||
$sql_info = "SELECT info_clef,info_texte,info_aide,pk_info,info_prg,'client' as bd FROM info where (info_prg='" . $page . "' or info_prg='global') and info_langue='" . $Langue . "'";
|
||||
|
||||
$info = $objDatabase->fxFetchArray(3, $sql_info);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
$sql_info = "SELECT info_clef,info_texte,pk_info,'client' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$sql_info = "SELECT info_clef,info_texte,info_aide,pk_info,'client' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$info2 = $objDatabase->fxFetchArray(3, $sql_info);
|
||||
|
||||
$sql_info = "SELECT info_clef,info_texte,'centrale' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$sql_info = "SELECT info_clef,info_texte,info_aide,'centrale' as bd FROM info where info_prg='" . $page . "' and info_langue='" . $Langue . "'";
|
||||
$info1 = $objDatabase->fxFetchArray(3, $sql_info);
|
||||
$info = array_merge($info1, $info2);
|
||||
break;
|
||||
@ -96,6 +148,429 @@ function modif_texte_bt() {
|
||||
if ($vadmin_texte == true)
|
||||
echo "<button onclick=" . chr(34) . "toggle_hideme('modif_texte','text');" . chr(34) . ">changer texte</button>";
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4379 — Édition textes (crayons, traduction.php) : serveur dev + sous-domaine preprod seulement.
|
||||
* Pas la préprod client (preprod.ms1inscription.com) — même code, autre rôle (BD inscription test).
|
||||
* Superadm uniquement ; défaut = aperçu (crayons masqués).
|
||||
*/
|
||||
function fxAdminIsDevPreprod() {
|
||||
global $vblnEnvironementDev, $vblnEnvironementPreProd, $strHost;
|
||||
|
||||
if (empty($vblnEnvironementDev) || empty($vblnEnvironementPreProd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stripos((string)$strHost, 'ms1inscriptiondev') !== false;
|
||||
}
|
||||
|
||||
function fxAdminCanUseTextEditTools() {
|
||||
return !empty($_SESSION['usa_id']) && fxAdminIsDevPreprod();
|
||||
}
|
||||
|
||||
/** Outils admin /php/* sans usa_id → login superadm (pas « Accès refusé »). */
|
||||
function fxAdminRequireSuperadmLogin() {
|
||||
if (!empty($_SESSION['usa_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $vDomaine;
|
||||
$_SESSION['superadm_redirect_after_login'] = $_SERVER['REQUEST_URI'];
|
||||
header('Location: ' . rtrim((string)$vDomaine, '/') . '/superadm/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
/** Sync tables statiques (sync_static_db.php) — superadm, préprod dev seulement. */
|
||||
function fxAdminStaticSyncAllowed() {
|
||||
return !empty($_SESSION['usa_id']) && fxAdminIsDevPreprod();
|
||||
}
|
||||
|
||||
function fxAdminTextEditModeActive() {
|
||||
return fxAdminCanUseTextEditTools() && !empty($_SESSION['ms1_text_edit_mode']);
|
||||
}
|
||||
|
||||
/** Mode inspecteur droits v2 (cadenas) — super admin connecte uniquement. */
|
||||
function fxAdminCanUsePermInspectTools() {
|
||||
return !empty($_SESSION['usa_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4401 — Créer / modifier / supprimer les définitions de kits (pas l'assignation).
|
||||
* Liste : $arrEveAccesKitEditors dans php/inc_settings.php.
|
||||
*/
|
||||
if (!function_exists('fxAdminCanEditEveAccesKits')) {
|
||||
function fxAdminCanEditEveAccesKits() {
|
||||
global $arrEveAccesKitEditors;
|
||||
|
||||
if (empty($_SESSION['usa_id']) || empty($_SESSION['usa_info']) || !is_array($_SESSION['usa_info'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arrAllow = (isset($arrEveAccesKitEditors) && is_array($arrEveAccesKitEditors))
|
||||
? $arrEveAccesKitEditors
|
||||
: array();
|
||||
|
||||
$arrNorm = array();
|
||||
foreach ($arrAllow as $strItem) {
|
||||
$strItem = strtolower(trim((string)$strItem));
|
||||
if ($strItem !== '') {
|
||||
$arrNorm[$strItem] = true;
|
||||
}
|
||||
}
|
||||
if (count($arrNorm) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arrCandidates = array(
|
||||
strtolower(trim((string)($_SESSION['usa_info']['com_courriel'] ?? ''))),
|
||||
strtolower(trim((string)($_SESSION['usa_info']['com_login'] ?? ''))),
|
||||
);
|
||||
|
||||
foreach ($arrCandidates as $strCand) {
|
||||
if ($strCand !== '' && isset($arrNorm[$strCand])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function fxAdminPermInspectModeActive() {
|
||||
return fxAdminCanUsePermInspectTools() && !empty($_SESSION['ms1_perm_inspect_mode']);
|
||||
}
|
||||
|
||||
function fxAdminPermInspectModeHandleRequest() {
|
||||
if (!fxAdminCanUsePermInspectTools() || !isset($_GET['ms1_perm_inspect'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION['ms1_perm_inspect_mode'] = ($_GET['ms1_perm_inspect'] === '1') ? 1 : 0;
|
||||
|
||||
$strPath = strtok($_SERVER['REQUEST_URI'], '?');
|
||||
$tabParams = $_GET;
|
||||
unset($tabParams['ms1_perm_inspect']);
|
||||
$strQuery = http_build_query($tabParams);
|
||||
|
||||
header('Location: ' . $strPath . ($strQuery !== '' ? '?' . $strQuery : ''));
|
||||
exit;
|
||||
}
|
||||
|
||||
function fxAdminPermInspectSwitchHtml() {
|
||||
$blnOn = fxAdminPermInspectModeActive();
|
||||
|
||||
return '
|
||||
<span class="ms1-env-text-edit ms1-env-perm-inspect">
|
||||
<label class="ms1-env-text-edit-label mb-0" for="ms1-perm-inspect-mode">
|
||||
<span class="ms1-env-text-edit-opt' . ($blnOn ? '' : ' is-active') . '">'
|
||||
. '<i class="fa fa-eye" aria-hidden="true"></i> Aperçu'
|
||||
. '</span>
|
||||
<input type="checkbox" id="ms1-perm-inspect-mode" class="ms1-env-text-edit-switch"'
|
||||
. ($blnOn ? ' checked' : '')
|
||||
. ' aria-label="Afficher les cadenas droits v2">
|
||||
<span class="ms1-env-text-edit-opt' . ($blnOn ? ' is-active' : '') . '">'
|
||||
. '<i class="fa fa-lock" aria-hidden="true"></i> Droits'
|
||||
. '</span>
|
||||
</label>
|
||||
</span>
|
||||
<script>
|
||||
(function () {
|
||||
var el = document.getElementById("ms1-perm-inspect-mode");
|
||||
if (!el || el.dataset.ms1Bound) return;
|
||||
el.dataset.ms1Bound = "1";
|
||||
el.addEventListener("change", function () {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set("ms1_perm_inspect", this.checked ? "1" : "0");
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
})();
|
||||
</script>';
|
||||
}
|
||||
|
||||
/** Échappement HTML — outils admin traduction. */
|
||||
function fxAdminEsc($str) {
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/** info_prg pour traduction.php — depuis $vtexte_page ou repli page / global. */
|
||||
function fxAdminTradInfoPrg($clef, $strDefaultPrg = '', $mem_global = 0) {
|
||||
global $vtexte_page, $vPage;
|
||||
|
||||
if (!empty($vtexte_page[$clef]['info_prg'])) {
|
||||
return $vtexte_page[$clef]['info_prg'];
|
||||
}
|
||||
if ($strDefaultPrg !== '') {
|
||||
return $strDefaultPrg;
|
||||
}
|
||||
if ($mem_global == 1) {
|
||||
return 'global';
|
||||
}
|
||||
return !empty($vPage) ? $vPage : 'compte.php';
|
||||
}
|
||||
|
||||
/** URL pop-up traduction.php — toute clé info (site entier). */
|
||||
function fxAdminTradUrl($clef, $strInfoPrg = null, $mem_global = 0) {
|
||||
global $vDomaine, $vPage;
|
||||
|
||||
if ($strInfoPrg === null || $strInfoPrg === '') {
|
||||
$strInfoPrg = fxAdminTradInfoPrg($clef, '', $mem_global);
|
||||
}
|
||||
$strPage = !empty($vPage) ? $vPage : $strInfoPrg;
|
||||
|
||||
return $vDomaine . '/php/traduction.php?' . http_build_query(array(
|
||||
'clef' => $clef,
|
||||
'pk_prg' => $strInfoPrg,
|
||||
'bd' => 'client',
|
||||
'page' => $strPage,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marqueur invisible (ZWSP) — sûr dans le JS ; remplacé par un lien traduction.php au chargement.
|
||||
*/
|
||||
function fxAdminTradMarker($clef, $mem_global = 0) {
|
||||
if (!fxAdminTextEditModeActive() || trim($clef) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strPayload = base64_encode(json_encode(array(
|
||||
'k' => $clef,
|
||||
'p' => fxAdminTradInfoPrg($clef, '', $mem_global),
|
||||
'g' => (int)$mem_global,
|
||||
), JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return "\xE2\x80\x8B\xE2\x80\x8C" . $strPayload . "\xE2\x80\x8C\xE2\x80\x8B";
|
||||
}
|
||||
|
||||
/** Classes CSS crayon traduction (sans btn-admin-trad — évite le handler bib sur data-trad-url). */
|
||||
function fxAdminTradLinkClass() {
|
||||
return 'btn-aide-bib btn-aide-trad ms1-trad-link';
|
||||
}
|
||||
|
||||
/** Lien crayon traduction.php — superadm dev/préprod + switch « Textes » ON. */
|
||||
function fxAdminTradButton($clef, $strInfoPrg = null, $mem_global = 0) {
|
||||
if (!fxAdminTextEditModeActive() || trim($clef) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<a href="' . fxAdminEsc(fxAdminTradUrl($clef, $strInfoPrg, $mem_global)) . '"'
|
||||
. ' class="' . fxAdminTradLinkClass() . '"'
|
||||
. ' title="Modifier textes FR/EN"'
|
||||
. ' aria-label="Modifier textes">'
|
||||
. '<i class="fa fa-pencil" aria-hidden="true"></i>'
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
/** Remplace les marqueurs afficheTexte() par des liens crayon (mode Textes). */
|
||||
function fxAdminTradMarkerScriptHtml() {
|
||||
global $vDomaine, $vPage;
|
||||
static $blnRendered = false;
|
||||
|
||||
if ($blnRendered || !fxAdminTextEditModeActive()) {
|
||||
return '';
|
||||
}
|
||||
$blnRendered = true;
|
||||
|
||||
return '
|
||||
<script>
|
||||
(function () {
|
||||
var re = /\u200B\u200C([A-Za-z0-9+/=]+)\u200C\u200B/g;
|
||||
var tradBase = ' . json_encode($vDomaine . '/php/traduction.php') . ';
|
||||
var pageDefault = ' . json_encode(!empty($vPage) ? $vPage : 'compte.php') . ';
|
||||
var tradLinkClass = ' . json_encode(fxAdminTradLinkClass()) . ';
|
||||
|
||||
function openTradPopup(url) {
|
||||
var w = window.open(
|
||||
url,
|
||||
"ms1Trad" + Date.now(),
|
||||
"width=960,height=640,scrollbars=yes,resizable=yes"
|
||||
);
|
||||
if (w) {
|
||||
var timer = setInterval(function () {
|
||||
if (w.closed) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
var link = e.target.closest("a.ms1-trad-link");
|
||||
if (!link || !link.href) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openTradPopup(link.href);
|
||||
}, true);
|
||||
|
||||
function skipParent(node) {
|
||||
var p = node.parentNode;
|
||||
while (p) {
|
||||
var tag = p.nodeName;
|
||||
if (tag === "SCRIPT" || tag === "STYLE") {
|
||||
return true;
|
||||
}
|
||||
p = p.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildUrl(o) {
|
||||
return tradBase + "?clef=" + encodeURIComponent(o.k)
|
||||
+ "&pk_prg=" + encodeURIComponent(o.p)
|
||||
+ "&bd=client"
|
||||
+ "&page=" + encodeURIComponent(pageDefault);
|
||||
}
|
||||
|
||||
function replaceMarkers(textNode) {
|
||||
if (skipParent(textNode)) {
|
||||
return;
|
||||
}
|
||||
var text = textNode.nodeValue;
|
||||
if (text.indexOf("\u200C") === -1) {
|
||||
return;
|
||||
}
|
||||
re.lastIndex = 0;
|
||||
if (!re.test(text)) {
|
||||
return;
|
||||
}
|
||||
re.lastIndex = 0;
|
||||
|
||||
var frag = document.createDocumentFragment();
|
||||
var last = 0;
|
||||
var m;
|
||||
while ((m = re.exec(text)) !== null) {
|
||||
if (m.index > last) {
|
||||
frag.appendChild(document.createTextNode(text.slice(last, m.index)));
|
||||
}
|
||||
try {
|
||||
var o = JSON.parse(atob(m[1]));
|
||||
var a = document.createElement("a");
|
||||
a.href = buildUrl(o);
|
||||
a.className = tradLinkClass;
|
||||
a.title = "Modifier textes FR/EN";
|
||||
a.setAttribute("aria-label", "Modifier textes");
|
||||
a.innerHTML = \'<i class="fa fa-pencil" aria-hidden="true"></i>\';
|
||||
frag.appendChild(a);
|
||||
} catch (e) {}
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
if (last < text.length) {
|
||||
frag.appendChild(document.createTextNode(text.slice(last)));
|
||||
}
|
||||
textNode.parentNode.replaceChild(frag, textNode);
|
||||
}
|
||||
|
||||
// root optionnel : rescan après HTML dynamique (ex. hint remboursement AJAX).
|
||||
function scan(root) {
|
||||
var start = root && root.nodeType ? root : document.body;
|
||||
if (!start) {
|
||||
return;
|
||||
}
|
||||
var walker = document.createTreeWalker(start, NodeFilter.SHOW_TEXT, null);
|
||||
var nodes = [];
|
||||
while (walker.nextNode()) {
|
||||
nodes.push(walker.currentNode);
|
||||
}
|
||||
nodes.forEach(replaceMarkers);
|
||||
}
|
||||
|
||||
window.MS1_ADMIN_TRAD_SCAN = scan;
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () { scan(); });
|
||||
} else {
|
||||
scan();
|
||||
}
|
||||
})();
|
||||
</script>';
|
||||
}
|
||||
|
||||
function fxAdminTextEditModeHandleRequest() {
|
||||
if (!fxAdminCanUseTextEditTools() || !isset($_GET['ms1_text_edit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION['ms1_text_edit_mode'] = ($_GET['ms1_text_edit'] === '1') ? 1 : 0;
|
||||
|
||||
$strPath = strtok($_SERVER['REQUEST_URI'], '?');
|
||||
$tabParams = $_GET;
|
||||
unset($tabParams['ms1_text_edit']);
|
||||
$strQuery = http_build_query($tabParams);
|
||||
|
||||
header('Location: ' . $strPath . ($strQuery !== '' ? '?' . $strQuery : ''));
|
||||
exit;
|
||||
}
|
||||
|
||||
function fxAdminTextEditSwitchHtml() {
|
||||
$blnEdit = fxAdminTextEditModeActive();
|
||||
|
||||
return '
|
||||
<span class="ms1-env-text-edit">
|
||||
<label class="ms1-env-text-edit-label mb-0" for="ms1-text-edit-mode">
|
||||
<span class="ms1-env-text-edit-opt' . ($blnEdit ? '' : ' is-active') . '">'
|
||||
. '<i class="fa fa-eye" aria-hidden="true"></i> Aperçu'
|
||||
. '</span>
|
||||
<input type="checkbox" id="ms1-text-edit-mode" class="ms1-env-text-edit-switch"'
|
||||
. ($blnEdit ? ' checked' : '')
|
||||
. ' aria-label="Basculer édition des textes">
|
||||
<span class="ms1-env-text-edit-opt' . ($blnEdit ? ' is-active' : '') . '">'
|
||||
. '<i class="fa fa-pencil" aria-hidden="true"></i> Textes'
|
||||
. '</span>
|
||||
</label>
|
||||
</span>
|
||||
<script>
|
||||
(function () {
|
||||
var el = document.getElementById("ms1-text-edit-mode");
|
||||
if (!el || el.dataset.ms1Bound) return;
|
||||
el.dataset.ms1Bound = "1";
|
||||
el.addEventListener("change", function () {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set("ms1_text_edit", this.checked ? "1" : "0");
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
})();
|
||||
</script>';
|
||||
}
|
||||
|
||||
function fxRenderEnvNoticeBar() {
|
||||
global $vEnvNotice;
|
||||
|
||||
$strExtraSwitches = '';
|
||||
if (function_exists('fxAdminCanUsePermInspectTools') && fxAdminCanUsePermInspectTools()) {
|
||||
$strExtraSwitches .= fxAdminPermInspectSwitchHtml();
|
||||
}
|
||||
|
||||
if (trim($vEnvNotice) === '') {
|
||||
if ($strExtraSwitches !== '') {
|
||||
echo '<div class="alert alert-secondary py-1 mb-0 text-center sticky-top">' . $strExtraSwitches . '</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fxAdminCanUseTextEditTools()) {
|
||||
echo $vEnvNotice;
|
||||
if ($strExtraSwitches !== '') {
|
||||
echo '<div class="alert alert-secondary py-1 mb-0 text-center sticky-top">' . $strExtraSwitches . '</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$strSwitch = fxAdminTextEditSwitchHtml() . $strExtraSwitches;
|
||||
$strHtml = preg_replace('/<\/strong>/', '</strong> ' . $strSwitch, trim($vEnvNotice), 1);
|
||||
|
||||
if ($strHtml === null || $strHtml === trim($vEnvNotice)) {
|
||||
echo $vEnvNotice;
|
||||
echo $strSwitch;
|
||||
echo fxAdminTradMarkerScriptHtml();
|
||||
return;
|
||||
}
|
||||
|
||||
echo $strHtml;
|
||||
echo fxAdminTradMarkerScriptHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description : Fonction pour ajouter le value dans une formes
|
||||
|
||||
444
php/inc_fx_bib_global_free.php
Normal file
@ -0,0 +1,444 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4445 — Assignation globale solo libre (sans création de séquence).
|
||||
* Plage Début/Fin (ou Début/Qté) fournie par l'utilisateur ; assignation via plages virtuelles.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calcule le feedback quantité vs inscriptions à assigner.
|
||||
*
|
||||
* @return array{status:string, amount:int, qty:int, pending:int}
|
||||
*/
|
||||
function fxBibFreeQtyFeedback($intQty, $intPending) {
|
||||
$intQty = (int)$intQty;
|
||||
$intPending = (int)$intPending;
|
||||
$intDiff = $intQty - $intPending;
|
||||
|
||||
if ($intDiff === 0) {
|
||||
return [
|
||||
'status' => 'juste',
|
||||
'amount' => 0,
|
||||
'qty' => $intQty,
|
||||
'pending' => $intPending,
|
||||
];
|
||||
}
|
||||
|
||||
if ($intDiff > 0) {
|
||||
return [
|
||||
'status' => 'supplementaire',
|
||||
'amount' => $intDiff,
|
||||
'qty' => $intQty,
|
||||
'pending' => $intPending,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'manquante',
|
||||
'amount' => abs($intDiff),
|
||||
'qty' => $intQty,
|
||||
'pending' => $intPending,
|
||||
];
|
||||
}
|
||||
|
||||
/** Libellé Info pour le feedback quantité libre. */
|
||||
function fxBibFreeQtyFeedbackMessage(array $tabFeedback) {
|
||||
$strStatus = $tabFeedback['status'] ?? '';
|
||||
$intAmount = (int)($tabFeedback['amount'] ?? 0);
|
||||
|
||||
if ($strStatus === 'juste') {
|
||||
return fxBibMsg('bib_v4_global_batch_free_qty_juste');
|
||||
}
|
||||
|
||||
if ($strStatus === 'supplementaire') {
|
||||
return fxBibMsg('bib_v4_global_batch_free_qty_supplementaire', $intAmount);
|
||||
}
|
||||
|
||||
if ($strStatus === 'manquante') {
|
||||
return fxBibMsg('bib_v4_global_batch_free_qty_manquante', $intAmount);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4445 — Nombre de dossards déjà assignés dans [start, end] pour l'événement.
|
||||
* Jointure via épreuve : plus fiable que p.eve_id seul.
|
||||
*/
|
||||
function fxBibCountAssignedBibsInRange($int_eve_id, $intStart, $intEnd) {
|
||||
global $objDatabase;
|
||||
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
|
||||
if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$sqlBib = fxBibSqlNoBibUnsigned('p.no_bib');
|
||||
|
||||
return (int)$objDatabase->fxGetVar("
|
||||
SELECT COUNT(DISTINCT $sqlBib)
|
||||
FROM resultats_participants p
|
||||
INNER JOIN inscriptions_epreuves e ON e.epr_id = p.epr_id
|
||||
WHERE e.eve_id = $int_eve_id
|
||||
AND IFNULL(p.is_cancelled, 0) = 0
|
||||
AND p.no_bib IS NOT NULL
|
||||
AND TRIM(p.no_bib) <> ''
|
||||
AND $sqlBib BETWEEN $intStart AND $intEnd
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4445 — Risque de doublons pour une plage libre.
|
||||
* Avertit si des dossards de la plage sont déjà portés, ou si une séquence
|
||||
* existante chevauche la plage (même hors épreuves sélectionnées).
|
||||
*
|
||||
* @return array{has:bool, count:int, message:string}
|
||||
*/
|
||||
function fxBibFreeDuplicateRisk($int_eve_id, $tabPlan, $intStart = 0, $intEnd = 0) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
|
||||
if ($intStart <= 0 || $intEnd < $intStart) {
|
||||
// Repli : bornes du plan si Début/Fin absents.
|
||||
foreach ((array)$tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
$intItemStart = (int)($item['start'] ?? 0);
|
||||
$intItemEnd = (int)($item['end'] ?? 0);
|
||||
if ($intItemStart <= 0 || $intItemEnd < $intItemStart) {
|
||||
continue;
|
||||
}
|
||||
if ($intStart <= 0 || $intItemStart < $intStart) {
|
||||
$intStart = $intItemStart;
|
||||
}
|
||||
if ($intItemEnd > $intEnd) {
|
||||
$intEnd = $intItemEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
||||
return ['has' => false, 'count' => 0, 'message' => ''];
|
||||
}
|
||||
|
||||
$intCount = fxBibCountAssignedBibsInRange($int_eve_id, $intStart, $intEnd);
|
||||
|
||||
// Séquences déjà configurées qui chevauchent la plage (autre épreuve).
|
||||
$tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, 0);
|
||||
$intOverlapSeq = is_array($tabOverlaps) ? count($tabOverlaps) : 0;
|
||||
|
||||
if ($intCount <= 0 && $intOverlapSeq <= 0) {
|
||||
return ['has' => false, 'count' => 0, 'message' => ''];
|
||||
}
|
||||
|
||||
return [
|
||||
'has' => true,
|
||||
'count' => max($intCount, $intOverlapSeq),
|
||||
'message' => fxBibMsg('bib_v4_global_batch_free_dupes_warn'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4445 — Valide et ordonne les epr_id du bloc libre (solo, avec ou sans séquence).
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
function fxBibParseGlobalBatchFreeEprIds($eprIds, $int_eve_id) {
|
||||
if (!is_array($eprIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$tabWanted = [];
|
||||
|
||||
foreach ($eprIds as $epr_id) {
|
||||
$epr_id = (int)$epr_id;
|
||||
if ($epr_id <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$check = fxBibAssertSoloEpreuveForGlobalBatch($epr_id, $int_eve_id);
|
||||
if (!$check['success']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabWanted[$epr_id] = true;
|
||||
}
|
||||
|
||||
if (empty($tabWanted)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tabIds = array_keys($tabWanted);
|
||||
sort($tabIds, SORT_NUMERIC);
|
||||
|
||||
return $tabIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan de plages virtuelles bornées par Début/Fin (sans écriture BD).
|
||||
* Enchaîne les épreuves comme le bloc généré, mais s'arrête à $intEnd.
|
||||
*
|
||||
* @return array[] [{epr_id, start, end, nb_pending, skip}]
|
||||
*/
|
||||
function fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
$tabEprIds = fxBibParseGlobalBatchFreeEprIds($tabEprIds, $int_eve_id);
|
||||
$intCursor = $intStart;
|
||||
$tabPlan = [];
|
||||
|
||||
foreach ($tabEprIds as $epr_id) {
|
||||
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
||||
$intPending = count($participants);
|
||||
|
||||
if ($intPending <= 0) {
|
||||
$tabPlan[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'start' => 0,
|
||||
'end' => 0,
|
||||
'nb_pending' => 0,
|
||||
'skip' => true,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$intRemaining = $intEnd - $intCursor + 1;
|
||||
if ($intRemaining <= 0) {
|
||||
$tabPlan[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'start' => 0,
|
||||
'end' => 0,
|
||||
'nb_pending' => $intPending,
|
||||
'skip' => true,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$intTake = min($intPending, $intRemaining);
|
||||
$intRangeStart = $intCursor;
|
||||
$intRangeEnd = $intCursor + $intTake - 1;
|
||||
|
||||
$tabPlan[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'start' => $intRangeStart,
|
||||
'end' => $intRangeEnd,
|
||||
'nb_pending' => $intPending,
|
||||
'skip' => false,
|
||||
];
|
||||
|
||||
$intCursor = $intRangeEnd + 1;
|
||||
}
|
||||
|
||||
return $tabPlan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide le plan libre (plage valide + au moins une assignation possible).
|
||||
* Pas de contrôle de chevauchement ni d'insertion de séquence (MSIN-4445).
|
||||
*/
|
||||
function fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
|
||||
if ($intStart <= 0 || $intEnd < $intStart) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
if (!is_array($tabPlan) || empty($tabPlan)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
$blnHasAssignable = false;
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$epr_id = (int)($item['epr_id'] ?? 0);
|
||||
$intItemStart = (int)($item['start'] ?? 0);
|
||||
$intItemEnd = (int)($item['end'] ?? 0);
|
||||
|
||||
if ($epr_id <= 0 || $intItemStart <= 0 || $intItemEnd < $intItemStart) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
// MSIN-4445 — OK même si une séquence existe déjà (aucune création de séquence).
|
||||
$blnHasAssignable = true;
|
||||
}
|
||||
|
||||
if (!$blnHasAssignable) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_no_pending'),
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/** Stats agrégées + feedback quantité pour l'analyse libre. */
|
||||
function fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
$intQty = ($intStart > 0 && $intEnd >= $intStart) ? ($intEnd - $intStart + 1) : 0;
|
||||
$tabStats = fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan);
|
||||
|
||||
// MSIN-4445 — Feedback vs total à assigner (y compris épreuves non couvertes par la plage).
|
||||
$intPendingAll = 0;
|
||||
foreach ($tabPlan as $item) {
|
||||
$intPendingAll += (int)($item['nb_pending'] ?? 0);
|
||||
}
|
||||
|
||||
$tabFeedback = fxBibFreeQtyFeedback($intQty, $intPendingAll);
|
||||
$tabDupes = fxBibFreeDuplicateRisk($int_eve_id, $tabPlan, $intStart, $intEnd);
|
||||
|
||||
return [
|
||||
'stats' => $tabStats,
|
||||
'feedback' => $tabFeedback,
|
||||
'duplicates' => $tabDupes,
|
||||
'info' => fxBibMsg(
|
||||
'bib_v4_ajax_global_free_analysis',
|
||||
$tabStats['nb_epreuves'],
|
||||
$intStart,
|
||||
$intEnd,
|
||||
$intPendingAll,
|
||||
fxBibFreeQtyFeedbackMessage($tabFeedback)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulation globale libre (plages virtuelles, aucune écriture BD).
|
||||
*
|
||||
* @return array{success:bool, html?:string, info?:string, message?:string}
|
||||
*/
|
||||
function fxBibSimulateGlobalBatchFree($int_eve_id, $tabPlan, $intStart, $intEnd, $strSort1, $strSort2, $strLangue) {
|
||||
$check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue);
|
||||
|
||||
if (!$check['success']) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
$htmlParts = [];
|
||||
$intParticipants = 0;
|
||||
$intBibs = 0;
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$epr_id = (int)$item['epr_id'];
|
||||
$intItemStart = (int)$item['start'];
|
||||
$intItemEnd = (int)$item['end'];
|
||||
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
||||
$tabRanges = fxBibBuildVirtualRangesForPlan($intItemStart, $intItemEnd);
|
||||
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
|
||||
|
||||
$intNbPending = count($participants);
|
||||
$intParticipants += $intNbPending;
|
||||
$intBibs += max(0, $intItemEnd - $intItemStart + 1);
|
||||
|
||||
$tabEpreuve = fxGetEpreuve($epr_id);
|
||||
$strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id);
|
||||
|
||||
$strRangeLabel = fxBibMsg(
|
||||
'bib_v4_global_batch_gen_range_preview',
|
||||
$intItemStart,
|
||||
$intItemEnd,
|
||||
$intNbPending
|
||||
);
|
||||
|
||||
$htmlParts[] = '<div class="bib-global-sim-section">'
|
||||
. '<h4 class="bib-global-sim-section__title">' . fxBibEsc($strEprLabel) . '</h4>'
|
||||
. '<p class="bib-global-sim-section__range text-muted small">' . fxBibEsc($strRangeLabel) . '</p>'
|
||||
. renderBibView(
|
||||
$epr_id,
|
||||
0,
|
||||
$strLangue,
|
||||
'simulation',
|
||||
$simu,
|
||||
fxBibTexte('bib_v4_ajax_title_simulation'),
|
||||
fxBibMsg(
|
||||
'bib_v4_ajax_sim_summary',
|
||||
max(0, $intItemEnd - $intItemStart + 1),
|
||||
$intNbPending,
|
||||
max(0, $intNbPending - count($simu))
|
||||
)
|
||||
)
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd);
|
||||
$info = fxBibMsg(
|
||||
'bib_v4_ajax_global_free_sim_summary',
|
||||
$tabAnalysis['stats']['nb_epreuves'],
|
||||
$intBibs,
|
||||
(int)($tabAnalysis['feedback']['pending'] ?? $intParticipants),
|
||||
fxBibFreeQtyFeedbackMessage($tabAnalysis['feedback'])
|
||||
);
|
||||
|
||||
$html = fxBibWrapGlobalSimView($info, implode('', $htmlParts), 'bib-global-sim-view--free');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
'info' => $info,
|
||||
'feedback' => $tabAnalysis['feedback'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prépare les plans GO libres (sans INSERT séquence).
|
||||
*
|
||||
* @return array{success:bool, plans?:array, stats?:array, feedback?:array, message?:string}
|
||||
*/
|
||||
function fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') {
|
||||
$check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue);
|
||||
|
||||
if (!$check['success']) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
$tabPlans = [];
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabPlans[] = [
|
||||
'epr_id' => (int)$item['epr_id'],
|
||||
'start' => (int)$item['start'],
|
||||
'end' => (int)$item['end'],
|
||||
];
|
||||
}
|
||||
|
||||
$tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'plans' => $tabPlans,
|
||||
'stats' => $tabAnalysis['stats'],
|
||||
'feedback' => $tabAnalysis['feedback'],
|
||||
'duplicates' => $tabAnalysis['duplicates'],
|
||||
];
|
||||
}
|
||||
490
php/inc_fx_bib_global_generated.php
Normal file
@ -0,0 +1,490 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4443 — Assignation globale solo sans séquence existante.
|
||||
* Création automatique de plages de dossards puis assignation via le moteur batch existant.
|
||||
*/
|
||||
|
||||
/** True si l'épreuve a au moins une séquence enregistrée. */
|
||||
function fxBibEpreuveHasAnySequence($epr_id) {
|
||||
global $objDatabase;
|
||||
|
||||
$epr_id = (int)$epr_id;
|
||||
if ($epr_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intCount = (int)$objDatabase->fxGetVar("
|
||||
SELECT COUNT(*)
|
||||
FROM inscriptions_epreuves_bib
|
||||
WHERE epr_id = $epr_id
|
||||
AND epr_bib_start IS NOT NULL
|
||||
AND epr_bib_finish IS NOT NULL
|
||||
");
|
||||
|
||||
return $intCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Épreuves solo actives (avec ou sans séquence), triées par epr_id.
|
||||
* MSIN-4445 — utilisé par l'assignation libre.
|
||||
*
|
||||
* @return array[] lignes inscriptions_epreuves
|
||||
*/
|
||||
function fxBibGetSoloEpreuves($int_eve_id, $tabEpreuves = null) {
|
||||
global $objDatabase;
|
||||
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
if ($int_eve_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!is_array($tabEpreuves)) {
|
||||
$tabEpreuves = $objDatabase->fxGetResults(
|
||||
"SELECT * FROM inscriptions_epreuves e"
|
||||
. " WHERE e.eve_id = $int_eve_id AND epr_actif = 1"
|
||||
. " ORDER BY epr_id"
|
||||
);
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($tabEpreuves as $epr) {
|
||||
if ((int)($epr['epr_equipe'] ?? 0) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = $epr;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Épreuves solo sans aucune séquence, triées par epr_id (ordre page).
|
||||
*
|
||||
* @return array[] lignes inscriptions_epreuves
|
||||
*/
|
||||
function fxBibGetSoloEpreuvesWithoutSequence($int_eve_id, $tabEpreuves = null) {
|
||||
$out = [];
|
||||
|
||||
foreach (fxBibGetSoloEpreuves($int_eve_id, $tabEpreuves) as $epr) {
|
||||
if (fxBibEpreuveHasAnySequence((int)$epr['epr_id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = $epr;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Bloc 1 disponible : au moins 2 épreuves solo avec séquences sélectionnables. */
|
||||
function fxBibGlobalBatchHasExistingBlock($int_eve_id, $tabEpreuves) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intCount = 0;
|
||||
|
||||
foreach ($tabEpreuves as $epr) {
|
||||
if ((int)($epr['epr_equipe'] ?? 0) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty(fxBibGetSelectableRangesForGlobalBatch((int)$epr['epr_id']))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$intCount++;
|
||||
if ($intCount >= 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Bloc 2 disponible : au moins 1 épreuve solo sans séquence. */
|
||||
function fxBibGlobalBatchHasGeneratedBlock($int_eve_id, $tabEpreuves) {
|
||||
return count(fxBibGetSoloEpreuvesWithoutSequence($int_eve_id, $tabEpreuves)) >= 1;
|
||||
}
|
||||
|
||||
/** MSIN-4445 — Bloc libre : au moins 1 épreuve solo (avec ou sans séquence). */
|
||||
function fxBibGlobalBatchHasFreeBlock($int_eve_id, $tabEpreuves) {
|
||||
return count(fxBibGetSoloEpreuves($int_eve_id, $tabEpreuves)) >= 1;
|
||||
}
|
||||
|
||||
/** Tuile / coquille assignation globale visible si l'un des blocs est utilisable. */
|
||||
function fxBibGlobalBatchToolIsAvailable($int_eve_id, $tabEpreuves) {
|
||||
return fxBibGlobalBatchHasExistingBlock($int_eve_id, $tabEpreuves)
|
||||
|| fxBibGlobalBatchHasGeneratedBlock($int_eve_id, $tabEpreuves)
|
||||
|| fxBibGlobalBatchHasFreeBlock($int_eve_id, $tabEpreuves);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et ordonne les epr_id du bloc généré (solo, sans séquence, ordre epr_id).
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
function fxBibParseGlobalBatchGeneratedEprIds($eprIds, $int_eve_id) {
|
||||
if (!is_array($eprIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$tabWanted = [];
|
||||
|
||||
foreach ($eprIds as $epr_id) {
|
||||
$epr_id = (int)$epr_id;
|
||||
if ($epr_id <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$check = fxBibAssertSoloEpreuveForGlobalBatch($epr_id, $int_eve_id);
|
||||
if (!$check['success']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fxBibEpreuveHasAnySequence($epr_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabWanted[$epr_id] = true;
|
||||
}
|
||||
|
||||
if (empty($tabWanted)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tabIds = array_keys($tabWanted);
|
||||
sort($tabIds, SORT_NUMERIC);
|
||||
|
||||
return $tabIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan de plages générées : taille = a_assigner (sans marge), enchaînement par epr_id.
|
||||
*
|
||||
* @return array[] [{epr_id, start, end, nb_pending, skip}]
|
||||
*/
|
||||
function fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$intStartBib = (int)$intStartBib;
|
||||
$tabEprIds = fxBibParseGlobalBatchGeneratedEprIds($tabEprIds, $int_eve_id);
|
||||
$intCursor = $intStartBib;
|
||||
$tabPlan = [];
|
||||
|
||||
foreach ($tabEprIds as $epr_id) {
|
||||
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
||||
$intPending = count($participants);
|
||||
|
||||
if ($intPending <= 0) {
|
||||
$tabPlan[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'start' => 0,
|
||||
'end' => 0,
|
||||
'nb_pending' => 0,
|
||||
'skip' => true,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$intStart = $intCursor;
|
||||
$intEnd = $intCursor + $intPending - 1;
|
||||
|
||||
$tabPlan[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'start' => $intStart,
|
||||
'end' => $intEnd,
|
||||
'nb_pending' => $intPending,
|
||||
'skip' => false,
|
||||
];
|
||||
|
||||
$intCursor = $intEnd + 1;
|
||||
}
|
||||
|
||||
return $tabPlan;
|
||||
}
|
||||
|
||||
/** Plages virtuelles pour simulation (même format que fxGetRangesForBatchAssign). */
|
||||
function fxBibBuildVirtualRangesForPlan($intStart, $intEnd) {
|
||||
$intStart = (int)$intStart;
|
||||
$intEnd = (int)$intEnd;
|
||||
|
||||
if ($intStart <= 0 || $intEnd < $intStart) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [[
|
||||
'id' => 0,
|
||||
'start' => $intStart,
|
||||
'end' => $intEnd,
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide le plan généré (chevauchements, épreuves sans séquence, au moins un à assigner).
|
||||
*/
|
||||
function fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $strLangue = 'fr') {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
|
||||
if (!is_array($tabPlan) || empty($tabPlan)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
$blnHasAssignable = false;
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$blnHasAssignable = true;
|
||||
$epr_id = (int)($item['epr_id'] ?? 0);
|
||||
$intStart = (int)($item['start'] ?? 0);
|
||||
$intEnd = (int)($item['end'] ?? 0);
|
||||
|
||||
if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
if (fxBibEpreuveHasAnySequence($epr_id)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'),
|
||||
];
|
||||
}
|
||||
|
||||
$tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, $epr_id);
|
||||
if (!empty($tabOverlaps)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibFormatOverlapConflictMessage($tabOverlaps, $strLangue),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$blnHasAssignable) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_no_pending'),
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée les séquences en BD pour le plan validé.
|
||||
*
|
||||
* @return array{success:bool, message?:string, plans?:array}
|
||||
*/
|
||||
function fxBibCreateGeneratedRanges($int_eve_id, $tabPlan, $strLangue = 'fr') {
|
||||
global $objDatabase;
|
||||
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$tabCreated = [];
|
||||
|
||||
if (fxBibProductionEventIsLocked($int_eve_id)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v5_production_locked_error'),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$epr_id = (int)($item['epr_id'] ?? 0);
|
||||
$intStart = (int)($item['start'] ?? 0);
|
||||
$intEnd = (int)($item['end'] ?? 0);
|
||||
|
||||
if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
|
||||
];
|
||||
}
|
||||
|
||||
if (fxBibEpreuveHasAnySequence($epr_id)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'),
|
||||
];
|
||||
}
|
||||
|
||||
$tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, $epr_id);
|
||||
if (!empty($tabOverlaps)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibFormatOverlapConflictMessage($tabOverlaps, $strLangue),
|
||||
];
|
||||
}
|
||||
|
||||
// MSIN-4443 — Création séquence : vérifier INSERT + last_id (évite le message générique « Erreur »).
|
||||
$sqlInsert = "
|
||||
INSERT INTO inscriptions_epreuves_bib
|
||||
(epr_id, epr_bib_start, epr_bib_finish, update_date, update_qui, eve_id)
|
||||
VALUES
|
||||
($epr_id, $intStart, $intEnd, NOW(), 'admin', $int_eve_id)
|
||||
";
|
||||
$qryInsert = $objDatabase->fxQuery($sqlInsert);
|
||||
if ($qryInsert === false) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_insert_failed'),
|
||||
];
|
||||
}
|
||||
|
||||
$intBibId = 0;
|
||||
if (method_exists($objDatabase, 'fxGetLastId')) {
|
||||
$intBibId = (int)$objDatabase->fxGetLastId();
|
||||
} elseif (isset($objDatabase->last_id)) {
|
||||
$intBibId = (int)$objDatabase->last_id;
|
||||
}
|
||||
|
||||
// Repli : si insert_id absent, retrouver la ligne fraîchement créée.
|
||||
if ($intBibId <= 0) {
|
||||
$intBibId = (int)$objDatabase->fxGetVar("
|
||||
SELECT epr_bib_id
|
||||
FROM inscriptions_epreuves_bib
|
||||
WHERE epr_id = $epr_id
|
||||
AND epr_bib_start = $intStart
|
||||
AND epr_bib_finish = $intEnd
|
||||
AND eve_id = $int_eve_id
|
||||
ORDER BY epr_bib_id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
}
|
||||
|
||||
if ($intBibId <= 0) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => fxBibMsg('bib_v4_global_batch_gen_insert_failed'),
|
||||
];
|
||||
}
|
||||
|
||||
$tabCreated[] = [
|
||||
'epr_id' => $epr_id,
|
||||
'ranges' => [$intBibId],
|
||||
'start' => $intStart,
|
||||
'end' => $intEnd,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'plans' => $tabCreated,
|
||||
];
|
||||
}
|
||||
|
||||
/** Statistiques agrégées pour l'analyse du bloc généré. */
|
||||
function fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan) {
|
||||
$intEpreuves = 0;
|
||||
$intPending = 0;
|
||||
$intBibs = 0;
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$intEpreuves++;
|
||||
$intPending += (int)($item['nb_pending'] ?? 0);
|
||||
$intStart = (int)($item['start'] ?? 0);
|
||||
$intEnd = (int)($item['end'] ?? 0);
|
||||
if ($intEnd >= $intStart && $intStart > 0) {
|
||||
$intBibs += ($intEnd - $intStart + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'nb_epreuves' => $intEpreuves,
|
||||
'nb_participants' => $intPending,
|
||||
'nb_bibs' => $intBibs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulation globale bloc généré (plages virtuelles, aucune écriture BD).
|
||||
*
|
||||
* @return array{success:bool, html?:string, info?:string, message?:string}
|
||||
*/
|
||||
function fxBibSimulateGlobalBatchGenerated($int_eve_id, $tabPlan, $strSort1, $strSort2, $strLangue) {
|
||||
$int_eve_id = (int)$int_eve_id;
|
||||
$check = fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $strLangue);
|
||||
|
||||
if (!$check['success']) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
$htmlParts = [];
|
||||
$intParticipants = 0;
|
||||
$intBibs = 0;
|
||||
|
||||
foreach ($tabPlan as $item) {
|
||||
if (!empty($item['skip'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$epr_id = (int)$item['epr_id'];
|
||||
$intStart = (int)$item['start'];
|
||||
$intEnd = (int)$item['end'];
|
||||
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
||||
$tabRanges = fxBibBuildVirtualRangesForPlan($intStart, $intEnd);
|
||||
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
|
||||
|
||||
$intNbPending = count($participants);
|
||||
$intParticipants += $intNbPending;
|
||||
$intBibs += max(0, $intEnd - $intStart + 1);
|
||||
|
||||
$tabEpreuve = fxGetEpreuve($epr_id);
|
||||
$strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id);
|
||||
|
||||
$strRangeLabel = fxBibMsg(
|
||||
'bib_v4_global_batch_gen_range_preview',
|
||||
$intStart,
|
||||
$intEnd,
|
||||
$intNbPending
|
||||
);
|
||||
|
||||
$htmlParts[] = '<div class="bib-global-sim-section">'
|
||||
. '<h4 class="bib-global-sim-section__title">' . fxBibEsc($strEprLabel) . '</h4>'
|
||||
. '<p class="bib-global-sim-section__range text-muted small">' . fxBibEsc($strRangeLabel) . '</p>'
|
||||
. renderBibView(
|
||||
$epr_id,
|
||||
0,
|
||||
$strLangue,
|
||||
'simulation',
|
||||
$simu,
|
||||
fxBibTexte('bib_v4_ajax_title_simulation'),
|
||||
fxBibMsg(
|
||||
'bib_v4_ajax_sim_summary',
|
||||
max(0, $intEnd - $intStart + 1),
|
||||
$intNbPending,
|
||||
max(0, $intNbPending - count($simu))
|
||||
)
|
||||
)
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$info = fxBibMsg(
|
||||
'bib_v4_ajax_global_gen_sim_summary',
|
||||
fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan)['nb_epreuves'],
|
||||
$intBibs,
|
||||
$intParticipants
|
||||
);
|
||||
|
||||
$html = fxBibWrapGlobalSimView($info, implode('', $htmlParts), 'bib-global-sim-view--generated');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
'info' => $info,
|
||||
];
|
||||
}
|
||||
926
php/inc_fx_bib_production.php
Normal file
@ -0,0 +1,926 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MSIN-4469 — Production Excel des dossards.
|
||||
*
|
||||
* Une commande active représente un instantané immuable des séquences. Le fichier
|
||||
* peut être téléchargé de nouveau, mais une seconde génération est refusée tant
|
||||
* que la commande active n'a pas été remplacée par un futur garde-fou.
|
||||
*/
|
||||
|
||||
function fxBibProductionTemplatePath() {
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'templates'
|
||||
. DIRECTORY_SEPARATOR . 'MSIN-4469-commande-dossards.xlsx';
|
||||
}
|
||||
|
||||
function fxBibProductionStorageRoot() {
|
||||
return dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data'
|
||||
. DIRECTORY_SEPARATOR . 'bib-production';
|
||||
}
|
||||
|
||||
function fxBibProductionGetActiveOrder($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = (int)$intEveId;
|
||||
if ($intEveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $objDatabase->fxGetRow(
|
||||
"SELECT *
|
||||
FROM inscriptions_bib_production_orders
|
||||
WHERE eve_id = $intEveId
|
||||
AND bpo_active_key = 1
|
||||
LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
function fxBibProductionGetOrder($intOrderId, $intEveId = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
$intOrderId = (int)$intOrderId;
|
||||
$intEveId = (int)$intEveId;
|
||||
if ($intOrderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$strWhereEve = $intEveId > 0 ? " AND eve_id = $intEveId" : '';
|
||||
return $objDatabase->fxGetRow(
|
||||
"SELECT *
|
||||
FROM inscriptions_bib_production_orders
|
||||
WHERE bpo_id = $intOrderId
|
||||
$strWhereEve
|
||||
LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
function fxBibProductionEventIsLocked($intEveId) {
|
||||
return fxBibProductionGetActiveOrder($intEveId) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4469 — Déverrouillage temporaire (dev) : même droit que la génération
|
||||
* (promoteur avec dossards.manage, ou superadmin).
|
||||
*/
|
||||
function fxBibProductionDevelopmentUnlock($intEveId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = (int)$intEveId;
|
||||
if ($intEveId <= 0 || !fxBibProductionUserCanManageEvent($intEveId)) {
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_unauthorized')];
|
||||
}
|
||||
|
||||
$tabOrder = fxBibProductionGetActiveOrder($intEveId);
|
||||
if (!$tabOrder) {
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
$strBy = fxBibGetCurrentPromoteurDisplayName();
|
||||
$objDatabase->fxQuery(
|
||||
"UPDATE inscriptions_bib_production_orders
|
||||
SET bpo_status = 'development_unlocked',
|
||||
bpo_active_key = NULL,
|
||||
bpo_superseded_at = NOW(),
|
||||
bpo_superseded_by = '" . $objDatabase->fxEscape($strBy) . "',
|
||||
bpo_superseded_reason = 'MSIN-4469 development unlock'
|
||||
WHERE bpo_id = " . (int)$tabOrder['bpo_id'] . "
|
||||
AND bpo_active_key = 1"
|
||||
);
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
function fxBibProductionEpreuveIsLocked($intEprId) {
|
||||
global $objDatabase;
|
||||
|
||||
$intEprId = (int)$intEprId;
|
||||
if ($intEprId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intEveId = (int)$objDatabase->fxGetVar(
|
||||
"SELECT eve_id FROM inscriptions_epreuves WHERE epr_id = $intEprId LIMIT 1"
|
||||
);
|
||||
return fxBibProductionEventIsLocked($intEveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie l'accès réel à l'événement, en plus de la session et du jeton CSRF.
|
||||
*/
|
||||
function fxBibProductionUserCanManageEvent($intEveId) {
|
||||
$intEveId = (int)$intEveId;
|
||||
if ($intEveId <= 0 || !fxBibRequirePromoteurSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['usa_id'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$intComId = (int)($_SESSION['com_id'] ?? 0);
|
||||
return $intComId > 0
|
||||
&& function_exists('fxEveAccesHasPermission')
|
||||
&& fxEveAccesHasPermission($intComId, $intEveId, 'dossards.manage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les épreuves actives et toutes leurs séquences, dans l'ordre d'affichage.
|
||||
*/
|
||||
function fxBibProductionCollectSnapshot($intEveId, $strLangue = 'fr') {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = (int)$intEveId;
|
||||
$strLangue = in_array($strLangue, ['fr', 'en'], true) ? $strLangue : 'fr';
|
||||
|
||||
$tabEvent = $objDatabase->fxGetRow(
|
||||
"SELECT eve_id, eve_nom_fr, eve_nom_en
|
||||
FROM inscriptions_evenements
|
||||
WHERE eve_id = $intEveId
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!$tabEvent) {
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_event_invalid')];
|
||||
}
|
||||
|
||||
$tabEpreuves = $objDatabase->fxGetResults(
|
||||
"SELECT *
|
||||
FROM inscriptions_epreuves
|
||||
WHERE eve_id = $intEveId
|
||||
AND epr_actif = 1
|
||||
ORDER BY epr_tri, epr_id"
|
||||
);
|
||||
if (!is_array($tabEpreuves)) {
|
||||
$tabEpreuves = [];
|
||||
}
|
||||
|
||||
$tabRaces = [];
|
||||
$intBibCount = 0;
|
||||
$intRangeCount = 0;
|
||||
|
||||
foreach ($tabEpreuves as $tabEpreuve) {
|
||||
$intEprId = (int)($tabEpreuve['epr_id'] ?? 0);
|
||||
if ($intEprId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// MSIN-4469 — La commande porte sur toute la séquence à imprimer,
|
||||
// même lorsqu’aucun coureur ni aucun dossard assigné ne s’y trouve encore.
|
||||
$tabRangesRaw = $objDatabase->fxGetResults(
|
||||
"SELECT epr_bib_id, epr_bib_start, epr_bib_finish
|
||||
FROM inscriptions_epreuves_bib
|
||||
WHERE epr_id = $intEprId
|
||||
ORDER BY epr_bib_start, epr_bib_id"
|
||||
);
|
||||
if (!is_array($tabRangesRaw)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabRanges = [];
|
||||
foreach ($tabRangesRaw as $tabRange) {
|
||||
$intStart = (int)($tabRange['epr_bib_start'] ?? 0);
|
||||
$intFinish = (int)($tabRange['epr_bib_finish'] ?? 0);
|
||||
if ($intStart <= 0 || $intFinish < $intStart) {
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_range_invalid')];
|
||||
}
|
||||
|
||||
$intQuantity = $intFinish - $intStart + 1;
|
||||
$intBibCount += $intQuantity;
|
||||
$intRangeCount++;
|
||||
$tabRanges[] = [
|
||||
'epr_bib_id' => (int)$tabRange['epr_bib_id'],
|
||||
'start' => $intStart,
|
||||
'finish' => $intFinish,
|
||||
'quantity' => $intQuantity,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($tabRanges)) {
|
||||
$tabRaces[] = [
|
||||
'epr_id' => $intEprId,
|
||||
// Le libellé type/catégorie est le nom court retenu pour la V1.
|
||||
'label' => fxBibEpreuveDisplayName($tabEpreuve, $strLangue),
|
||||
'ranges' => $tabRanges,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($intRangeCount === 0) {
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_no_ranges')];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'snapshot' => [
|
||||
'event' => [
|
||||
'eve_id' => $intEveId,
|
||||
'name' => trim((string)($tabEvent['eve_nom_' . $strLangue] ?? '')),
|
||||
],
|
||||
'language' => $strLangue,
|
||||
'bib_count' => $intBibCount,
|
||||
'range_count' => $intRangeCount,
|
||||
'races' => $tabRaces,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function fxBibProductionSafeFilePart($strValue) {
|
||||
$strValue = trim((string)$strValue);
|
||||
if (function_exists('iconv')) {
|
||||
$strAscii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $strValue);
|
||||
if ($strAscii !== false) {
|
||||
$strValue = $strAscii;
|
||||
}
|
||||
}
|
||||
$strValue = strtolower($strValue);
|
||||
$strValue = preg_replace('/[^a-z0-9]+/', '-', $strValue);
|
||||
$strValue = trim((string)$strValue, '-');
|
||||
return $strValue !== '' ? $strValue : 'evenement';
|
||||
}
|
||||
|
||||
function fxBibProductionEnsureDirectory($strDirectory) {
|
||||
if (is_dir($strDirectory)) {
|
||||
return true;
|
||||
}
|
||||
return @mkdir($strDirectory, 0770, true) || is_dir($strDirectory);
|
||||
}
|
||||
|
||||
function fxBibProductionXmlLoad($strXml) {
|
||||
$objDom = new DOMDocument('1.0', 'UTF-8');
|
||||
$objDom->preserveWhiteSpace = false;
|
||||
$objDom->formatOutput = false;
|
||||
if (!@$objDom->loadXML($strXml)) {
|
||||
throw new Exception('MSIN-4469 invalid OpenXML part');
|
||||
}
|
||||
return $objDom;
|
||||
}
|
||||
|
||||
/** @return DOMElement|null */
|
||||
function fxBibProductionXmlFindRow(DOMXPath $objXpath, $intRow) {
|
||||
$objRow = $objXpath->query('//m:sheetData/m:row[@r="' . (int)$intRow . '"]')->item(0);
|
||||
return $objRow instanceof DOMElement ? $objRow : null;
|
||||
}
|
||||
|
||||
function fxBibProductionXmlClearCell(DOMElement $objCell) {
|
||||
while ($objCell->firstChild) {
|
||||
$objCell->removeChild($objCell->firstChild);
|
||||
}
|
||||
$objCell->removeAttribute('t');
|
||||
}
|
||||
|
||||
function fxBibProductionXmlFindCell(DOMElement $objRow, $strColumn) {
|
||||
foreach ($objRow->childNodes as $objChild) {
|
||||
if ($objChild instanceof DOMElement && $objChild->localName === 'c') {
|
||||
$strRef = $objChild->getAttribute('r');
|
||||
if (preg_replace('/[0-9]+/', '', $strRef) === $strColumn) {
|
||||
return $objChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function fxBibProductionXmlEnsureCell(
|
||||
DOMDocument $objDom,
|
||||
DOMElement $objRow,
|
||||
$strColumn,
|
||||
$intRow,
|
||||
$strStyle = ''
|
||||
) {
|
||||
$objCell = fxBibProductionXmlFindCell($objRow, $strColumn);
|
||||
if (!$objCell) {
|
||||
$objCell = $objDom->createElementNS(
|
||||
'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
||||
'c'
|
||||
);
|
||||
$objRow->appendChild($objCell);
|
||||
}
|
||||
$objCell->setAttribute('r', $strColumn . (int)$intRow);
|
||||
if ($strStyle !== '') {
|
||||
$objCell->setAttribute('s', $strStyle);
|
||||
}
|
||||
return $objCell;
|
||||
}
|
||||
|
||||
function fxBibProductionXmlSetInlineString(
|
||||
DOMDocument $objDom,
|
||||
DOMElement $objRow,
|
||||
$strColumn,
|
||||
$intRow,
|
||||
$strValue,
|
||||
$strStyle = ''
|
||||
) {
|
||||
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
||||
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
|
||||
fxBibProductionXmlClearCell($objCell);
|
||||
$objCell->setAttribute('t', 'inlineStr');
|
||||
$objInline = $objDom->createElementNS($strNs, 'is');
|
||||
$objText = $objDom->createElementNS($strNs, 't');
|
||||
$objText->appendChild($objDom->createTextNode((string)$strValue));
|
||||
$objInline->appendChild($objText);
|
||||
$objCell->appendChild($objInline);
|
||||
}
|
||||
|
||||
function fxBibProductionXmlSetNumber(
|
||||
DOMDocument $objDom,
|
||||
DOMElement $objRow,
|
||||
$strColumn,
|
||||
$intRow,
|
||||
$mixValue,
|
||||
$strStyle = ''
|
||||
) {
|
||||
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
||||
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
|
||||
fxBibProductionXmlClearCell($objCell);
|
||||
$objValue = $objDom->createElementNS($strNs, 'v');
|
||||
$objValue->appendChild($objDom->createTextNode((string)$mixValue));
|
||||
$objCell->appendChild($objValue);
|
||||
}
|
||||
|
||||
function fxBibProductionXmlSetFormula(
|
||||
DOMDocument $objDom,
|
||||
DOMElement $objRow,
|
||||
$strColumn,
|
||||
$intRow,
|
||||
$strFormula,
|
||||
$strStyle = '',
|
||||
$mixCachedValue = null
|
||||
) {
|
||||
$strNs = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
||||
$objCell = fxBibProductionXmlEnsureCell($objDom, $objRow, $strColumn, $intRow, $strStyle);
|
||||
fxBibProductionXmlClearCell($objCell);
|
||||
$objFormula = $objDom->createElementNS($strNs, 'f');
|
||||
$objFormula->appendChild($objDom->createTextNode($strFormula));
|
||||
$objCell->appendChild($objFormula);
|
||||
// MSIN-4469 — Valeur calculée en cache : sinon Excel/LibreOffice peut
|
||||
// afficher le TOTAL vide tant que le recalcul n'a pas tourné.
|
||||
if ($mixCachedValue !== null && $mixCachedValue !== '') {
|
||||
$objValue = $objDom->createElementNS($strNs, 'v');
|
||||
$objValue->appendChild($objDom->createTextNode((string)$mixCachedValue));
|
||||
$objCell->appendChild($objValue);
|
||||
}
|
||||
}
|
||||
|
||||
function fxBibProductionXmlCloneRow(DOMElement $objTemplateRow, $intTargetRow) {
|
||||
$objRow = $objTemplateRow->cloneNode(true);
|
||||
$objRow->setAttribute('r', (string)$intTargetRow);
|
||||
foreach ($objRow->childNodes as $objChild) {
|
||||
if ($objChild instanceof DOMElement && $objChild->localName === 'c') {
|
||||
$strColumn = preg_replace('/[0-9]+/', '', $objChild->getAttribute('r'));
|
||||
$objChild->setAttribute('r', $strColumn . $intTargetRow);
|
||||
fxBibProductionXmlClearCell($objChild);
|
||||
}
|
||||
}
|
||||
return $objRow;
|
||||
}
|
||||
|
||||
function fxBibProductionXmlShiftReference($strReference, $intFromRow, $intOffset) {
|
||||
return preg_replace_callback(
|
||||
'/([A-Z]+)([0-9]+)/',
|
||||
function ($tabMatch) use ($intFromRow, $intOffset) {
|
||||
$intRow = (int)$tabMatch[2];
|
||||
if ($intRow >= $intFromRow) {
|
||||
$intRow += $intOffset;
|
||||
}
|
||||
return $tabMatch[1] . $intRow;
|
||||
},
|
||||
$strReference
|
||||
);
|
||||
}
|
||||
|
||||
function fxBibProductionBuildSupplierXml($strXml, array $tabSnapshot, array $tabLabels) {
|
||||
$objDom = fxBibProductionXmlLoad($strXml);
|
||||
$objXpath = new DOMXPath($objDom);
|
||||
$objXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
|
||||
$objSheetData = $objXpath->query('//m:sheetData')->item(0);
|
||||
$objHeaderTemplate = fxBibProductionXmlFindRow($objXpath, 1);
|
||||
$objDataTemplate = fxBibProductionXmlFindRow($objXpath, 2);
|
||||
if (!$objSheetData || !$objHeaderTemplate || !$objDataTemplate) {
|
||||
throw new Exception('MSIN-4469 invalid supplier template');
|
||||
}
|
||||
|
||||
while ($objSheetData->firstChild) {
|
||||
$objSheetData->removeChild($objSheetData->firstChild);
|
||||
}
|
||||
|
||||
// MSIN-4469 — Convention imprimeur : cellules bleues = imprimées sur le dossard.
|
||||
// Colonne A (numéro) = bleu ; colonne B (épreuve) = neutre (info, non imprimée).
|
||||
$objHeader = fxBibProductionXmlCloneRow($objHeaderTemplate, 1);
|
||||
$strHeaderStyleA = (string)(fxBibProductionXmlFindCell($objHeader, 'A')->getAttribute('s'));
|
||||
$strHeaderStyleB = (string)(fxBibProductionXmlFindCell($objHeader, 'B')->getAttribute('s'));
|
||||
fxBibProductionXmlSetInlineString($objDom, $objHeader, 'A', 1, $tabLabels['bib'], $strHeaderStyleA);
|
||||
fxBibProductionXmlSetInlineString($objDom, $objHeader, 'B', 1, $tabLabels['race'], $strHeaderStyleB);
|
||||
$objSheetData->appendChild($objHeader);
|
||||
|
||||
$strDataStyleA = (string)(fxBibProductionXmlFindCell($objDataTemplate, 'A')->getAttribute('s'));
|
||||
$strDataStyleB = (string)(fxBibProductionXmlFindCell($objDataTemplate, 'B')->getAttribute('s'));
|
||||
$intRow = 2;
|
||||
foreach ($tabSnapshot['races'] as $intRaceIndex => $tabRace) {
|
||||
if ($intRaceIndex > 0) {
|
||||
$intRow++;
|
||||
}
|
||||
foreach ($tabRace['ranges'] as $tabRange) {
|
||||
for ($intBib = (int)$tabRange['start']; $intBib <= (int)$tabRange['finish']; $intBib++) {
|
||||
$objRow = fxBibProductionXmlCloneRow($objDataTemplate, $intRow);
|
||||
fxBibProductionXmlSetNumber($objDom, $objRow, 'A', $intRow, $intBib, $strDataStyleA);
|
||||
fxBibProductionXmlSetInlineString(
|
||||
$objDom,
|
||||
$objRow,
|
||||
'B',
|
||||
$intRow,
|
||||
$tabRace['label'],
|
||||
$strDataStyleB
|
||||
);
|
||||
$objSheetData->appendChild($objRow);
|
||||
$intRow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$objDimension = $objXpath->query('//m:dimension')->item(0);
|
||||
if ($objDimension instanceof DOMElement) {
|
||||
$objDimension->setAttribute('ref', 'A1:B' . max(1, $intRow - 1));
|
||||
}
|
||||
return $objDom->saveXML();
|
||||
}
|
||||
|
||||
function fxBibProductionBuildOrderXml($strXml, array $tabSnapshot, array $tabLabels) {
|
||||
$objDom = fxBibProductionXmlLoad($strXml);
|
||||
$objXpath = new DOMXPath($objDom);
|
||||
$objXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
|
||||
$objSheetData = $objXpath->query('//m:sheetData')->item(0);
|
||||
$objBaseEvent = fxBibProductionXmlFindRow($objXpath, 4);
|
||||
$objBaseSecond = fxBibProductionXmlFindRow($objXpath, 5);
|
||||
$objBaseBlank = fxBibProductionXmlFindRow($objXpath, 6);
|
||||
$objTotal = fxBibProductionXmlFindRow($objXpath, 31);
|
||||
if (!$objSheetData || !$objBaseEvent || !$objBaseSecond || !$objTotal) {
|
||||
throw new Exception('MSIN-4469 invalid order template');
|
||||
}
|
||||
if (!$objBaseBlank) {
|
||||
$objBaseBlank = $objBaseSecond;
|
||||
}
|
||||
|
||||
$tabOrderRows = [];
|
||||
foreach ($tabSnapshot['races'] as $tabRace) {
|
||||
foreach ($tabRace['ranges'] as $intRangeIndex => $tabRange) {
|
||||
$tabOrderRows[] = [
|
||||
// MSIN-4470 — Une ligne reste réservée à chaque séquence,
|
||||
// mais le nom de l'épreuve n'est répété que sur la première.
|
||||
'label' => $intRangeIndex === 0 ? $tabRace['label'] : '',
|
||||
'start' => (int)$tabRange['start'],
|
||||
'finish' => (int)$tabRange['finish'],
|
||||
'quantity' => (int)$tabRange['quantity'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// MSIN-4469 — Le gabarit a 9 emplacements ; on n'en garde que le nombre
|
||||
// réel de séquences (sinon lignes jaunes vides avant le TOTAL).
|
||||
$intTemplateSlots = 9;
|
||||
$intSlotCount = count($tabOrderRows);
|
||||
$intRowDelta = ($intSlotCount - $intTemplateSlots) * 3;
|
||||
$intTotalRow = 31 + $intRowDelta;
|
||||
|
||||
$tabRemove = [];
|
||||
foreach ($objXpath->query('//m:sheetData/m:row') as $objRow) {
|
||||
if (!($objRow instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
$intCurrentRow = (int)$objRow->getAttribute('r');
|
||||
if ($intCurrentRow >= 4 && $intCurrentRow <= 30) {
|
||||
$tabRemove[] = $objRow;
|
||||
}
|
||||
}
|
||||
foreach ($tabRemove as $objRow) {
|
||||
$objSheetData->removeChild($objRow);
|
||||
}
|
||||
|
||||
if ($intRowDelta !== 0) {
|
||||
foreach ($objXpath->query('//m:sheetData/m:row') as $objRow) {
|
||||
if (!($objRow instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
$intCurrentRow = (int)$objRow->getAttribute('r');
|
||||
if ($intCurrentRow < 31) {
|
||||
continue;
|
||||
}
|
||||
$intShiftedRow = $intCurrentRow + $intRowDelta;
|
||||
$objRow->setAttribute('r', (string)$intShiftedRow);
|
||||
foreach ($objRow->childNodes as $objCell) {
|
||||
if ($objCell instanceof DOMElement && $objCell->localName === 'c') {
|
||||
$strColumn = preg_replace('/[0-9]+/', '', $objCell->getAttribute('r'));
|
||||
$objCell->setAttribute('r', $strColumn . $intShiftedRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($objXpath->query('//m:mergeCell') as $objMerge) {
|
||||
if (!($objMerge instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
$objMerge->setAttribute(
|
||||
'ref',
|
||||
fxBibProductionXmlShiftReference($objMerge->getAttribute('ref'), 31, $intRowDelta)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$objTotal = fxBibProductionXmlFindRow($objXpath, $intTotalRow);
|
||||
if (!$objTotal) {
|
||||
throw new Exception('MSIN-4469 total row missing');
|
||||
}
|
||||
|
||||
for ($intSlot = 0; $intSlot < $intSlotCount; $intSlot++) {
|
||||
$intRow = 4 + ($intSlot * 3);
|
||||
$objEventRow = fxBibProductionXmlCloneRow($objBaseEvent, $intRow);
|
||||
$objSecondRow = fxBibProductionXmlCloneRow($objBaseSecond, $intRow + 1);
|
||||
$objBlankRow = fxBibProductionXmlCloneRow($objBaseBlank, $intRow + 2);
|
||||
|
||||
$tabRow = $tabOrderRows[$intSlot];
|
||||
$objCellF = fxBibProductionXmlFindCell($objEventRow, 'F');
|
||||
$strStyleK = $objCellF ? (string)$objCellF->getAttribute('s') : '';
|
||||
fxBibProductionXmlSetInlineString($objDom, $objEventRow, 'A', $intRow, $tabRow['label']);
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'F', $intRow, $tabRow['quantity']);
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'G', $intRow, $tabRow['start']);
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'H', $intRow, $tabRow['finish']);
|
||||
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'I', $intRow, 'H' . $intRow . '-G' . $intRow . '+1');
|
||||
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'J', $intRow, 'I' . $intRow . '=F' . $intRow);
|
||||
// MSIN-4469 — À côté de la quantité : lignes Imprimeur pour cette séquence.
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'K', $intRow, $tabRow['quantity'], $strStyleK);
|
||||
// MSIN-4469 — V1 : une puce par dossard, avec le même numéro.
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'L', $intRow, $tabRow['quantity']);
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'M', $intRow, $tabRow['start']);
|
||||
fxBibProductionXmlSetNumber($objDom, $objEventRow, 'N', $intRow, $tabRow['finish']);
|
||||
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'O', $intRow, 'G' . $intRow . '=M' . $intRow);
|
||||
fxBibProductionXmlSetFormula($objDom, $objEventRow, 'P', $intRow, 'H' . $intRow . '=N' . $intRow);
|
||||
|
||||
$objSheetData->insertBefore($objEventRow, $objTotal);
|
||||
$objSheetData->insertBefore($objSecondRow, $objTotal);
|
||||
$objSheetData->insertBefore($objBlankRow, $objTotal);
|
||||
}
|
||||
|
||||
$objTitleRow = fxBibProductionXmlFindRow($objXpath, 1);
|
||||
if ($objTitleRow) {
|
||||
fxBibProductionXmlSetInlineString(
|
||||
$objDom,
|
||||
$objTitleRow,
|
||||
'A',
|
||||
1,
|
||||
$tabSnapshot['event']['name']
|
||||
);
|
||||
}
|
||||
|
||||
// MSIN-4469 — Colonne K : validation quantité vs lignes onglet Imprimeur (hors vides).
|
||||
$objHeaderRow = fxBibProductionXmlFindRow($objXpath, 3);
|
||||
if ($objHeaderRow) {
|
||||
$objHeaderCellF = fxBibProductionXmlFindCell($objHeaderRow, 'F');
|
||||
$strHeaderStyleK = $objHeaderCellF
|
||||
? (string)$objHeaderCellF->getAttribute('s')
|
||||
: '';
|
||||
fxBibProductionXmlSetInlineString(
|
||||
$objDom,
|
||||
$objHeaderRow,
|
||||
'K',
|
||||
3,
|
||||
isset($tabLabels['printer_lines']) ? $tabLabels['printer_lines'] : 'Lignes Imprimeur',
|
||||
$strHeaderStyleK
|
||||
);
|
||||
}
|
||||
|
||||
$intBibTotal = 0;
|
||||
foreach ($tabOrderRows as $tabRow) {
|
||||
$intBibTotal += (int)$tabRow['quantity'];
|
||||
}
|
||||
|
||||
// Reprendre la ligne TOTAL après les insertions (référence DOM à jour).
|
||||
$objTotal = fxBibProductionXmlFindRow($objXpath, $intTotalRow);
|
||||
if (!$objTotal) {
|
||||
throw new Exception('MSIN-4469 total row missing');
|
||||
}
|
||||
|
||||
fxBibProductionXmlSetInlineString($objDom, $objTotal, 'A', $intTotalRow, $tabLabels['total']);
|
||||
fxBibProductionXmlSetFormula(
|
||||
$objDom,
|
||||
$objTotal,
|
||||
'F',
|
||||
$intTotalRow,
|
||||
'SUM(F4:F' . ($intTotalRow - 1) . ')',
|
||||
'',
|
||||
$intBibTotal
|
||||
);
|
||||
fxBibProductionXmlSetFormula(
|
||||
$objDom,
|
||||
$objTotal,
|
||||
'I',
|
||||
$intTotalRow,
|
||||
'SUM(I4:I' . ($intTotalRow - 1) . ')',
|
||||
'',
|
||||
$intBibTotal
|
||||
);
|
||||
fxBibProductionXmlSetFormula(
|
||||
$objDom,
|
||||
$objTotal,
|
||||
'L',
|
||||
$intTotalRow,
|
||||
'SUM(L4:L' . ($intTotalRow - 1) . ')',
|
||||
'',
|
||||
$intBibTotal
|
||||
);
|
||||
// COUNT ignore l'en-tête texte et les lignes blanches entre épreuves.
|
||||
fxBibProductionXmlSetFormula(
|
||||
$objDom,
|
||||
$objTotal,
|
||||
'K',
|
||||
$intTotalRow,
|
||||
'COUNT(Imprimeur!A:A)',
|
||||
'',
|
||||
$intBibTotal
|
||||
);
|
||||
fxBibProductionXmlSetFormula(
|
||||
$objDom,
|
||||
$objTotal,
|
||||
'J',
|
||||
$intTotalRow,
|
||||
'F' . $intTotalRow . '=K' . $intTotalRow,
|
||||
'',
|
||||
'1'
|
||||
);
|
||||
|
||||
$objDimension = $objXpath->query('//m:dimension')->item(0);
|
||||
if ($objDimension instanceof DOMElement) {
|
||||
$objDimension->setAttribute('ref', 'A1:Q' . (48 + $intRowDelta));
|
||||
}
|
||||
return $objDom->saveXML();
|
||||
}
|
||||
|
||||
function fxBibProductionBuildWorkbook(array $tabSnapshot, $strDestination, array $tabLabels) {
|
||||
$strTemplate = fxBibProductionTemplatePath();
|
||||
if (!is_file($strTemplate)) {
|
||||
throw new Exception('MSIN-4469 template missing');
|
||||
}
|
||||
if (!class_exists('ZipArchive') || !class_exists('DOMDocument')) {
|
||||
throw new Exception('MSIN-4469 ZIP/XML extensions missing');
|
||||
}
|
||||
if (!copy($strTemplate, $strDestination)) {
|
||||
throw new Exception('MSIN-4469 template copy failed');
|
||||
}
|
||||
|
||||
$objZip = new ZipArchive();
|
||||
if ($objZip->open($strDestination) !== true) {
|
||||
throw new Exception('MSIN-4469 workbook open failed');
|
||||
}
|
||||
|
||||
try {
|
||||
$strSupplierXml = $objZip->getFromName('xl/worksheets/sheet1.xml');
|
||||
$strOrderXml = $objZip->getFromName('xl/worksheets/sheet2.xml');
|
||||
if ($strSupplierXml === false || $strOrderXml === false) {
|
||||
throw new Exception('MSIN-4469 worksheet missing');
|
||||
}
|
||||
|
||||
$strSupplierXml = fxBibProductionBuildSupplierXml($strSupplierXml, $tabSnapshot, $tabLabels);
|
||||
$strOrderXml = fxBibProductionBuildOrderXml($strOrderXml, $tabSnapshot, $tabLabels);
|
||||
|
||||
$objZip->deleteName('xl/worksheets/sheet1.xml');
|
||||
$objZip->addFromString('xl/worksheets/sheet1.xml', $strSupplierXml);
|
||||
$objZip->deleteName('xl/worksheets/sheet2.xml');
|
||||
$objZip->addFromString('xl/worksheets/sheet2.xml', $strOrderXml);
|
||||
|
||||
$strWorkbookXml = $objZip->getFromName('xl/workbook.xml');
|
||||
if ($strWorkbookXml !== false) {
|
||||
$objWorkbookDom = fxBibProductionXmlLoad($strWorkbookXml);
|
||||
$objWorkbookXpath = new DOMXPath($objWorkbookDom);
|
||||
$objWorkbookXpath->registerNamespace('m', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
$objCalcPr = $objWorkbookXpath->query('//m:calcPr')->item(0);
|
||||
if ($objCalcPr instanceof DOMElement) {
|
||||
$objCalcPr->setAttribute('fullCalcOnLoad', '1');
|
||||
$objCalcPr->setAttribute('forceFullCalc', '1');
|
||||
}
|
||||
$objZip->deleteName('xl/workbook.xml');
|
||||
$objZip->addFromString('xl/workbook.xml', $objWorkbookDom->saveXML());
|
||||
}
|
||||
} catch (Exception $objException) {
|
||||
$objZip->close();
|
||||
@unlink($strDestination);
|
||||
throw $objException;
|
||||
}
|
||||
|
||||
$objZip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée la commande officielle et verrouille immédiatement les séquences via
|
||||
* bpo_active_key. Une requête concurrente retrouve la commande déjà active.
|
||||
*/
|
||||
function fxBibProductionCreateOfficialOrder($intEveId, $strLangue = 'fr') {
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = (int)$intEveId;
|
||||
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
|
||||
if ($tabExisting) {
|
||||
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
|
||||
}
|
||||
|
||||
$tabCollected = fxBibProductionCollectSnapshot($intEveId, $strLangue);
|
||||
if (empty($tabCollected['success'])) {
|
||||
return $tabCollected;
|
||||
}
|
||||
$tabSnapshot = $tabCollected['snapshot'];
|
||||
$strSnapshot = json_encode($tabSnapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($strSnapshot === false) {
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
|
||||
}
|
||||
|
||||
$strCreatedBy = fxBibGetCurrentPromoteurDisplayName();
|
||||
$intComId = (int)($_SESSION['com_id'] ?? 0);
|
||||
$intUsaId = (int)($_SESSION['usa_id'] ?? 0);
|
||||
$strEventPart = fxBibProductionSafeFilePart($tabSnapshot['event']['name']);
|
||||
|
||||
$objDatabase->fxQuery('START TRANSACTION');
|
||||
$objDatabase->fxGetVar(
|
||||
"SELECT eve_id FROM inscriptions_evenements WHERE eve_id = $intEveId FOR UPDATE"
|
||||
);
|
||||
|
||||
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
|
||||
if ($tabExisting) {
|
||||
$objDatabase->fxQuery('COMMIT');
|
||||
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
|
||||
}
|
||||
|
||||
$intVersion = (int)$objDatabase->fxGetVar(
|
||||
"SELECT COALESCE(MAX(bpo_version), 0) + 1
|
||||
FROM inscriptions_bib_production_orders
|
||||
WHERE eve_id = $intEveId"
|
||||
);
|
||||
if ($intVersion <= 0) {
|
||||
$intVersion = 1;
|
||||
}
|
||||
|
||||
$strFileName = 'commande-dossards-' . $strEventPart . '-v' . $intVersion . '.xlsx';
|
||||
$strInsert = "
|
||||
INSERT INTO inscriptions_bib_production_orders (
|
||||
eve_id,
|
||||
bpo_com_id,
|
||||
bpo_usa_id,
|
||||
bpo_status,
|
||||
bpo_active_key,
|
||||
bpo_version,
|
||||
bpo_file_name,
|
||||
bpo_file_path,
|
||||
bpo_sha256,
|
||||
bpo_snapshot,
|
||||
bpo_created_at,
|
||||
bpo_created_by
|
||||
) VALUES (
|
||||
$intEveId,
|
||||
$intComId,
|
||||
$intUsaId,
|
||||
'generating',
|
||||
1,
|
||||
$intVersion,
|
||||
'" . $objDatabase->fxEscape($strFileName) . "',
|
||||
'',
|
||||
'',
|
||||
'" . $objDatabase->fxEscape($strSnapshot) . "',
|
||||
NOW(),
|
||||
'" . $objDatabase->fxEscape($strCreatedBy) . "'
|
||||
)
|
||||
";
|
||||
$qryInsert = $objDatabase->fxQuery($strInsert);
|
||||
$intOrderId = $qryInsert ? (int)$objDatabase->fxGetLastId() : 0;
|
||||
if ($intOrderId <= 0) {
|
||||
$objDatabase->fxQuery('ROLLBACK');
|
||||
$tabExisting = fxBibProductionGetActiveOrder($intEveId);
|
||||
if ($tabExisting) {
|
||||
return ['success' => true, 'order' => $tabExisting, 'existing' => true];
|
||||
}
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
|
||||
}
|
||||
|
||||
foreach ($tabSnapshot['races'] as $tabRace) {
|
||||
foreach ($tabRace['ranges'] as $tabRange) {
|
||||
$qryRange = $objDatabase->fxQuery(
|
||||
"INSERT INTO inscriptions_bib_production_ranges (
|
||||
bpo_id,
|
||||
eve_id,
|
||||
epr_id,
|
||||
epr_bib_id,
|
||||
bpr_epr_label,
|
||||
bpr_start,
|
||||
bpr_finish,
|
||||
bpr_quantity
|
||||
) VALUES (
|
||||
$intOrderId,
|
||||
$intEveId,
|
||||
" . (int)$tabRace['epr_id'] . ",
|
||||
" . (int)$tabRange['epr_bib_id'] . ",
|
||||
'" . $objDatabase->fxEscape($tabRace['label']) . "',
|
||||
" . (int)$tabRange['start'] . ",
|
||||
" . (int)$tabRange['finish'] . ",
|
||||
" . (int)$tabRange['quantity'] . "
|
||||
)"
|
||||
);
|
||||
if ($qryRange === false) {
|
||||
$objDatabase->fxQuery('ROLLBACK');
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
|
||||
}
|
||||
}
|
||||
}
|
||||
$objDatabase->fxQuery('COMMIT');
|
||||
|
||||
$strRelativeDir = 'eve-' . $intEveId . DIRECTORY_SEPARATOR . 'order-' . $intOrderId;
|
||||
$strAbsoluteDir = fxBibProductionStorageRoot() . DIRECTORY_SEPARATOR . $strRelativeDir;
|
||||
$strAbsoluteFile = $strAbsoluteDir . DIRECTORY_SEPARATOR . $strFileName;
|
||||
|
||||
try {
|
||||
if (!fxBibProductionEnsureDirectory($strAbsoluteDir)) {
|
||||
throw new Exception('MSIN-4469 storage unavailable');
|
||||
}
|
||||
|
||||
$strStorageRoot = fxBibProductionStorageRoot();
|
||||
if (fxBibProductionEnsureDirectory($strStorageRoot)) {
|
||||
$strDenyFile = $strStorageRoot . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
if (!is_file($strDenyFile)) {
|
||||
@file_put_contents($strDenyFile, "Deny from all\n");
|
||||
}
|
||||
}
|
||||
|
||||
fxBibProductionBuildWorkbook(
|
||||
$tabSnapshot,
|
||||
$strAbsoluteFile,
|
||||
[
|
||||
'bib' => fxBibTexte('bib_v5_production_col_bib', 0),
|
||||
'race' => fxBibTexte('bib_v5_production_col_race', 0),
|
||||
'total' => fxBibTexte('bib_v5_production_total', 0),
|
||||
'title' => fxBibTexte('bib_v5_production_excel_title', 0),
|
||||
'printer_lines' => fxBibTexte('bib_v5_production_col_printer_lines', 0),
|
||||
]
|
||||
);
|
||||
|
||||
if (!is_file($strAbsoluteFile)) {
|
||||
throw new Exception('MSIN-4469 workbook not written');
|
||||
}
|
||||
|
||||
$strRelativeFile = str_replace('\\', '/', $strRelativeDir . DIRECTORY_SEPARATOR . $strFileName);
|
||||
$strSha256 = hash_file('sha256', $strAbsoluteFile);
|
||||
$objDatabase->fxQuery(
|
||||
"UPDATE inscriptions_bib_production_orders
|
||||
SET bpo_status = 'ready',
|
||||
bpo_file_path = '" . $objDatabase->fxEscape($strRelativeFile) . "',
|
||||
bpo_sha256 = '" . $objDatabase->fxEscape($strSha256) . "'
|
||||
WHERE bpo_id = $intOrderId
|
||||
AND bpo_active_key = 1"
|
||||
);
|
||||
} catch (Exception $objException) {
|
||||
if (is_file($strAbsoluteFile)) {
|
||||
@unlink($strAbsoluteFile);
|
||||
}
|
||||
$objDatabase->fxQuery(
|
||||
"UPDATE inscriptions_bib_production_orders
|
||||
SET bpo_status = 'failed',
|
||||
bpo_active_key = NULL,
|
||||
bpo_error = '" . $objDatabase->fxEscape($objException->getMessage()) . "'
|
||||
WHERE bpo_id = $intOrderId"
|
||||
);
|
||||
return ['success' => false, 'message' => fxBibMsg('bib_v5_production_generation_error')];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'order' => fxBibProductionGetOrder($intOrderId, $intEveId),
|
||||
'existing' => false,
|
||||
];
|
||||
}
|
||||
|
||||
function fxBibProductionOrderAbsolutePath(array $tabOrder) {
|
||||
$strRelative = str_replace('\\', '/', (string)($tabOrder['bpo_file_path'] ?? ''));
|
||||
if ($strRelative === '' || strpos($strRelative, '..') !== false || strpos($strRelative, "\0") !== false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return fxBibProductionStorageRoot() . DIRECTORY_SEPARATOR
|
||||
. str_replace('/', DIRECTORY_SEPARATOR, $strRelative);
|
||||
}
|
||||
|
||||
function fxBibProductionStreamOrder(array $tabOrder) {
|
||||
if (($tabOrder['bpo_status'] ?? '') !== 'ready') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strFile = fxBibProductionOrderAbsolutePath($tabOrder);
|
||||
if ($strFile === '' || !is_file($strFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
$strDownloadName = basename((string)$tabOrder['bpo_file_name']);
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment; filename="' . str_replace('"', '', $strDownloadName) . '"');
|
||||
header('Content-Length: ' . filesize($strFile));
|
||||
header('Cache-Control: private, no-store, max-age=0');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
readfile($strFile);
|
||||
return true;
|
||||
}
|
||||
@ -1162,9 +1162,33 @@ function fxCourrielPerduCompte($strLangue)
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL post-login MSIN-4339 — ignore login/mot de passe pour éviter les rebonds en boucle.
|
||||
*/
|
||||
function fxCompteRedirectAfterLoginConsume()
|
||||
{
|
||||
if (empty($_SESSION['redirect_after_login'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$strUrl = $_SESSION['redirect_after_login'];
|
||||
unset($_SESSION['redirect_after_login']);
|
||||
|
||||
$strPath = parse_url($strUrl, PHP_URL_PATH) ?: '';
|
||||
if ($strPath === '/compte' || $strPath === '/account') {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('#/(compte|account)/(login|motdepasse|nouveau|reset)(/|$)#i', $strPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $strUrl;
|
||||
}
|
||||
|
||||
function fxLoginCompte($infoLogin, $strLangue)
|
||||
{
|
||||
global $objDatabase, $vDomaine;
|
||||
|
||||
$arrRetour = array();
|
||||
$arrRetour['state'] = 'error';
|
||||
|
||||
@ -1198,7 +1222,8 @@ function fxLoginCompte($infoLogin, $strLangue)
|
||||
fxcreer_log("ERREUR SQL UPDATE - fxLoginCompte()" . $sqlUpdate);
|
||||
}
|
||||
|
||||
//$_SESSION['logged'] = 1;
|
||||
// Login public : com_id + com_info seulement. Jamais usa_id ni canal superadm.
|
||||
// com_info contient toute la ligne SQL (dont com_superadm) — ne pas s'en servir côté site public.
|
||||
$_SESSION['com_id'] = $infoCompte['com_id'];
|
||||
$_SESSION['vadmin_texte'] = false;
|
||||
$_SESSION['com_info'] = $infoCompte;
|
||||
@ -1236,18 +1261,13 @@ function fxLoginCompte($infoLogin, $strLangue)
|
||||
|
||||
$arrRetour['state'] = 'true';
|
||||
|
||||
|
||||
//MSIN-4339
|
||||
if (!empty($_SESSION['redirect_after_login'])) {
|
||||
$url = $_SESSION['redirect_after_login'];
|
||||
|
||||
|
||||
unset($_SESSION['redirect_after_login']);
|
||||
header("Location: " . $url);
|
||||
// MSIN-4339 — page protégée demandée avant login (pas login/mot de passe).
|
||||
$strAfterLogin = fxCompteRedirectAfterLoginConsume();
|
||||
if ($strAfterLogin !== null) {
|
||||
header('Location: ' . $strAfterLogin);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
if ($strLangue == 'fr')
|
||||
$_SESSION['msg'] = 'e112';
|
||||
@ -1273,8 +1293,9 @@ function fxLoginCompte($infoLogin, $strLangue)
|
||||
else
|
||||
$strPage = 'account';
|
||||
|
||||
//header("Location: " . $vDomaine . '/' . $strPage);
|
||||
header("Location: " . fxGetReferer());
|
||||
// Un seul atterrissage : /compte (hub v2 ou accueil selon compte.php).
|
||||
$strDest = $vDomaine . '/' . $strPage;
|
||||
header('Location: ' . $strDest);
|
||||
}
|
||||
|
||||
exit;
|
||||
@ -1284,32 +1305,68 @@ function fxLogout($strLangue = 'fr')
|
||||
{
|
||||
global $vDomaine;
|
||||
|
||||
$tabSuperAdmin = array();
|
||||
if (!empty($_SESSION['usa_id'])) {
|
||||
$tabSuperAdmin = array(
|
||||
'usa_id' => $_SESSION['usa_id'],
|
||||
'usa_info' => $_SESSION['usa_info'] ?? null,
|
||||
'logged' => time(),
|
||||
);
|
||||
}
|
||||
|
||||
unset($_SESSION['com_id']);
|
||||
unset($_SESSION['com_info']);
|
||||
unset($_SESSION['com_prenom']);
|
||||
//unset($_SESSION['logged']);
|
||||
unset($_SESSION['vadmin_texte']);
|
||||
unset($_SESSION['url_retour']);
|
||||
unset($_SESSION['msg_erreur']);
|
||||
unset($_SESSION['promoteur_eve_id']);
|
||||
unset($_SESSION['epr_id_bib']);
|
||||
unset($_SESSION['com_affilie']);
|
||||
//
|
||||
$_SESSION = []; // Vide toutes les variables de session
|
||||
|
||||
// Optionnel : détruit la session complètement (ID, fichier, etc.)
|
||||
session_destroy();
|
||||
$strNoPanier = $_SESSION['no_panier'] ?? null;
|
||||
|
||||
$_SESSION = array();
|
||||
$_SESSION['date_maj'] = time();
|
||||
|
||||
// session_unset();
|
||||
// session_destroy();
|
||||
foreach ($tabSuperAdmin as $strKey => $mixValue) {
|
||||
if ($mixValue !== null) {
|
||||
$_SESSION[$strKey] = $mixValue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strLangue == 'fr')
|
||||
if ($strLangue == 'fr') {
|
||||
$strPage = 'compte';
|
||||
else
|
||||
} else {
|
||||
$strPage = 'account';
|
||||
}
|
||||
|
||||
fxViderPanier($_SESSION['no_panier'], 1);
|
||||
if ($strNoPanier !== null && $strNoPanier !== '') {
|
||||
fxViderPanier($strNoPanier, 1);
|
||||
}
|
||||
|
||||
if (!empty($_GET['redirect']) && $_GET['redirect'] === 'mobile') {
|
||||
$strTarget = $vDomaine . ($strLangue === 'en' ? '/mobile/en' : '/mobile');
|
||||
header('Location: ' . $strTarget);
|
||||
exit;
|
||||
}
|
||||
|
||||
// MSIN-4401 — retour sécurisé après déconnexion (reset OU lien invite /mobile).
|
||||
if (!empty($_GET['return'])) {
|
||||
$strReturn = (string)$_GET['return'];
|
||||
$blnReturnOk = ($strReturn !== '' && $strReturn[0] === '/'
|
||||
&& strpos($strReturn, '//') === false
|
||||
&& strpos($strReturn, "\n") === false
|
||||
&& strpos($strReturn, "\r") === false
|
||||
&& (
|
||||
preg_match('#^/(compte|account)/reset(\?|$)#i', $strReturn)
|
||||
|| preg_match('#^/mobile(/en)?(\?|$)#i', $strReturn)
|
||||
));
|
||||
if ($blnReturnOk) {
|
||||
header('Location: ' . $vDomaine . $strReturn);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ' . $vDomaine . '/' . $strPage);
|
||||
exit;
|
||||
@ -1324,6 +1381,59 @@ function fxShowFormReset($arrData, $strLangue)
|
||||
$infoCompte = $objDatabase->fxGetRow($sqlCourriel);
|
||||
|
||||
if ($infoCompte != null) {
|
||||
// MSIN-4401 — déjà connecté sous un autre compte : ne pas afficher le formulaire.
|
||||
$intSessionComId = intval($_SESSION['com_id'] ?? 0);
|
||||
$intTargetComId = intval($infoCompte['com_id']);
|
||||
if ($intSessionComId > 0 && $intSessionComId !== $intTargetComId) {
|
||||
$strSessionLabel = trim(
|
||||
(string)($_SESSION['com_info']['com_prenom'] ?? $_SESSION['com_prenom'] ?? '')
|
||||
. ' '
|
||||
. (string)($_SESSION['com_info']['com_nom'] ?? '')
|
||||
);
|
||||
if ($strSessionLabel === '') {
|
||||
$strSessionLabel = (string)($_SESSION['com_info']['com_courriel'] ?? ('#' . $intSessionComId));
|
||||
}
|
||||
$strTargetLabel = trim(($infoCompte['com_prenom'] ?? '') . ' ' . ($infoCompte['com_nom'] ?? ''));
|
||||
if ($strTargetLabel === '') {
|
||||
$strTargetLabel = (string)$infoCompte['com_courriel'];
|
||||
}
|
||||
$strReturnPath = ($strLangue === 'en' ? '/account' : '/compte') . '/reset?'
|
||||
. http_build_query(array(
|
||||
't' => $arrData['t'],
|
||||
'm' => $arrData['m'],
|
||||
'id' => $arrData['id'],
|
||||
));
|
||||
$strLogoutUrl = $vDomaine . '/logout.php?lang=' . urlencode($strLangue)
|
||||
. '&return=' . rawurlencode($strReturnPath);
|
||||
$strStayUrl = $vDomaine . '/' . ($strLangue === 'en' ? 'account' : 'compte');
|
||||
?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<?php if ($strLangue === 'en') { ?>
|
||||
<p class="mb-2">You are currently signed in as
|
||||
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
|
||||
<p class="mb-3">This link is for
|
||||
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong>
|
||||
(<?php echo htmlspecialchars((string)$infoCompte['com_courriel'], ENT_QUOTES, 'UTF-8'); ?>).
|
||||
Sign out first to set that account’s password.</p>
|
||||
<?php } else { ?>
|
||||
<p class="mb-2">Vous êtes actuellement connecté en tant que
|
||||
<strong><?php echo htmlspecialchars($strSessionLabel, ENT_QUOTES, 'UTF-8'); ?></strong>.</p>
|
||||
<p class="mb-3">Ce lien concerne le compte
|
||||
<strong><?php echo htmlspecialchars($strTargetLabel, ENT_QUOTES, 'UTF-8'); ?></strong>
|
||||
(<?php echo htmlspecialchars((string)$infoCompte['com_courriel'], ENT_QUOTES, 'UTF-8'); ?>).
|
||||
Déconnectez-vous d’abord pour choisir le mot de passe de ce compte.</p>
|
||||
<?php } ?>
|
||||
<a class="btn btn-primary rounded-0 mr-2" href="<?php echo htmlspecialchars($strLogoutUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo ($strLangue === 'en') ? 'Sign out and continue' : 'Se déconnecter et continuer'; ?>
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary rounded-0" href="<?php echo htmlspecialchars($strStayUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo ($strLangue === 'en') ? 'Stay on my current account' : 'Rester sur mon compte actuel'; ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
if (true) {
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
@ -1414,13 +1524,37 @@ function fxResetPassword($arrData, $strLangue)
|
||||
{
|
||||
global $vDomaine, $objDatabase;
|
||||
|
||||
$intComId = intval($arrData['id']);
|
||||
$intSessionComId = intval($_SESSION['com_id'] ?? 0);
|
||||
// MSIN-4401 — refuse si une autre session compte est active
|
||||
if ($intSessionComId > 0 && $intSessionComId !== $intComId) {
|
||||
$_SESSION['msg'] = 'e212';
|
||||
$strPage = ($strLangue === 'en') ? 'account' : 'compte';
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
document.location.href = '<?php echo $vDomaine . '/' . $strPage . '/reset?'
|
||||
. http_build_query(array('t' => $arrData['t'], 'm' => $arrData['m'], 'id' => $arrData['id'])); ?>';
|
||||
</script>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
$strPassword = password_hash($objDatabase->fxEscape($arrData['com_password_a']), PASSWORD_DEFAULT);
|
||||
|
||||
$sqlUpdate = "UPDATE inscriptions_comptes SET com_password = '" . $strPassword . "' WHERE com_reset_token = '" . $objDatabase->fxEscape($arrData['t']) . "' AND com_courriel = '" . $objDatabase->fxEscape($arrData['m']) . "' AND com_actif = 1 AND com_id = " . intval($arrData['id']);
|
||||
$sqlUpdate = "UPDATE inscriptions_comptes SET com_password = '" . $strPassword . "',
|
||||
com_reset_token = ''
|
||||
WHERE com_reset_token = '" . $objDatabase->fxEscape($arrData['t']) . "'
|
||||
AND com_courriel = '" . $objDatabase->fxEscape($arrData['m']) . "'
|
||||
AND com_actif = 1 AND com_id = " . $intComId;
|
||||
$qryUpdate = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
if ($qryUpdate !== false) { // succès
|
||||
$_SESSION['msg'] = 'p211';
|
||||
// MSIN-4401 — invitation promoteur : accès passe de invite → actif
|
||||
if (!function_exists('fxEveAccesActivateInvitesForCompte')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_eve_acces.php';
|
||||
}
|
||||
fxEveAccesActivateInvitesForCompte($intComId);
|
||||
} else { // erreur
|
||||
$_SESSION['msg'] = 'e212';
|
||||
}
|
||||
@ -1442,8 +1576,15 @@ function fxShowMenuCompte($arrCompteClient, $strLangue)
|
||||
{ // affiche le menu du compte
|
||||
global $objDatabase, $vDomaine;
|
||||
$strPage = ($strLangue == 'fr') ? 'compte' : 'account';
|
||||
|
||||
// MSIN-4401 — retour hub V2 depuis l'espace client
|
||||
if (!function_exists('fxShowPromoteurHubEspaceClientToolbar')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_promoteur_hub.php';
|
||||
}
|
||||
$strRetour = fxShowPromoteurHubEspaceClientToolbar(intval($arrCompteClient['com_id'] ?? 0), $strLangue);
|
||||
|
||||
// modifier mon compte
|
||||
$strRetour = '
|
||||
$strRetour .= '
|
||||
<ul class="nav flex-column ms1-menu-compte">
|
||||
<li class="nav-item">
|
||||
<a class="nav-list" href="' . $vDomaine . '/' . $strPage . '/modifiercompte"><i class="fa fa-user" aria-hidden="true"></i> ' . afficheTexte('compte_modifcompte_texte', 0) . '</a>
|
||||
@ -1471,14 +1612,30 @@ function fxShowMenuCompte($arrCompteClient, $strLangue)
|
||||
';
|
||||
}
|
||||
|
||||
// menu promoteur
|
||||
// menu promoteur v2 (hub) — comptes migres uniquement
|
||||
if (!function_exists('fxPromoteurHubComUsesV2')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_promoteur_hub.php';
|
||||
}
|
||||
$strRetour .= fxShowPromoteurHubMenuLink($arrCompteClient, $strLangue);
|
||||
|
||||
if (trim($arrCompteClient['com_eve_promoteur']) != '' || trim($arrCompteClient['com_rapports']) != '') { // si le compte client a des événements dont il est le promoteur
|
||||
$arrEvePromoteur = explode(',', $arrCompteClient['com_eve_promoteur']);
|
||||
$intNbEvePromoteur = fxcount($arrEvePromoteur);
|
||||
$strNbEvenements = '<small>(' . $intNbEvePromoteur . ' ';
|
||||
$strNbEvenements .= ($intNbEvePromoteur > 1) ? afficheTexte('compte_evenement_pluriel', 0) : afficheTexte('compte_evenement_singulier', 0);
|
||||
$strNbEvenements .= ')</small>';
|
||||
// menu promoteur legacy
|
||||
if (!function_exists('fxInscrGestionUserHasLegacyPromoteurMenu')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_inscriptions_gestion.php';
|
||||
}
|
||||
|
||||
$blnLegacyPromoteurMenu = fxInscrGestionUserHasLegacyPromoteurMenu($arrCompteClient['com_id']);
|
||||
$blnHasRapports = trim($arrCompteClient['com_rapports']) != '';
|
||||
|
||||
if ($blnLegacyPromoteurMenu || $blnHasRapports) {
|
||||
if ($blnLegacyPromoteurMenu) {
|
||||
$arrEvePromoteur = explode(',', $arrCompteClient['com_eve_promoteur']);
|
||||
$intNbEvePromoteur = fxcount($arrEvePromoteur);
|
||||
$strNbEvenements = '<small>(' . $intNbEvePromoteur . ' ';
|
||||
$strNbEvenements .= ($intNbEvePromoteur > 1) ? afficheTexte('compte_evenement_pluriel', 0) : afficheTexte('compte_evenement_singulier', 0);
|
||||
$strNbEvenements .= ')</small>';
|
||||
} else {
|
||||
$strNbEvenements = '';
|
||||
}
|
||||
|
||||
$strRetour .= '
|
||||
<li class="nav-item">
|
||||
@ -1488,11 +1645,33 @@ function fxShowMenuCompte($arrCompteClient, $strLangue)
|
||||
';
|
||||
// MSIN-3880
|
||||
|
||||
$strRetour .= fxShowInfoPromoteurtitre($_SESSION['com_info'], $strLangue);
|
||||
if ($blnLegacyPromoteurMenu) {
|
||||
$strRetour .= fxShowInfoPromoteurtitre($_SESSION['com_info'], $strLangue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Inscriptions mobile v2 — lien /mobile seulement si pas de menu promoteur legacy
|
||||
if (!function_exists('fxInscrGestionGetPromoteurEveIds')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_inscriptions_gestion.php';
|
||||
}
|
||||
$arrMobileEveIds = fxInscrGestionGetPromoteurEveIds($arrCompteClient['com_id']);
|
||||
$blnShowMobileDirect = (count($arrMobileEveIds) > 0 && !fxInscrGestionUserHasPromoteurMenu($arrCompteClient['com_id']));
|
||||
if ($blnShowMobileDirect) {
|
||||
$strMobileUrl = $vDomaine . '/mobile';
|
||||
if (count($arrMobileEveIds) === 1) {
|
||||
$strMobileUrl .= '?promoteur_eve_id=' . urlencode(base64_encode($arrMobileEveIds[0]));
|
||||
}
|
||||
$strMobileLabel = fxInscrGestionPromoteurMenuLabel();
|
||||
$strRetour .= '
|
||||
<li class="nav-item">
|
||||
<a class="nav-list" href="' . $strMobileUrl . '"><i class="fa fa-users" aria-hidden="true"></i> '
|
||||
. htmlspecialchars($strMobileLabel, ENT_QUOTES, 'UTF-8') . '</a>
|
||||
</li>
|
||||
';
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
|
||||
597
php/inc_fx_doc.php
Normal file
@ -0,0 +1,597 @@
|
||||
<?php
|
||||
/**
|
||||
* Module documentation MS1 — overlay paginé HTML (distinct de info / info_aide).
|
||||
*
|
||||
* Clés d'appel (doc_anchor_clef) → module (doc_mod_clef) → pages (doc_page).
|
||||
* Usage :
|
||||
* fxDocRenderModule('bib_v4_assign_doc'); — overlay complet
|
||||
* fxDocRenderTrigger('bib_v4_assign_doc'); — bouton ? seul
|
||||
* afficheDocPage('bib_doc_overview'); — HTML d'une page (comme afficheTexte)
|
||||
* afficheDocTitre('bib_doc_overview'); — titre d'une page
|
||||
*/
|
||||
|
||||
/** Échappement attributs HTML doc module. */
|
||||
function fxDocEsc($str) {
|
||||
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/** Page PHP courante pour doc_prg (miroir fxBibInfoPage / info_prg). */
|
||||
function fxDocPrg($mem_global = 0) {
|
||||
global $vPage;
|
||||
|
||||
if ($mem_global == 1) {
|
||||
return 'global';
|
||||
}
|
||||
|
||||
return !empty($vPage) ? $vPage : 'compte.php';
|
||||
}
|
||||
|
||||
/** Langue courante doc. */
|
||||
function fxDocLangue() {
|
||||
global $strLangue;
|
||||
|
||||
return !empty($strLangue) ? $strLangue : 'fr';
|
||||
}
|
||||
|
||||
/** Tables doc_* présentes en BD (évite erreur si SQL module pas encore exécuté). */
|
||||
function fxDocSchemaReady() {
|
||||
global $objDatabase;
|
||||
|
||||
static $blnReady = null;
|
||||
|
||||
if ($blnReady !== null) {
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
$blnReady = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$qry = $objDatabase->fxQuery("SHOW TABLES LIKE 'doc_page'");
|
||||
$blnReady = ($qry && mysqli_num_rows($qry) > 0);
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge en mémoire toutes les pages doc pour une page PHP (comme obtenirTextepage).
|
||||
* Remplit $vdoc_page[doc_page_clef] = row.
|
||||
*/
|
||||
function obtenirDocPage($page = null, $langue = null) {
|
||||
global $objDatabase, $vdoc_page;
|
||||
|
||||
if ($page === null || $page === '') {
|
||||
$page = fxDocPrg(0);
|
||||
}
|
||||
if ($langue === null || $langue === '') {
|
||||
$langue = fxDocLangue();
|
||||
}
|
||||
|
||||
$vdoc_page = array();
|
||||
|
||||
if (!isset($objDatabase) || !fxDocSchemaReady()) {
|
||||
return $vdoc_page;
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM doc_page
|
||||
WHERE doc_actif = 1
|
||||
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
|
||||
AND doc_prg IN ('" . $objDatabase->fxEscape($page) . "', 'global')
|
||||
ORDER BY doc_tri, pk_doc_page";
|
||||
$rows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return $vdoc_page;
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= count($rows); $i++) {
|
||||
if (!empty($rows[$i]['doc_page_clef'])) {
|
||||
$vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $vdoc_page;
|
||||
}
|
||||
|
||||
/** Ligne doc_anchor par clé d'appel. */
|
||||
function fxDocGetAnchorRow($clef, $mem_global = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
static $anchorCache = array();
|
||||
|
||||
$strPrg = fxDocPrg($mem_global);
|
||||
$cacheKey = $clef . '|' . $strPrg;
|
||||
|
||||
if (array_key_exists($cacheKey, $anchorCache)) {
|
||||
return $anchorCache[$cacheKey];
|
||||
}
|
||||
|
||||
if (!isset($objDatabase) || $clef === '' || !fxDocSchemaReady()) {
|
||||
$anchorCache[$cacheKey] = array();
|
||||
return $anchorCache[$cacheKey];
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM doc_anchor
|
||||
WHERE doc_actif = 1
|
||||
AND doc_anchor_clef = '" . $objDatabase->fxEscape($clef) . "'
|
||||
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
|
||||
ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global')
|
||||
LIMIT 1";
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
$anchorCache[$cacheKey] = is_array($row) ? $row : array();
|
||||
|
||||
return $anchorCache[$cacheKey];
|
||||
}
|
||||
|
||||
/** Résout doc_mod_clef depuis une clé d'ancre ou un code module direct. */
|
||||
function fxDocResolveModClef($clef, $mem_global = 0) {
|
||||
$anchor = fxDocGetAnchorRow($clef, $mem_global);
|
||||
|
||||
if (!empty($anchor['doc_mod_clef'])) {
|
||||
return $anchor['doc_mod_clef'];
|
||||
}
|
||||
|
||||
return $clef;
|
||||
}
|
||||
|
||||
/** Pages actives d'un module, triées (pour carrousel overlay). */
|
||||
function fxDocGetModulePages($modClef, $langue = null, $mem_global = 0) {
|
||||
global $objDatabase, $vdoc_page;
|
||||
|
||||
if ($langue === null || $langue === '') {
|
||||
$langue = fxDocLangue();
|
||||
}
|
||||
|
||||
$strPrg = fxDocPrg($mem_global);
|
||||
$modClef = fxDocResolveModClef($modClef, $mem_global);
|
||||
|
||||
if (!isset($objDatabase) || $modClef === '' || !fxDocSchemaReady()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM doc_page
|
||||
WHERE doc_actif = 1
|
||||
AND doc_mod_clef = '" . $objDatabase->fxEscape($modClef) . "'
|
||||
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
|
||||
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
|
||||
ORDER BY doc_tri, pk_doc_page";
|
||||
$rows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
$pages = array();
|
||||
if (!is_array($rows)) {
|
||||
return $pages;
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= count($rows); $i++) {
|
||||
$pages[] = $rows[$i];
|
||||
if (!empty($rows[$i]['doc_page_clef'])) {
|
||||
if (!isset($vdoc_page) || !is_array($vdoc_page)) {
|
||||
$vdoc_page = array();
|
||||
}
|
||||
$vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/** Une page par doc_page_clef (mémoire ou BD). */
|
||||
function fxDocGetPageRow($clef, $mem_global = 0) {
|
||||
global $vdoc_page, $objDatabase;
|
||||
|
||||
if (!empty($vdoc_page[$clef])) {
|
||||
return $vdoc_page[$clef];
|
||||
}
|
||||
|
||||
if (!isset($objDatabase) || $clef === '' || !fxDocSchemaReady()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$strPrg = fxDocPrg($mem_global);
|
||||
$langue = fxDocLangue();
|
||||
|
||||
static $pageCache = array();
|
||||
$cacheKey = $clef . '|' . $langue . '|' . $strPrg;
|
||||
|
||||
if (array_key_exists($cacheKey, $pageCache)) {
|
||||
return $pageCache[$cacheKey];
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM doc_page
|
||||
WHERE doc_actif = 1
|
||||
AND doc_page_clef = '" . $objDatabase->fxEscape($clef) . "'
|
||||
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
|
||||
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
|
||||
ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global')
|
||||
LIMIT 1";
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
$pageCache[$cacheKey] = is_array($row) ? $row : array();
|
||||
|
||||
return $pageCache[$cacheKey];
|
||||
}
|
||||
|
||||
/** Crée une page doc vide si absente (miroir afficheTexte). */
|
||||
function fxDocEnsurePage($clef, $mem_global = 0, $modClef = '') {
|
||||
global $objDatabase;
|
||||
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
if (!empty($row)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
$strPrg = fxDocPrg($mem_global);
|
||||
$langue = fxDocLangue();
|
||||
|
||||
if ($modClef === '') {
|
||||
$modClef = $clef;
|
||||
}
|
||||
|
||||
$tabData = array(
|
||||
'doc_mod_clef' => $modClef,
|
||||
'doc_page_clef' => $clef,
|
||||
'doc_langue' => $langue,
|
||||
'doc_titre' => $clef,
|
||||
'doc_sous_titre' => '',
|
||||
'doc_html' => '',
|
||||
'doc_prg' => $strPrg,
|
||||
'doc_tri' => 0,
|
||||
'doc_actif' => 1,
|
||||
);
|
||||
fxSetDocPage('add', $tabData);
|
||||
|
||||
if ($langue === 'fr') {
|
||||
$tabDataEn = $tabData;
|
||||
$tabDataEn['doc_langue'] = 'en';
|
||||
fxSetDocPage('add', $tabDataEn);
|
||||
}
|
||||
|
||||
return fxDocGetPageRow($clef, $mem_global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Titre doc_titre — équivalent afficheTexte pour doc_page.
|
||||
*/
|
||||
function afficheDocTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
|
||||
if (empty($row)) {
|
||||
fxDocEnsurePage($clef, $mem_global, $modClef);
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
}
|
||||
|
||||
$str = trim($row['doc_titre'] ?? '');
|
||||
if ($str === '' || $str === $clef) {
|
||||
$str = '*' . $clef . '*';
|
||||
}
|
||||
|
||||
if ($mem_echo == 1) {
|
||||
echo fxDocEsc($str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corps HTML doc_html — équivalent afficheAide (contenu long, HTML admin).
|
||||
*/
|
||||
function afficheDocHtml($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
|
||||
if (empty($row)) {
|
||||
fxDocEnsurePage($clef, $mem_global, $modClef);
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
}
|
||||
|
||||
$str = $row['doc_html'] ?? '';
|
||||
|
||||
if ($mem_echo == 1) {
|
||||
echo $str;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/** Sous-titre doc_sous_titre. */
|
||||
function afficheDocSousTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
|
||||
if (empty($row)) {
|
||||
fxDocEnsurePage($clef, $mem_global, $modClef);
|
||||
$row = fxDocGetPageRow($clef, $mem_global);
|
||||
}
|
||||
|
||||
$str = trim($row['doc_sous_titre'] ?? '');
|
||||
|
||||
if ($mem_echo == 1) {
|
||||
echo fxDocEsc($str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/** URL pop-up doc_edition.php — module documentation paginé. */
|
||||
function fxAdminDocUrl($anchorClef, $mem_global = 0) {
|
||||
global $vDomaine, $vPage;
|
||||
|
||||
$strPrg = fxDocPrg($mem_global);
|
||||
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
|
||||
|
||||
return $vDomaine . '/php/doc_edition.php?' . http_build_query(array(
|
||||
'anchor' => $anchorClef,
|
||||
'mod' => $modClef,
|
||||
'prg' => $strPrg,
|
||||
'bd' => 'client',
|
||||
'page' => !empty($vPage) ? $vPage : 'compte.php',
|
||||
));
|
||||
}
|
||||
|
||||
/** Lien crayon doc_edition.php — superadm dev/préprod + switch Textes ON. */
|
||||
function fxAdminDocButton($anchorClef, $mem_global = 0) {
|
||||
if (!fxAdminTextEditModeActive() || trim($anchorClef) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<a href="' . fxDocEsc(fxAdminDocUrl($anchorClef, $mem_global)) . '"'
|
||||
. ' class="' . fxAdminTradLinkClass() . ' ms1-doc-edit-link"'
|
||||
. ' title="Modifier le guide documentation"'
|
||||
. ' aria-label="Modifier le guide documentation">'
|
||||
. '<i class="fa fa-book" aria-hidden="true"></i>'
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
/** Bouton ? pour ouvrir le module doc (clé doc_anchor_clef) — même rond que btn-aide-bib. */
|
||||
function fxDocRenderTrigger($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') {
|
||||
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
|
||||
|
||||
return '<button type="button"'
|
||||
. ' class="btn-aide-bib ms1-doc-trigger btn-doc-trigger"'
|
||||
. ' data-doc-anchor="' . fxDocEsc($anchorClef) . '"'
|
||||
. ' data-doc-mod="' . fxDocEsc($modClef) . '"'
|
||||
. ' aria-label="' . fxDocEsc($strAriaLabel) . '"'
|
||||
. ' aria-haspopup="dialog">?</button>';
|
||||
}
|
||||
|
||||
/** Panneau overlay paginé pour un module (clé doc_anchor_clef ou doc_mod_clef). */
|
||||
function fxDocRenderModule($anchorClef, $mem_global = 0) {
|
||||
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
|
||||
$pages = fxDocGetModulePages($modClef, null, $mem_global);
|
||||
|
||||
if (empty($pages)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$overlayId = 'ms1-doc-overlay-' . preg_replace('/[^a-z0-9_-]/i', '-', $anchorClef);
|
||||
|
||||
$html = '<div id="' . fxDocEsc($overlayId) . '"'
|
||||
. ' class="ms1-doc-overlay"'
|
||||
. ' data-doc-anchor="' . fxDocEsc($anchorClef) . '"'
|
||||
. ' data-doc-mod="' . fxDocEsc($modClef) . '"'
|
||||
. ' hidden'
|
||||
. ' role="dialog"'
|
||||
. ' aria-modal="true"'
|
||||
. ' aria-labelledby="' . fxDocEsc($overlayId) . '-title">';
|
||||
|
||||
$html .= '<div class="ms1-doc-backdrop" data-ms1-doc-close="1"></div>';
|
||||
|
||||
$html .= '<div class="ms1-doc-panel">';
|
||||
$html .= '<header class="ms1-doc-header">';
|
||||
$html .= '<div class="ms1-doc-header-text">';
|
||||
$html .= '<h2 class="ms1-doc-title" id="' . fxDocEsc($overlayId) . '-title"></h2>';
|
||||
$html .= '<p class="ms1-doc-subtitle"></p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="ms1-doc-header-actions">';
|
||||
$html .= fxAdminDocButton($anchorClef, $mem_global);
|
||||
$html .= '<button type="button" class="ms1-doc-close" data-ms1-doc-close="1" aria-label="Fermer">×</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '</header>';
|
||||
|
||||
$html .= '<div class="ms1-doc-body">';
|
||||
foreach ($pages as $idx => $page) {
|
||||
$html .= '<div class="ms1-doc-page"'
|
||||
. ($idx > 0 ? ' hidden' : '')
|
||||
. ' data-doc-page-index="' . (int)$idx . '"'
|
||||
. ' data-doc-page-clef="' . fxDocEsc($page['doc_page_clef']) . '">';
|
||||
$html .= '<div class="ms1-doc-content ms1-doc-format">' . ($page['doc_html'] ?? '') . '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<footer class="ms1-doc-footer">';
|
||||
$html .= '<div class="ms1-doc-nav-dots">';
|
||||
foreach ($pages as $idx => $page) {
|
||||
$active = $idx === 0 ? ' is-active' : '';
|
||||
$label = trim($page['doc_titre'] ?? '') !== '' ? $page['doc_titre'] : (string)($idx + 1);
|
||||
$html .= '<button type="button"'
|
||||
. ' class="ms1-doc-dot' . $active . '"'
|
||||
. ' data-doc-page-index="' . (int)$idx . '"'
|
||||
. ' title="' . fxDocEsc($label) . '"'
|
||||
. ' aria-label="' . fxDocEsc($label) . '">'
|
||||
. (int)($idx + 1)
|
||||
. '</button>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="ms1-doc-nav-arrows">';
|
||||
$html .= '<button type="button" class="ms1-doc-prev" data-ms1-doc-prev="1">Précédent</button>';
|
||||
$html .= '<span class="ms1-doc-counter"><span class="ms1-doc-counter-current">1</span> / <span class="ms1-doc-counter-total">' . count($pages) . '</span></span>';
|
||||
$html .= '<button type="button" class="ms1-doc-next" data-ms1-doc-next="1">Suivant</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '</footer>';
|
||||
|
||||
$html .= '</div></div>';
|
||||
|
||||
// Métadonnées titres pour JS (init header sans parser le HTML)
|
||||
$html .= '<script type="application/json" class="ms1-doc-meta" data-doc-anchor="' . fxDocEsc($anchorClef) . '">';
|
||||
$meta = array();
|
||||
foreach ($pages as $page) {
|
||||
$meta[] = array(
|
||||
'clef' => $page['doc_page_clef'],
|
||||
'titre' => $page['doc_titre'] ?? '',
|
||||
'sous_titre' => $page['doc_sous_titre'] ?? '',
|
||||
);
|
||||
}
|
||||
$html .= json_encode($meta, JSON_UNESCAPED_UNICODE);
|
||||
$html .= '</script>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/** Trigger + overlay (appel typique à côté d'un titre de bloc). */
|
||||
function fxDocRenderBlock($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') {
|
||||
return fxDocRenderTrigger($anchorClef, $mem_global, $strAriaLabel) . fxDocRenderModule($anchorClef, $mem_global);
|
||||
}
|
||||
|
||||
/** Clés doc — panneau remboursement promoteur (fiche gestion inscriptions). */
|
||||
function fxDocRefundPromoteurAnchorClef() {
|
||||
return 'inscr_gestion_refund_doc';
|
||||
}
|
||||
|
||||
function fxDocRefundPromoteurPageClef() {
|
||||
return 'inscr_gestion_refund_promoteur_info';
|
||||
}
|
||||
|
||||
function fxDocRefundPromoteurModClef() {
|
||||
return 'inscr_gestion_refund_doc';
|
||||
}
|
||||
|
||||
/** Texte par défaut si doc_html vide (avant import SQL ou édition). */
|
||||
function fxDocRefundPromoteurFallback($strLangue) {
|
||||
if ($strLangue === 'en') {
|
||||
return array(
|
||||
'titre' => 'Refunds and MS1 fees',
|
||||
'html' => '<article class="ms1-doc-article">'
|
||||
. '<p>As a promoter, you may refund the customer <strong>up to 100%</strong> of the transaction amount.</p>'
|
||||
. '<p>However, <strong>MS1 platform fees are not refunded</strong> to the promoter. If you refund the customer, <strong>you absorb those fees</strong> — they remain your responsibility.</p>'
|
||||
. '<p>The « Max refundable » amount is the PayPal balance available for this capture, not a reimbursement of MS1 fees.</p>'
|
||||
. '</article>',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'titre' => 'Remboursement et frais MS1',
|
||||
'html' => '<article class="ms1-doc-article">'
|
||||
. '<p>En tant que promoteur, vous pouvez rembourser le client <strong>jusqu\'à 100 %</strong> du montant de la transaction.</p>'
|
||||
. '<p>En revanche, <strong>les frais MS1 ne sont pas remboursés</strong> par la plateforme au promoteur. Si vous remboursez le client, <strong>vous assumez ces frais</strong> : ils restent à votre charge.</p>'
|
||||
. '<p>Le montant « Max remboursable » correspond au solde PayPal disponible pour cette capture, et non au remboursement des frais MS1.</p>'
|
||||
. '</article>',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colonne droite du panneau remboursement (fiche gestion promoteur) — doc_html inline.
|
||||
* Éditable via doc_edition.php (mode Textes, icône livre).
|
||||
*/
|
||||
function fxDocRenderRefundPromoteurAside() {
|
||||
if (!fxDocSchemaReady()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strAnchor = fxDocRefundPromoteurAnchorClef();
|
||||
$strPageClef = fxDocRefundPromoteurPageClef();
|
||||
$strModClef = fxDocRefundPromoteurModClef();
|
||||
$strLangue = fxDocLangue();
|
||||
|
||||
fxDocEnsurePage($strPageClef, 0, $strModClef);
|
||||
|
||||
$strTitre = trim((string)afficheDocTitre($strPageClef, 0, 0, $strModClef));
|
||||
$strHtml = trim((string)afficheDocHtml($strPageClef, 0, 0, $strModClef));
|
||||
|
||||
$tabFallback = fxDocRefundPromoteurFallback($strLangue);
|
||||
if ($strTitre === '' || $strTitre === '*' . $strPageClef . '*') {
|
||||
$strTitre = $tabFallback['titre'];
|
||||
}
|
||||
if ($strHtml === '') {
|
||||
$strHtml = $tabFallback['html'];
|
||||
}
|
||||
|
||||
$strEditBtn = function_exists('fxAdminDocButton') ? fxAdminDocButton($strAnchor, 0) : '';
|
||||
|
||||
$html = '<aside class="ms1-refund-doc-aside" aria-labelledby="ms1-refund-doc-title">';
|
||||
$html .= '<div class="ms1-refund-doc-panel">';
|
||||
$html .= '<header class="ms1-refund-doc-head">';
|
||||
$html .= '<h3 class="ms1-refund-doc-title" id="ms1-refund-doc-title">' . fxDocEsc($strTitre) . '</h3>';
|
||||
if ($strEditBtn !== '') {
|
||||
$html .= '<div class="ms1-refund-doc-head-actions">' . $strEditBtn . '</div>';
|
||||
}
|
||||
$html .= '</header>';
|
||||
$html .= '<div class="ms1-refund-doc-body ms1-doc-format ms1-doc-content">' . $strHtml . '</div>';
|
||||
$html .= '</div></aside>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
function fxSetDocMod($strAction, $tabData, $id = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
switch ($strAction) {
|
||||
case 'add':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlValide = "SELECT COUNT(doc_mod_clef) FROM doc_mod
|
||||
WHERE doc_mod_clef = '" . $objDatabase->fxEscape($tabData['doc_mod_clef']) . "'
|
||||
AND doc_mod_prg = '" . $objDatabase->fxEscape($tabData['doc_mod_prg']) . "'";
|
||||
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
|
||||
break;
|
||||
}
|
||||
$sqlInsert = "INSERT INTO doc_mod " . $tabFieldsValues['set'] . ', doc_mod_creation = NOW()';
|
||||
$objDatabase->fxQuery($sqlInsert);
|
||||
break;
|
||||
|
||||
case 'mod':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlUpdate = "UPDATE doc_mod " . $tabFieldsValues['set'] . ", doc_mod_maj = NOW() WHERE " . $tabFieldsValues['key'];
|
||||
$objDatabase->fxQuery($sqlUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function fxSetDocPage($strAction, $tabData, $id = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
switch ($strAction) {
|
||||
case 'add':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlValide = "SELECT COUNT(doc_page_clef) FROM doc_page
|
||||
WHERE doc_page_clef = '" . $objDatabase->fxEscape($tabData['doc_page_clef']) . "'
|
||||
AND doc_langue = '" . $objDatabase->fxEscape($tabData['doc_langue']) . "'
|
||||
AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'";
|
||||
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
|
||||
break;
|
||||
}
|
||||
$sqlInsert = "INSERT INTO doc_page " . $tabFieldsValues['set'] . ', doc_creation = NOW()';
|
||||
$objDatabase->fxQuery($sqlInsert);
|
||||
break;
|
||||
|
||||
case 'mod':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlUpdate = "UPDATE doc_page " . $tabFieldsValues['set'] . ", doc_maj = NOW() WHERE " . $tabFieldsValues['key'];
|
||||
$objDatabase->fxQuery($sqlUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function fxSetDocAnchor($strAction, $tabData, $id = 0) {
|
||||
global $objDatabase;
|
||||
|
||||
switch ($strAction) {
|
||||
case 'add':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlValide = "SELECT COUNT(doc_anchor_clef) FROM doc_anchor
|
||||
WHERE doc_anchor_clef = '" . $objDatabase->fxEscape($tabData['doc_anchor_clef']) . "'
|
||||
AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'";
|
||||
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
|
||||
break;
|
||||
}
|
||||
$sqlInsert = "INSERT INTO doc_anchor " . $tabFieldsValues['set'] . ', doc_creation = NOW()';
|
||||
$objDatabase->fxQuery($sqlInsert);
|
||||
break;
|
||||
|
||||
case 'mod':
|
||||
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']);
|
||||
$sqlUpdate = "UPDATE doc_anchor " . $tabFieldsValues['set'] . " WHERE " . $tabFieldsValues['key'];
|
||||
$objDatabase->fxQuery($sqlUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
662
php/inc_fx_dossard_print.php
Normal file
@ -0,0 +1,662 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4485 — Impression dossard PDF (1 participant).
|
||||
*
|
||||
* Zone utile : 8.5" × 5.5" (demi-Lettre coupée sur le 11").
|
||||
* Format feuille PDF : à trancher selon le mode d’impression (voir discussion produit).
|
||||
*
|
||||
* Impression type : échelle 100 %, sans « ajuster à la page » si le papier = taille PDF.
|
||||
*/
|
||||
|
||||
define('MSIN_DOSSARD_PRINT_PERM', 'inscriptions_gestion.dossard_print');
|
||||
|
||||
/**
|
||||
* Zone utile dossard (pouces → mm) : 8.5" × 5.5".
|
||||
*
|
||||
* @return array{0:float,1:float} [largeur, hauteur]
|
||||
*/
|
||||
function fxDossardPrintPageSizeMm()
|
||||
{
|
||||
return array(8.5 * 25.4, 5.5 * 25.4);
|
||||
}
|
||||
|
||||
/** Dossier des fonds JPG/PNG (même ratio 8.5:5.5 recommandé). */
|
||||
function fxDossardPrintTemplatesDir()
|
||||
{
|
||||
return dirname(__DIR__) . '/assets/dossard_templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fond : eve_{id}.jpg|png sinon default.jpg|png.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function fxDossardPrintResolveTemplatePath($intEveId)
|
||||
{
|
||||
$strDir = fxDossardPrintTemplatesDir();
|
||||
$intEveId = intval($intEveId);
|
||||
$arrCandidates = array();
|
||||
if ($intEveId > 0) {
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpg';
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpeg';
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.png';
|
||||
}
|
||||
$arrCandidates[] = $strDir . '/default.jpg';
|
||||
$arrCandidates[] = $strDir . '/default.jpeg';
|
||||
$arrCandidates[] = $strDir . '/default.png';
|
||||
|
||||
foreach ($arrCandidates as $strPath) {
|
||||
if (is_file($strPath)) {
|
||||
return $strPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type FPDF d'après le fichier réel.
|
||||
*
|
||||
* @return string JPG|PNG|GIF|''
|
||||
*/
|
||||
function fxDossardPrintImageType($strPath)
|
||||
{
|
||||
$arrInfo = @getimagesize($strPath);
|
||||
if (!is_array($arrInfo) || empty($arrInfo[2])) {
|
||||
return '';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_JPEG) {
|
||||
return 'JPG';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_PNG) {
|
||||
return 'PNG';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_GIF) {
|
||||
return 'GIF';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Texte FPDF (core fonts = Windows-1252). */
|
||||
function fxDossardPrintPdfText($str)
|
||||
{
|
||||
$str = (string)$str;
|
||||
if (function_exists('iconv')) {
|
||||
$strConverted = @iconv('UTF-8', 'Windows-1252//TRANSLIT', $str);
|
||||
if ($strConverted !== false) {
|
||||
return $strConverted;
|
||||
}
|
||||
}
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
return (string)mb_convert_encoding($str, 'Windows-1252', 'UTF-8');
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/** Droit impression (hors kit total). */
|
||||
function fxDossardPrintCan($intComId, $intEveId)
|
||||
{
|
||||
if (!function_exists('fxEveAccesHasPermission')) {
|
||||
require_once __DIR__ . '/inc_fx_eve_acces.php';
|
||||
}
|
||||
|
||||
return fxEveAccesHasPermission(intval($intComId), intval($intEveId), MSIN_DOSSARD_PRINT_PERM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
function fxDossardPrintLoadParticipant($intParId, $intEveId)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intParId <= 0 || $intEveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sql = 'SELECT p.par_id, p.par_prenom, p.par_nom, p.no_bib, p.eve_id, p.epr_id, p.pec_id,
|
||||
e.pec_id_original, e.pec_actif, e.is_cancelled
|
||||
FROM resultats_participants p
|
||||
INNER JOIN resultats_epreuves_commandees e
|
||||
ON e.pec_id_original = p.pec_id AND e.epr_id = p.epr_id
|
||||
WHERE p.par_id = ' . $intParId . '
|
||||
AND p.eve_id = ' . $intEveId . '
|
||||
LIMIT 1';
|
||||
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
|
||||
return ($row === null || $row === false) ? null : $row;
|
||||
}
|
||||
|
||||
function fxDossardPrintQrUrl($intEveId)
|
||||
{
|
||||
if (!function_exists('fxKcEveResultatsQrUrl')) {
|
||||
require_once __DIR__ . '/inc_fx_eve_extra.php';
|
||||
}
|
||||
|
||||
return fxKcEveResultatsQrUrl(intval($intEveId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false chemin PNG temp
|
||||
*/
|
||||
function fxDossardPrintBuildQrTempFile($intEveId)
|
||||
{
|
||||
require_once __DIR__ . '/../libs/phpqrcode-master/qrlib.php';
|
||||
|
||||
$strUrl = fxDossardPrintQrUrl($intEveId);
|
||||
if ($strUrl === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strTmp = tempnam(sys_get_temp_dir(), 'ms1dossardqr_');
|
||||
if ($strTmp === false) {
|
||||
return false;
|
||||
}
|
||||
$strPng = $strTmp . '.png';
|
||||
@unlink($strTmp);
|
||||
|
||||
QRcode::png($strUrl, $strPng, QR_ECLEVEL_M, 6, 1);
|
||||
if (!is_file($strPng)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $strPng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fichier layout calibré (GUI drag & drop → %) — vérité pour le PDF.
|
||||
*
|
||||
* @param int $intEveId 0 = default
|
||||
* @return string
|
||||
*/
|
||||
function fxDossardPrintLayoutPath($intEveId = 0)
|
||||
{
|
||||
$strDir = fxDossardPrintTemplatesDir();
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId > 0) {
|
||||
$strEve = $strDir . '/eve_' . $intEveId . '.layout.json';
|
||||
if (is_file($strEve)) {
|
||||
return $strEve;
|
||||
}
|
||||
}
|
||||
|
||||
return $strDir . '/default.layout.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Catalogue des champs exploitables dans une boîte (extensible).
|
||||
*
|
||||
* @return array<string,array{label:string,sample:string,font_max:int}>
|
||||
*/
|
||||
function fxDossardPrintFieldCatalog()
|
||||
{
|
||||
return array(
|
||||
'no_bib' => array('label' => 'N° dossard', 'sample' => '2347', 'font_max' => 220),
|
||||
'par_prenom' => array('label' => 'Prénom', 'sample' => 'Cynthia', 'font_max' => 28),
|
||||
'par_nom' => array('label' => 'Nom', 'sample' => 'Tremblay', 'font_max' => 28),
|
||||
'par_prenom_nom' => array('label' => 'Prénom + nom', 'sample' => 'Cynthia Tremblay', 'font_max' => 22),
|
||||
// Réservés pour plus tard (valeur vide tant que non chargé en SQL)
|
||||
'par_age' => array('label' => 'Âge', 'sample' => '42', 'font_max' => 24),
|
||||
'par_sexe' => array('label' => 'Sexe', 'sample' => 'F', 'font_max' => 24),
|
||||
'epr_nom' => array('label' => 'Épreuve', 'sample' => '10K', 'font_max' => 22),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout défaut : 2 boîtes (n° + prénom) — modèle extensible N boîtes.
|
||||
*
|
||||
* @return array{version:int,page:string,boxes:array}
|
||||
*/
|
||||
function fxDossardPrintDefaultLayout()
|
||||
{
|
||||
return array(
|
||||
'version' => 2,
|
||||
'page' => '8.5x5.5',
|
||||
'boxes' => array(
|
||||
array(
|
||||
'id' => 'bib',
|
||||
'field' => 'no_bib',
|
||||
'align' => 'C',
|
||||
'font_max' => 220,
|
||||
'left' => 0.20,
|
||||
'right' => 0.78,
|
||||
'top' => 0.36,
|
||||
'bottom' => 0.66,
|
||||
),
|
||||
array(
|
||||
'id' => 'prenom',
|
||||
'field' => 'par_prenom',
|
||||
'align' => 'L',
|
||||
'font_max' => 28,
|
||||
'left' => 0.32,
|
||||
'right' => 0.68,
|
||||
'top' => 0.72,
|
||||
'bottom' => 0.84,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise JSON v1 (bib/name) ou v2 (boxes) → layout v2.
|
||||
*
|
||||
* @param array|null $arrData
|
||||
* @return array
|
||||
*/
|
||||
function fxDossardPrintNormalizeLayout($arrData)
|
||||
{
|
||||
$arrDefault = fxDossardPrintDefaultLayout();
|
||||
if (!is_array($arrData)) {
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
// v1 legacy
|
||||
if (empty($arrData['boxes']) && !empty($arrData['bib']) && !empty($arrData['name'])) {
|
||||
$arrBoxes = $arrDefault['boxes'];
|
||||
foreach (array('left', 'right', 'top', 'bottom') as $strEdge) {
|
||||
if (isset($arrData['bib'][$strEdge])) {
|
||||
$arrBoxes[0][$strEdge] = floatval($arrData['bib'][$strEdge]);
|
||||
}
|
||||
if (isset($arrData['name'][$strEdge])) {
|
||||
$arrBoxes[1][$strEdge] = floatval($arrData['name'][$strEdge]);
|
||||
}
|
||||
}
|
||||
$arrDefault['boxes'] = $arrBoxes;
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
if (empty($arrData['boxes']) || !is_array($arrData['boxes'])) {
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
$arrCatalog = fxDossardPrintFieldCatalog();
|
||||
$arrBoxes = array();
|
||||
foreach ($arrData['boxes'] as $intIdx => $arrBox) {
|
||||
if (!is_array($arrBox)) {
|
||||
continue;
|
||||
}
|
||||
$strField = isset($arrBox['field']) ? (string)$arrBox['field'] : '';
|
||||
if ($strField === '' || !isset($arrCatalog[$strField])) {
|
||||
continue;
|
||||
}
|
||||
foreach (array('left', 'right', 'top', 'bottom') as $strEdge) {
|
||||
if (!isset($arrBox[$strEdge])) {
|
||||
continue 2;
|
||||
}
|
||||
$flt = floatval($arrBox[$strEdge]);
|
||||
if ($flt < 0 || $flt > 1) {
|
||||
continue 2;
|
||||
}
|
||||
$arrBox[$strEdge] = $flt;
|
||||
}
|
||||
if ($arrBox['right'] <= $arrBox['left'] || $arrBox['bottom'] <= $arrBox['top']) {
|
||||
continue;
|
||||
}
|
||||
$strAlign = strtoupper((string)($arrBox['align'] ?? 'C'));
|
||||
if ($strAlign !== 'L' && $strAlign !== 'R') {
|
||||
$strAlign = 'C';
|
||||
}
|
||||
$intFontMax = isset($arrBox['font_max'])
|
||||
? max(8, intval($arrBox['font_max']))
|
||||
: intval($arrCatalog[$strField]['font_max']);
|
||||
$strId = trim((string)($arrBox['id'] ?? ''));
|
||||
if ($strId === '') {
|
||||
$strId = 'box_' . ($intIdx + 1);
|
||||
}
|
||||
$arrBoxes[] = array(
|
||||
'id' => $strId,
|
||||
'field' => $strField,
|
||||
'align' => $strAlign,
|
||||
'font_max' => $intFontMax,
|
||||
'left' => round($arrBox['left'], 5),
|
||||
'right' => round($arrBox['right'], 5),
|
||||
'top' => round($arrBox['top'], 5),
|
||||
'bottom' => round($arrBox['bottom'], 5),
|
||||
);
|
||||
}
|
||||
|
||||
if (count($arrBoxes) < 1) {
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
return array(
|
||||
'version' => 2,
|
||||
'page' => '8.5x5.5',
|
||||
'boxes' => $arrBoxes,
|
||||
'updated_at' => isset($arrData['updated_at']) ? (string)$arrData['updated_at'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit le layout calibré (JSON GUI) ou défaut.
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @return array
|
||||
*/
|
||||
function fxDossardPrintLoadLayout($intEveId = 0)
|
||||
{
|
||||
$strPath = fxDossardPrintLayoutPath($intEveId);
|
||||
if (!is_file($strPath)) {
|
||||
return fxDossardPrintDefaultLayout();
|
||||
}
|
||||
$strJson = @file_get_contents($strPath);
|
||||
$arrData = is_string($strJson) ? json_decode($strJson, true) : null;
|
||||
|
||||
return fxDossardPrintNormalizeLayout($arrData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated alias GUI ancienne — préfère fxDossardPrintLoadLayout
|
||||
* @return array
|
||||
*/
|
||||
function fxDossardPrintLoadLayoutPct($intEveId = 0)
|
||||
{
|
||||
return fxDossardPrintLoadLayout($intEveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre le layout calibré (liste de boîtes).
|
||||
*
|
||||
* @param array $arrLayout version + boxes[]
|
||||
* @param int $intEveId 0 = default.layout.json
|
||||
* @return array{ok:bool,error?:string,path?:string}
|
||||
*/
|
||||
function fxDossardPrintSaveLayout($arrLayout, $intEveId = 0)
|
||||
{
|
||||
$intEveId = intval($intEveId);
|
||||
$strDir = fxDossardPrintTemplatesDir();
|
||||
if (!is_dir($strDir)) {
|
||||
return array('ok' => false, 'error' => 'Dossier templates absent.');
|
||||
}
|
||||
|
||||
$arrNorm = fxDossardPrintNormalizeLayout($arrLayout);
|
||||
if (empty($arrNorm['boxes'])) {
|
||||
return array('ok' => false, 'error' => 'Aucuneune boîte valide.');
|
||||
}
|
||||
|
||||
$strPath = ($intEveId > 0)
|
||||
? $strDir . '/eve_' . $intEveId . '.layout.json'
|
||||
: $strDir . '/default.layout.json';
|
||||
|
||||
$arrOut = array(
|
||||
'version' => 2,
|
||||
'page' => '8.5x5.5',
|
||||
'boxes' => $arrNorm['boxes'],
|
||||
'updated_at' => date('c'),
|
||||
);
|
||||
$strJson = json_encode($arrOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
if ($strJson === false || @file_put_contents($strPath, $strJson) === false) {
|
||||
return array('ok' => false, 'error' => 'Écriture impossible (droits fichier?).');
|
||||
}
|
||||
|
||||
return array('ok' => true, 'path' => $strPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated — accepte encore {bib,name} ou {boxes}
|
||||
* @param array $arrPct
|
||||
* @param int $intEveId
|
||||
* @return array
|
||||
*/
|
||||
function fxDossardPrintSaveLayoutPct($arrPct, $intEveId = 0)
|
||||
{
|
||||
if (isset($arrPct['boxes'])) {
|
||||
return fxDossardPrintSaveLayout($arrPct, $intEveId);
|
||||
}
|
||||
|
||||
return fxDossardPrintSaveLayout($arrPct, $intEveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valeur texte pour un champ boîte.
|
||||
*
|
||||
* @param array $arrRow
|
||||
* @param string $strField
|
||||
* @return string
|
||||
*/
|
||||
function fxDossardPrintResolveFieldValue($arrRow, $strField)
|
||||
{
|
||||
$fnUn = function ($str) {
|
||||
$str = (string)$str;
|
||||
return function_exists('fxUnescape') ? fxUnescape($str) : $str;
|
||||
};
|
||||
|
||||
switch ((string)$strField) {
|
||||
case 'no_bib':
|
||||
$str = trim((string)($arrRow['no_bib'] ?? ''));
|
||||
return ($str === '' || $str === '0') ? '-' : $str;
|
||||
case 'par_prenom':
|
||||
return trim($fnUn($arrRow['par_prenom'] ?? ''));
|
||||
case 'par_nom':
|
||||
return trim($fnUn($arrRow['par_nom'] ?? ''));
|
||||
case 'par_prenom_nom':
|
||||
return trim($fnUn($arrRow['par_prenom'] ?? '') . ' ' . $fnUn($arrRow['par_nom'] ?? ''));
|
||||
case 'par_age':
|
||||
return trim((string)($arrRow['par_age'] ?? $arrRow['age'] ?? ''));
|
||||
case 'par_sexe':
|
||||
return trim((string)($arrRow['par_sexe'] ?? ''));
|
||||
case 'epr_nom':
|
||||
return trim($fnUn($arrRow['epr_nom'] ?? $arrRow['epr_libelle'] ?? ''));
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout mm pour le PDF — lit le JSON calibré (N boîtes).
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function fxDossardPrintLayout($intEveId = 0)
|
||||
{
|
||||
$arrSize = fxDossardPrintPageSizeMm();
|
||||
$fltW = $arrSize[0];
|
||||
$fltH = $arrSize[1];
|
||||
$arrLayout = fxDossardPrintLoadLayout($intEveId);
|
||||
|
||||
$arrBoxesMm = array();
|
||||
foreach ($arrLayout['boxes'] as $arrBox) {
|
||||
$arrRect = fxDossardPrintBoxFromPct($fltW, $fltH, $arrBox);
|
||||
$arrBoxesMm[] = array_merge($arrBox, $arrRect);
|
||||
}
|
||||
|
||||
$fltQr = 28.0;
|
||||
|
||||
return array(
|
||||
'page_w' => $fltW,
|
||||
'page_h' => $fltH,
|
||||
'boxes' => $arrBoxesMm,
|
||||
'ascent_ratio' => 0.72,
|
||||
'qr_size' => $fltQr,
|
||||
'qr_x' => $fltW - $fltQr - 11.5,
|
||||
'qr_y' => $fltH - $fltQr - 14.5,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rectangle mm depuis fractions 0–1 de la page.
|
||||
*
|
||||
* @param float $fltPageW
|
||||
* @param float $fltPageH
|
||||
* @param array<string,float> $arrPct left|right|top|bottom
|
||||
* @return array{x:float,y:float,w:float,h:float}
|
||||
*/
|
||||
function fxDossardPrintBoxFromPct($fltPageW, $fltPageH, $arrPct)
|
||||
{
|
||||
$fltX = $fltPageW * floatval($arrPct['left']);
|
||||
$fltY = $fltPageH * floatval($arrPct['top']);
|
||||
$fltW = $fltPageW * (floatval($arrPct['right']) - floatval($arrPct['left']));
|
||||
$fltH = $fltPageH * (floatval($arrPct['bottom']) - floatval($arrPct['top']));
|
||||
|
||||
return array(
|
||||
'x' => $fltX,
|
||||
'y' => $fltY,
|
||||
'w' => $fltW,
|
||||
'h' => $fltH,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plus grande police Helvetica Bold qui tient dans une boîte (largeur + hauteur).
|
||||
*
|
||||
* @param FPDF $pdf
|
||||
* @param string $strText
|
||||
* @param float $fltMaxW
|
||||
* @param float $fltMaxH
|
||||
* @param int $intFontMax
|
||||
* @param float $fltAscentRatio
|
||||
* @return int
|
||||
*/
|
||||
function fxDossardPrintFitBibFont($pdf, $strText, $fltMaxW, $fltMaxH, $intFontMax, $fltAscentRatio = 0.72)
|
||||
{
|
||||
$strPdf = fxDossardPrintPdfText($strText);
|
||||
$fltAscentRatio = ($fltAscentRatio > 0) ? $fltAscentRatio : 0.72;
|
||||
$fltPtPerMm = 72 / 25.4;
|
||||
$intCapByH = ($fltMaxH > 0)
|
||||
? (int)floor($fltMaxH * $fltPtPerMm / $fltAscentRatio)
|
||||
: intval($intFontMax);
|
||||
$intHi = max(12, min(intval($intFontMax), $intCapByH));
|
||||
$intLo = 12;
|
||||
$intBest = $intLo;
|
||||
|
||||
while ($intLo <= $intHi) {
|
||||
$intMid = (int)floor(($intLo + $intHi) / 2);
|
||||
$pdf->SetFont('Helvetica', 'B', $intMid);
|
||||
if ($pdf->GetStringWidth($strPdf) <= $fltMaxW) {
|
||||
$intBest = $intMid;
|
||||
$intLo = $intMid + 1;
|
||||
} else {
|
||||
$intHi = $intMid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $intBest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Texte dans une boîte (Cell FPDF : align H + centre V de la cellule).
|
||||
*
|
||||
* @param FPDF $pdf
|
||||
* @param array $arrBox x,y,w,h
|
||||
* @param string $strText
|
||||
* @param int $intFont
|
||||
* @param string $strAlign C|L|R
|
||||
*/
|
||||
function fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, $strAlign = 'C')
|
||||
{
|
||||
$strAlign = strtoupper((string)$strAlign);
|
||||
if ($strAlign !== 'L' && $strAlign !== 'R') {
|
||||
$strAlign = 'C';
|
||||
}
|
||||
$pdf->SetFont('Helvetica', 'B', $intFont);
|
||||
$pdf->SetXY($arrBox['x'], $arrBox['y']);
|
||||
$pdf->Cell(
|
||||
$arrBox['w'],
|
||||
$arrBox['h'],
|
||||
fxDossardPrintPdfText($strText),
|
||||
0,
|
||||
0,
|
||||
$strAlign
|
||||
);
|
||||
}
|
||||
|
||||
/** @deprecated alias — préfère fxDossardPrintDrawInBox(..., 'C') */
|
||||
function fxDossardPrintDrawCenteredInBox($pdf, $arrBox, $strText, $intFont)
|
||||
{
|
||||
fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, 'C');
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère et envoie le PDF (exit).
|
||||
*
|
||||
* @param array $arrRow
|
||||
*/
|
||||
function fxDossardPrintOutputPdf($arrRow)
|
||||
{
|
||||
$intEveId = intval($arrRow['eve_id'] ?? 0);
|
||||
$strTemplate = fxDossardPrintResolveTemplatePath($intEveId);
|
||||
if ($strTemplate === null) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Template dossard introuvable (assets/dossard_templates/default.jpg).';
|
||||
exit;
|
||||
}
|
||||
|
||||
$strFpdf = dirname(__DIR__) . '/fpdf182/fpdf.php';
|
||||
if (!is_file($strFpdf)) {
|
||||
$strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
|
||||
$strFpdf = rtrim((string)$strDocRoot, '/\\') . '/fpdf182/fpdf.php';
|
||||
}
|
||||
if (!is_file($strFpdf)) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'FPDF introuvable (fpdf182/fpdf.php).';
|
||||
exit;
|
||||
}
|
||||
require_once $strFpdf;
|
||||
|
||||
$arrLayout = fxDossardPrintLayout($intEveId);
|
||||
$strQrFile = fxDossardPrintBuildQrTempFile($intEveId);
|
||||
$strTplType = fxDossardPrintImageType($strTemplate);
|
||||
|
||||
$pdf = new FPDF('L', 'mm', array($arrLayout['page_w'], $arrLayout['page_h']));
|
||||
$pdf->SetAutoPageBreak(false);
|
||||
$pdf->SetMargins(0, 0, 0);
|
||||
$pdf->AddPage();
|
||||
|
||||
$pdf->Image(
|
||||
$strTemplate,
|
||||
0,
|
||||
0,
|
||||
$arrLayout['page_w'],
|
||||
$arrLayout['page_h'],
|
||||
$strTplType
|
||||
);
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// MSIN-4485 — N boîtes configurables (champ + align + %)
|
||||
$strBibForFile = '-';
|
||||
foreach ($arrLayout['boxes'] as $arrBox) {
|
||||
$strText = fxDossardPrintResolveFieldValue($arrRow, $arrBox['field']);
|
||||
if ($arrBox['field'] === 'no_bib') {
|
||||
$strBibForFile = $strText;
|
||||
}
|
||||
if ($strText === '') {
|
||||
continue;
|
||||
}
|
||||
$intFont = fxDossardPrintFitBibFont(
|
||||
$pdf,
|
||||
$strText,
|
||||
$arrBox['w'] * 0.98,
|
||||
$arrBox['h'] * 0.92,
|
||||
intval($arrBox['font_max']),
|
||||
$arrLayout['ascent_ratio']
|
||||
);
|
||||
fxDossardPrintDrawInBox($pdf, $arrBox, $strText, $intFont, $arrBox['align']);
|
||||
}
|
||||
|
||||
if ($strQrFile !== false) {
|
||||
$pdf->Image(
|
||||
$strQrFile,
|
||||
$arrLayout['qr_x'],
|
||||
$arrLayout['qr_y'],
|
||||
$arrLayout['qr_size'],
|
||||
$arrLayout['qr_size'],
|
||||
'PNG'
|
||||
);
|
||||
@unlink($strQrFile);
|
||||
}
|
||||
|
||||
$strSafeBib = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $strBibForFile);
|
||||
$strFilename = 'dossard-' . $strSafeBib . '-par' . intval($arrRow['par_id']) . '.pdf';
|
||||
|
||||
$pdf->Output('I', $strFilename);
|
||||
exit;
|
||||
}
|
||||
2186
php/inc_fx_eve_acces.php
Normal file
1133
php/inc_fx_eve_acces_promoteur.php
Normal file
149
php/inc_fx_eve_extra.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-3487 — Métadonnées supplémentaires d'événement
|
||||
* (table inscriptions_evenements_extra).
|
||||
*
|
||||
* Fonctions indépendantes : utilisables depuis kc.php, impression dossards,
|
||||
* hub promoteur, etc. Ne pas mélanger avec key_chain (QR physiques).
|
||||
*/
|
||||
|
||||
/**
|
||||
* URL de repli si aucun lien résultats n'est configuré pour l'événement.
|
||||
* Aligné sur le défaut historique de kc.php / key_chain.
|
||||
*/
|
||||
function fxEveExtraResultatsFallbackUrl()
|
||||
{
|
||||
return 'https://ms1.ca';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit la ligne extra d'un événement (ou null).
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @return array|null
|
||||
*/
|
||||
function fxEveExtraGetRow($intEveId)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sql = 'SELECT * FROM inscriptions_evenements_extra WHERE eve_id = ' . $intEveId . ' LIMIT 1';
|
||||
$tabRow = $objDatabase->fxGetRow($sql);
|
||||
|
||||
return ($tabRow === null || $tabRow === false) ? null : $tabRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le lien résultats brut, ou null s'il est absent / vide.
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @return string|null
|
||||
*/
|
||||
function fxEveExtraGetResultatsUrl($intEveId)
|
||||
{
|
||||
$tabRow = fxEveExtraGetRow($intEveId);
|
||||
if ($tabRow === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$strUrl = trim((string) ($tabRow['eve_resultats_url'] ?? ''));
|
||||
|
||||
return ($strUrl !== '') ? $strUrl : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lien résultats à utiliser pour une redirection :
|
||||
* URL configurée, sinon fallback (ms1.ca).
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @return string
|
||||
*/
|
||||
function fxEveExtraResolveResultatsUrl($intEveId)
|
||||
{
|
||||
$strUrl = fxEveExtraGetResultatsUrl($intEveId);
|
||||
|
||||
return ($strUrl !== null) ? $strUrl : fxEveExtraResultatsFallbackUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée ou met à jour le lien résultats pour un événement.
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @param string $strUrl Chaîne vide = effacer le lien (NULL en BD)
|
||||
* @return bool
|
||||
*/
|
||||
function fxEveExtraSetResultatsUrl($intEveId, $strUrl)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strUrl = trim((string) $strUrl);
|
||||
if ($strUrl === '') {
|
||||
$strSqlUrl = 'NULL';
|
||||
} else {
|
||||
$strSqlUrl = "'" . $objDatabase->fxEscape($strUrl) . "'";
|
||||
}
|
||||
|
||||
// MSIN-3487 — upsert : une ligne max par eve_id
|
||||
$sql = 'INSERT INTO inscriptions_evenements_extra (eve_id, eve_resultats_url, eve_extra_maj)'
|
||||
. ' VALUES (' . $intEveId . ', ' . $strSqlUrl . ', NOW())'
|
||||
. ' ON DUPLICATE KEY UPDATE'
|
||||
. ' eve_resultats_url = VALUES(eve_resultats_url),'
|
||||
. ' eve_extra_maj = NOW()';
|
||||
|
||||
return (bool) $objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL courte à encoder dans un QR (kc.php redirige vers les résultats).
|
||||
* Exemple : https://ms1inscription.com/kc.php?t=eve&id=760
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @param string|null $strBaseUrl Défaut = $vDomaine
|
||||
* @return string
|
||||
*/
|
||||
function fxKcEveResultatsQrUrl($intEveId, $strBaseUrl = null)
|
||||
{
|
||||
global $vDomaine;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($strBaseUrl === null || trim((string) $strBaseUrl) === '') {
|
||||
$strBaseUrl = !empty($vDomaine) ? $vDomaine : 'https://ms1inscription.com';
|
||||
}
|
||||
|
||||
return rtrim($strBaseUrl, '/') . '/kc.php?t=eve&id=' . $intEveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* PNG binaire du QR résultats événement (avec logo MS1).
|
||||
* MSIN-3487
|
||||
*
|
||||
* @param int $intEveId
|
||||
* @param string|null $strBaseUrl
|
||||
* @param int $intModuleSize
|
||||
* @param bool $blnWithLogo
|
||||
* @return string|false
|
||||
*/
|
||||
function fxKcEveResultatsQrPngBinary($intEveId, $strBaseUrl = null, $intModuleSize = 10, $blnWithLogo = true)
|
||||
{
|
||||
require_once __DIR__ . '/inc_fx_kc_qr.php';
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fxKcQrGeneratePngBinary(
|
||||
fxKcEveResultatsQrUrl($intEveId, $strBaseUrl),
|
||||
$intModuleSize,
|
||||
$blnWithLogo
|
||||
);
|
||||
}
|
||||
666
php/inc_fx_fiche_audit.php
Normal file
@ -0,0 +1,666 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4464 — Traçabilité fiches (config whitelist + rétention + writers).
|
||||
* UI fiche (« ? ») + permission promoteur : phase suivante.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Même allowlist que les kits ($arrEveAccesKitEditors dans inc_settings.php).
|
||||
*/
|
||||
if (!function_exists('fxAdminCanEditFicheAudit')) {
|
||||
function fxAdminCanEditFicheAudit()
|
||||
{
|
||||
return function_exists('fxAdminCanEditEveAccesKits') && fxAdminCanEditEveAccesKits();
|
||||
}
|
||||
}
|
||||
|
||||
/** Tables MSIN-4464 présentes ? */
|
||||
function fxFicheAuditTablesReady()
|
||||
{
|
||||
global $objDatabase;
|
||||
static $blnReady = null;
|
||||
|
||||
if ($blnReady !== null) {
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
$blnReady = false;
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
$tab = $objDatabase->fxGetResults("SHOW TABLES LIKE 'inscriptions_fiche_audit_settings'");
|
||||
$blnReady = ($tab != null && count($tab) > 0);
|
||||
|
||||
return $blnReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Catalogue connu (ordre d'affichage admin). Les clés absentes en BD sont
|
||||
* proposées à l'écran et créées au premier enregistrement.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function fxFicheAuditFieldCatalog()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'field_key' => 'par_statut_course',
|
||||
'field_label_fr' => 'Statut de course',
|
||||
'field_label_en' => 'Race status',
|
||||
'field_tri' => 10,
|
||||
),
|
||||
array(
|
||||
'field_key' => 'no_bib',
|
||||
'field_label_fr' => 'Numéro de dossard',
|
||||
'field_label_en' => 'Bib number',
|
||||
'field_tri' => 20,
|
||||
),
|
||||
array(
|
||||
'field_key' => 'no_bib_remis',
|
||||
'field_label_fr' => 'Dossard remis',
|
||||
'field_label_en' => 'Bib handed out',
|
||||
'field_tri' => 30,
|
||||
),
|
||||
array(
|
||||
'field_key' => 'is_cancelled',
|
||||
'field_label_fr' => 'Annulation',
|
||||
'field_label_en' => 'Cancellation',
|
||||
'field_tri' => 40,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{retention_days:int,retention_max_per_field:int,track_last_touch:int}
|
||||
*/
|
||||
function fxFicheAuditGetSettings()
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$arrDefault = array(
|
||||
'retention_days' => 90,
|
||||
'retention_max_per_field' => 10,
|
||||
'track_last_touch' => 1,
|
||||
);
|
||||
|
||||
if (!isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
$sql = 'SELECT retention_days, retention_max_per_field, track_last_touch'
|
||||
. ' FROM inscriptions_fiche_audit_settings WHERE setting_id = 1 LIMIT 1';
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
if ($row === null || !is_array($row)) {
|
||||
return $arrDefault;
|
||||
}
|
||||
|
||||
return array(
|
||||
'retention_days' => max(1, intval($row['retention_days'])),
|
||||
'retention_max_per_field' => max(1, intval($row['retention_max_per_field'])),
|
||||
'track_last_touch' => (intval($row['track_last_touch']) === 1) ? 1 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Champs whitelist fusionnés avec le catalogue (actif = 0 si absent en BD).
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function fxFicheAuditGetFieldsForAdmin()
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$arrByKey = array();
|
||||
if (isset($objDatabase) && fxFicheAuditTablesReady()) {
|
||||
$sql = 'SELECT field_key, field_label_fr, field_label_en, field_actif, field_tri'
|
||||
. ' FROM inscriptions_fiche_audit_fields ORDER BY field_tri, field_key';
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
if (is_array($arrRows)) {
|
||||
foreach ($arrRows as $row) {
|
||||
if (!is_array($row) || empty($row['field_key'])) {
|
||||
continue;
|
||||
}
|
||||
$arrByKey[$row['field_key']] = array(
|
||||
'field_key' => (string)$row['field_key'],
|
||||
'field_label_fr' => (string)$row['field_label_fr'],
|
||||
'field_label_en' => (string)$row['field_label_en'],
|
||||
'field_actif' => (intval($row['field_actif']) === 1) ? 1 : 0,
|
||||
'field_tri' => intval($row['field_tri']),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$arrOut = array();
|
||||
foreach (fxFicheAuditFieldCatalog() as $arrCat) {
|
||||
$strKey = $arrCat['field_key'];
|
||||
if (isset($arrByKey[$strKey])) {
|
||||
$arrOut[] = $arrByKey[$strKey];
|
||||
unset($arrByKey[$strKey]);
|
||||
} else {
|
||||
$arrOut[] = array(
|
||||
'field_key' => $strKey,
|
||||
'field_label_fr' => $arrCat['field_label_fr'],
|
||||
'field_label_en' => $arrCat['field_label_en'],
|
||||
'field_actif' => 0,
|
||||
'field_tri' => intval($arrCat['field_tri']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Champs éventuels hors catalogue (déjà en BD)
|
||||
foreach ($arrByKey as $row) {
|
||||
$arrOut[] = $row;
|
||||
}
|
||||
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clés actives (pour usage futur des writers).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
function fxFicheAuditGetActiveFieldKeys()
|
||||
{
|
||||
global $objDatabase;
|
||||
static $arrCache = null;
|
||||
|
||||
if ($arrCache !== null) {
|
||||
return $arrCache;
|
||||
}
|
||||
|
||||
$arrCache = array();
|
||||
if (!isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return $arrCache;
|
||||
}
|
||||
|
||||
$sql = 'SELECT field_key FROM inscriptions_fiche_audit_fields'
|
||||
. ' WHERE field_actif = 1 ORDER BY field_tri, field_key';
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
if (is_array($arrRows)) {
|
||||
foreach ($arrRows as $row) {
|
||||
if (!empty($row['field_key'])) {
|
||||
$arrCache[] = (string)$row['field_key'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $arrCache;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
function fxFicheAuditIsFieldActive($strFieldKey)
|
||||
{
|
||||
$strFieldKey = (string)$strFieldKey;
|
||||
if ($strFieldKey === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($strFieldKey, fxFicheAuditGetActiveFieldKeys(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acteur depuis la session (promoteur ou superadm lié).
|
||||
*
|
||||
* @return array{com_id:int,label:string}
|
||||
*/
|
||||
function fxFicheAuditResolveActor()
|
||||
{
|
||||
$intComId = intval($_SESSION['com_info']['com_id'] ?? ($_SESSION['com_id'] ?? 0));
|
||||
$strLabel = trim(
|
||||
(string)($_SESSION['com_info']['com_prenom'] ?? '')
|
||||
. ' '
|
||||
. (string)($_SESSION['com_info']['com_nom'] ?? '')
|
||||
);
|
||||
|
||||
if ($strLabel === '' && $intComId > 0) {
|
||||
$strLabel = 'compte#' . $intComId;
|
||||
}
|
||||
|
||||
if ($intComId <= 0 && !empty($_SESSION['usa_id'])) {
|
||||
$intUsaId = intval($_SESSION['usa_id']);
|
||||
$intComId = intval($_SESSION['usa_info']['com_id'] ?? 0);
|
||||
$strLabel = trim(
|
||||
(string)($_SESSION['usa_info']['com_prenom'] ?? '')
|
||||
. ' '
|
||||
. (string)($_SESSION['usa_info']['com_nom'] ?? '')
|
||||
);
|
||||
if ($strLabel === '') {
|
||||
$strLabel = 'admin#' . $intUsaId;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'com_id' => $intComId,
|
||||
'label' => $strLabel,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4464 — Point unique d'écriture audit après un UPDATE réussi.
|
||||
* - Si champ whitelisté et valeur changée → ligne détail
|
||||
* - Si track_last_touch → ligne last_touch (même hors whitelist)
|
||||
*
|
||||
* @param int $intParId
|
||||
* @param string $strFieldKey
|
||||
* @param string|null $strOld
|
||||
* @param string|null $strNew
|
||||
* @param string $strSource
|
||||
* @param int $intEveId
|
||||
*/
|
||||
function fxFicheAuditRecordChange(
|
||||
$intParId,
|
||||
$strFieldKey,
|
||||
$strOld,
|
||||
$strNew,
|
||||
$strSource = '',
|
||||
$intEveId = 0
|
||||
) {
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$strFieldKey = preg_replace('/[^a-z0-9_]/i', '', (string)$strFieldKey);
|
||||
if ($intParId <= 0 || $strFieldKey === '' || !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$strOldNorm = ($strOld === null) ? '' : trim((string)$strOld);
|
||||
$strNewNorm = ($strNew === null) ? '' : trim((string)$strNew);
|
||||
if ($strOldNorm === $strNewNorm) {
|
||||
return;
|
||||
}
|
||||
|
||||
$arrSettings = fxFicheAuditGetSettings();
|
||||
$blnDetail = fxFicheAuditIsFieldActive($strFieldKey);
|
||||
$blnLastTouch = (intval($arrSettings['track_last_touch']) === 1);
|
||||
|
||||
if (!$blnDetail && !$blnLastTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($intEveId <= 0) {
|
||||
$intEveId = intval($objDatabase->fxGetVar(
|
||||
'SELECT eve_id FROM resultats_participants WHERE par_id = ' . $intParId . ' LIMIT 1'
|
||||
));
|
||||
}
|
||||
|
||||
$arrActor = fxFicheAuditResolveActor();
|
||||
|
||||
if ($blnDetail) {
|
||||
fxFicheAuditInsertLogRow(
|
||||
$intParId,
|
||||
$intEveId,
|
||||
$strFieldKey,
|
||||
$strOldNorm,
|
||||
$strNewNorm,
|
||||
$arrActor,
|
||||
$strSource
|
||||
);
|
||||
fxFicheAuditTrimField($intParId, $strFieldKey, $arrSettings);
|
||||
}
|
||||
|
||||
if ($blnLastTouch) {
|
||||
fxFicheAuditInsertLogRow(
|
||||
$intParId,
|
||||
$intEveId,
|
||||
'last_touch',
|
||||
'',
|
||||
$strFieldKey,
|
||||
$arrActor,
|
||||
$strSource
|
||||
);
|
||||
fxFicheAuditTrimField($intParId, 'last_touch', $arrSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4464 — Dernière intervention seule (édition fiche, etc.), sans old→new.
|
||||
*/
|
||||
function fxFicheAuditRecordLastTouchOnly($intParId, $strTriggerField, $strSource = '', $intEveId = 0)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$strTriggerField = preg_replace('/[^a-z0-9_]/i', '', (string)$strTriggerField);
|
||||
if ($strTriggerField === '') {
|
||||
$strTriggerField = 'fiche';
|
||||
}
|
||||
if ($intParId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$arrSettings = fxFicheAuditGetSettings();
|
||||
if (intval($arrSettings['track_last_touch']) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($intEveId <= 0) {
|
||||
$intEveId = intval($objDatabase->fxGetVar(
|
||||
'SELECT eve_id FROM resultats_participants WHERE par_id = ' . $intParId . ' LIMIT 1'
|
||||
));
|
||||
}
|
||||
|
||||
$arrActor = fxFicheAuditResolveActor();
|
||||
fxFicheAuditInsertLogRow(
|
||||
$intParId,
|
||||
$intEveId,
|
||||
'last_touch',
|
||||
'',
|
||||
$strTriggerField,
|
||||
$arrActor,
|
||||
$strSource
|
||||
);
|
||||
fxFicheAuditTrimField($intParId, 'last_touch', $arrSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Après annulation / rétablissement sur une commande (tous les par_id du pec).
|
||||
*/
|
||||
function fxFicheAuditRecordCancelRestoreForPec($intPecId, $strOld, $strNew, $strSource, $intEveId = 0)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intPecId = intval($intPecId);
|
||||
if ($intPecId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = 'SELECT par_id, eve_id FROM resultats_participants WHERE pec_id = ' . $intPecId;
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
if (!is_array($arrRows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($arrRows as $row) {
|
||||
if (!is_array($row) || empty($row['par_id'])) {
|
||||
continue;
|
||||
}
|
||||
$intEveRow = intval($row['eve_id'] ?? 0);
|
||||
if ($intEveRow <= 0) {
|
||||
$intEveRow = intval($intEveId);
|
||||
}
|
||||
fxFicheAuditRecordChange(
|
||||
intval($row['par_id']),
|
||||
'is_cancelled',
|
||||
$strOld,
|
||||
$strNew,
|
||||
$strSource,
|
||||
$intEveRow
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Événements pour le sélecteur admin (actifs récents + ceux déjà dans les logs).
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function fxFicheAuditAdminListEvents()
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (!isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Pas de LIMIT : le select admin a une recherche (bootstrap-select).
|
||||
$sql = 'SELECT e.eve_id, e.eve_nom_fr, e.eve_date_debut,'
|
||||
. ' (SELECT COUNT(*) FROM inscriptions_fiche_audit_log l WHERE l.eve_id = e.eve_id) AS fal_nb'
|
||||
. ' FROM inscriptions_evenements e'
|
||||
. ' WHERE e.eve_actif = 1'
|
||||
. ' OR EXISTS (SELECT 1 FROM inscriptions_fiche_audit_log l2 WHERE l2.eve_id = e.eve_id)'
|
||||
. ' ORDER BY'
|
||||
. ' CASE WHEN EXISTS (SELECT 1 FROM inscriptions_fiche_audit_log lx WHERE lx.eve_id = e.eve_id) THEN 0 ELSE 1 END,'
|
||||
. ' e.eve_date_debut DESC, e.eve_id DESC';
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
return is_array($arrRows) ? $arrRows : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Champs (hors last_touch) qui ont au moins 1 log pour ce participant.
|
||||
*
|
||||
* @return array<string,true>
|
||||
*/
|
||||
function fxFicheAuditFieldsWithLogsForPar($intParId)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$arrOut = array();
|
||||
if ($intParId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
$sql = 'SELECT field_key FROM inscriptions_fiche_audit_log'
|
||||
. ' WHERE par_id = ' . $intParId
|
||||
. " AND field_key <> 'last_touch'"
|
||||
. ' GROUP BY field_key';
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
if (is_array($arrRows)) {
|
||||
foreach ($arrRows as $row) {
|
||||
if (!empty($row['field_key'])) {
|
||||
$arrOut[(string)$row['field_key']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $arrOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Historique détail d'un champ (fiche / AJAX), limité par la config.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function fxFicheAuditGetRecentForField($intParId, $strFieldKey, $intLimit = 0)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$strFieldKey = preg_replace('/[^a-z0-9_]/i', '', (string)$strFieldKey);
|
||||
if ($intParId <= 0 || $strFieldKey === '' || $strFieldKey === 'last_touch'
|
||||
|| !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ($intLimit <= 0) {
|
||||
$arrSettings = fxFicheAuditGetSettings();
|
||||
$intLimit = max(1, intval($arrSettings['retention_max_per_field']));
|
||||
}
|
||||
$intLimit = min(100, max(1, intval($intLimit)));
|
||||
|
||||
$sql = 'SELECT fal_id, field_key, old_value, new_value, changed_by_label, source, created_at'
|
||||
. ' FROM inscriptions_fiche_audit_log'
|
||||
. ' WHERE par_id = ' . $intParId
|
||||
. " AND field_key = '" . $objDatabase->fxEscape($strFieldKey) . "'"
|
||||
. ' ORDER BY created_at DESC, fal_id DESC'
|
||||
. ' LIMIT ' . $intLimit;
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
return is_array($arrRows) ? $arrRows : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derniers logs d'un événement (contrôle admin).
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function fxFicheAuditAdminListRecentByEve($intEveId, $intLimit = 100)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
$intLimit = intval($intLimit);
|
||||
if ($intLimit !== 200) {
|
||||
$intLimit = 100;
|
||||
}
|
||||
if ($intEveId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sql = 'SELECT l.fal_id, l.par_id, l.eve_id, l.field_key, l.old_value, l.new_value,'
|
||||
. ' l.changed_by_com_id, l.changed_by_label, l.source, l.created_at,'
|
||||
. ' p.par_prenom, p.par_nom, p.no_bib'
|
||||
. ' FROM inscriptions_fiche_audit_log l'
|
||||
. ' LEFT JOIN resultats_participants p ON p.par_id = l.par_id'
|
||||
. ' WHERE l.eve_id = ' . $intEveId
|
||||
. ' ORDER BY l.created_at DESC, l.fal_id DESC'
|
||||
. ' LIMIT ' . $intLimit;
|
||||
$arrRows = $objDatabase->fxGetResults($sql);
|
||||
|
||||
return is_array($arrRows) ? $arrRows : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{com_id:int,label:string} $arrActor
|
||||
*/
|
||||
function fxFicheAuditInsertLogRow(
|
||||
$intParId,
|
||||
$intEveId,
|
||||
$strFieldKey,
|
||||
$strOld,
|
||||
$strNew,
|
||||
$arrActor,
|
||||
$strSource
|
||||
) {
|
||||
global $objDatabase;
|
||||
|
||||
$strSource = preg_replace('/[^a-z0-9_\-]/i', '', (string)$strSource);
|
||||
if (strlen($strSource) > 32) {
|
||||
$strSource = substr($strSource, 0, 32);
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO inscriptions_fiche_audit_log'
|
||||
. ' (par_id, eve_id, field_key, old_value, new_value,'
|
||||
. ' changed_by_com_id, changed_by_label, source, created_at)'
|
||||
. ' VALUES ('
|
||||
. intval($intParId) . ', '
|
||||
. (intval($intEveId) > 0 ? intval($intEveId) : 'NULL') . ', '
|
||||
. "'" . $objDatabase->fxEscape($strFieldKey) . "', "
|
||||
. "'" . $objDatabase->fxEscape($strOld) . "', "
|
||||
. "'" . $objDatabase->fxEscape($strNew) . "', "
|
||||
. (intval($arrActor['com_id']) > 0 ? intval($arrActor['com_id']) : 'NULL') . ', '
|
||||
. "'" . $objDatabase->fxEscape((string)$arrActor['label']) . "', "
|
||||
. ($strSource !== '' ? "'" . $objDatabase->fxEscape($strSource) . "'" : 'NULL') . ', '
|
||||
. 'NOW()'
|
||||
. ')';
|
||||
$objDatabase->fxQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Garde les N dernières entrées + purge par âge pour par_id × field_key.
|
||||
*
|
||||
* @param array{retention_days:int,retention_max_per_field:int,track_last_touch:int} $arrSettings
|
||||
*/
|
||||
function fxFicheAuditTrimField($intParId, $strFieldKey, $arrSettings)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$strFieldKey = (string)$strFieldKey;
|
||||
if ($intParId <= 0 || $strFieldKey === '' || !isset($objDatabase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$intDays = max(1, intval($arrSettings['retention_days']));
|
||||
$intMax = max(1, intval($arrSettings['retention_max_per_field']));
|
||||
$strEscKey = $objDatabase->fxEscape($strFieldKey);
|
||||
|
||||
$sqlAge = 'DELETE FROM inscriptions_fiche_audit_log'
|
||||
. ' WHERE par_id = ' . $intParId
|
||||
. " AND field_key = '" . $strEscKey . "'"
|
||||
. ' AND created_at < DATE_SUB(NOW(), INTERVAL ' . $intDays . ' DAY)';
|
||||
$objDatabase->fxQuery($sqlAge);
|
||||
|
||||
// MySQL : sous-requête dérivée obligatoire pour DELETE + LIMIT même table.
|
||||
$sqlMax = 'DELETE FROM inscriptions_fiche_audit_log'
|
||||
. ' WHERE par_id = ' . $intParId
|
||||
. " AND field_key = '" . $strEscKey . "'"
|
||||
. ' AND fal_id NOT IN ('
|
||||
. ' SELECT fal_id FROM ('
|
||||
. ' SELECT fal_id FROM inscriptions_fiche_audit_log'
|
||||
. ' WHERE par_id = ' . $intParId
|
||||
. " AND field_key = '" . $strEscKey . "'"
|
||||
. ' ORDER BY created_at DESC, fal_id DESC'
|
||||
. ' LIMIT ' . $intMax
|
||||
. ' ) AS keep_rows'
|
||||
. ' )';
|
||||
$objDatabase->fxQuery($sqlMax);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $arrPost
|
||||
* @return array{state:string,message:string}
|
||||
*/
|
||||
function fxFicheAuditSaveConfig($arrPost)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (!isset($objDatabase)) {
|
||||
return array('state' => 'error', 'message' => 'Base de données indisponible.');
|
||||
}
|
||||
if (!fxFicheAuditTablesReady()) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => 'Tables absentes : exécuter sql/MSIN-4464-fiche-audit-config.sql',
|
||||
);
|
||||
}
|
||||
|
||||
$intDays = isset($arrPost['retention_days']) ? intval($arrPost['retention_days']) : 90;
|
||||
$intMax = isset($arrPost['retention_max_per_field']) ? intval($arrPost['retention_max_per_field']) : 10;
|
||||
$intLastTouch = !empty($arrPost['track_last_touch']) ? 1 : 0;
|
||||
|
||||
if ($intDays < 1 || $intDays > 3650) {
|
||||
return array('state' => 'error', 'message' => 'Rétention (jours) : entre 1 et 3650.');
|
||||
}
|
||||
if ($intMax < 1 || $intMax > 100) {
|
||||
return array('state' => 'error', 'message' => 'Max par champ : entre 1 et 100.');
|
||||
}
|
||||
|
||||
$arrActif = array();
|
||||
if (!empty($arrPost['field_actif']) && is_array($arrPost['field_actif'])) {
|
||||
foreach ($arrPost['field_actif'] as $strKey) {
|
||||
$strKey = preg_replace('/[^a-z0-9_]/i', '', (string)$strKey);
|
||||
if ($strKey !== '') {
|
||||
$arrActif[$strKey] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sqlSettings = 'INSERT INTO inscriptions_fiche_audit_settings'
|
||||
. ' (setting_id, retention_days, retention_max_per_field, track_last_touch)'
|
||||
. ' VALUES (1, ' . $intDays . ', ' . $intMax . ', ' . $intLastTouch . ')'
|
||||
. ' ON DUPLICATE KEY UPDATE'
|
||||
. ' retention_days = VALUES(retention_days),'
|
||||
. ' retention_max_per_field = VALUES(retention_max_per_field),'
|
||||
. ' track_last_touch = VALUES(track_last_touch)';
|
||||
if (!$objDatabase->fxQuery($sqlSettings)) {
|
||||
return array('state' => 'error', 'message' => 'Erreur à l\'enregistrement des réglages.');
|
||||
}
|
||||
|
||||
foreach (fxFicheAuditGetFieldsForAdmin() as $arrField) {
|
||||
$strKey = $arrField['field_key'];
|
||||
$intActif = isset($arrActif[$strKey]) ? 1 : 0;
|
||||
$strLabelFr = $objDatabase->fxEscape($arrField['field_label_fr']);
|
||||
$strLabelEn = $objDatabase->fxEscape($arrField['field_label_en']);
|
||||
$intTri = intval($arrField['field_tri']);
|
||||
|
||||
$sqlField = 'INSERT INTO inscriptions_fiche_audit_fields'
|
||||
. ' (field_key, field_label_fr, field_label_en, field_actif, field_tri)'
|
||||
. " VALUES ('" . $objDatabase->fxEscape($strKey) . "', '" . $strLabelFr . "', '"
|
||||
. $strLabelEn . "', " . $intActif . ', ' . $intTri . ')'
|
||||
. ' ON DUPLICATE KEY UPDATE'
|
||||
. ' field_label_fr = VALUES(field_label_fr),'
|
||||
. ' field_label_en = VALUES(field_label_en),'
|
||||
. ' field_actif = VALUES(field_actif),'
|
||||
. ' field_tri = VALUES(field_tri)';
|
||||
if (!$objDatabase->fxQuery($sqlField)) {
|
||||
return array('state' => 'error', 'message' => 'Erreur à l\'enregistrement du champ ' . $strKey . '.');
|
||||
}
|
||||
}
|
||||
|
||||
return array('state' => 'ok', 'message' => 'Configuration enregistrée.');
|
||||
}
|
||||
2737
php/inc_fx_import_resultats.php
Normal file
2815
php/inc_fx_inscriptions_gestion.php
Normal file
250
php/inc_fx_kc_qr.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../libs/phpqrcode-master/qrlib.php';
|
||||
|
||||
/**
|
||||
* Chemin du logo MS1 pour le centre des QR key_chain.
|
||||
*/
|
||||
function fxKcQrLogoPath()
|
||||
{
|
||||
return __DIR__ . '/../images/ms1-kc-logo.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge et redimensionne le logo PNG (carré de sortie $intSize).
|
||||
* Repli : dessin texte si le fichier est absent.
|
||||
*/
|
||||
function fxKcQrCreateLogoImage($intSize)
|
||||
{
|
||||
$intSize = max(24, (int) $intSize);
|
||||
$strLogoPath = fxKcQrLogoPath();
|
||||
|
||||
if (is_file($strLogoPath)) {
|
||||
$imgSrc = @imagecreatefrompng($strLogoPath);
|
||||
if ($imgSrc !== false) {
|
||||
$intSrcW = imagesx($imgSrc);
|
||||
$intSrcH = imagesy($imgSrc);
|
||||
$img = imagecreatetruecolor($intSize, $intSize);
|
||||
imagealphablending($img, false);
|
||||
imagesavealpha($img, true);
|
||||
$colTransparent = imagecolorallocatealpha($img, 0, 0, 0, 127);
|
||||
imagefilledrectangle($img, 0, 0, $intSize, $intSize, $colTransparent);
|
||||
imagealphablending($img, true);
|
||||
|
||||
// Conserve le ratio (158×180) dans le carré cible.
|
||||
$fltScale = min($intSize / max(1, $intSrcW), $intSize / max(1, $intSrcH));
|
||||
$intDstW = max(1, (int) round($intSrcW * $fltScale));
|
||||
$intDstH = max(1, (int) round($intSrcH * $fltScale));
|
||||
$intDstX = (int) floor(($intSize - $intDstW) / 2);
|
||||
$intDstY = (int) floor(($intSize - $intDstH) / 2);
|
||||
|
||||
imagecopyresampled(
|
||||
$img,
|
||||
$imgSrc,
|
||||
$intDstX,
|
||||
$intDstY,
|
||||
0,
|
||||
0,
|
||||
$intDstW,
|
||||
$intDstH,
|
||||
$intSrcW,
|
||||
$intSrcH
|
||||
);
|
||||
imagedestroy($imgSrc);
|
||||
|
||||
return $img;
|
||||
}
|
||||
}
|
||||
|
||||
// Repli : dessin texte (si PNG manquant).
|
||||
$img = imagecreatetruecolor($intSize, $intSize);
|
||||
imagealphablending($img, true);
|
||||
imagesavealpha($img, true);
|
||||
|
||||
$colBlack = imagecolorallocate($img, 0, 0, 0);
|
||||
$colWhite = imagecolorallocate($img, 255, 255, 255);
|
||||
imagefilledrectangle($img, 0, 0, $intSize, $intSize, $colBlack);
|
||||
|
||||
$strFont = __DIR__ . '/../fonts/arial.ttf';
|
||||
if (!is_file($strFont)) {
|
||||
$strFont = 'C:/Windows/Fonts/arial.ttf';
|
||||
}
|
||||
|
||||
if (is_file($strFont)) {
|
||||
$intLeftX = (int) round($intSize * 0.12);
|
||||
$intRightX = (int) round($intSize * 0.52);
|
||||
$intFontSmall = max(8, (int) round($intSize * 0.16));
|
||||
$intFontBig = max(12, (int) round($intSize * 0.42));
|
||||
|
||||
imagettftext($img, $intFontSmall, 0, $intLeftX, (int) round($intSize * 0.30), $colWhite, $strFont, 'm');
|
||||
imagettftext($img, (int) round($intFontSmall * 0.55), 0, $intLeftX + (int) round($intSize * 0.04), (int) round($intSize * 0.46), $colWhite, $strFont, chr(183));
|
||||
imagettftext($img, $intFontSmall, 0, $intLeftX, (int) round($intSize * 0.62), $colWhite, $strFont, 's');
|
||||
imagettftext($img, $intFontBig, 0, $intRightX, (int) round($intSize * 0.68), $colWhite, $strFont, '1');
|
||||
} else {
|
||||
imagestring($img, 5, (int) round($intSize * 0.18), (int) round($intSize * 0.28), 'm.s', $colWhite);
|
||||
imagestring($img, 5, (int) round($intSize * 0.55), (int) round($intSize * 0.40), '1', $colWhite);
|
||||
}
|
||||
|
||||
return $img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Superpose le logo au centre d'un PNG QR existant.
|
||||
*/
|
||||
function fxKcQrOverlayLogo($strQrPath, $fltLogoRatio = 0.22)
|
||||
{
|
||||
$imgQr = imagecreatefrompng($strQrPath);
|
||||
if ($imgQr === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intQrW = imagesx($imgQr);
|
||||
$intQrH = imagesy($imgQr);
|
||||
$intLogoSize = (int) max(24, round(min($intQrW, $intQrH) * $fltLogoRatio));
|
||||
$intPad = (int) max(2, round($intLogoSize * 0.08));
|
||||
|
||||
$imgLogo = fxKcQrCreateLogoImage($intLogoSize);
|
||||
$imgBlock = imagecreatetruecolor($intLogoSize + ($intPad * 2), $intLogoSize + ($intPad * 2));
|
||||
$colWhite = imagecolorallocate($imgBlock, 255, 255, 255);
|
||||
imagefilledrectangle($imgBlock, 0, 0, $intLogoSize + ($intPad * 2), $intLogoSize + ($intPad * 2), $colWhite);
|
||||
imagecopy($imgBlock, $imgLogo, $intPad, $intPad, 0, 0, $intLogoSize, $intLogoSize);
|
||||
|
||||
$intX = (int) floor(($intQrW - $intLogoSize - ($intPad * 2)) / 2);
|
||||
$intY = (int) floor(($intQrH - $intLogoSize - ($intPad * 2)) / 2);
|
||||
imagecopy($imgQr, $imgBlock, $intX, $intY, 0, 0, $intLogoSize + ($intPad * 2), $intLogoSize + ($intPad * 2));
|
||||
|
||||
imagepng($imgQr, $strQrPath, 0);
|
||||
|
||||
imagedestroy($imgQr);
|
||||
imagedestroy($imgLogo);
|
||||
imagedestroy($imgBlock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un PNG QR pour une URL quelconque (niveau H, logo optionnel).
|
||||
* MSIN-3487 — réutilisé key_chain + QR résultats événement.
|
||||
*
|
||||
* @param string $strUrl
|
||||
* @param string $strOutputFile
|
||||
* @param int $intModuleSize
|
||||
* @param bool $blnWithLogo
|
||||
* @return string Chemin du fichier généré
|
||||
*/
|
||||
function fxKcQrGeneratePngFromUrl($strUrl, $strOutputFile, $intModuleSize = 18, $blnWithLogo = true)
|
||||
{
|
||||
$strDir = dirname($strOutputFile);
|
||||
if (!is_dir($strDir)) {
|
||||
mkdir($strDir, 0755, true);
|
||||
}
|
||||
|
||||
QRcode::png(
|
||||
$strUrl,
|
||||
$strOutputFile,
|
||||
QR_ECLEVEL_H,
|
||||
max(4, (int) $intModuleSize),
|
||||
2
|
||||
);
|
||||
|
||||
if ($blnWithLogo) {
|
||||
fxKcQrOverlayLogo($strOutputFile);
|
||||
}
|
||||
|
||||
return $strOutputFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un PNG QR en mémoire (binaire) pour une URL.
|
||||
*
|
||||
* @param string $strUrl
|
||||
* @param int $intModuleSize
|
||||
* @param bool $blnWithLogo
|
||||
* @return string|false
|
||||
*/
|
||||
function fxKcQrGeneratePngBinary($strUrl, $intModuleSize = 10, $blnWithLogo = true)
|
||||
{
|
||||
$strTmpBase = tempnam(sys_get_temp_dir(), 'kcqr_');
|
||||
if ($strTmpBase === false) {
|
||||
return false;
|
||||
}
|
||||
@unlink($strTmpBase);
|
||||
$strTmpPng = $strTmpBase . '.png';
|
||||
|
||||
fxKcQrGeneratePngFromUrl($strUrl, $strTmpPng, $intModuleSize, $blnWithLogo);
|
||||
if (!is_file($strTmpPng)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$binPng = file_get_contents($strTmpPng);
|
||||
@unlink($strTmpPng);
|
||||
|
||||
return ($binPng === false || $binPng === '') ? false : $binPng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un QR key_chain prêt pour l'impression.
|
||||
*/
|
||||
function fxKcQrGeneratePng($intParam1, $strBaseUrl, $strOutputFile, $intModuleSize = 18, $blnWithLogo = true)
|
||||
{
|
||||
$strUrl = rtrim($strBaseUrl, '/') . '/kc.php?param1=' . intval($intParam1);
|
||||
fxKcQrGeneratePngFromUrl($strUrl, $strOutputFile, $intModuleSize, $blnWithLogo);
|
||||
|
||||
return [
|
||||
'param1' => intval($intParam1),
|
||||
'url' => $strUrl,
|
||||
'file' => $strOutputFile,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une série de QR codes key_chain.
|
||||
*/
|
||||
function fxKcQrGenerateBatch(array $options)
|
||||
{
|
||||
$intStart = intval($options['start'] ?? 500);
|
||||
$intCount = max(1, intval($options['count'] ?? 100));
|
||||
$strBaseUrl = rtrim($options['base_url'] ?? 'https://ms1inscription.com', '/');
|
||||
$strOutputDir = rtrim($options['output_dir'] ?? (__DIR__ . '/../output/kc_qr'), DIRECTORY_SEPARATOR);
|
||||
$intModuleSize = max(6, intval($options['module_size'] ?? 18));
|
||||
$blnWithLogo = !empty($options['with_logo']);
|
||||
|
||||
if (!is_dir($strOutputDir)) {
|
||||
mkdir($strOutputDir, 0755, true);
|
||||
}
|
||||
|
||||
$tabGenerated = [];
|
||||
for ($i = 0; $i < $intCount; $i++) {
|
||||
$intParam1 = $intStart + $i;
|
||||
$strFile = $strOutputDir . DIRECTORY_SEPARATOR . 'qr_kc_' . $intParam1 . '.png';
|
||||
$tabGenerated[] = fxKcQrGeneratePng($intParam1, $strBaseUrl, $strFile, $intModuleSize, $blnWithLogo);
|
||||
}
|
||||
|
||||
$strManifest = $strOutputDir . DIRECTORY_SEPARATOR . 'manifest.csv';
|
||||
$fh = fopen($strManifest, 'w');
|
||||
if ($fh !== false) {
|
||||
fputcsv($fh, ['param1', 'vehicule_no', 'url', 'filename']);
|
||||
foreach ($tabGenerated as $row) {
|
||||
$intVehicule = $row['param1'];
|
||||
if (defined('KEYCHAIN_HTTP_EDIT_SHIFT')) {
|
||||
$intVehicule = $row['param1'] - KEYCHAIN_HTTP_EDIT_SHIFT;
|
||||
}
|
||||
fputcsv($fh, [
|
||||
$row['param1'],
|
||||
$intVehicule,
|
||||
$row['url'],
|
||||
basename($row['file']),
|
||||
]);
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
return [
|
||||
'output_dir' => $strOutputDir,
|
||||
'count' => count($tabGenerated),
|
||||
'start' => $intStart,
|
||||
'end' => $intStart + $intCount - 1,
|
||||
'manifest' => $strManifest,
|
||||
'files' => array_column($tabGenerated, 'file'),
|
||||
];
|
||||
}
|
||||
@ -2,13 +2,16 @@
|
||||
// fonction qui vérifie si le rabais fait déjà partie du panier et retourner le pra_id (JSP)
|
||||
// param : id du rabais
|
||||
// créé : 2013-06-19
|
||||
function fx_liste_attente_btactiver($mem_eve_id, $mem_epreuve)
|
||||
function fx_liste_attente_btactiver($mem_eve_id, $mem_epreuve, $strFrom = '')
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$memcode_eve_id = base64_encode(urlencode($mem_eve_id));
|
||||
$memcode_epreuve = base64_encode(urlencode($mem_epreuve));
|
||||
$mem_lien = "/compte/inc_tableau_liste_attente?promoteur_eve_id=" . $memcode_eve_id . "&epr_id=" . $memcode_epreuve;
|
||||
if ($strFrom !== '') {
|
||||
$mem_lien .= '&from=' . urlencode($strFrom);
|
||||
}
|
||||
if (fx_liste_attente_validelisteactive($mem_epreuve)) {
|
||||
?>
|
||||
<a class="btn btn-primary rounded-pill" id="link_back"
|
||||
|
||||
@ -310,6 +310,18 @@ function fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id) {
|
||||
if (!$qryInscription || !$qryParticipants) {
|
||||
$tabRetour['state'] = 'error';
|
||||
fxcreer_log("Erreur d'annulation d'inscription : " . $_SESSION['com_info']['com_courriel']);
|
||||
} else {
|
||||
// MSIN-4464 — audit annulation
|
||||
if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||||
}
|
||||
fxFicheAuditRecordCancelRestoreForPec(
|
||||
intval($int_pec_id),
|
||||
'0',
|
||||
'1',
|
||||
'annuler',
|
||||
intval($tabEpreuve['eve_id'] ?? 0)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$tabRetour['action'] = 'retablir';
|
||||
@ -327,6 +339,18 @@ function fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id) {
|
||||
if (!$qryInscription || !$qryParticipants) {
|
||||
$tabRetour['state'] = 'error';
|
||||
fxcreer_log("Erreur d'annulation d'inscription : " . $_SESSION['com_info']['com_courriel']);
|
||||
} else {
|
||||
// MSIN-4464 — audit rétablissement
|
||||
if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||||
}
|
||||
fxFicheAuditRecordCancelRestoreForPec(
|
||||
intval($int_pec_id),
|
||||
'1',
|
||||
'0',
|
||||
'retablir',
|
||||
intval($tabEpreuve['eve_id'] ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
// ajuster qte epreuve
|
||||
|
||||
@ -4,6 +4,7 @@ require_once('inc_fx_options.php'); // inclure les fonctions des options
|
||||
require_once('inc_fx_participants.php'); // inclure les fonctions des participants
|
||||
require_once('inc_fx_code_qr.php');
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/inc_fx_panier_js.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/inc_fx_promoteur.php';
|
||||
|
||||
// Retourner le numéro du panier ou en générer un (JSP)
|
||||
// param : langue
|
||||
@ -796,11 +797,26 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id
|
||||
$intDiff = ($tabAncienneEpreuve['epr_nb_participants'] - $tabNouvelleEpreuve['epr_nb_participants']);
|
||||
|
||||
for ($i = count($tabInscription); $i > $tabNouvelleEpreuve['epr_nb_participants']; $i--) {
|
||||
$sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1 WHERE par_id = " . intval($tabInscription[$i]['par_id']);
|
||||
$intParCancel = intval($tabInscription[$i]['par_id']);
|
||||
$sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1 WHERE par_id = " . $intParCancel;
|
||||
$qryUpdate = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
if (!$qryUpdate)
|
||||
if (!$qryUpdate) {
|
||||
fxcreer_log("Erreur lors du changemenet d'épreuve par le promoteur.");
|
||||
} else {
|
||||
// MSIN-4464 — participants surplus annulés au changement d'épreuve
|
||||
if (!function_exists('fxFicheAuditRecordChange')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||||
}
|
||||
fxFicheAuditRecordChange(
|
||||
$intParCancel,
|
||||
'is_cancelled',
|
||||
'0',
|
||||
'1',
|
||||
'changement_epreuve',
|
||||
intval($tabInscription[$i]['eve_id'] ?? ($tabEvenement['general']['eve_id'] ?? 0))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sqlUpdate = "UPDATE resultats_epreuves_commandees SET pec_equipe = " . $tabNouvelleEpreuve['epr_equipe'] . " WHERE pec_id_original = " . $intpec_id;
|
||||
@ -911,8 +927,11 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id
|
||||
$intAge = fxGetAge($strNaissance, $tabEpreuve['epr_date_age']);
|
||||
|
||||
// if ($strPrenom != '' && $strNomFamille != '' && $strSexe != '' && $strNaissance != '') {
|
||||
// MSIN-4474 — ne pas écraser l'identité si prénom/nom absents du POST (champs disabled)
|
||||
if ((isset($_SESSION['no_panier']) && $tabData['promoteur'] == 0) || (!isset($_SESSION['no_panier']) && $tabData['promoteur'] == 1 && $tabEvenement['general']['te_id'] != 13) || (!isset($_SESSION['no_panier']) && $tabData['promoteur'] == 0 && $i > 1)) {
|
||||
$strFields = ", par_prenom = '" . $objDatabase->fxEscape($strPrenom) . "', par_nom = '" . $objDatabase->fxEscape($strNomFamille) . "', par_naissance = '" . $objDatabase->fxEscape($strNaissance) . "', par_sexe = '" . $objDatabase->fxEscape($strSexe) . "'";
|
||||
if (isset($tabData['par_prenom_' . $i]) && isset($tabData['par_nom_' . $i])) {
|
||||
$strFields = ", par_prenom = '" . $objDatabase->fxEscape($strPrenom) . "', par_nom = '" . $objDatabase->fxEscape($strNomFamille) . "', par_naissance = '" . $objDatabase->fxEscape($strNaissance) . "', par_sexe = '" . $objDatabase->fxEscape($strSexe) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
$sqlUpdatePart = "UPDATE " . $strTableSqlPart . " SET " . $strModMarker . "epr_id = " . intval($intEprId) . ", par_age = " . intval($intAge) . ", pro_id = " . intval($intProvince) . ", par_codepostal = '" . $objDatabase->fxEscape($strCodepostal) . "', pay_id = " . intval($intPays) . ", par_adresse = '" . $objDatabase->fxEscape($strAdresse) . "', par_adresse2 = '" . $objDatabase->fxEscape($strAdresse2) . "', par_ville = '" . $objDatabase->fxEscape($strVille) . "', par_maj = '" . fxGetDateTime() . "', par_telephone1 = '" . $objDatabase->fxEscape($strTelephone1) . "', par_telephone2 = '" . $objDatabase->fxEscape($strTelephone2) . "', par_nom_equipe = '" . $objDatabase->fxEscape($strParNomEquipe) . "', par_courriel = '" . $objDatabase->fxEscape($strCourriel) . "', par_equipe = " . intval($tabEpreuve['epr_equipe']) . $strCancel . $strFields . ", par_contact_urgence_nom = '" . $objDatabase->fxEscape($strUrgenceNom) . "', par_contact_urgence_telephone = '" . $objDatabase->fxEscape($strUrgenceTel) . "' WHERE " . $strWhereSql . " = " . intval($intParId);
|
||||
@ -920,6 +939,17 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id
|
||||
|
||||
if (!$qryUpdate) {
|
||||
fxcreer_log('Erreur dans la mise à jour des participants.(' . $tabInscription[1]['par_courriel'] . ')');
|
||||
} elseif ($strTableSqlPart === 'resultats_participants' && $strModMarker !== '') {
|
||||
// MSIN-4464 — édition fiche promoteur : dernière intervention (pas le détail de chaque champ).
|
||||
if (!function_exists('fxFicheAuditRecordLastTouchOnly')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||||
}
|
||||
fxFicheAuditRecordLastTouchOnly(
|
||||
intval($intParId),
|
||||
'fiche',
|
||||
'fiche_edit',
|
||||
intval($tabEvenement['general']['eve_id'] ?? 0)
|
||||
);
|
||||
}
|
||||
// catégorie
|
||||
if ($tabEvenement['general']['eve_categorie_auto'] == 1) {
|
||||
@ -1243,9 +1273,14 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id
|
||||
else
|
||||
$strPage = 'account';
|
||||
|
||||
if ($_POST['promoteur'] == 1)
|
||||
header("Location: " . $vDomaine . '/' . $strPage . '/inc_tableau_promoteur?promoteur_eve_id=' . urlencode(base64_encode($tabEvenement['general']['eve_id'])));
|
||||
else
|
||||
if ($_POST['promoteur'] == 1) {
|
||||
// MSIN-4474 — retour à la page d'origine (gestion / promoteur), sinon tableau promoteur legacy
|
||||
$strReturnAfterMod = fxModInscriptionReturnUrl('');
|
||||
if ($strReturnAfterMod === '') {
|
||||
$strReturnAfterMod = $vDomaine . '/' . $strPage . '/inc_tableau_promoteur?promoteur_eve_id=' . urlencode(base64_encode($tabEvenement['general']['eve_id']));
|
||||
}
|
||||
header("Location: " . $strReturnAfterMod);
|
||||
} else
|
||||
header("Location: " . $vDomaine . '/' . $strPage);
|
||||
} else {
|
||||
header("Location: " . $tabData['url']);
|
||||
@ -3041,6 +3076,16 @@ function fxMajCommande($strNoPanier, $strLangue = 'fr', $blnResend = false)
|
||||
$sqlInsert = "INSERT INTO resultats_participants(eve_id, epr_id, par_prenom, par_nom, par_naissance, par_age, par_sexe, par_adresse, par_adresse2, par_ville, pro_id, par_codepostal, pay_id, par_telephone1, par_telephone1_ext, par_telephone2, par_courriel, par_capitaine, com_id, par_nom_equipe, no_panier, par_note, par_maj, no_commande, par_id_original, par_no_equipe, par_equipe, pec_id, rol_id, no_invitation, epr_objectif_dons, epr_objectif_dons_original, par_categorie_1_long_fr, par_categorie_1_long_en,par_categorie_1_court_fr, par_categorie_1_court_en, par_categorie_2_long_fr, par_categorie_2_long_en, par_categorie_2_court_fr, par_categorie_2_court_en, par_categorie_3_long_fr, par_categorie_3_long_en, par_categorie_3_court_fr, par_categorie_3_court_en, epr_objectif_distance, epr_objectif_distance_original, epr_objectif_temps, epr_objectif_temps_original, par_contact_urgence_nom, par_contact_urgence_telephone) SELECT eve_id, epr_id, par_prenom, par_nom, par_naissance, par_age, par_sexe, par_adresse, par_adresse2, par_ville, pro_id, par_codepostal, pay_id, par_telephone1, par_telephone1_ext, par_telephone2, par_courriel, par_capitaine, com_id, par_nom_equipe, no_panier, par_note, '" . fxGetDateTime() . "', '" . $no_commande . "', par_id, par_no_equipe, par_equipe, pec_id, rol_id, no_invitation, epr_objectif_dons, epr_objectif_dons, par_categorie_1_long_fr, par_categorie_1_long_en,par_categorie_1_court_fr, par_categorie_1_court_en, par_categorie_2_long_fr, par_categorie_2_long_en, par_categorie_2_court_fr, par_categorie_2_court_en, par_categorie_3_long_fr, par_categorie_3_long_en, par_categorie_3_court_fr, par_categorie_3_court_en, epr_objectif_distance, epr_objectif_distance, epr_objectif_temps, epr_objectif_temps, par_contact_urgence_nom, par_contact_urgence_telephone FROM inscriptions_panier_participants WHERE par_id = " . intval($tabParticipants[$tabEpreuves[$intCtr]['pec_id']][$intCtr2]['par_id']);
|
||||
$qryInsert = $objDatabase->fxQuery($sqlInsert);
|
||||
|
||||
// MSIN-4379 — Étape 7 : assignation auto du dossard après création du participant (achat).
|
||||
if ($qryInsert !== false) {
|
||||
$intNewParId = (int)$objDatabase->fxGetLastId();
|
||||
$intEprIdAutoBib = (int)$tabParticipants[$tabEpreuves[$intCtr]['pec_id']][$intCtr2]['epr_id'];
|
||||
|
||||
if ($intNewParId > 0 && $intEprIdAutoBib > 0) {
|
||||
fxAssignAutoBibForParticipant($intEprIdAutoBib, $intNewParId);
|
||||
}
|
||||
}
|
||||
|
||||
// insérer les questions
|
||||
if (isset($tabQuestions[$tabParticipants[$tabEpreuves[$intCtr]['pec_id']][$intCtr2]['par_id']])) {
|
||||
$list_file = [];
|
||||
@ -4083,8 +4128,15 @@ function fxMajEpreuvesCommandees($str_ancien_no_panier, $pec_id_old, $pec_id_new
|
||||
$sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1, par_maj = '" . fxGetDateTime() . "' WHERE pec_id = " . intval($pec_id_old);
|
||||
$qryUpdateParticipants = $objDatabase->fxQuery($sqlUpdate);
|
||||
|
||||
if (!$qryUpdateEpreuve || !$qryUpdateParticipants)
|
||||
if (!$qryUpdateEpreuve || !$qryUpdateParticipants) {
|
||||
fxcreer_log("Erreur de désactivation de l'ancienne épreuve lors d'un upgrade ou transfert.");
|
||||
} else {
|
||||
// MSIN-4464 — annulation suite upgrade / transfert
|
||||
if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) {
|
||||
require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php';
|
||||
}
|
||||
fxFicheAuditRecordCancelRestoreForPec(intval($pec_id_old), '0', '1', 'transfert_upgrade', 0);
|
||||
}
|
||||
}
|
||||
|
||||
unset($_SESSION['ancien_no_panier']);
|
||||
|
||||