Compare commits

..

175 commits

Author SHA1 Message Date
Jeremy Munsch
f7f93bb47b
Merge branch 'trunk' 2026-10-26 12:46:50 +01:00
Jeremy Munsch
db840f94a9
Merge branch 'develop' into trunk 2026-10-26 12:46:45 +01:00
Jeremy Munsch
b058a1a8e2
build: fix manifest user to 82 2026-10-26 12:46:39 +01:00
Noah Lancien
60d9bda89f Merge branch 'trunk' 2026-10-22 17:56:23 +02:00
Noah Lancien
99d2df7711 Merge branch 'develop' into trunk 2026-10-22 17:56:00 +02:00
Noix
d3e4075dc7 Merge branch 'issue-61' into 'develop'
chore: ⬆️ bump php to 8.5 #61

See merge request ldc-web/projets/lionor!56
2026-10-22 15:54:08 +00:00
Alexis Gousseau
9081187949
fix: 🚨 resolve phpstan errors 2026-10-22 17:37:44 +02:00
Alexis Gousseau
2dc3dd08aa
chore: ⬆️ bump php to 8.5 #61 2026-10-22 17:37:28 +02:00
K Muller
af077eb74a Merge branch 'trunk' 2026-07-15 10:08:57 +02:00
K Muller
7f71e3550d Merge branch 'develop' into trunk 2026-07-09 10:05:01 +02:00
Karl M'muller
2bc0d1b30d Merge branch 'fix-local' into 'develop'
fix: 🐛 local page was not a static page

See merge request ldc-web/projets/lionor!55
2026-07-09 08:04:38 +00:00
K Muller
a7fa202f4c fix: 🐛 local page was not a static page 2026-07-09 09:57:52 +02:00
K Muller
9cd61a1c6e Merge branch 'trunk' 2026-07-08 09:34:19 +02:00
K Muller
b061599ab1 Merge branch 'develop' into trunk 2026-06-18 15:22:30 +02:00
karl.muller
0a94df329a Merge branch 'fix-docker-multistage' into 'develop'
fix: 🐛 add multi stage for php 8.3

See merge request ldc-web/projets/lionor!54
2026-06-18 13:21:56 +00:00
K Muller
2f99b0f5fa fix: 🐛 add multi stage for php 8.3 2026-06-18 15:10:51 +02:00
K Muller
04910c331e Merge branch 'develop' into trunk 2026-06-18 11:29:01 +02:00
karl.muller
d6b77b6a50 Merge branch '56-admin-content' into 'develop'
feat: ✨ add global text administration

See merge request ldc-web/projets/lionor!53
2026-06-18 09:27:39 +00:00
Corentin Huet
1152dcb995 feat: ✨ add global text administration 2026-06-11 17:04:36 +02:00
Vincent BRIDIER
608ede77e2 Merge branch 'trunk' 2026-02-09 11:45:03 +01:00
Vincent BRIDIER
7875d41a7b Merge branch 'develop' into trunk 2026-02-09 11:44:54 +01:00
Vincent BRIDIER
5f72787e37 Merge branch 'issue-48' into 'develop'
feat ✨ make seo title dynamic in breadcrumbs

See merge request ldc-web/projets/lionor!51
2026-02-09 10:43:41 +00:00
System User
1f84322ea9 revert variable 2026-02-06 11:53:34 +01:00
System User
a7896ef3fa fix ✨: make seo title dynamic in breadcrumbs 2026-02-06 11:34:12 +01:00
Noah Lancien
0c03ee97e7 Merge branch 'develop' into trunk 2026-02-05 11:36:02 +01:00
noah.lancien
0eaf145401 Merge branch '55-wording' into 'develop'
feat: ✨ update mentions 100% local chicken

See merge request ldc-web/projets/lionor!50
2026-02-05 10:35:04 +00:00
Corentin Huet
344389b5b5 feat: ✨ update mentions 100% local chicken 2026-01-30 17:16:10 +01:00
Noah Lancien
c3dd439ea0 Merge branch 'develop' into trunk 2026-01-27 10:20:55 +01:00
Noah Lancien
affddeea9f ci: migrate to mysql + limit cronjobs 2026-01-27 10:20:39 +01:00
Noah Lancien
9e83930c18 fix: resized local logo 2026-01-27 10:20:07 +01:00
Noah Lancien
6ac4d8b7df Merge branch 'develop' into trunk 2026-01-15 09:47:48 +01:00
noah.lancien
3838c7eab6 Merge branch '53-remove-cert-part' into 'develop'
feat: remove cert part in footer

See merge request ldc-web/projets/lionor!48
2026-01-15 08:47:01 +00:00
noah.lancien
e17135a7af Merge branch '50-update-logo-local' into 'develop'
feat: updated 100% local logo

See merge request ldc-web/projets/lionor!49
2026-01-15 08:45:50 +00:00
Noah Lancien
8df51ef55d Merge branch 'trunk' 2026-01-12 15:01:38 +01:00
Noah Lancien
12fb5dfd12 Merge branch 'develop' into trunk 2026-01-12 14:53:19 +01:00
Noah Lancien
ef41add69a feat: updated admin-secure 2026-01-12 14:53:00 +01:00
Noah Lancien
ee1a56e232 Merge branch 'develop' into trunk 2026-01-09 09:25:34 +01:00
Noah Lancien
3e8e6cb40b ci: added ci variables to define pod resources 2026-01-09 09:25:11 +01:00
Noah Lancien
60fcd21ca1 feat: updated 100% local logo 2026-01-08 10:00:17 +01:00
Noah Lancien
e29f423e98 fix: remove unused classes 2026-01-08 08:51:51 +01:00
Noah Lancien
b4188ba963 feat: remove cert part in footer 2026-01-07 17:37:43 +01:00
Noah Lancien
2e3d03e53b Merge branch 'trunk' 2025-11-20 10:22:09 +01:00
Noah Lancien
3fc041d9a8 Merge branch 'develop' into trunk 2025-11-12 17:11:19 +01:00
Noah Lancien
6f2ebfb4b6 ci: updated port from 443 to 80 + docker image version 2025-11-12 17:10:59 +01:00
Noah Lancien
c1713e38a2 Merge branch 'develop' into trunk 2025-11-12 16:54:10 +01:00
Noah Lancien
d7e5f7b4ab ci: added staging-resources.yaml 2025-11-12 16:53:56 +01:00
Noah Lancien
7a14c0542f Merge branch 'develop' into trunk 2025-11-12 16:44:07 +01:00
noah.lancien
20366f7f14 Merge branch '51-logo-apvf' into 'develop'
fix: remove apvf in footer

See merge request ldc-web/projets/lionor!47
2025-11-12 15:41:44 +00:00
noah.lancien
4a9bfd1a31 Merge branch '49-allergene' into 'develop'
fix: product texts could not be bolded

See merge request ldc-web/projets/lionor!46
2025-11-12 15:41:26 +00:00
Noah Lancien
073ff3da04 fix: remove apvf in footer 2025-11-05 16:33:48 +01:00
Corentin Huet
68cc0d1581 fix: product texts could not be bolded 2025-11-03 12:56:56 +01:00
Noah Lancien
6dbedc0af9 Merge branch 'trunk' 2025-04-16 09:33:30 +02:00
Noah Lancien
c33ad114f0 Merge branch 'develop' into trunk 2025-04-16 09:18:51 +02:00
noah.lancien
0ffce35844 Merge branch 'issue-46' into 'develop'
fix: 🐛 add leaflet package and resolve csp errors

See merge request ldc-web/projets/lionor!45
2025-04-16 07:17:08 +00:00
Alexis Gousseau
2284d3ca0f fix: 🐛 add leaflet package and resolve csp errors 2025-04-15 12:48:19 +02:00
Noah Lancien
6f02efb2d9 Merge branch 'trunk' 2025-03-17 14:38:01 +01:00
Noah Lancien
c836b36751 Merge branch 'develop' into trunk 2025-03-17 12:21:54 +01:00
Noah Lancien
aa66e8977e fix: crash because of a ',' 2025-03-17 12:21:41 +01:00
Noah Lancien
2dc7a1ee86 Merge branch 'develop' into trunk 2025-03-17 11:31:18 +01:00
noah.lancien
3b50fc3923 Merge branch 'issue-45-sso' into 'develop'
feat: Added SSO for BO

See merge request ldc-web/projets/lionor!43
2025-03-17 10:28:54 +00:00
Noah Lancien
c1f966bdd4 Merge branch 'trunk' 2025-03-11 12:51:13 +01:00
Noah Lancien
9fcf5f8c08 Merge branch 'develop' into trunk 2025-03-11 12:35:39 +01:00
noah.lancien
a30b3b5ca4 Merge branch 'print-recipe-fix' into 'develop'
fix: recipe printsheet is now downloaded

See merge request ldc-web/projets/lionor!44
2025-03-11 11:35:15 +00:00
Noah Lancien
18839e26ed fix: recipe printsheet is now downloaded 2025-03-10 12:06:51 +01:00
Noah Lancien
4324dd1e8a feat: Added SSO for BO 2025-03-07 11:29:42 +01:00
Vincent BRIDIER
1bb3c146b3 Merge branch 'laravel-11' into 'develop'
Bump laravel 11 + php 8.3 + added sitemap generator + added robots.txt

See merge request ldc-web/projets/lionor!42
2025-03-06 16:24:07 +00:00
Corentin Huet
9339abd082 chore: 📦️bump laravel 11 + php 8.3 + added sitemap generator + added robots.txt 2025-03-06 17:11:01 +01:00
Noah Lancien
eeff942229 Merge branch 'trunk' 2025-01-14 15:30:39 +01:00
Noah Lancien
db54ff36f9 Merge branch 'develop' into trunk 2025-01-14 15:30:29 +01:00
Noah Lancien
1062fe432a fix: composer update to fix CVE 2025-01-14 15:29:32 +01:00
Noah Lancien
fba3a18795 Merge branch 'trunk' 2025-01-14 15:21:23 +01:00
Noah Lancien
14d536f58d Merge branch 'develop' into trunk 2025-01-14 15:21:09 +01:00
Noah Lancien
a562ecf249 ci: Added 'concurrencyPolicy: Forbid' in cron jobs 2025-01-14 15:20:58 +01:00
Noah Lancien
b234ecefab Merge branch 'trunk' 2024-09-06 11:58:41 +02:00
Noah Lancien
dfa574c548 Merge branch 'develop' into trunk 2024-09-06 11:46:38 +02:00
Noah Lancien
9ef2204305 fix: interpret html for description and preparation fields in recipe print 2024-09-06 11:46:23 +02:00
Noah Lancien
08480aed92 Merge branch 'develop' into trunk 2024-09-06 10:55:43 +02:00
Vincent BRIDIER
5c08d0c34c Merge branch 'issue-35' into 'develop'
create a RecipesIndexRequest to handle nullable case

See merge request ldc-web/projets/lionor!25
2024-09-06 08:52:05 +00:00
nicolas.demangeat
9787710128 create a RecipesIndexRequest to handle nullable case Close #35 2024-09-06 08:52:05 +00:00
Vincent BRIDIER
1c735f1200 Merge branch 'phpstan' into 'develop'
Phpstan

See merge request ldc-web/projets/lionor!24
2024-09-06 08:30:55 +00:00
noah.lancien
8278419606 Phpstan 2024-09-06 08:30:55 +00:00
Vincent BRIDIER
fb062e11aa Merge branch 'issue-31' into 'develop'
Issue 31

See merge request ldc-web/projets/lionor!23
2024-09-06 07:59:44 +00:00
sebastien.hattry
a5f4d522c4 Close #31 - Print recipe 2024-09-06 07:59:44 +00:00
Vincent BRIDIER
55e5871096
Merge branch 'trunk' 2024-07-19 09:27:58 +02:00
Vincent BRIDIER
d75d51af8c
Merge branch 'develop' into trunk 2024-07-19 09:27:28 +02:00
Vincent BRIDIER
07be96c762 Merge branch 'issue-42' into 'develop'
Issue 42

See merge request ldc-web/projets/lionor!41
2024-07-19 07:27:12 +00:00
Nicolas Demangeat
170f1b2552 fix: title for Static Pages BO 2024-07-19 09:24:01 +02:00
Nicolas Demangeat
566ff421aa Merge branch 'develop' into issue-42 2024-07-19 09:13:34 +02:00
Vincent BRIDIER
0943b300d1
Merge branch 'develop' into trunk 2024-07-19 08:40:17 +02:00
Vincent BRIDIER
821cf7eabd Merge branch 'issue-42' into 'develop'
feat: add static pages for legals and confidentiality

See merge request ldc-web/projets/lionor!40
2024-07-19 06:39:51 +00:00
nicolas.demangeat
93ec9f9396 feat: add static pages for legals and confidentiality 2024-07-19 06:39:51 +00:00
Nicolas Demangeat
61675911f0 fix: change cookie notice color button 2024-07-18 16:52:03 +02:00
Nicolas Demangeat
d71530cb3d fix: forget to save the last change + phpcs 2024-07-18 16:24:53 +02:00
Nicolas Demangeat
85ffb4e762 feat: add static pages for legals and confidentiality 2024-07-18 16:20:56 +02:00
Vincent BRIDIER
bfcffbb223
Merge branch 'develop' into trunk 2024-07-18 10:05:34 +02:00
Vincent BRIDIER
60557ccba6 Merge branch 'bump-cookie' into 'develop'
fix: update cookie notice 6.0.3

See merge request ldc-web/projets/lionor!39
2024-07-18 08:04:30 +00:00
nicolas.demangeat
4e7268ae44 fix: update cookie notice 6.0.3 2024-07-18 08:04:30 +00:00
Thibault Raquet
6128e763eb Merge branch 'trunk' into 'master'
Trunk

See merge request ldc-web/projets/lionor!38
2024-07-10 07:55:05 +00:00
Thibault Raquet
4de27ac36c Merge branch 'develop' into 'trunk'
Update manifest.yaml

See merge request ldc-web/projets/lionor!37
2024-07-10 07:53:18 +00:00
Thibault Raquet
739e863667 Update manifest.yaml 2024-07-10 07:51:47 +00:00
Thibault Raquet
f1c1f185e3 Merge branch 'trunk' into 'master'
Trunk

See merge request ldc-web/projets/lionor!36
2024-07-09 13:43:15 +00:00
Thibault Raquet
0878350923 Merge branch 'develop' into 'trunk'
Update manifest.yaml

See merge request ldc-web/projets/lionor!35
2024-07-09 13:42:38 +00:00
Thibault Raquet
90040f9fb4 Update manifest.yaml 2024-07-09 13:41:35 +00:00
Vincent BRIDIER
1716fb8adf Merge branch 'trunk' into 'master'
Trunk

See merge request ldc-web/projets/lionor!34
2024-07-05 13:15:02 +00:00
Louis Rossignol
80c5b7d916 Merge branch 'develop' into 'trunk'
Préparation migration

See merge request ldc-web/projets/lionor!33
2024-07-01 08:02:56 +00:00
LOR
470f64e5ca Préparation migration 2024-07-01 09:53:18 +02:00
Vincent BRIDIER
e580f7f56f
Merge branch 'develop' into trunk 2024-06-24 14:03:11 +02:00
Vincent BRIDIER
7f7596ba2c Merge branch 'rgpd-06-2024' into 'develop'
fix: Added privacy policy link in cookie-notice

See merge request ldc-web/projets/lionor!32
2024-06-24 12:02:47 +00:00
Noah Lancien
1811553c79 fix: Added privacy policy link in cookie-notice 2024-06-24 10:47:36 +02:00
Vincent BRIDIER
eafaffe116
Merge branch 'develop' into trunk 2024-06-21 10:21:28 +02:00
Vincent BRIDIER
2c8335dfbd Merge branch 'rgpd-06-2024' into 'develop'
Mentions légales et CGU + font awesome

See merge request ldc-web/projets/lionor!31
2024-06-21 08:21:11 +00:00
Noah Lancien
206438ffc3 fix: Added missing font awesome icons 2024-06-21 09:54:05 +02:00
Noah Lancien
38ad8c8d0b fix: added 'et CGU' to legals link in the footer 2024-06-21 09:53:40 +02:00
Vincent BRIDIER
ff44ac613b
Merge branch 'develop' into trunk 2024-06-21 08:55:21 +02:00
Vincent BRIDIER
4afa7eb1e8 Merge branch 'issue-32-imgs' into 'develop'
Replaced unoptimized pictures

See merge request ldc-web/projets/lionor!22
2024-06-21 06:50:03 +00:00
Vincent BRIDIER
428e92ca44 Merge branch 'issue-38-responsive' into 'develop'
fix: #38 - Responsive was broken : axis x was sometimes larger than 100vw

See merge request ldc-web/projets/lionor!29
2024-06-21 06:48:05 +00:00
Vincent BRIDIER
1867442f98 Merge branch 'rgpd-06-2024' into 'develop'
fix: #40 - Updated the contact form text

See merge request ldc-web/projets/lionor!30
2024-06-21 06:47:14 +00:00
Noah Lancien
ba2de1bd90 fix: #40 - Updated the contact form text 2024-06-20 18:10:41 +02:00
Noah Lancien
cb3f61501b fix: #38 - Responsive was broken : axis x was sometimes larger than 100vw 2024-06-20 17:55:18 +02:00
Vincent BRIDIER
1c67975317 Merge branch 'develop' into trunk 2024-06-20 17:48:05 +02:00
Vincent BRIDIER
334431bb5a Merge branch 'rgpd-06-2024' into 'develop'
Fix: #39 #40 - Bumped cookie notice to 6.0.1

See merge request ldc-web/projets/lionor!28
2024-06-20 15:47:21 +00:00
Noah Lancien
af13501909 Fix: #39 #40 - Bumped cookie notice to 6.0.1 2024-06-20 17:13:58 +02:00
Vincent BRIDIER
0a7809ed7f Merge branch 'trunk' 2024-05-30 11:14:51 +02:00
Vincent BRIDIER
a1424d0348 Merge branch 'develop' into trunk 2024-05-30 11:14:28 +02:00
Vincent BRIDIER
4a609d8285 Merge branch 'add-limit-to-modal' into 'develop'
Added a condition to hide the modal after 2024-06-30

See merge request ldc-web/projets/lionor!27
2024-05-30 09:13:48 +00:00
Noah Lancien
613d853229 Updated readme to remember to remove the dead code after the 2024-06-30 2024-05-30 10:28:37 +02:00
Noah Lancien
95e0d8841e Added a condition to hide the modal after 2024-06-30 2024-05-30 10:02:20 +02:00
Vincent BRIDIER
d495c928c8
Merge branch 'trunk' 2024-05-27 15:42:15 +02:00
Vincent BRIDIER
d4c9c96173
Merge branch 'develop' into trunk 2024-05-27 15:10:08 +02:00
Vincent BRIDIER
7be8c8fc17 Merge branch 'ad-modal-2024' into 'develop'
Added an advertising modal for the game

See merge request ldc-web/projets/lionor!26
2024-05-27 13:09:47 +00:00
Noah Lancien
befd6848e0 Added an advertising modal for the game 2024-05-27 14:46:07 +02:00
Vincent BRIDIER
ba94a40144 Update manifest 2023-12-11 11:14:32 +00:00
Noah Lancien
984746ce41 Replaced unoptimized pictures 2023-11-10 16:29:28 +01:00
Vincent BRIDIER
59b3db0ae1
Merge branch 'trunk' 2023-11-08 11:22:43 +01:00
Vincent BRIDIER
ac7335c312
Merge branch 'develop' into trunk 2023-11-08 11:22:26 +01:00
Vincent BRIDIER
849a21be60 Fix sitemap error 2023-11-08 10:21:55 +00:00
Vincent BRIDIER
9177972a4c Merge branch 'trunk' 2023-11-03 09:47:39 +01:00
Vincent BRIDIER
dec24e1342 Merge branch 'develop' into trunk 2023-11-03 09:47:25 +01:00
Vincent BRIDIER
6204166b00 Fix #33 2023-11-03 08:05:21 +00:00
Vincent BRIDIER
949c80e6d3
Merge branch 'trunk' 2023-11-02 14:49:21 +01:00
Vincent BRIDIER
a37537b48b
Merge branch 'develop' into trunk 2023-11-02 14:49:03 +01:00
Vincent BRIDIER
49c0bac402
Fix reCaptcha for contact 2023-11-02 14:48:50 +01:00
Vincent BRIDIER
d1ff745707
Merge branch 'trunk' 2023-11-02 12:41:31 +01:00
Vincent BRIDIER
52f144d96e
Merge branch 'develop' into trunk 2023-11-02 12:41:10 +01:00
Vincent BRIDIER
1917f514b5
Remove useless cookie notice file 2023-11-02 12:23:54 +01:00
Vincent BRIDIER
937d267b74
Remove false conception credit 2023-11-02 12:21:36 +01:00
Jérémy Munsch
689fe2b23d
Merge branch 'trunk' 2023-10-31 15:14:50 +01:00
Jérémy Munsch
ecb359a73a
Merge branch 'develop' into trunk 2023-10-25 12:22:41 +02:00
Jérémy Munsch
994834d812
Fix CSP Rules 2023-10-25 12:22:30 +02:00
Jérémy Munsch
bef0cc1cc7
Merge branch 'develop' into trunk 2023-10-25 09:36:47 +02:00
Jérémy Munsch
0284861336
fix typo on engaements 2023-10-25 09:36:40 +02:00
Jérémy Munsch
2c94a47891
Merge branch 'develop' into trunk 2023-10-24 13:41:23 +02:00
Jérémy Munsch
9b1908fc97
Fixes #25 Product description as optional 2023-10-24 13:41:03 +02:00
Jérémy Munsch
3f853dd746
Fixes #26 remove forgotten button know more 2023-10-24 13:36:14 +02:00
Jérémy Munsch
60deae8b64
Merge branch 'develop' into trunk 2023-10-23 12:47:04 +02:00
Jérémy Munsch
f4cf0fe26d
Merge branch 'issue-28' into develop 2023-10-23 12:42:30 +02:00
Jérémy Munsch
d932c7125c
Merge branch 'issue-26' into develop 2023-10-23 12:42:25 +02:00
Jérémy Munsch
cf3945c939
Merge branch 'issue-27' into develop 2023-10-23 12:42:20 +02:00
Jérémy Munsch
aeb80ba7e8
Merge branch 'issue-25' into develop 2023-10-23 12:42:11 +02:00
Jérémy Munsch
86c82c4015
Merge branch 'issue-24' into develop 2023-10-23 12:42:07 +02:00
Jérémy Munsch
3dce4ee563
Merge branch 'issue-22' into develop 2023-10-23 12:42:01 +02:00
Jérémy Munsch
ac9726632c
Closes #22
Page Engagments
- modification images
Page 100% local
- modification images
2023-10-23 12:40:38 +02:00
Jérémy Munsch
1bb2a3fe61
Closes #24
Header menu wording change
2023-10-23 12:13:28 +02:00
Jérémy Munsch
56e3583d19
Closes #25
Products description can be empty
2023-10-23 12:11:09 +02:00
Jérémy Munsch
f7250ca0b7
Closes #27
Add cert img on footer
Fixes scss certs sizes
2023-10-23 12:06:29 +02:00
Jérémy Munsch
8a49d0296c
Closes #26
Engagements blade remove some know more buttons
2023-10-23 12:00:39 +02:00
Jérémy Munsch
79a4e573cb
Closes #28 Product.show banner is dynamic 2023-10-23 11:58:02 +02:00
Vincent BRIDIER
80294115f6
Merge branch 'develop' into trunk 2023-10-11 12:13:13 +02:00
Vincent BRIDIER
506bf2f90b Merge branch 'issue-23' into 'develop'
Issue 23

See merge request ldc-web/projets/lionor!21
2023-10-11 10:11:46 +00:00
Vincent BRIDIER
77780724cc
Merge branch 'develop' into trunk 2023-09-25 15:04:21 +02:00
Vincent BRIDIER
e78738ead9 Merge branch 'develop' into trunk 2023-09-07 15:19:02 +02:00
Vincent BRIDIER
bed321e492
Merge branch 'develop' into trunk 2023-09-05 17:17:12 +02:00
Vincent BRIDIER
57638149d3
Merge branch 'develop' into trunk 2023-09-05 17:04:18 +02:00
Vincent BRIDIER
879197c215
Merge branch 'develop' into trunk 2023-08-28 11:01:36 +02:00
Vincent BRIDIER
6bf8bd2c31
Merge branch 'develop' into trunk 2023-06-23 17:05:23 +02:00
218 changed files with 9752 additions and 6762 deletions

View file

@ -43,5 +43,15 @@ GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=
# LDC Web Admin Secure
LDC_WEB_API_KEY="MY_KEY"
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
# SAML
SAML2_DEFAULT_TENANT_UUID=
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=

2
.gitignore vendored
View file

@ -6,6 +6,8 @@
/public/uploads
/public/cache
/public/sitemap.xml
/public/marker-icon.png
/public/marker-shadow.png
/storage/*.key
/vendor
.env

View file

@ -7,7 +7,7 @@ include:
file: 'security/security.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'build/build.yml'
file: 'build/docker-multistage.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'test/test.yml'

View file

@ -1,5 +1,45 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest
COPY --chown=www-data:www-data . /app/srcs
FROM composer:2.8 AS vendor
ARG GITLAB_USER
ARG GITLAB_PASSWORD
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
RUN composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist
FROM node:20.18 AS frontend
ARG GITLAB_PASSWORD
RUN mkdir -p /app/public/build
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
WORKDIR /app
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci && npm run prod
ARG PHP_8_5_TAG=latest
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
ARG USER=www-data
COPY --chown=${USER}:${USER} . /app/srcs
COPY --chown=${USER}:${USER} ./resources/favicon /app/srcs/public
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root
RUN chmod -R 755 /app/srcs/storage
USER www-data
USER ${USER}

View file

@ -15,8 +15,6 @@ php artisan schedule:run
```
ldcweb:admin-secure-sync
```
### Refresh-Sitemap
#### Available Commands
```
sitemap:refresh --dry-run
```
## TODO
Après le 30 juin 2024, retirer le code de la modal concernant le jeu (cf [ce commit](https://lab.s-web.fr/ldc-web/projets/lionor/-/commit/befd6848e0bb31bf9d79bac2a1f30d3015d8b14b))

View file

@ -15,10 +15,10 @@ class HtmlColor implements CastsAttributes
/**
* Cast the given value.
*
* @param Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return string
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
@ -32,10 +32,10 @@ class HtmlColor implements CastsAttributes
/**
* Prepare the given value for storage.
*
* @param Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return integer
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing

View file

@ -11,10 +11,10 @@ class RgbaColor extends HtmlColor
/**
* Cast the given value.
*
* @param Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return string
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
@ -24,6 +24,7 @@ class RgbaColor extends HtmlColor
public function get($model, $key, $value, $attributes)
{
// phpcs:enable
/** @var \Illuminate\Support\Collection<array-key, float> */
$intValues = collect(\str_split($this->zeropad(\dechex(\intval($value)), 8), 2))->map(function ($hex) {
return intval(\hexdec($hex));
});
@ -35,10 +36,10 @@ class RgbaColor extends HtmlColor
/**
* Prepare the given value for storage.
*
* @param Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return integer
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing

View file

@ -1,177 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a backend admin user';
/**
* The firstname of the new user.
*
* @var string
*/
protected $firstname;
/**
* The lastname of the new user.
*
* @var string
*/
protected $lastname;
/**
* The email of the new user.
*
* @var string
*/
protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
$this->firstname = null;
$this->addFirstname();
$this->lastname = null;
$this->addLastname();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$user = new User([
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'email' => $this->email,
'password' => $this->password,
]);
$user->saveOrFail();
$this->info('User created 👌');
return 0;
}
/**
* Add a firstname to the user.
*
* @return void
*/
private function addFirstname(): void
{
while (is_null($this->firstname)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a lastname to the user.
*
* @return void
*/
private function addLastname(): void
{
while (is_null($this->lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
}

View file

@ -0,0 +1,204 @@
<?php
namespace App\Console\Commands;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
class UserCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a backend admin user';
/**
* The first name of the new user.
*
* @var string|null
*/
protected $first_name;
/**
* The last name of the new user.
*
* @var string|null
*/
protected $last_name;
/**
* The email of the new user.
*
* @var string|null
*/
protected $email;
/**
* The role of the new user.
*
* @var string|null
*/
protected $role;
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$this->first_name = null;
$this->addFirstName();
$this->last_name = null;
$this->addLastName();
$this->email = null;
$this->addEmail();
$this->role = null;
$this->addRole();
$user = new User([
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'role' => $this->role,
'published' => true,
]);
$user->saveOrFail();
$this->info('User created 👌');
}
/**
* Add a first name to the user.
*
* @return void
*/
private function addFirstName(): void
{
while (is_null($this->first_name)) {
try {
$tmp = $this->ask('Type the wanted user first name', 'Admin');
$validator = Validator::make(['first_name' => $tmp], [
'first_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->first_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid first name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a last name to the user.
*
* @return void
*/
private function addLastName(): void
{
while (is_null($this->last_name)) {
try {
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
$validator = Validator::make(['last_name' => $tmp], [
'last_name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->last_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid last_name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a role to the user.
*
* @return void
*/
private function addRole(): void
{
while (is_null($this->role)) {
try {
$tmp = $this->choice(
'Select his role',
[
Str::ucFirst(RoleEnum::conceptor->label()),
Str::ucFirst(RoleEnum::admin->label()),
Str::ucFirst(RoleEnum::visitor->label())
],
Str::ucFirst(RoleEnum::visitor->label()),
);
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
$tmp = RoleEnum::conceptor;
}
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
$tmp = RoleEnum::admin;
}
if ($tmp === Str::ucFirst(RoleEnum::visitor->label())) {
$tmp = RoleEnum::visitor;
}
$validator = Validator::make(['role' => $tmp], [
'role' => 'required'
]);
$validator->validate();
$this->role = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Enums\ActivityLogs;
use App\Traits\Enums\BaseEnum;
enum ActivityLogsEventEnum: int
{
use BaseEnum;
case created = 1;
case updated = 2;
case duplicated = 3;
case deleted = 4;
/**
* Optionnal labels definition
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::created->name => 'Création',
self::updated->name => 'Mise à jour',
self::duplicated->name => 'Duplication',
self::deleted->name => 'Suppression',
];
/**
* Custom added classes definition
*/
private const BOOTSTRAPCLASS = [
self::created->name => 'success',
self::updated->name => 'warning',
self::duplicated->name => 'secondary',
self::deleted->name => 'danger',
];
/**
* Get Class
*
* @return string
*/
public function bootstrapClass(): string
{
return self::BOOTSTRAPCLASS[$this->name];
}
}

View file

@ -17,6 +17,7 @@ enum CookingModeEnum: int
* Enum labels
*
* @return array
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::withoutCooking->name => 'Sans cuisson',

View file

@ -12,6 +12,12 @@ enum DifficultyEnum: int
case medium = 2;
case hard = 3;
/**
* Enum labels
*
* @return array
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::easy->name => 'Facile',
self::medium->name => 'Moyen',

View file

@ -15,6 +15,12 @@ enum NutriscoreEnum: int
case dLetter = 5;
case eLetter = 6;
/**
* Enum labels
*
* @return array
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::noLetter->name => 'N/A',
self::aLetter->name => 'A',
@ -24,6 +30,11 @@ enum NutriscoreEnum: int
self::eLetter->name => 'E'
];
/**
* Enum stickers
*
* @return array
*/
private const STICKERS = [
self::noLetter->name => '',
self::aLetter->name => 'resources/assets/images/nutriscore/logo-nutriscore-A.png',

View file

@ -0,0 +1,16 @@
<?php
namespace App\Enums\Pagination;
use App\Traits\Enums\BaseEnum;
enum ItemsPerPaginationEnum: int
{
use BaseEnum;
case five = 5;
case twelve = 12;
case twentyfive = 25;
case fifty = 50;
case onehundred = 100;
}

View file

@ -13,6 +13,12 @@ enum PreparationTimeEnum: int
case interval3 = 3;
case interval4 = 4;
/**
* Enum labels
*
* @return array
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::interval1->name => '0-30 min',
self::interval2->name => '30min - 2h',
@ -20,6 +26,11 @@ enum PreparationTimeEnum: int
self::interval4->name => '> 4h',
];
/**
* Enum times
*
* @return array
*/
private const TIMES = [
self::interval1->name => ['min' => 0, 'max' => 30],
self::interval2->name => ['min' => 31, 'max' => 120],
@ -40,7 +51,7 @@ enum PreparationTimeEnum: int
/**
* Get enum images
*
* @return array<string>
* @return array<string, array<string, int>>
*/
public static function toTimes(): array
{

View file

@ -20,6 +20,12 @@ enum SignsEnum: int
case uexpress = 10;
case netto = 11;
/**
* Enum labels
*
* @return array
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::leclerc->name => 'Leclerc',
self::auchan->name => 'Auchan',

View file

@ -0,0 +1,31 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum StaticPageType: int
{
use BaseEnum;
case legals = 1;
case confidentiality = 2;
case homepage = 3;
case our_products = 4;
case our_commitments = 5;
case banner_section = 6;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::legals->name => 'Mentions légales',
self::confidentiality->name => 'Confidentialité',
self::homepage->name => 'Accueil',
self::our_products->name => 'Nos produits',
self::our_commitments->name => 'Nos engagements',
self::banner_section->name => 'Section bannière',
];
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Enums\Users;
use App\Traits\Enums\BaseEnum;
enum RoleEnum: int
{
use BaseEnum;
case conceptor = 1;
case admin = 2;
case visitor = 99;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::conceptor->name => 'enums.user-role.conceptor',
self::admin->name => 'enums.user-role.admin',
self::visitor->name => 'enums.user-role.visitor',
];
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
class ActivityLogController extends Controller
{
use ChangesModelOrder;
use UpdateModelPublished;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$this->authorize('isConceptor');
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = ActivityLog::query()->with('user');
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'model_class',
);
}
$searchFields = \implode(', ', [
trans('validation.custom.target'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$activitylogModels = $this->paginate($query);
return view('bo.pages.activity_logs.index', compact('activitylogModels', 'search', 'searchFields'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ActivityLog $activity_log
* @return \Illuminate\Contracts\View\View
*/
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View
{
$this->authorize('isConceptor');
$targetModelClass = $activity_log->model_class;
$targetModel = $activity_log->model_class::where(
(new $targetModelClass())->getRouteKeyName(),
$activity_log->model_id
)->first();
return view('bo.pages.activity_logs.show', [
'activitylogModel' => $activity_log,
'targetModel' => $targetModel,
]);
}
}

View file

@ -16,9 +16,9 @@ class CertificationController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $certifications */
$certifications = Certification::query();
@ -43,9 +43,9 @@ class CertificationController extends Controller
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(): \Illuminate\Contracts\View\View
{
return view('bo.pages.certifications.create');
}
@ -54,9 +54,9 @@ class CertificationController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Certifications\StoreCertificationRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreCertificationRequest $request)
public function store(StoreCertificationRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$certification = new Certification();
@ -84,9 +84,9 @@ class CertificationController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Certification $certification
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Certification $certification)
public function edit(Certification $certification): \Illuminate\Contracts\View\View
{
return view('bo.pages.certifications.edit', [
'certificationModel' => $certification,
@ -98,10 +98,12 @@ class CertificationController extends Controller
*
* @param \App\Http\Requests\Bo\Certifications\UpdateCertificationRequest $request
* @param \App\Models\Certification $certification
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateCertificationRequest $request, Certification $certification)
{
public function update(
UpdateCertificationRequest $request,
Certification $certification
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $certification) {
$certification->fill($request->validated());
@ -126,9 +128,9 @@ class CertificationController extends Controller
* Remove the specified resource from storage.
*
* @param \App\Models\Certification $certification
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Certification $certification)
public function destroy(Certification $certification): \Illuminate\Http\RedirectResponse
{
if ($certification->deleteOrFail()) {
return redirect()

View file

@ -3,7 +3,6 @@
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
class DashboardController extends Controller
{
@ -12,7 +11,7 @@ class DashboardController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): View
public function index(): \Illuminate\Contracts\View\View
{
return view('bo.pages.dashboard');
}

View file

@ -22,9 +22,9 @@ class ProductController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $products */
$products = Product::query();
@ -50,9 +50,9 @@ class ProductController extends Controller
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(): \Illuminate\Contracts\View\View
{
$ranges = Range::all();
$certifications = Certification::all();
@ -65,9 +65,9 @@ class ProductController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreProductRequest $request)
public function store(StoreProductRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$product = new Product();
@ -97,9 +97,9 @@ class ProductController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Product $product)
public function edit(Product $product): \Illuminate\Contracts\View\View
{
$ranges = Range::all();
$certifications = Certification::all();
@ -118,9 +118,9 @@ class ProductController extends Controller
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateProductRequest $request, Product $product)
public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $product) {
$product->fill($request->validated());
@ -148,9 +148,9 @@ class ProductController extends Controller
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Product $product)
public function destroy(Product $product): \Illuminate\Http\RedirectResponse
{
if ($product->deleteOrFail()) {
return redirect()

View file

@ -6,23 +6,21 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Range;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RangeController extends Controller
{
use ChangesModelOrder;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $ranges */
$ranges = Range::query();
@ -48,9 +46,9 @@ class RangeController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Range $range)
public function edit(Range $range): \Illuminate\Contracts\View\View
{
return view('bo.pages.ranges.edit', [
'rangeModel' => $range
@ -62,9 +60,9 @@ class RangeController extends Controller
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateRangeRequest $request, Range $range)
public function update(UpdateRangeRequest $request, Range $range): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());

View file

@ -20,9 +20,9 @@ class RecipeController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $recipes */
$recipes = Recipe::query();
@ -48,9 +48,9 @@ class RecipeController extends Controller
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(): \Illuminate\Contracts\View\View
{
$products = Product::all();
$recipes = Recipe::all();
@ -65,9 +65,9 @@ class RecipeController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Recipes\StoreRecipeRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreRecipeRequest $request)
public function store(StoreRecipeRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$recipe = new Recipe();
@ -98,12 +98,12 @@ class RecipeController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Recipe $recipe)
public function edit(Recipe $recipe): \Illuminate\Contracts\View\View
{
$products = Product::all();
$recipes = Recipe::where('id', '!=', $recipe->id)->get();
$recipes = Recipe::query()->where('id', '!=', $recipe->id)->get();
return view('bo.pages.recipes.edit', [
'recipeModel' => $recipe,
@ -117,9 +117,9 @@ class RecipeController extends Controller
*
* @param \App\Http\Requests\Bo\Recipes\UpdateRecipeRequest $request
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateRecipeRequest $request, Recipe $recipe)
public function update(UpdateRecipeRequest $request, Recipe $recipe): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $recipe) {
$recipe->products()->sync($request->product_list);
@ -149,9 +149,9 @@ class RecipeController extends Controller
* Remove the specified resource from storage.
*
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Recipe $recipe)
public function destroy(Recipe $recipe): \Illuminate\Http\RedirectResponse
{
DB::table('recipe_recipe')->where($recipe->recipes()->getRelatedPivotKeyName(), $recipe->id)->delete();
$recipe->products()->sync([]);

View file

@ -16,9 +16,9 @@ class ShopController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $shops */
$shops = Shop::query();
@ -46,9 +46,9 @@ class ShopController extends Controller
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(): \Illuminate\Contracts\View\View
{
return view('bo.pages.shops.create');
}
@ -57,9 +57,9 @@ class ShopController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Shops\StoreShopRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreShopRequest $request)
public function store(StoreShopRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$shop = new Shop();
@ -87,9 +87,9 @@ class ShopController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Shop $shop)
public function edit(Shop $shop): \Illuminate\Contracts\View\View
{
return view('bo.pages.shops.edit', [
'shopModel' => $shop,
@ -101,9 +101,9 @@ class ShopController extends Controller
*
* @param \App\Http\Requests\Bo\Shops\UpdateShopRequest $request
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateShopRequest $request, Shop $shop)
public function update(UpdateShopRequest $request, Shop $shop): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $shop) {
$shop->fill($request->validated());
@ -129,9 +129,9 @@ class ShopController extends Controller
* Remove the specified resource from storage.
*
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Shop $shop)
public function destroy(Shop $shop): \Illuminate\Http\RedirectResponse
{
if ($shop->deleteOrFail()) {
return redirect()

View file

@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest;
use App\Models\StaticPage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StaticPageController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(StaticPage::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = StaticPage::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'title',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.title'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$staticPageModelList = $this->paginate($query);
return view('bo.pages.static_pages.index', compact('staticPageModelList', 'search', 'searchFields'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Contracts\View\View
*/
public function edit(StaticPage $static_page): \Illuminate\Contracts\View\View
{
return view('bo.pages.static_pages.edit', ['staticPageModel' => $static_page]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest $request
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateStaticPageRequest $request, StaticPage $static_page): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $static_page) {
$validated = $request->validated();
$data = $static_page->data;
foreach ($validated['data'] as $key => $input) {
if (isset($input['value']) && is_array($input['value'])) {
$data[$key]['value'] = $input['value'];
}
}
$static_page->data = $data;
$static_page->fill(array_replace_recursive(
$static_page->getFillableFields($static_page),
$validated
));
if ($static_page->saveOrFail()) {
return redirect()->route('bo.static_pages.edit', $static_page)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1))
]));
}
return redirect()->route('bo.static_pages.edit', $static_page)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1))
]));
});
}
}

View file

@ -2,12 +2,16 @@
namespace App\Http\Controllers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Cache;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Support\Str;
class Controller extends BaseController
{
@ -29,7 +33,7 @@ class Controller extends BaseController
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
?callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
@ -107,10 +111,7 @@ class Controller extends BaseController
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
if (!in_array(request()->sort_col, $ignore)) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
@ -120,4 +121,30 @@ class Controller extends BaseController
}
return $query;
}
/**
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
protected function paginate(
Builder $query,
?ItemsPerPaginationEnum $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
$pagination = ToolboxHelper::getValidatedEnum(
Cache::get($cacheKey, $pagination->value()),
'pagination',
'\App\Enums\Pagination\ItemsPerPaginationEnum',
);
if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination);
}
$query = $query->paginate($pagination);
return $query;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\StaticPage;
class CommitmentController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::our_commitments)->first();
return view('fo.pages.engagments', compact('staticPageModel'));
}
}

View file

@ -2,9 +2,11 @@
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\Range;
use App\Models\Recipe;
use App\Models\StaticPage;
class HomeController extends Controller
{
@ -15,16 +17,19 @@ class HomeController extends Controller
*/
public function index()
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::homepage)->first();
// Gammes.
$ranges = Range::where('status', true)
$ranges = Range::query()->where('status', true)
->get();
// 3 Recettes.
$publishedRecipes = Recipe::where('status', true)
$publishedRecipes = Recipe::query()
->where('status', true)
->inRandomOrder()
->take(3)
->get();
return view('fo.pages.home', compact('ranges', 'publishedRecipes'));
return view('fo.pages.home', compact('staticPageModel', 'ranges', 'publishedRecipes'));
}
}

View file

@ -2,9 +2,11 @@
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Range;
use App\Models\StaticPage;
use Illuminate\Database\Eloquent\Builder;
class ProductsController extends Controller
@ -16,11 +18,13 @@ class ProductsController extends Controller
*/
public function index()
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::our_products)->first();
// Gammes.
$ranges = Range::where('status', true)
$ranges = Range::query()->where('status', true)
->get();
return view('fo.pages.products.index', compact('ranges'));
return view('fo.pages.products.index', compact('staticPageModel', 'ranges'));
}
/**
@ -35,7 +39,7 @@ class ProductsController extends Controller
$q->where('id', $publishedRange->id);
})
->where('status', true)
->paginate(9);
->paginate(9);
return view('fo.pages.products.range-products', [
'products' => $products,
@ -51,6 +55,7 @@ class ProductsController extends Controller
*/
public function show(Product $publishedProduct)
{
$publishedProduct->load('range');
$certifications = $publishedProduct->certifications()->where('status', true)->get();
$associatedRecipes = $publishedProduct->recipes()->where('status', true)->take(3)->get();
$productModel = $publishedProduct;

View file

@ -2,54 +2,64 @@
namespace App\Http\Controllers\Fo;
use App\Enums\CookingModeEnum;
use App\Enums\DifficultyEnum;
use App\Enums\PreparationTimeEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\RecipesIndexRequest;
use App\Models\Recipe;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use App\Http\Resources\Fo\Recipes\RecipeResource;
use Illuminate\Support\Str;
class RecipesController extends Controller
{
/**
* Return a list of filtered recipes.
*
* @param \Illuminate\Http\Request $request
* @param \App\Http\Requests\Fo\RecipesIndexRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(RecipesIndexRequest $request)
{
// Get recipes.
$filters_activated = $request->filtered_infos;
$query = Recipe::query();
$publishedRecipes = Recipe::when(
isset($filters_activated),
function ($query) use ($filters_activated) {
if ($filters_activated[0] >= 0) {
$diff_oper = '=';
} else {
$diff_oper = '>';
}
if ($filters_activated[1] >= 0) {
$min_time = PreparationTimeEnum::from($filters_activated[1])->time()['min'];
$max_time = PreparationTimeEnum::from($filters_activated[1])->time()['max'];
} else {
$min_time = 0;
$max_time = 9999;
}
if ($filters_activated[2] >= 0) {
$cook_oper = '=';
} else {
$cook_oper = '>';
}
$query->where('difficulty', $diff_oper, $filters_activated[0])
->whereBetween('preparation_time', [$min_time, $max_time])
->where('cooking_mode', $cook_oper, $filters_activated[2]);
}
)
/** @var \App\Enums\CookingModeEnum|null */
$cooking_mode_enum = !empty($request->get('cooking_mode_enum')) ?
CookingModeEnum::make($request->get('cooking_mode_enum')) :
null;
/** @var \App\Enums\PreparationTimeEnum|null */
$preparation_time_enum = !empty($request->get('preparation_time_enum')) ?
PreparationTimeEnum::make($request->get('preparation_time_enum')) :
null;
/** @var \App\Enums\DifficultyEnum|null */
$difficulty_enum = !empty($request->get('difficulty_enum')) ?
DifficultyEnum::make($request->get('difficulty_enum')) :
null;
if ($cooking_mode_enum) {
$query->where('cooking_mode', $cooking_mode_enum);
}
if ($preparation_time_enum) {
/** @var integer */
$min_time = Arr::get($preparation_time_enum->time(), 'min', 0);
/** @var integer */
$max_time = Arr::get($preparation_time_enum->time(), 'max', 9999);
$query->whereBetween('preparation_time', [$min_time, $max_time]);
}
if ($difficulty_enum) {
$query->where('difficulty', $difficulty_enum);
}
$publishedRecipes = $query
->where('status', true)
->orderBy('created_at')
->paginate(9);
return view('fo.pages.recipes.index', compact('publishedRecipes', 'filters_activated'));
return view('fo.pages.recipes.index', compact('publishedRecipes'));
}
/**
@ -64,7 +74,8 @@ class RecipesController extends Controller
$relatedRecipes = $publishedRecipe->recipes()->where('status', true)->get();
// 3 Recettes.
$publishedRecipes = Recipe::where('status', true)
$publishedRecipes = Recipe::query()
->where('status', true)
->where('id', '!=', $publishedRecipe->id)
->take(3)
->get();
@ -76,4 +87,102 @@ class RecipesController extends Controller
compact('recipeModel', 'products', 'relatedRecipes')
);
}
/**
* Print recipe informations
*
* @param \App\Models\Recipe $publishedRecipe
* @return \Symfony\Component\HttpFoundation\Response
*/
public function printRecipe(Recipe $publishedRecipe): \Symfony\Component\HttpFoundation\Response
{
$title = trans('Fiche commerciale du produit : :label - :appname', [
'label' => $publishedRecipe->name,
'appname' => \config('app.name', 'Lionor')
]);
$filename = trans('fiche-commerciale-:slug-:date', [
'slug' => Str::slug($publishedRecipe->name),
'date' => \now()->format('Y-m-d')
]);
/**
* ! Réponse mano car en fait \Mccarlosen\LaravelMpdf\LaravelMpdf
* ! ne retourne pas vraiemnt une http response, mais provoque une 500
* ! apres avoir header et echo dans le corps de la réponse php (Nasty trick)
*
* (From LeGauloisPro)
*/
return \response(
$this->generateRecipePdf($publishedRecipe, $title)->download($filename),
200,
[
'Content-Type' => 'application/pdf',
'Content-disposition' => "inline; filename=\"{$filename}\"",
'Cache-Control' => 'public, must-revalidate, max-age=0',
'Pragma' => 'public',
// * For glory
'X-Generator' => 'mPDF',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
]
);
}
/**
* Generate recipe PDF
*
* @param \App\Models\Recipe $recipe
* @param string $title
* @return \Mccarlosen\LaravelMpdf\LaravelMpdf
*/
private function generateRecipePdf(Recipe $recipe, string $title): \Mccarlosen\LaravelMpdf\LaravelMpdf
{
$tabRecipe = \json_decode(\json_encode(new RecipeResource($recipe)));
$mpdf = app('laravel-mpdf');
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
$pdf = $mpdf->loadView(
'fo.documents.recipe',
// * Dirty convert to object.
[
'title' => $title,
'recipe' => $tabRecipe
],
[],
[
'mode' => 'utf-8',
'format' => 'A4',
'default_font_size' => '12',
'default_font' => 'sans-serif',
// Recto Verso.
'margin_left' => 10,
'margin_right' => 10,
'mirrorMargins' => true,
'margin_top' => 25,
'margin_bottom' => 20,
'margin_header' => 0,
'margin_footer' => 0,
'orientation' => 'P',
'title' => $title,
'author' => \config('app.name', 'Lionor'),
'watermark' => '',
'show_watermark' => false,
'watermark_font' => 'sans-serif',
'display_mode' => 'fullpage',
'watermark_text_alpha' => 0.1,
'custom_font_dir' => '',
'custom_font_data' => [],
'auto_language_detection' => false,
'temp_dir' => rtrim(
ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(),
DIRECTORY_SEPARATOR
),
'pdfa' => false,
'pdfaauto' => false,
]
);
return $pdf;
}
}

View file

@ -14,9 +14,9 @@ class ShopController extends Controller
/**
* Get all shops
*
* @return json
* @return \Illuminate\Http\JsonResponse
*/
public function index()
public function index(): \Illuminate\Http\JsonResponse
{
if (request()->wantsJson()) {
return response()
@ -28,10 +28,10 @@ class ShopController extends Controller
/**
* Get the 5 nearest shops with the given coords in the request.
*
* @param App\Http\Requests\Fo\Shop\GetNearestShopRequest $request
* @return json
* @param \App\Http\Requests\Fo\Shop\GetNearestShopRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function getNearest(GetNearestShopRequest $request)
public function getNearest(GetNearestShopRequest $request): \Illuminate\Http\JsonResponse
{
if (!$request->wantsJson()) {
return abort(404);

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\StaticPage;
class StaticPageController extends Controller
{
/**
* Legal show
*
* @param \App\Models\StaticPage $legalPage
* @return \Illuminate\Contracts\View\View
*/
public function legals(StaticPage $legalPage)
{
$legalPage = StaticPage::query()->where('type', StaticPageType::legals)->first();
return view('fo.pages.legals', ['staticPageModel' => $legalPage]);
}
/**
* Policies show
*
* @param \App\Models\StaticPage $policiesPage
* @return \Illuminate\Contracts\View\View
*/
public function policies(StaticPage $policiesPage)
{
$policiesPage = StaticPage::query()->where('type', StaticPageType::confidentiality)->first();
return view('fo.pages.policies', ['staticPageModel' => $policiesPage]);
}
}

View file

@ -40,6 +40,12 @@ class Kernel extends HttpKernel
\App\Http\Middleware\SecureHeaders::class
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,

View file

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
@ -31,7 +30,7 @@ class SecureHeaders
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): Response
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
// phpcs:enable
View::share('nonce', $nonce = Vite::useCspNonce());
@ -51,12 +50,20 @@ class SecureHeaders
"default-src 'none';",
"manifest-src 'self';",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob:;",
"img-src * data: blob:;",
"connect-src 'self' blob: " . implode(' ', [
'https://yl.s-web.fr',
'https://api-adresse.data.gouv.fr'
]) . ';',
"img-src 'self' data: blob: " . implode(' ', [
'https://tile.openstreetmap.org/'
]) . ';',
// ! Blob required for printjs
'frame-src ' . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/'
'https://www.google.com/maps/',
'https://www.youtube.com/',
'blob:',
]) . ';',
"font-src 'self' data: https://fonts.gstatic.com;",
// ! Using CKEditor will must have unsafe-inline style
@ -67,7 +74,7 @@ class SecureHeaders
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
'https://use.fontawesome.com',
'https://fonts.googleapis.com'
'https://fonts.googleapis.com',
]) . ';',
// ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';",

View file

@ -27,7 +27,7 @@ class StoreCertificationRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'status' => $this->status ? true : false
'status' => $this->boolean('status')
]);
$this->mergePicture('sticker');
}

View file

@ -24,7 +24,7 @@ class ChangeOrderRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'direction' => $this->action === 'up' ? true : false
'direction' => $this->direction === 'up' ? true : false
]);
}
@ -36,7 +36,6 @@ class ChangeOrderRequest extends FormRequest
public function rules()
{
return [
'action' => 'required|string|in:up,down',
'direction' => 'required|boolean',
];
}

View file

@ -7,6 +7,7 @@ use App\Enums\NutriscoreEnum;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Support\Str;
class StoreProductRequest extends FormRequest
{
@ -30,14 +31,15 @@ class StoreProductRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'status' => $this->status ? true : false,
'nutritional_values' => $this->nutritional_values ?? [],
'status' => $this->boolean('status'),
'nutritional_values' => $this->input('nutritional_values') ?? [],
'certification_list' => \collect($this->certifications)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(),
'recipe_list' => \collect($this->recipes)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(),
'description' => (string)Str::of($this->input('description'))
]);
$this->mergePicture('picture');
$this->mergePicture('sticker');
@ -60,7 +62,7 @@ class StoreProductRequest extends FormRequest
'sticker_alt' => 'required|min:3|max:255',
'sticker_title' => 'required|min:3|max:255',
'description' => 'required|string|min:15',
'description' => 'present|string',
'preparation' => 'nullable|string|min:15',
'ingredients' => 'required|string|min:15',

View file

@ -28,8 +28,8 @@ class UpdateRangeRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->name)),
'status' => $this->status ? true : false
'slug' => Str::slug(strip_tags($this->input('name'))),
'status' => $this->boolean('status')
]);
$this->mergePicture('picture');
$this->mergePicture('banner');

View file

@ -31,12 +31,12 @@ class StoreRecipeRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->title)),
'status' => $this->status ? true : false,
'ingredients' => $this->ingredients ?? [],
'products' => $this->products ?? [],
'recipes' => $this->recipes ?? [],
'cooking_time' => $this->cooking_time ?? 0,
'slug' => Str::slug(strip_tags($this->input('title'))),
'status' => $this->boolean('status'),
'ingredients' => $this->input('ingredients') ?? [],
'products' => $this->input('products') ?? [],
'recipes' => $this->input('recipes') ?? [],
'cooking_time' => $this->input('cooking_time') ?? 0,
// * For sync relations
'product_list' => \collect($this->products)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
@ -75,7 +75,7 @@ class StoreRecipeRequest extends FormRequest
'difficulty' => ['required', new Enum(DifficultyEnum::class)],
'cooking_mode' => [
'required_with:cooking_time',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->cooking_time == 0 and $value != 0) {
$fail(\trans(":attribute doit être à :enumValue quand temps de cuisson est à 0.", [
@ -97,7 +97,7 @@ class StoreRecipeRequest extends FormRequest
'preparation_time' => 'required|numeric|min:1|max:1440',
'status' => 'required|boolean',
'products' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (count($value) < 1 || count($value) > 3) {
$fail(\trans('Vous devez associer 1 à 3 produits.'));
@ -108,7 +108,7 @@ class StoreRecipeRequest extends FormRequest
'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string',
'recipes' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 recettes.'));

View file

@ -0,0 +1,75 @@
<?php
namespace App\Http\Requests\Bo\StaticPages;
use App\Enums\StaticPageType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
class UpdateStaticPageRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'content' => $this->input('content') ? Str::of($this->input('content'))->value() : null
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$rules = [
'seo_title' => 'nullable',
'seo_description' => 'nullable',
'title' => 'required|min:3|max:255',
'content' => 'nullable',
'data' => 'present|array',
'data.*' => 'required|array',
];
/** @var \App\Models\StaticPage $staticPageModel */
$staticPageModel = request()->route()->parameter('static_page');
if (!$staticPageModel->type->equals(StaticPageType::banner_section)) {
$rules = \array_merge(
$rules,
[
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
],
);
}
if (
!$staticPageModel->type->equals(StaticPageType::homepage) &&
!$staticPageModel->type->equals(StaticPageType::our_products) &&
!$staticPageModel->type->equals(StaticPageType::our_commitments) &&
!$staticPageModel->type->equals(StaticPageType::banner_section)
) {
$rules = \array_merge(
$rules,
['content' => ['present']],
);
}
return $rules;
}
}

View file

@ -109,7 +109,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
$rules = array_merge($rules, [
"data.$iptName.value" => [
function (string $attribute, $value, callable $fail) use ($sizes) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
],
$this->compileValidate(

View file

@ -30,7 +30,7 @@ class ContactRequest extends FormRequest
return [
'civility' => [
'required',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if (!in_array($value, CivilityEnum::toValues())) {
$fail(__('Veuillez choisir entre :values', [
@ -48,7 +48,6 @@ class ContactRequest extends FormRequest
'city' => 'required|string|max:255',
'subject' => 'required|string|max:255',
'message' => 'required|string|min:20|max:255',
'rgpd' => 'accepted'
];
}
@ -88,7 +87,6 @@ class ContactRequest extends FormRequest
'city' => trans('ville'),
'subject' => trans('objet e la demande'),
'message' => trans('message'),
'rgpd' => trans('condition d\'utilisation'),
];
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Http\Requests\Fo;
use App\Enums\CookingModeEnum;
use App\Enums\DifficultyEnum;
use App\Enums\PreparationTimeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class RecipesIndexRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'cooking_mode_enum' => $this->makeEnum(CookingModeEnum::class, $this->get('cooking_mode_enum')),
'preparation_time_enum' => $this->makeEnum(PreparationTimeEnum::class, $this->get('preparation_time_enum')),
'difficulty_enum' => $this->makeEnum(DifficultyEnum::class, $this->get('difficulty_enum'))
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'cooking_mode_enum' => ['nullable', Rule::enum(CookingModeEnum::class)],
'preparation_time_enum' => ['nullable', Rule::enum(PreparationTimeEnum::class)],
'difficulty_enum' => ['nullable', Rule::enum(DifficultyEnum::class)]
];
}
/**
* Make enum handle nullable case.
*
* @param string $enum_class
* @param string|null $query_index
* @return string|integer|null
*/
private function makeEnum(string $enum_class, ?string $query_index): string|int|null
{
if (!$query_index) {
return null;
}
return optional(call_user_func([$enum_class, 'make'], $query_index))->value();
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace App\Http\Resources\Fo\Recipes;
use App\Models\Recipe;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class RecipeResource extends JsonResource
{
/** @var \App\Models\Recipe */
private $recipe;
/** @var array */
private static $skeleton = [
'id' => '',
'name' => '',
'slug' => '',
'picture' => '',
'description' => '',
'preparation' => '',
'ingredients' => [],
'portions' => '',
'difficulty' => [
'label' => 'none',
'value' => 0
],
'preparation_time' => '',
'cooking_time' => '',
];
/**
* RecipeResource
*
* @param \App\Models\Recipe $recipe
* @return void
*/
public function __construct(Recipe $recipe)
{
$this->recipe = $recipe;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request|null $request
* @return array
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray(?Request $request = null): array
{
// phpcs:enable
return array_intersect_key(
[
'id' => $this->recipe->id,
'name' => $this->recipe->name,
'slug' => $this->recipe->slug,
'picture' => $this->recipe->picture,
'description' => $this->recipe->description,
'preparation' => $this->recipe->preparation,
'ingredients' => $this->recipe->ingredients,
'portions' => $this->recipe->portions,
'difficulty' => $this->recipe->difficulty,
'preparation_time' => $this->recipe->preparation_time,
'cooking_time' => $this->recipe->cooking_time,
],
$this->getSkeleton()
);
}
/**
* Returns the resource skeletton
*
* @return array
*/
public static function getSkeleton(): array
{
return self::$skeleton;
}
}

View file

@ -145,7 +145,7 @@ class ExceptionsHelper
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
} else {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);

View file

@ -91,7 +91,7 @@ class FileStorageHelper
Model $model,
$file,
bool $slugify = false,
string $filename = null,
?string $filename = null,
bool $private = false
): string {
// phpcs:enable
@ -118,7 +118,7 @@ class FileStorageHelper
);
return str_replace('/private/', '', $newPath);
}//end if
} //end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
@ -152,7 +152,7 @@ class FileStorageHelper
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
?string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
@ -309,7 +309,6 @@ class FileStorageHelper
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
@ -404,7 +403,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
$filename = self::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */

View file

@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@ -169,11 +170,10 @@ class ToolboxHelper
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Assert route matches existing routes.
$routes->match($request);
// ? Is url internal ?
if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
@ -199,14 +199,14 @@ class ToolboxHelper
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string
* @return array|string|false
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string {
): array|string|false {
if (!is_array($search) && is_array($replace)) {
return false;
}
@ -283,9 +283,12 @@ class ToolboxHelper
public static function getOriginal(Model $model, string $attribute): mixed
{
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
// @phpstan-ignore-next-line
return $model->getTranslation(
$attribute,
// @phpstan-ignore-next-line
$model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale()
);
}
@ -309,7 +312,7 @@ class ToolboxHelper
): Builder {
$currentLocale = app()->currentLocale();
$locales = collect(config('app.locales'))
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
if (!$locales->has($defaultLocale)) {
// * Remove default locale.
$locales->pull($defaultLocale);
@ -355,55 +358,55 @@ class ToolboxHelper
/**
* Convert deeply an object to array
*
* @param array|object $input
* @param mixed $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
public static function objectToArray(mixed $input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @param mixed $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
public static function arrayToObject(mixed $input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
}
/**
* Validate picture
*
* @param string $attribute
* @param string|null $value
* @param callback $fail
* @param callable $fail
* @param object $sizes
* @return void
*/
@ -426,7 +429,7 @@ class ToolboxHelper
'attribute' => $attribute
]));
}
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
if (!($dimensions = \getimagesize($fileFullPath))) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
]));
@ -481,4 +484,24 @@ class ToolboxHelper
{
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
}
/**
* Get validated pagination from request.
*
* @param integer $default
* @param string $field
* @param string $enumPath
* @return integer
*/
public static function getValidatedEnum(int $default, string $field, string $enumPath): int
{
try {
return \intval(Validator::make(
[$field => \request()->get($field)],
[$field => new Enum($enumPath)]
)->validated()[$field]);
} catch (ValidationException $e) {
return $default;
}
}
}

179
app/Models/ActivityLog.php Normal file
View file

@ -0,0 +1,179 @@
<?php
namespace App\Models;
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
/**
* @property string $model Target model.
* @property string $model_id Id of the target model.
* @property \App\Enums\ActivityLogs\ActivityLogsEventEnum $event Event of this activity (ActivityLogsEventEnum).
* @property array $data Changes.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
*
* @property-read \App\Models\User $user User BelongsTo relation.
*/
class ActivityLog extends Model
{
use HasFactory;
use Notifiable;
/**
* Indicates if the model should be timestamped.
*
* @var boolean
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'is_anonymous',
'model_class',
'model_id',
'event',
'data',
'created_at',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'is_anonymous' => 'bool',
'event' => ActivityLogsEventEnum::class,
'data' => 'json',
'created_at' => 'datetime',
];
// * METHODS
/**
* Add new activity to the activity logs list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
* @return void
*/
public static function addActivity(Model $model, ActivityLogsEventEnum $eventEnum): void
{
$isAnonymous = true;
/** @var \Illuminate\Database\Eloquent\Model|null */
$userModel = auth('backend')->user();
if (is_object($userModel)) {
$userModel = $userModel->getKeyForSelectQuery();
$isAnonymous = false;
}
$activity = new self();
$activity->user_id = $userModel;
$activity->is_console = app()->runningInConsole();
$activity->is_anonymous = $isAnonymous;
$activity->model_class = \get_class($model);
$activity->model_id = $model->getKey();
$activity->event = $eventEnum;
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
// @phpstan-ignore-next-line
$activity->created_at = now();
$activity->saveOrFail();
}
/**
* Get old, new columns that has changed.
*
* @param self $activity
* @param \Illuminate\Database\Eloquent\Model $model
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
* @return array
*/
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array
{
$modelClassName = $activity->model_class;
/** Get type of each fields of the target model */
/** @var \Illuminate\Database\Eloquent\Model|null $targetModel */
$targetModel = $activity->model_class::where(
(new $modelClassName())->getRouteKeyName(),
$activity->model_id
)->first();
if (\is_null($targetModel)) {
return [];
}
$targetModelTypes = collect($targetModel->toArray())
->map(fn ($value) => self::getValueType($value))
->toArray();
/** Return old, new and type of values changed */
return match ($eventEnum) {
ActivityLogsEventEnum::created => array_intersect_key($targetModelTypes, $model->toArray()),
ActivityLogsEventEnum::duplicated => array_intersect_key($targetModelTypes, $model->toArray()),
ActivityLogsEventEnum::updated => array_intersect_key($targetModelTypes, $model->getChanges()),
default => []
};
}
/**
* Get a value type as a string
* ! Use this for display purpose only
*
* @param mixed $value
* @return string
* @throws \RuntimeException If type is unhlanded.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
*/
private static function getValueType(mixed $value): string
{
// phpcs:enable
$type = \gettype($value);
switch ($type) {
// phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
case 'string':
return match (true) {
Str::length(\strip_tags($value)) !== Str::length($value) => 'html',
Str::startsWith($value, 'storage/modelfiles') => 'file',
Str::isUuid($value) => 'uuid',
Str::isUlid($value) => 'ulid',
Str::isUrl($value) => 'url',
Str::isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/', $value) => 'youtube_video_id',
\is_numeric($value) => 'numeric',
Str::isJson($value) and
(Str::startsWith($value, '[') or Str::startsWith($value, '{')) => 'json',
default => 'string'
};
case 'NULL':
case 'object':
case 'array':
case 'integer':
case 'boolean':
return $type;
case 'double':
return is_float($value) ? 'float' : 'double';
case 'resource (closed)':
case 'resource':
throw new \RuntimeException('Laravel model properties should not contain resources !');
case 'unknown type':
default:
throw new \RuntimeException('Unhandled Type ' . \gettype($value));
} //end switch
}
// * RELATIONS
/**
* User BelongsTo relation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
}

View file

@ -25,7 +25,7 @@ class Certification extends Model
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -38,7 +38,7 @@ class Certification extends Model
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'status' => 'bool'
@ -61,7 +61,7 @@ class Certification extends Model
$model->sticker = FileStorageHelper::storeFile($model, $model->sticker, true);
});
static::updated(function (self $model) {
$model->sticker = FileStorageHelper::removeOldFile($model, 'sticker');
FileStorageHelper::removeOldFile($model, 'sticker');
self::flashStatus($model, true);
});
static::deleting(function (self $model) {

View file

@ -10,8 +10,11 @@ use App\Traits\Models\HasFlashStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
class Product extends Model
class Product extends Model implements Sitemapable
{
use HasFactory;
use HasFlashStatus;
@ -19,7 +22,7 @@ class Product extends Model
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -47,7 +50,7 @@ class Product extends Model
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
/** @var array<ingredient> */
@ -67,7 +70,7 @@ class Product extends Model
// * EVENT CLOSURES.
static::creating(function (self $model) {
// * ASSERTIONS
static::assertFieldIsUnique('name', $model->name);
self::assertFieldIsUnique('name', $model->name);
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation ?? '');
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
@ -82,7 +85,7 @@ class Product extends Model
});
static::updating(function (self $model) {
// * ASSERTIONS
static::assertFieldIsUnique('name', $model->name, $model->id);
self::assertFieldIsUnique('name', $model->name, $model->id);
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation ?? '');
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
@ -152,6 +155,19 @@ class Product extends Model
return $this->belongsToMany(Certification::class, 'product_certification');
}
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this->loadMissing('range');
$route = 'products.show';
$params = ['publishedProduct' => $model->slug];
return SitemapHelper::generateUrl($route, $params, $model);
}
/**
* BelongsTo certifications relation
*

View file

@ -9,6 +9,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
/**
* Gamme
@ -40,7 +43,7 @@ use Illuminate\Validation\ValidationException;
*
* @package App\Models
*/
class Range extends Model
class Range extends Model implements Sitemapable
{
use HasFactory;
use HasFlashStatus;
@ -48,7 +51,7 @@ class Range extends Model
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -80,8 +83,8 @@ class Range extends Model
});
static::updating(function (self $model) {
// * ASSERTIONS
static::assertFieldIsUnique('name', $model->name, $model->id);
static::assertAtLeastOneIsPublished($model);
self::assertFieldIsUnique('name', $model->name, $model->id);
self::assertAtLeastOneIsPublished($model);
// * TRANSFORMATIONS
$model->slug = Str::slug($model->name);
@ -90,9 +93,9 @@ class Range extends Model
$model->sticker = FileStorageHelper::storeFile($model, $model->sticker, true);
});
static::updated(function (self $model) {
$model->picture = FileStorageHelper::removeOldFile($model, 'picture');
$model->banner = FileStorageHelper::removeOldFile($model, 'banner');
$model->banner = FileStorageHelper::removeOldFile($model, 'sticker');
FileStorageHelper::removeOldFile($model, 'picture');
FileStorageHelper::removeOldFile($model, 'banner');
FileStorageHelper::removeOldFile($model, 'sticker');
static::flashStatus($model, true);
});
static::deleting(function () {
@ -134,6 +137,19 @@ class Range extends Model
}
}
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this->loadMissing('products');
$route = 'ranges.products';
$params = ['publishedRange' => $model->slug, 'publishedProduct' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
// * RELATIONSHIPS
/**

View file

@ -10,8 +10,11 @@ use App\Traits\Models\HasFlashStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
class Recipe extends Model
class Recipe extends Model implements Sitemapable
{
use HasFactory;
use HasFlashStatus;
@ -19,7 +22,7 @@ class Recipe extends Model
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -47,7 +50,7 @@ class Recipe extends Model
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'ingredients' => 'array',
@ -65,14 +68,14 @@ class Recipe extends Model
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
static::assertFieldIsUnique('name', $model->name);
self::assertFieldIsUnique('name', $model->name);
self::manageFields($model);
});
static::created(function (self $model) {
self::flashStatus($model);
});
static::updating(function (self $model) {
static::assertFieldIsUnique('name', $model->name, $model->id);
self::assertFieldIsUnique('name', $model->name, $model->id);
self::manageFields($model);
});
static::updated(function (self $model) {
@ -91,10 +94,10 @@ class Recipe extends Model
/**
* Manage fields on this model event.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param self $model
* @return void
*/
private static function manageFields(Model $model)
private static function manageFields(self $model)
{
// * ASSERTIONS
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
@ -161,6 +164,19 @@ class Recipe extends Model
return "{$nbHours}H {$nbMinutes}M";
}
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this->loadMissing('recipes');
$route = 'recipes.show';
$params = ['publishedRecipe' => $model->slug, 'publishedProduct' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
// * RELATIONSHIPS
/**

View file

@ -14,7 +14,7 @@ class Shop extends Model
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -29,7 +29,7 @@ class Shop extends Model
/**
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'signs' => SignsEnum::class,
@ -45,11 +45,11 @@ class Shop extends Model
// * EVENT CLOSURES.
static::creating(function (self $model) {
// * ASSERTIONS
static::assertFieldIsUnique('name', $model->name);
self::assertFieldIsUnique('name', $model->name);
});
static::updating(function (self $model) {
// * ASSERTIONS
static::assertFieldIsUnique('name', $model->name, $model->id);
self::assertFieldIsUnique('name', $model->name, $model->id);
});
}

118
app/Models/StaticPage.php Normal file
View file

@ -0,0 +1,118 @@
<?php
namespace App\Models;
use App\Enums\StaticPageType;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\ActivityLog;
use App\Traits\Models\MiniCrudModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* @property \App\Enums\StaticPageType $type Page type (StaticPageEnum).
* @property string $seo_title Seo title about 70 chars as recommended.
* @property string $seo_description Seo description about 160 chars as recommended.
* @property string $title Title for the page.
* @property string $slug Slug of the title for the page.
* @property string $content WYSIWYG text content using ckeditor.
* @property array<string, array<string, mixed>> $data Static page json data.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/
class StaticPage extends Model
{
use ActivityLog;
use HasFactory;
use MiniCrudModel;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'seo_title',
'seo_description',
'title',
'content',
'data'
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'type' => StaticPageType::class,
'data' => 'object',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
// * ASSERTIONS
self::assertTitleIsUnique($model->title);
// * CHANGES
$model->slug = Str::slug($model->title);
});
static::updating(function (self $model) {
// * ASSERTIONS
self::assertTitleIsUnique($model->title, $model->id);
// * CHANGES
$model->slug = Str::slug($model->title);
});
}
// * ROUTABLE
/**
* Get the value of the model's route key.
*
* @return mixed
*/
public function getRouteKey()
{
/** @var \App\Enums\StaticPageType */
$enum = $this->getAttribute($this->getRouteKeyName());
return $enum->value();
}
/**
* Get the route key for the model.
*
* ! Type is the routable parameter
*
* @return string
*/
public function getRouteKeyName(): string
{
return 'type';
}
// * METHODS
/**
* Asserts using validation that the title is unique
*
* @param string|null $title
* @param integer|null $id
* @return void
* @throws \Illuminate\Validation\ValidationException If an actu title already exists.
*/
private static function assertTitleIsUnique(string|null $title, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
}
}

View file

@ -2,14 +2,29 @@
namespace App\Models;
use App\Enums\Users\RoleEnum;
use App\Traits\Models\ActivityLog;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @property string $first_name Firstname.
* @property string $last_name Lastname.
* @property string $email Email.
* @property string $remember_token Remember_token.
* @property \App\Enums\Users\RoleEnum $role Role.
* @property \Illuminate\Support\Carbon $last_connection Last_connection.
* @property \Illuminate\Support\Carbon $published Published status.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs HasMany relationship.
*/
class User extends Authenticatable
{
use ActivityLog;
use HasFactory;
use Notifiable;
@ -19,10 +34,11 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $fillable = [
'firstname',
'lastname',
'first_name',
'last_name',
'email',
'password',
'role',
'published',
];
/**
@ -31,7 +47,6 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
@ -41,8 +56,9 @@ class User extends Authenticatable
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_connection' => 'datetime',
'role' => RoleEnum::class,
'last_connection' => 'date',
'published' => 'boolean',
];
/**
@ -50,20 +66,28 @@ class User extends Authenticatable
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
// * EVENT CLOSURES.
static::creating(function (self $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
$user->remember_token = Str::random(10);
static::creating(function (self $model) {
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
});
static::updating(function (self $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
static::updating(function (self $model) {
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
\validator(
['published' => $model->published],
['published' => 'required|boolean|accepted'],
['published.accepted' => trans('crud.messages.user_unpublished')],
)->validate();
}
$user->remember_token = Str::random(10);
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
});
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Observers;
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
use App\Models\ActivityLog;
use Illuminate\Database\Eloquent\Model;
class ModelObserver
{
/**
* Handle the model "created" event.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public static function created(Model $model): void
{
ActivityLog::addActivity($model, ActivityLogsEventEnum::created);
}
/**
* Handle the model "updated" event.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public static function updated(Model $model): void
{
ActivityLog::addActivity($model, ActivityLogsEventEnum::updated);
}
/**
* Handle the model "duplicated" event.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public static function duplicated(Model $model): void
{
ActivityLog::addActivity($model, ActivityLogsEventEnum::duplicated);
}
/**
* Handle the model "deleted" event.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public static function deleted(Model $model): void
{
ActivityLog::addActivity($model, ActivityLogsEventEnum::deleted);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace App\Policies;
use App\Models\StaticPage;
use App\Models\User;
class StaticPagePolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.Found
*/
public function viewAny(User $authUser): bool
{
// phpcs:enable
return true;
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\StaticPage $static_page
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, StaticPage $static_page): bool
{
// phpcs:enable
return true;
}
}

View file

@ -2,14 +2,16 @@
namespace App\Providers;
use App\Enums\StaticPageType;
use App\Models\Range;
use App\Models\StaticPage;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
@ -38,7 +40,7 @@ class AppServiceProvider extends ServiceProvider
return request()->getClientIp();
} else {
return request()->server('HTTP_X_FORWARDED_FOR') ??
request()->getClientIp();
request()->getClientIp();
}
});
@ -71,9 +73,14 @@ class AppServiceProvider extends ServiceProvider
// * GAMMES
if (Schema::hasTable('ranges')) {
// On partage les ranges (gammes produit).
$ranges = Range::where('status', true)->get();
$ranges = Range::query()->where('status', true)->get();
View::share('PublishedRangesGlobal', $ranges);
}
if (Schema::hasTable('static_pages')) {
$bannerSection = StaticPage::query()->where('type', StaticPageType::banner_section->value())->get();
View::share('bannerSection', $bannerSection);
}
}
}
}

View file

@ -5,6 +5,13 @@ namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Slides\Saml2\Events\SignedIn;
use Slides\Saml2\Events\SignedOut;
class EventServiceProvider extends ServiceProvider
{
@ -19,15 +26,6 @@ class EventServiceProvider extends ServiceProvider
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot(): void
{
}
/**
* Determine if events and listeners should be automatically discovered.
*
@ -37,4 +35,49 @@ class EventServiceProvider extends ServiceProvider
{
return false;
}
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
Event::listen('Slides\Saml2\Events\SignedIn', function (SignedIn $event) {
$messageId = $event->getAuth()->getLastMessageId();
$fromCache = Cache::get(config('cache.prefix') . "_saml_message_id_" . $messageId);
if ($fromCache) {
logger()->error(
'SAML MESSAGE ID HANDLER',
['error' => 'Message ID : ' . $messageId . " replayed !"]
);
abort(403);
} else {
Cache::put(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
// Cache for 8h.
}
$user = $event->getSaml2User();
$email = $user->getNameId();
$lUser = User::where('email', '=', $email)->first();
if (!$lUser) {
abort(403);
}
Auth::guard('backend')->login($lUser);
session(['sessionIndex' => $user->getSessionIndex()]);
$lUser->last_connection = now();
$lUser->saveQuietly();
});
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
Event::listen('Slides\Saml2\Events\SignedOut', function (SignedOut $event) {
Auth::guard('backend')->logout();
Session::save();
});
}
}

View file

@ -32,7 +32,7 @@ class TranslationServiceProvider extends ServiceProvider
/**
* Gather provided langs checking from files
*
* @return \Illuminate\Support\Collection<string, string>
* @return \Illuminate\Support\Collection<int, string>
*/
private function collectLocalesStrings(): Collection
{

View file

@ -3,59 +3,56 @@
namespace App\Traits\Controllers;
use App\Http\Requests\Bo\ChangeOrderRequest;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
trait ChangesModelOrder
{
/**
* Change order
*
* ! requires x64 server + order column as unique integer
* Change order.
*
* @param \App\Http\Requests\Bo\ChangeOrderRequest $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé.
*/
public function changeOrder(ChangeOrderRequest $request, Model $model): RedirectResponse
public function changeOrder(ChangeOrderRequest $request, Model $model): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $model) {
$params = collect(request()->route()->parameterNames);
$className = Str::ucfirst(Str::lower($params->get($params->count() - 2)));
$className = "\\App\\Models\\$className";
if (!class_exists($className)) {
throw new \RuntimeException("The $className class doesn't exist.");
}
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
if (!$model) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = (\get_class($model))::where('order', $request->direction ? '<' : '>', $model->order)
$tmp = $className::query()->where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) {
return back()->with('bo.error', trans('L\'ordre n\'a pas pu être modifié'));
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$oldOrder = $model->order;
$newOrder = $tmp->order;
// * Silent update timestamps.
$tmp->timestamps = false;
$model->timestamps = false;
$newOrder = $tmp->getOriginal('order');
// Didn't use ->update() because it didn't work with Models which use MiniCrudModel.
$tmp->fill(['order' => $model->getOriginal('order')]);
$tmp->save();
$model->fill(['order' => $newOrder]);
$model->save();
$saveOk = false;
Session::flash('success', trans('crud.messages.order_changed'));
// ! Fake value to prevent unique SQL key to trigger.
$tmp->order = 4294967295;
$saveOk = $tmp->saveQuietly();
$model->order = $newOrder;
$saveOk = $model->saveQuietly() & $saveOk;
$tmp->order = $oldOrder;
$saveOk = $tmp->saveQuietly() & $saveOk;
if (\in_array(IsCachedAsCollection::class, class_uses_recursive(\get_class($model)))) {
$model::removeCached();
}
if (!$saveOk) {
throw ValidationException::withMessages([
'error' => \trans('L\'ordre n\'a pas pu être modifié')
]);
}
Session::flash('bo.success', trans('L\'ordre a été modifié'));
return back();
});
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Traits\Controllers;
use Illuminate\Database\Eloquent\Model;
/**
* This trait works with Route::bind in App service provider
* to resolve the model and the DB row
* It does not work with binder Service
*/
trait UpdateModelPublished
{
/**
* Change model published.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Http\RedirectResponse
*/
public function changePublished(Model $model)
{
$model->update(['published' => !$model->getOriginal('published')]);
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
}
}

View file

@ -12,19 +12,14 @@ use Illuminate\Database\Eloquent\Model;
trait UpdateModelStatus
{
/**
* Change model published
* Change model published.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Http\RedirectResponse
*/
public function changePublished(Model $model)
{
$model->status = !$model->status;
$model->saveOrFail();
if (!$model->saveOrFail()) {
return redirect()->back()->with('error', __('The status could not be changed'));
}
return redirect()->back()->with('success', __('The status has been changed'));
$model->update(['published' => !$model->getOriginal('published')]);
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
}
}

View file

@ -120,8 +120,8 @@ trait BaseEnum
/**
* Fetch enum using value
*
* @param \App\Traits\Enums\BaseEnum|integer|string $value
* @return \App\Traits\Enums\BaseEnum|null
* @param self|integer|string $value
* @return self|null
*/
public static function make(self|int|string $value): self|null
{

View file

@ -0,0 +1,23 @@
<?php
namespace App\Traits\Models;
use App\Observers\ModelObserver;
/**
* If there is a static function in a trait (named boot[TraitName]),
* it will be executed as the boot() function would on an Eloquent model.
* Futhermore, event in the Model observer doesn't erase actual events in the model,
* but adds them.
*/
trait ActivityLog
{
/**
*
* @return void
*/
public static function bootActivityLog(): void
{
static::observe(ModelObserver::class);
}
}

View file

@ -10,7 +10,8 @@ use Illuminate\Support\Facades\Cache;
/**
* ! Use this only on limited elements, (not on tags, or products, or posts for ex.)
*
* @property private static array $cacheConditions Eloquent condition to cache elements (to be used in where statement).
* @property array<string,string|integer|float|boolean> $cacheConditions
* Eloquent condition to cache elements (to be used in where statement).
*/
trait IsCachedAsCollection
{
@ -52,7 +53,7 @@ trait IsCachedAsCollection
{
$conditions = [];
if (property_exists(get_called_class(), 'cacheConditions')) {
$conditions = collect(static::${'cacheConditions'})->all();
$conditions = collect(get_called_class()::${'cacheConditions'})->all();
}
Cache::put(static::getCacheKeyName(), static::queryCached(static::query())
->where($conditions)->get()

View file

@ -11,18 +11,20 @@ trait MiniCrudModel
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return void
* @param array<string, mixed> $attributes
* @return $this
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
*/
public function fill(array $attributes)
public function fill(array $attributes): static
{
// phpcs:enable
parent::fill($attributes);
if (isset($attributes['data'])) {
$this->setData($attributes['data']);
}
return $this;
}
/**
@ -62,7 +64,6 @@ trait MiniCrudModel
*/
public function minicrudUpdated()
{
/** @var array */
foreach ($this->data as $index => $input) {
switch ($input['inputType']) {
case InputType::text->value:
@ -177,4 +178,21 @@ trait MiniCrudModel
} //end foreach
return $data;
}
/**
* Get fillable properties and their values from the model.
*
* @param self $model
* @return array
*/
public function getFillableFields(self $model): array
{
$datas = [];
foreach ($model->toArray() as $property => $value) {
if (in_array($property, $model->getFillable())) {
$datas = array_merge($datas, [$property => $value]);
}
}
return $datas;
}
}

View file

@ -66,7 +66,7 @@ trait HasPicture
$name => [
!$nullable ? 'required' : 'nullable',
function (string $attribute, $value, callable $fail) use ($sizes) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
]
];
@ -101,7 +101,7 @@ trait HasPicture
"{$name}Validate.*" => [
'required',
function (string $attribute, $value, callable $fail) use ($sizes) {
return $this->validatePicture($attribute, $value, $fail, $sizes);
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
]
];

View file

@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class LocalBlockComponent extends Component
{
/** @var \App\Models\StaticPage $staticPageModel */
private $staticPageModel;
/**
* Create a new component instance.
*/
public function __construct()
{
$this->staticPageModel = StaticPage::query()->where('type', StaticPageType::our_commitments)->first();
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.local-block-component', [
'staticPageModel' => $this->staticPageModel
]);
}
}

View file

@ -3,32 +3,34 @@
"type": "project",
"description": "Site de la marque Lionor",
"require": {
"php": "^8.2",
"php": "^8.5",
"scaler-tech/laravel-saml2": "^2.5",
"axlon/laravel-postal-code-validation": "^3.3",
"diglactic/laravel-breadcrumbs": "^8.1.1",
"carlos-meneses/laravel-mpdf": "^2.1",
"diglactic/laravel-breadcrumbs": "^9.0",
"guzzlehttp/guzzle": "^7.2",
"kaishiyoku/laravel-color-validation": "^2.1.1",
"kwaadpepper/image-resizer": "^2.1",
"kwaadpepper/refresh-sitemap": "^2.0",
"laravel/framework": "^10.0",
"kaishiyoku/laravel-color-validation": "^3.2",
"kwaadpepper/image-resizer": "^3.1",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"ldc/web-admin-secure-laravel": "^2.0.0",
"mews/purifier": "^3.4",
"propaganistas/laravel-phone": "^5.0.3",
"sentry/sentry-laravel": "*",
"uwil/k8s-laravel": "1.1.0"
"uwil/laravel-admin-secure": "^6.0",
"uwil/k8s-laravel": "^1.4",
"uwil/sitemap-generator": "^3.0"
},
"require-dev": {
"doctrine/dbal": "^3.3",
"fakerphp/faker": "^1.9.1",
"kitloong/laravel-migrations-generator": "^6.8",
"krlove/eloquent-model-generator": "dev-l10-compatibility",
"kitloong/laravel-migrations-generator": "^7.0",
"larastan/larastan": "^2.6",
"laravel-lang/lang": "10.0",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "^8.10",
"spatie/laravel-ignition": "^2.0",

6766
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -56,8 +56,6 @@ return [
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Application Timezone

View file

@ -23,7 +23,7 @@ return [
|
*/
'view' => 'modules/breadcrumb/breadcrumb-layout',
'view' => 'modules.breadcrumbs.breadcrumb-layout',
/*
|--------------------------------------------------------------------------

View file

@ -1,106 +0,0 @@
<?php
use Spatie\Sitemap\Tags\Url;
return [
/**
* A list of url to ignore
* These are hard coded
* Each url does not have to be exhaustive
*
* For example, if you want to ignore all routes
* of the backoffice group
* just add '/backoffice' into this table
*/
'ignoreRoutes' => [
'/bo',
],
/**
* This table is needed to resolve models matching
* routes
*
* it has to be filled like following
*
* 'products.meat-type' => [
* 'slugMeatType' => [MeatTypes::class, 'slug']
* ],
* 'kitchen-side.health-nutrition.balanced-menu.menu' => [
* 'type' => [BalancedMenuType::class, 'type']
* ]
*
* The index is the complete route name, the array content is an array
* within index is the argument name with its model class.
* This will allow the generator to generate a route for each of the models
* in this table
*/
'routesBinder' => [],
/**
* You can put route conditions here
* These will be used to decide whether the route should appear
* in the sitemap or not
*
* These are called in Eloquent where
*
* example
*
* 'queryConditions' => [
* 'status' => true,
* 'ocbStatus' => EleveurOcbStatus::yes()->value,
* 'star_moment' => true
* ]
*
*/
'queryConditions' => [
'status' => true,
],
/**
* The default route frequency to use with sitemap generator
*/
'routeDefaultFrequency' => Url::CHANGE_FREQUENCY_DAILY,
/**
* The default route priority to use with sitemap generator
*
* priority should be a number between 0 and 1
* https://www.sitemaps.org/protocol.html
*/
'routeDefaultPriority' => 0.5,
/**
* Frequencies for routes
* use the full or partial route name as index
* and a frequency within \Spatie\Sitemap\Tags\Url constants
*/
'routeFrequencies' => [],
/**
* Priorities for routes
* use the full or partial route name as index
* and a priority number between 0 and 1
*/
'routePriorities' => [],
/**
* Enable or disable schedule for the job
* If set to true the sitemap generator will be run
* using the 'cron' instruction
*/
'schedule' => true,
/**
* If you want to set specific
* instruction for when to schedule job
* you can set this with a cron instruction
* You can generate one using https://crontab.guru/
* https://laravel.com/docs/8.x/scheduling#schedule-frequency-options
*
* schedule must be set to true
*
* Default is every day at 15:45
*/
'cron' => '45 15 * * *'
];

397
config/saml2.php Normal file
View file

@ -0,0 +1,397 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Tenant Model
|--------------------------------------------------------------------------
|
| This will allow you to override the tenant model with your own.
|
*/
'tenantModel' => \Slides\Saml2\Models\Tenant::class,
/*
|--------------------------------------------------------------------------
| Use built-in routes
|--------------------------------------------------------------------------
|
| If "useRoutes" set to true, the package defines five new routes:
|
| Method | URI | Name
| -------|---------------------------------|------------------
| POST | {routesPrefix}/{uuid}/acs | saml.acs
| GET | {routesPrefix}/{uuid}/login | saml.login
| GET | {routesPrefix}/{uuid}/logout | saml.logout
| GET | {routesPrefix}/{uuid}/metadata | saml.metadata
| GET | {routesPrefix}/{uuid}/sls | saml.sls
|
*/
'useRoutes' => true,
/*
|--------------------------------------------------------------------------
| Built-in routes prefix
|--------------------------------------------------------------------------
|
| Here you may define the prefix for built-in routes.
|
*/
'routesPrefix' => '/saml2',
/*
|--------------------------------------------------------------------------
| Middle groups to use for the SAML routes
|--------------------------------------------------------------------------
|
| Note, Laravel 5.2 requires a group which includes StartSession
|
*/
'routesMiddleware' => ['saml'],
/*
|--------------------------------------------------------------------------
| Signature validation
|--------------------------------------------------------------------------
|
| Set to true if you want to use parameters from $_SERVER to validate the signature.
|
*/
'retrieveParametersFromServer' => false,
/*
|--------------------------------------------------------------------------
| Login redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after successful login.
|
*/
'loginRoute' => '/bo',
/*
|--------------------------------------------------------------------------
| Logout redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after successful logout.
|
*/
'logoutRoute' => '/bo',
/*
|--------------------------------------------------------------------------
| Login error redirection URL.
|--------------------------------------------------------------------------
|
| The redirection URL after login failing.
|
*/
'errorRoute' => '/bo/login-error',
/*
|--------------------------------------------------------------------------
| Strict mode.
|--------------------------------------------------------------------------
|
| If 'strict' is True, then the PHP Toolkit will reject unsigned
| or unencrypted messages if it expects them signed or encrypted
| Also will reject the messages if not strictly follow the SAML
| standard: Destination, NameId, Conditions... are validated too.
|
*/
'strict' => true,
/*
|--------------------------------------------------------------------------
| Debug mode.
|--------------------------------------------------------------------------
|
| When enabled, errors must be printed.
|
*/
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
/*
|--------------------------------------------------------------------------
| Whether to use `X-Forwarded-*` headers to determine port/domain/protocol.
|--------------------------------------------------------------------------
|
| If 'proxyVars' is True, then the Saml lib will trust proxy headers
| e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
| your application is running behind a load balancer which terminates SSL.
|
*/
'proxyVars' => true,
/*
|--------------------------------------------------------------------------
| Service Provider configuration.
|--------------------------------------------------------------------------
|
| General setting of the service provider.
|
*/
'sp' => [
/*
|--------------------------------------------------------------------------
| NameID format.
|--------------------------------------------------------------------------
|
| Specifies constraints on the name identifier to be used to
| represent the requested subject.
|
*/
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
/*
|--------------------------------------------------------------------------
| SP Certificates.
|--------------------------------------------------------------------------
|
| Usually x509cert and privateKey of the SP are provided by files placed at
| the certs folder. But we can also provide them with the following parameters.
|
*/
'x509cert' => env('SAML2_SP_CERT_x509', ''),
'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''),
/*
|--------------------------------------------------------------------------
| Identifier (URI) of the SP entity.
|--------------------------------------------------------------------------
|
| Leave blank to use the 'saml.metadata' route.
|
*/
'entityId' => env('SAML2_SP_ENTITYID', ''),
/*
|--------------------------------------------------------------------------
| The Assertion Consumer Service (ACS) URL.
|--------------------------------------------------------------------------
|
| URL Location where the <Response> from the IdP will be returned, using HTTP-POST binding.
| Leave blank to use the 'saml.acs' route.
|
*/
'assertionConsumerService' => [
'url' => '',
],
/*
|--------------------------------------------------------------------------
| The Single Logout Service URL.
|--------------------------------------------------------------------------
|
| Specifies info about where and how the <Logout Response> message MUST be
| returned to the requester, in this case our SP.
|
| URL Location where the <Response> from the IdP will be returned, using HTTP-Redirect binding.
| Leave blank to use the 'saml.sls' route.
|
*/
'singleLogoutService' => [
'url' => ''
],
],
/*
|--------------------------------------------------------------------------
| OneLogin security settings.
|--------------------------------------------------------------------------
|
|
|
*/
'security' => [
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
/*
|--------------------------------------------------------------------------
| NameId encryption
|--------------------------------------------------------------------------
|
| Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
| will be encrypted.
|
*/
'nameIdEncrypted' => true,
/*
|--------------------------------------------------------------------------
| AuthnRequest signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:AuthnRequest> messages sent by
| this SP will be signed. The Metadata of the SP will offer this info
|
*/
'authnRequestsSigned' => true,
/*
|--------------------------------------------------------------------------
| Logout request signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:logoutRequest> messages sent by this SP
| will be signed.
|
*/
'logoutRequestSigned' => true,
/*
|--------------------------------------------------------------------------
| Logout response signage
|--------------------------------------------------------------------------
|
| Indicates whether the <samlp:logoutResponse> messages sent by this SP
| will be signed.
|
*/
'logoutResponseSigned' => true,
/*
|--------------------------------------------------------------------------
| Whether need to sign metadata.
|--------------------------------------------------------------------------
|
| The possible values:
| - false
| - true (use certs)
| - array:
| ```
| [
| 'keyFileName' => 'metadata.key',
| 'certFileName' => 'metadata.crt'
| ]
| ```
|
*/
'signMetadata' => true,
/*
|--------------------------------------------------------------------------
| Requirement to sign messages.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
| <samlp:LogoutResponse> elements received by this SP to be signed.
|
*/
'wantMessagesSigned' => true,
/*
|--------------------------------------------------------------------------
| Requirement to sign assertion elements.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the <saml:Assertion> elements received by
| this SP to be signed.
|
*/
'wantAssertionsSigned' => true,
/*
|--------------------------------------------------------------------------
| Requirement to encrypt NameID.
|--------------------------------------------------------------------------
|
| Indicates a requirement for the NameID received by this SP to be encrypted.
|
*/
'wantNameIdEncrypted' => false,
/*
|--------------------------------------------------------------------------
| Authentication context.
|--------------------------------------------------------------------------
|
| Set to false and no AuthContext will be sent in the AuthNRequest,
|
| Set true or don't present this parameter and you will get an
| AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
| Set an array with the possible auth context values:
| ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509']
|
*/
'requestedAuthnContext' => true,
],
/*
|--------------------------------------------------------------------------
| Contact information.
|--------------------------------------------------------------------------
|
| It is recommended to supply a technical and support contacts.
|
*/
'contactPerson' => [
'technical' => [
'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')),
'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com')
],
'support' => [
'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')),
'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com')
],
],
/*
|--------------------------------------------------------------------------
| Organization information.
|--------------------------------------------------------------------------
|
| The info in en_US lang is recommended, add more if required.
|
*/
'organization' => [
'en-US' => [
'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url'))
],
],
/**
* Default tenant UUID
*/
'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null)
];

View file

@ -0,0 +1,82 @@
<?php
return [
/**
* Single route names
*
* Ex:
*
* 'singlePages' => [
* 'homepage',
* ],
*/
'singlePages' => [
'home',
'engagments',
'local',
'recipes.index',
'ranges.index',
'contact.show',
'map',
'policies',
'legals'
],
/**
* Model namespaces with filters
*
* Ex:
* 'cruds' => [
* [
* 'model' => 'App\Models\Product',
* 'filters' => [
* ['published', '=', true]
* ]
* ],
* [
* 'model' => 'App\Models\Article',
* 'filters' => []
* ]
* ],
*/
'cruds' => [
[
'model' => 'App\Models\Product',
'filters' => [
['status', '=', true]
],
],
[
'model' => 'App\Models\Recipe',
'filters' => [
['status', '=', true]
],
],
[
'model' => 'App\Models\Range',
'filters' => [
['status', '=', true]
],
]
],
/**
* Enable or disable schedule for the job
* If set to true the sitemap generator will be run
* using the 'cron' instruction
*/
'schedule' => true,
/**
* If you want to set specific
* instruction for when to schedule job
* you can set this with a cron instruction
* You can generate one using https://crontab.guru/
*
* schedule must be set to true
*
* Default is every day at 15:45
*/
'cron' => '45 15 * * *',
];

View file

@ -15,8 +15,8 @@ class UserFactory extends Factory
public function definition()
{
return [
'firstname' => $this->faker->firstName(),
'lastname' => $this->faker->lastName(),
'first_name' => $this->faker->firstName(),
'last_name' => $this->faker->lastName(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::create('static_pages', function (Blueprint $table) {
$table->id();
$table->unsignedTinyInteger('type')->unique()->comment('Page type (StaticPageEnum)');
$table->string('seo_title')->comment('Seo title about 70 chars as recommended');
$table->string('seo_description')->comment('Seo description about 160 chars as recommended');
$table->string('title')->comment('Title for the page');
$table->string('slug')->unique()->comment('Unique title slug');
$table->text('content')->comment('WYSIWYG text content using ckeditor');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::dropIfExists('static_pages');
}
};

View file

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete()
->comment('Id of the user associated, or nullable for a deleted user.');
$table->boolean('is_anonymous')->comment('If there is no user connected when action was realised.');
$table->boolean('is_console')->comment('If the action was realised in console.');
$table->string('model_class')->comment('Target model.');
$table->integer('model_id')->comment('Id of the target model.');
$table->integer('event')->comment('Event of this activity (ActivityLogsEventEnum).');
$table->json('data')->nullable()->comment('List of changes (old and new values).');
$table->timestamp('created_at')->comment('The date on which the user was published.');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::dropIfExists('activity_logs');
}
};

View file

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
// Drop.
$table->dropColumn('email_verified_at');
$table->dropColumn('password');
// Rename.
$table->renameColumn('firstname', 'first_name');
$table->renameColumn('lastname', 'last_name');
// New columns.
$table->unsignedTinyInteger('role')->after('remember_token')
->comment('Role of the user who defines the rights/access.');
$table->boolean('published')->after('role')
->comment('Is the user published ?');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
// New columns.
$table->timestamp('email_verified_at')->nullable()->after('email');
$table->string('password')->after('email_verified_at');
// Rename.
$table->renameColumn('first_name', 'firstname');
$table->renameColumn('last_name', 'lastname');
// Drop.
$table->dropColumn('role');
$table->dropColumn('published');
});
}
};

View file

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('static_pages', function (Blueprint $table) {
$table->text('seo_title')->nullable()->comment('Seo title about 70 chars as recommended')->change();
$table->text('seo_description')->nullable()->comment('Seo description about 160 chars as recommended')->change();
$table->text('content')->nullable()->comment('WYSIWYG text content using ckeditor')->change();
$table->mediumText('data')->nullable()->after('content')->comment('Static page data.');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

View file

@ -0,0 +1,154 @@
<?php
use App\Enums\InputType;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// HOMEPAGE
$data = [
'left_title' => [
'label' => 'Titre de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'Des produits bien élevés qui ont le goût de la qualité',
'validate' => 'required|string|between:3,255',
'required' => true
],
'left_subtitle' => [
'label' => 'Sous-titre de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'Garantis Poulet 100% local',
'validate' => 'required|string|between:3,255',
'required' => true
],
'left_button_text' => [
'label' => 'Texte du bouton de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'Nous Découvrir >',
'validate' => 'required|string|between:3,255',
'required' => true
],
'right_title' => [
'label' => 'Titre de la section de droite',
'inputType' => InputType::string->value,
'value' => 'Des produits de qualité bons et accessibles',
'validate' => 'required|string|between:3,255',
'required' => true
],
'right_subtitle' => [
'label' => 'Sous-titre de la section de droite',
'inputType' => InputType::string->value,
'value' => 'Pour manger sain et régional',
'validate' => 'required|string|between:3,255',
'required' => true
],
'right_button_text' => [
'label' => 'Texte du bouton de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'Voir nos recettes >',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_title' => [
'label' => 'Titre de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Chez Lionor le',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_title_2' => [
'label' => 'Titre n°2 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Poulet 100% local',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_title_3' => [
'label' => 'Titre n°3 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'C\'est...',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_1' => [
'label' => 'Texte n°1 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des Volailles nées, élevées et préparées en Hauts-de-France.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_2' => [
'label' => 'Texte n°2 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des éleveurs animés par le développement de leur région.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_3' => [
'label' => 'Texte n°3 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des volailles bien élevées qui assurent des produits bons et accessibles.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_4' => [
'label' => 'Texte n°4 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Une filière soucieuse de la santé des hommes, des animaux et de la terre.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'product_title' => [
'label' => 'Titre de la section n°3',
'inputType' => InputType::string->value,
'value' => 'Découvrez nos volailles bien élevées',
'validate' => 'required|string|between:3,255',
'required' => true
],
'product_subtitle' => [
'label' => 'Titre n°2 de la section n°3',
'inputType' => InputType::string->value,
'value' => 'qui ont le goût de la qualité',
'validate' => 'required|string|between:3,255',
'required' => true
],
];
$staticPage = StaticPage::where('type', StaticPageType::homepage)->first();
if ($staticPage) {
$staticPage->data = $data;
$staticPage->saveQuietly();
} else {
StaticPage::query()->forceCreateQuietly(
[
'type' => StaticPageType::homepage->value(),
'seo_title' => 'Lionor, des volailles en Nord',
'seo_description' => '100% local, des produits bien élevés, de qualité et accessibles.',
'title' => 'Accueil',
'slug' => 'accueil',
'data' => $data
]
);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('static_pages', function () {
$staticPage = StaticPage::query()->where('type', StaticPageType::homepage)->first();
$staticPage->data = [];
$staticPage->saveQuietly();
});
}
};

View file

@ -0,0 +1,119 @@
<?php
use App\Enums\InputType;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// OUR PRODUCTS
$data = [
'page_title' => [
'label' => 'Titre de la section n°1',
'inputType' => InputType::string->value,
'value' => 'Nos volailles bien élevées',
'validate' => 'required|string|between:3,255',
'required' => true
],
'page_title_2' => [
'label' => 'Titre n°2 de la section n°1',
'inputType' => InputType::string->value,
'value' => 'qui ont le goût de la qualité',
'validate' => 'required|string|between:3,255',
'required' => true
],
'left_title_1' => [
'label' => 'Titre n°1 de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'Trouvez l\'inspiration',
'validate' => 'required|string|min:15',
'required' => true
],
'left_title_2' => [
'label' => 'Titre n°2 de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'avec nos recettes',
'validate' => 'required|string|min:15',
'required' => true
],
'left_title_3' => [
'label' => 'Titre n°3 de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'de volailles bonnes',
'validate' => 'required|string|min:15',
'required' => true
],
'left_title_4' => [
'label' => 'Titre n°4 de la section de gauche',
'inputType' => InputType::string->value,
'value' => 'et accessibles',
'validate' => 'required|string|min:15',
'required' => true
],
'right_title_1' => [
'label' => 'Titre n°1 de la section de droite',
'inputType' => InputType::string->value,
'value' => 'Découvrez les engagements',
'validate' => 'required|string|min:15',
'required' => true
],
'right_title_2' => [
'label' => 'Titre n°2 de la section de droite',
'inputType' => InputType::string->value,
'value' => 'd\'une filière engagée',
'validate' => 'required|string|min:15',
'required' => true
],
'right_title_3' => [
'label' => 'Titre n°3 de la section de droite',
'inputType' => InputType::string->value,
'value' => 'pour manger bon, sain',
'validate' => 'required|string|min:15',
'required' => true
],
'right_title_4' => [
'label' => 'Titre n°4 de la section de droite',
'inputType' => InputType::string->value,
'value' => 'et Poulet 100% local',
'validate' => 'required|string|min:15',
'required' => true
],
];
$staticPage = StaticPage::where('type', StaticPageType::our_products)->first();
if ($staticPage) {
$staticPage->data = $data;
$staticPage->saveQuietly();
} else {
StaticPage::query()->forceCreateQuietly(
[
'type' => StaticPageType::our_products->value(),
'seo_title' => 'Nos volailles bien élevées qui ont le goût de la qualité',
'seo_description' => 'Liste de toutes nos gammes de produits.',
'title' => 'Nos produits',
'slug' => 'gammes',
'data' => $data
]
);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('static_pages', function () {
$staticPage = StaticPage::query()->where('type', StaticPageType::our_products)->first();
$staticPage->data = [];
$staticPage->saveQuietly();
});
}
};

View file

@ -0,0 +1,162 @@
<?php
use App\Enums\InputType;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// OUR PRODUCTS
$data = [
'commitment_title_1' => [
'label' => 'Titre de l\'engagement n°1',
'inputType' => InputType::string->value,
'value' => 'Des produits de qualité bons et accessibles',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_subtitle_1' => [
'label' => 'Sous-titre de l\'engagement n°1',
'inputType' => InputType::string->value,
'value' => 'Garantis Poulet 100% Hauts-De-France',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_text_1' => [
'label' => 'Texte de l\'engagement n°1',
'inputType' => InputType::text->value,
'value' => '<p>Avec notre label Poulet 100% LOCAL, certifié et contrôlé par un cahier des charges précis, nous garantissons l\'origine de nos produits issus de poulets nés, élevés et préparés en région des Hauts de France.</p>',
'validate' => 'required|string|min:15',
'required' => true
],
'commitment_title_2' => [
'label' => 'Titre de l\'engagement n°2',
'inputType' => InputType::string->value,
'value' => 'Une filière soucieuse de la santé',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_subtitle_2' => [
'label' => 'Sous-titre de l\'engagement n°2',
'inputType' => InputType::string->value,
'value' => 'Des hommes, des animaux et de la terre',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_text_2' => [
'label' => 'Texte de l\'engagement n°2',
'inputType' => InputType::text->value,
'value' => '<p>LIONOR, c\'est une filière qui s\'engage pour élever des volailles dans le respect du vivant et qui favorise l\'économie locale. Fiers de nos 70 éleveurs et de nos 235 salariés, unis par la passion de leur métier, nous participons activement à la vie sociale et économique de notre région.</p>',
'validate' => 'required|string|min:15',
'required' => true
],
'commitment_title_3' => [
'label' => 'Titre de l\'engagement n°3',
'inputType' => InputType::string->value,
'value' => 'Des femmes et des hommes',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_subtitle_3' => [
'label' => 'Sous-titre de l\'engagement n°3',
'inputType' => InputType::string->value,
'value' => 'Qui maîtrisent la filière du grain à l\'assiette',
'validate' => 'required|string|between:3,255',
'required' => true
],
'commitment_text_3' => [
'label' => 'Texte de l\'engagement n°3',
'inputType' => InputType::text->value,
'value' => '<p>Grâce à la maitrise totale de notre filière, allant de l\'alimentation aux produits régionaux de qualité, en passant pas nos élevages se situant dans un rayon de 50kms autour de notre entreprise, nous garantissons aux consommateurs finaux, une qualité et une traçabilité irréprochables. Notre crédo : "élever bien pour manger sain en région des Hauts de France."</p>',
'validate' => 'required|string|min:15',
'required' => true
],
'container_title' => [
'label' => 'Titre de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Chez Lionor le',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_title_2' => [
'label' => 'Titre n°2 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Poulet 100% local',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_title_3' => [
'label' => 'Titre n°3 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'C\'est...',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_1' => [
'label' => 'Texte n°1 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des Volailles nées, élevées et préparées en Hauts-de-France.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_2' => [
'label' => 'Texte n°2 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des éleveurs animés par le développement de leur région.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_3' => [
'label' => 'Texte n°3 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Des volailles bien élevées qui assurent des produits bons et accessibles.',
'validate' => 'required|string|between:3,255',
'required' => true
],
'container_item_4' => [
'label' => 'Texte n°4 de la section n°2',
'inputType' => InputType::string->value,
'value' => 'Une filière soucieuse de la santé des hommes, des animaux et de la terre.',
'validate' => 'required|string|between:3,255',
'required' => true
],
];
$staticPage = StaticPage::where('type', StaticPageType::our_commitments)->first();
if ($staticPage) {
$staticPage->data = $data;
$staticPage->saveQuietly();
} else {
StaticPage::query()->forceCreateQuietly(
[
'type' => StaticPageType::our_commitments->value(),
'seo_title' => 'Les engagements Lionor',
'seo_description' => 'Des produits de qualité, une filière soucieuse de la santé, maîtrise la filière du grain à l\'assiette',
'title' => 'Nos engagements',
'slug' => 'nos-engagements',
'data' => $data
]
);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('static_pages', function () {
$staticPage = StaticPage::query()->where('type', StaticPageType::our_commitments)->first();
$staticPage->data = [];
$staticPage->saveQuietly();
});
}
};

View file

@ -0,0 +1,110 @@
<?php
use App\Enums\InputType;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// BANNER SECTION
$data = [
'banner_title_1' => [
'label' => 'Titre n°1 du bandeau',
'inputType' => InputType::string->value,
'value' => 'Choisir',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_title_2' => [
'label' => 'Titre n°2 du bandeau',
'inputType' => InputType::string->value,
'value' => 'les volailles',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_title_3' => [
'label' => 'Titre n°3 du bandeau',
'inputType' => InputType::string->value,
'value' => 'Lionor, c\'est...',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_title_1' => [
'label' => 'Titre de l\'élément n°1',
'inputType' => InputType::string->value,
'value' => 'Soutenir',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_text_1' => [
'label' => 'Texte de l\'élément n°1',
'inputType' => InputType::string->value,
'value' => 'L\'emploi local et les éleveurs régionaux',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_title_2' => [
'label' => 'Titre de l\'élément n°2',
'inputType' => InputType::string->value,
'value' => 'Choisir la fraîcheur',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_text_2' => [
'label' => 'Texte de l\'élément n°2',
'inputType' => InputType::string->value,
'value' => 'Et consommer des produits de proximité',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_title_3' => [
'label' => 'Titre de l\'élément n°3',
'inputType' => InputType::string->value,
'value' => 'Goûter la qualité',
'validate' => 'required|string|between:3,255',
'required' => true
],
'banner_item_text_3' => [
'label' => 'Texte de l\'élément n°3',
'inputType' => InputType::string->value,
'value' => 'D\'un bout à l\'autre de la filière',
'validate' => 'required|string|between:3,255',
'required' => true
],
];
$staticPage = StaticPage::where('type', StaticPageType::banner_section)->first();
if ($staticPage) {
$staticPage->data = $data;
$staticPage->saveQuietly();
} else {
StaticPage::query()->forceCreateQuietly(
[
'type' => StaticPageType::banner_section->value(),
'title' => 'Bannière',
'slug' => 'banner',
'data' => $data
]
);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('static_pages', function () {
$staticPage = StaticPage::query()->where('type', StaticPageType::banner_section)->first();
$staticPage->data = [];
$staticPage->saveQuietly();
});
}
};

View file

@ -2,6 +2,7 @@
namespace Database\Seeders;
use Database\Seeders\StaticPageSeeder;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
@ -19,6 +20,7 @@ class DatabaseSeeder extends Seeder
ProductSeeder::class,
RecipeSeeder::class,
ShopSeeder::class,
StaticPageSeeder::class,
]);
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Database\Seeders;
use App\Enums\StaticPageType;
use App\Models\StaticPage;
use Illuminate\Database\Seeder;
class StaticPageSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// Legals.
StaticPage::query()->updateOrCreate(
[
'type' => StaticPageType::legals,
],
[
'seo_title' => 'Mentions légales et CGU',
'seo_description' => 'Mentions légales et conditions générales d\'utilisation du site internet du site internet Lionor',
'title' => 'Mentions légales',
'type' => StaticPageType::legals,
'content' => "
<h2>Introduction</h2>
<p>Nous vous remercions de lire ces conditions générales d'utilisation qui régissent les conditions d'accès et d'utilisation du site <a target='_blank' href='https://lionor.s-web.test' rel='noreferrer noopener'>lionor.fr</a>
<br><br>
<strong>Nous vous invitons à consulter régulièrement cette rubrique car la société Lionor peut être amenée à la modifier à tout moment.</strong>
<br><br>
<strong>Tout utilisateur déclare avoir lu et accepté les mentions légales et les conditions générales d'utilisation et s'engage à s'y conformer.</strong><br><br><br>
Mise à jour le 15/03/2023.</p>
<h2>Mentions légales</h2>
<p>Lionor RTE DE LA GARE 59189 STEENBECQUE<br>Tél : +33(0)328436055 SAS au capital de 1 646 400 euros - RCS de Dunkerque sous le numéro 305 507 303<br><br>
<strong>Editeur du Site</strong> : LDC Services 98 à 102 Rue Albert Einstein 72000 Le Mans<br>
<strong>Directeur de la publication</strong> : Sylvain Gouet, Responsable Web Marketing Groupe LDC<br>
<strong>Conception du site et réalisation</strong> : LDC Services 98 à 102 Rue Albert Einstein 72000 Le Mans<br>
<strong>Illustrations</strong> : Médiapole 2020<br>
<strong>Le site est hébergé par</strong> : OVH, via LDC, SAS au capital de 10 069 020 euros, RCS Lille Métropole 537 407 926, 2 rue Kellermann - 59100 Roubaix, Tel : 09 72 10 10 07, Mail : hosting@ml.ovh.net<br></p>
<h2>Droit d'auteur - Droit des marques - Protection des bases de données</h2>
<p>La présentation et le contenu du site constituent, ensemble, une œuvre protégée par les lois en vigueur des droits d'auteur, de la propriété intellectuelle et industrielle.
<br><br>
Les créations apparaissant sur le site (dessins, photographies, images, textes, séquences animées sonores ou non et autres documentations représentées), sont objets de droits d'auteur, de propriété industrielle et/ou intellectuelle et sont, selon les cas, propriété de la société Lionor ou de tiers ayant autorisé la société Lionor à les utiliser.
<br>Aucune exploitation commerciale, reproduction, représentation, utilisation, adaptation, modification, incorporation, traduction, commercialisation, partielle ou intégrale de ces éléments (y compris ceux téléchargeables ou copiables) ne pourra en être faite sans l'accord préalable et écrit de la société Lionor, à l'exception de l'utilisation pour un usage privé sous réserve des dispositions différentes voire plus restrictives du Code de la propriété intellectuelle.
<br>Toute utilisation non expressément autorisée peut engager la responsabilité civile et/ou pénale de son auteur. Lionor se réserve le droit d'engager des poursuites judiciaires à l'encontre de toute personne qui n'aurait pas respecté cette interdiction.</p>
<h2>Vie privée - Données personnelles - Cookies</h2>
<p>Nous vous invitons à vous reporter à notre Charte de confidentialité en <a target='_blank' href='https://lionor.s-web.test/politique-de-donnees-personnelles' rel='noreferrer noopener'>cliquant ici</a>.</p>
<h2>Mentions Sanitaires :</h2>
<p>Pour votre santé :<br>- mangez au moins cinq fruits et légumes par jour<br>- pratiquez une activité physique régulière<br>- évitez de manger trop gras, trop sucré, trop salé<br>- évitez de grignoter entre les repas</p>
<p>Pour toute information complémentaire, consultez le site <a target='_blank' href='https://www.mangerbouger.fr' rel='noreferrer noopener'>www.mangerbouger.fr</a></p>
<h2>Responsabilité</h2>
<p>Lionor a déployé ses meilleurs efforts pour s'assurer de l'exactitude des informations présentes sur son site internet (ci-après le Site). Cependant, Lionor n'est responsable ni de l'exactitude, ni des erreurs, ni des omissions contenues sur le Site. L'utilisateur est seul responsable de l'utilisation de telles informations.
<br>Lionor ne donne aucune garantie, explicite ou implicite, et n'assume aucune responsabilité qui pourrait résulter de l'accès ou de l'utilisation de ce Site incluant toutes détériorations ou virus qui pourraient infecter l'équipement informatique ou tout autre bien de l'utilisateur.
<br>Par ailleurs.
<br>Lionor pourra unilatéralement et à tout moment interrompre temporairement ou définitivement l'accès à tout ou partie du Site, y compris notamment le contenu, les fonctionnalités ou les heures de disponibilité, sans que sa responsabilité ne puisse être recherchée et sans que cela ne puisse donner lieu à l'allocation de dommages et intérêts de quelque nature que ce soit.
<br>Les pages du Site peuvent contenir des liens hypertextes renvoyant vers d'autres sites internet gérés par des sociétés distinctes de Lionor, et sur lesquels Lionor n'exerce aucune sorte de contrôle. Lionor n'est pas responsable du contenu de tout autre site auquel vous pourriez avoir accès via le Site ou les autres sites internet du groupe LDC. Tout utilisateur ou visiteur du Site ne peut mettre en place un lien hypertexte en direction de ce Site sans l'autorisation expresse préalable de Lionor.</p>",
]
);
// Policies.
StaticPage::query()->updateOrCreate(
[
'type' => StaticPageType::confidentiality,
],
[
'seo_title' => 'Politique des données personnelles',
'seo_description' => 'Lionor vous informe sur les données que nous collectons, comment nous les traitons, dans quelles finalités.',
'title' => 'Politique des données personnelles',
'type' => StaticPageType::confidentiality,
'content' => "
<h2>Introduction</h2>
<p>Le nouveau Règlement Général de l'Union Européenne sur la Protection des Données (RGPD), entré en vigueur le 25 mai 2018, vise à mieux encadrer la collecte, le traitement et l'utilisation de vos données personnelles.<br>Lionor, en sa qualité de responsable du traitement, renforce ses engagements pour garantir la protection et la sécurité des données à caractère personnel que vous lui confiez.<br><br></p>
<p>A ce titre, la présente Charte de confidentialité vous informe sur le type de données que nous collectons, comment nous les traitons, dans quelles finalités, de manière à rendre leur utilisation la plus adaptée à vos besoins.<br><br>
Le responsable de traitement, qui collecte et gère vos données, est la société Lionor, SA au capital social de 1 646 400 immatriculée au registre du commerce et des sociétés de Dunkerque sous le numéro 305 507 303, dont le siège social est RTE DE LA GARE 59189 STEENBECQUE.<br><br>
Pour toute question relative à notre Charte de confidentialité, merci de nous contacter via le formulaire de contact suivant:<br><a target='_blank' href='https://www.ldc.fr/rgpd/professionnels/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/professionnels/</a><br><a target='_blank' href='https://www.ldc.fr/rgpd/consommateurs/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/consommateurs/</a>
<br><a target='_blank' href='https://www.ldc.fr/rgpd/salaries/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/salaries/</a><br><br>Nous vous invitons à consulter régulièrement cette rubrique car Lionor peut être amené à la modifier à tout moment.
<br>La présente Charte de confidentialité a été mise à jour le 15/03/2023.</p>
<h2>Nos Engagements</h2>
<p>Conformément à la réglementation relative à la protection des données personnelles et à la vie privée, notamment la loi n°78-17 du 6 janvier 1978 relative à l'informatique, aux fichiers et aux libertés dite loi « Informatique et Libertés » et au Règlement (UE) 2016/679 du 25 mai 2018 relatif à la protection des personnes physiques à l'égard du traitement des données à caractère personnel et à la libre circulation de ces données, (ci-après le « Règlement RGPD»), Lionor s'engage à respecter les principes suivants : <br><br>
<strong>Consentement</strong>: vous devez avoir consenti à la collecte et au traitement de vos données. Si tel n'était pas le cas ou si vous avez changé d'avis, merci de vous reporter à l'article ci-dessous relatif à vos droits.<br><br>
<strong>Limitation des données collectées</strong>: nous ne collectons que les données qui sont strictement nécessaires au traitement et à l'utilisation envisagée. Nous vous rappelons qu'il n'y a aucune obligation à nous transmettre ces données. Toutefois certains services ou opérations promotionnelles nécessitent certaines données, faute de quoi vous ne pourriez y accéder.<br><br>
<strong>Limitation des finalités</strong>: les données que nous collectons sont traitées uniquement dans les finalités décrites ci-dessous.<br><br>
<strong>Limitation des durées de conservation</strong>: vos données sont conservées uniquement le temps nécessaire aux finalités de traitement pour lequel elles ont été collectées. Au-delà de cette durée si aucune obligation légale impose leur conservation, nous veillerons à ce que vos données soient supprimées, détruites ou anonymisées.<br><br>
<strong>Protection des données</strong>: en vue de sécuriser vos données et leur traitement, nous avons mis en place des mesures techniques et organisationnelles détaillées ci-dessous.<br><br>
<strong>Transparence et loyauté</strong>: lorsque Lionor collecte une donnée vous concernant, nous vous informons de l'utilisation qui en sera faite. C'est pourquoi nous vous conseillons de lire attentivement les informations qui vous sont transmises à travers notre site internet.</p>
<h2>Quelles données personnelles sont collectées ?</h2>
<p>Au sens du Règlement RGPD, une donnée à caractère personnel désigne toute information relative à une personne physique identifiée ou identifiable.<br><br>Les données personnelles collectées varient en fonction du but de la collecte et du service dont vous souhaitez bénéficier.<br>
<br>La société Lionor est amenée à collecter les catégories de données personnelles suivantes:<br><br>- coordonnées personnelles (civilité, nom, prénom, adresse e-mail, adresse postale) ; <br>- coordonnées personnelles non obligatoires (date de naissance, numéro de téléphone); <br>- identifiants utilisés pour accéder à votre compte en ligne (adresse IP, adresse e-mail et mot de passe);<br>- données relatives aux tickets de caisse de l'utilisateur ainsi que son RIB ou son compte Paypal;<br>- historiques de navigation, telles que les pages visitées et actions sur le site.<br>
<br>Vous vous engagez à fournir des données personnelles actualisées et valides et garantissez ne faire aucune fausse déclaration et ne donner aucune information erronée.</p>
<h2>Pourquoi vos données personnelles sont collectées ?</h2><p>Vos données peuvent être collectées dans la mesure où vous avez consenti de manière spécifique, libre et éclairée. Elles peuvent également être collectées pour satisfaire à l'exécution d'une obligation légale ou dans le cadre de la relation contractuelle construite avec vous (notamment conditions générales d'utilisation, conditions générales de vente) ou encore dans l'intérêt légitime de Lionor (par exemple pour assurer la sécurité des transactions).<br>
<br>Nous collectons vos données personnelles afin de vous fournir la meilleure expérience utilisateur à travers notre site internet.<br><br><strong>Ces données sont collectées aux fins suivantes </strong><br>- Gestion des fonctionnalités et de la qualité du site internet<br>- Gestion de votre inscription à notre Newsletter et de votre Compte utilisateur<br>- Accès à votre Compte utilisateur<br>- Gestion du recrutement<br>- Traitement de vos demandes de renseignements<br>- Communication des informations (promotions, nouveaux produits, activité de la marque)<br>- Gestion de votre participation à nos jeux, tirages au sort, activités promotionnelles<br>- Développement et amélioration des produits Lionor<br>- Statistiques<br>- Proposition de produits personnalisés et de communications adaptées ou pour recommandation de produits en fonction des besoins de consommateurs<br>- Partages sur les réseaux sociaux</p>
<h2>Quels sont vos droits ?</h2><p>Vous disposez d'un certain nombre de droits concernant vos données personnelles listés ci-dessous. <br><br>
<strong>Droit d'accès</strong> Vous pouvez demander la communication de vos données sous une forme accessible, la confirmation que vos données font ou ne font plus l'objet d'un traitement ainsi que la communication des informations relatives à vos données. Afin que vos données personnelles soient exactes, complètes, pertinentes et à jour, vous avez le droit d'accéder, de corriger ou de mettre à jour vos données personnelles à tout moment.<br><br>
<strong>Droit de rectification</strong><br>Vous êtes en droit de demander à ce que vos données soient rectifiées, complétées ou mises à jour lorsqu'elles sont inexactes, incomplètes, équivoques ou périmées.<br><br>
<strong>Droit à l'effacement </strong><br>Vous avez le droit de demander la suppression de vos données personnelles notamment lorsqu'elles ne sont plus nécessaires aux finalités pour lesquelles elles ont été collectées. Si les données personnelles collectées ne sont plus nécessaires à aucune finalité et qu'aucune obligation légale nous impose de les conserver, nous mettrons tout en œuvre pour supprimer, détruire ou anonymiser ces données.<br><br>
<strong>Droit à la limitation du traitement</strong><br>Vous avez le droit de demander la limitation du traitement de vos données personnelles si : <br><br>- vous contestez l'exactitude des données;<br>- vous souhaitez limiter le traitement des données qui n'avaient pas été traitées de manière licite plutôt que leur suppression; <br>- nous n'avons plus besoin de vos données personnelles mais celles-ci sont nécessaires pour la constatation, l'exercice ou la défense de vos droits en justice; <br>- vous vous êtes opposés au traitement de vos données personnelles et attendez de savoir si vos intérêts liés à cette objection l'emportent sur les motifs légitimes que nous poursuivons. <br><br>
<strong>Droit à la portabilité de vos données </strong><br>Lorsque le traitement est effectué à partir de procédés automatisés et que vous avez librement consenti au traitement de vos données personnelles pour une finalité que nous vous avions communiqué au préalable; ou lorsque nous traitons vos données dans le cadre d'une opération commerciale (ex: vous fournir un produit) vous avez le droit de demander:<br>- à recevoir vos données personnelles dans un format structuré, couramment utilisé et lisible par une machine;<br>- à ce que vos données personnelles soient transmises directement à une autre responsable de traitement, lorsque que cela est techniquement possible.<br><br>
<strong>Droit d'opposition</strong><br>Lorsque vos données sont traitées à des fins de prospection commerciale, vous avez le droit, à tout moment, de vous opposer au traitement de vos données personnelles et ce sans motif.</p>
<h2>Droit de porter réclamation auprès de la CNIL</h2>
<p>Si vous estimez que le traitement de vos données personnelles constitue une violation de la réglementation, vous pouvez introduire une réclamation auprès d'une autorité de contrôle, telle que la CNIL.<br><br>
<strong>Vous pouvez exercer ces droits à tout moment en nous contactant via le formulaire suivant :</strong><br><a target='_blank' href='https://www.ldc.fr/rgpd/professionnels/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/professionnels/</a><br><a target='_blank' href='https://www.ldc.fr/rgpd/consommateurs/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/consommateurs/</a><br><a target='_blank' href='https://www.ldc.fr/rgpd/salaries/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/salaries/</a><br><br>
<strong>Vous recevrez une réponse de notre part dans un délai d'un mois à compter de la réception de la demande. En cas de demande complexe ou du nombre important de demandes, le délai peut être prolongé de deux mois. </strong><br><br>
<strong>L'exercice des droits se fait à titre gratuit. Si toutefois la demande était manifestement infondée ou excessive, Lionor se réserve la possibilité d'appliquer des frais administratifs ou de refuser de donner suite à la demande. </strong></p>
<h2>Comment vos données personnelles sont protégées ?</h2>
<p>Afin d'assurer la sécurité de vos données personnelles nous avons mis en place des mesures de sécurité, notamment des contrôles d'accès, des pare-feux, des serveurs sécurisés.<br><br>Ces mesures visent à protéger vos données personnelles contre toute perte, détournement, mauvaise utilisation, divulgation, modification, interférence, dommage, accès non autorisé des données.<br><br>En cas de violation de vos données personnelles susceptible d'engendrer un risque pour vos droits et libertés, Lionor en notifie la violation à la CNIL dans les meilleurs délais et, si possible, 72 heures au plus tard après en avoir pris connaissance. Lionor vous en informera dans les meilleurs délais.</p>
<h2>Accès à vos données personnelles :</h2>
<p>Vos données personnelles sont destinées au personnel de Lionor habilité à les traiter, notamment le service marketing, développement durable, informatique, commercial, qualité et administratif.</p>
<h2>Partage de vos données personnelles :</h2>
<p>Conformément à la règlementation, quand nous sommes amenés à partager vos données personnelles avec des sous-traitants pour certaines finalités décrites dans la présente Charte, nous nous assurons que leur dispositif en matière de sécurité des données et de leur stockage est au moins aussi important que celui de notre organisation. Nous veillons également à ce qu'ils respectent les lois relatives à la protection des données personnelles dans des conditions au moins équivalentes à celles que nous respectons.<br>
<br>Par ailleurs, les sous-traitants étant amenés à traiter de vos données personnelles pour notre compte et sur nos instructions, nous nous engageons à ce que vos données personnelles ne soient pas partagées ou divulguées pour des finalités autres que celles décrites dans la présente Charte. Nous pouvons cependant être amenés à transmettre vos données, pour des finalités autres que celles prévues dans la Charte, dans les cas où leur communication est requise par la loi et les autorités gouvernementales.</p>
<h2>Transfert de vos données en dehors de l'espace économique Européen :</h2>
<p>Nous nous informons que vos données personnelles ne sont pas traitées en dehors de l'Espace Economique Européen (EEE). En cas de changement, nous vous en avertirons et mettrons tout en œuvre pour que ce transfert international de données dispose d'un niveau et de garanties de sécurité adéquats.</p>
<h2>Utilisation des cookies</h2>
<p>Les cookies sont des petits fichiers textes contenant des informations propres vous concernant stockés sur le disque dur de l'équipement informatique avec lequel vous vous êtes connectés sur le site internet et pouvant être lus seulement par le serveur qui l'a fourni. Les cookies sont gérés par votre navigateur internet.<br><br>Les cookies indiquent la fréquentation des pages que vous visitez, ce qui nous aide à connaître les informations qui vous intéressent. Cela permet de vous offrir plus de contenu qui vous intéresse et moins de contenu qui ne vous intéresse pas. <br><br>Les cookies vous permettent de stocker des préférences et les noms des utilisateurs, enregistrer des produits et des services, personnaliser des pages ou faire de la publicité ciblée.<br>
<br>A travers ces cookies, vos données personnelles peuvent être recueilles telles que :<br>
<br>- des informations concernant le navigateur de votre appareil et le système d'exploitation que vous utilisez;
<br>- l'adresse IP de l'appareil que vous utilisez;
<br>- les pages Web que vous consultez;
<br>- les liens sur lesquelles vous cliquez pendant que vous êtes en interaction avec nos services.<br>
<br>Le site internet <a target='_blank' href='https://lionor.s-web.test' rel='noreferrer noopener'>lionor.fr</a> utilise trois types de cookies qui ont des finalités différentes. Tous les cookies de type non techniques doivent recueillir votre consentement avant d'être installés. Vous pouvez donner votre consentement sur la page d'accueil du site internet, en cliquant sur « accepter ».<br>
<br>- Les cookies strictement nécessaires et fonctionnels Ces cookies sont nécessaires au bon fonctionnement du site internet. Ils vous permettent d'utiliser les principales fonctionnalités. Sans ces cookies, vous ne pourrez pas utiliser normalement le site internet.
<br>- Les cookies analytiques Ces cookies nous permettent de connaître l'utilisation et les performances du site internet et d'en améliorer son fonctionnement. <br>
<br>- Les cookies de ciblage<br>
<br>Ces cookies sont utilisés pour vous présenter des publicités ou vous adresser des informations adaptées à vos centres d'intérêts en dehors du site <a target='_blank' href='https://lionor.s-web.test' rel='noreferrer noopener'>lionor.fr</a> lors de votre navigation sur Internet. Le refus de ces cookies publicitaires n'a pas d'impact sur l'utilisation du site internet. Cependant, le fait de refuser les cookies publicitaires n'entraînera pas l'arrêt de la publicité sur Internet. Cela aura seulement pour effet d'afficher une publicité qui ne tiendra pas compte de vos centres d'intérêt ou de vos préférences.<br><br>
<strong>Vous avez le choix :</strong><br>
<br><strong>- d'accepter</strong> ces cookies en cliquant sur le bouton «Accepter» présent sur le bandeau Cookies, apparaissant au bas de votre page internet. Tant que vous n'avez pas accepté, les cookies ne pourront être stockés sur votre appareil; ou<br><br>
<strong>de refuser</strong> ces cookies via le bouton «Paramétrer» présent sur le bandeau Cookies ou les paramètres de votre navigateur internet.<br>
<br>Lorsque vous cliquez sur le bouton «Paramétrer» du bandeau Cookies, une liste s'affiche présentant les différents types de cookies par finalité. Si vous consentez au dépôt des cookies, il vous suffit de les activer puis de cliquer sur le bouton «Enregistrer» en bas de la fenêtre. Si vous refusez le dépôt des cookies, il vous suffit de cliquer sur le bouton «Enregistrer», le dépôt des cookies étant par défaut désactivé.<br>
<br>Pour en savoir plus sur la désactivation des cookies via votre navigateur internet :<br>
<br>Internet Explorer: <a target='_blank' href='http://windows.microsoft.com/fr-FR/windows-vista/Block-or-allow-cookies' rel='noreferrer noopener'>http://windows.microsoft.com/fr-FR/windows-vista/Block-or-allow-cookies</a>
<br>Chrome: <a target='_blank' href='http://support.google.com/chrome/bin/answer.py?hl=fr&amp;hlrm=en&amp;answer=95647' rel='noreferrer noopener'>http://support.google.com/chrome/bin/answer.py?hl=fr&amp;hlrm=en&amp;answer=95647</a>
<br>Firefox: <a target='_blank' href='https://support.mozilla.org/fr/kb/protection-renforcee-contre-pistage-firefox-ordinateur' rel='noreferrer noopener'>https://support.mozilla.org/fr/kb/protection-renforcee-contre-pistage-firefox-ordinateur</a>
<br>Safari: <a target='_blank' href='https://support.apple.com/fr-fr/guide/safari/sfri11471/mac' rel='noreferrer noopener'>https://support.apple.com/fr-fr/guide/safari/sfri11471/mac</a><br>
<br>La durée de conservation de ces informations dans votre appareil est d'une durée maximale de 13 mois. A l'expiration de ce délai, nous recueillerons de nouveau votre consentement. <br>Pour plus d'informations sur le paramétrage de vos cookies, vous pouvez consulter :<br><a target='_blank' href='http://www.youronlinechoices.com/fr/' rel='noreferrer noopener'>http://www.youronlinechoices.com/fr/</a><br><a target='_blank' href='https://www.allaboutcookies.org/' rel='noreferrer noopener'>https://www.allaboutcookies.org/</a></p>
<h2>Sites internet de tiers</h2>
<p>Le site internet Lionor peut être associé à des sites internet tiers. Les sites hébergés par des tiers sont hors de notre contrôle. Lorsque vous renseignez des données personnelles sur un site internet tiers, le traitement de ces données n'est pas effectué sur notre site internet, mais sur le site internet tiers concerné. Toute donnée personnelle que vous fournissez sera collectée et traitée conformément à la politique relative à la confidentialité des données du tiers concerné.<br>
<br>Par conséquent, nous n'assumons aucune responsabilité concernant les pratiques exercées par lesdits tiers en matière de protection des données.</p>
<h2>Droit applicable</h2>
<p>La présente Charte de confidentialité est régie et sera interprétée conformément au droit français.</p>
<h2>Nous contacter</h2>
<p>Si vous avez des questions, remarques ou réclamations relatives à la présente Charte de confidentialité, ou le traitement de vos données personnelles, veuillez-nous contacter à l'adresse :<br><br><a target='_blank' href='https://www.ldc.fr/rgpd/professionnels/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/professionnels/</a><br><a target='_blank' href='https://www.ldc.fr/rgpd/consommateurs/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/consommateurs/</a><br><a target='_blank' href='https://www.ldc.fr/rgpd/salaries/' rel='noreferrer noopener'>https://www.ldc.fr/rgpd/salaries/</a></p>
",
]
);
}
}

View file

@ -8,9 +8,9 @@ data:
APP_ENV: "${APP_APP_ENV}"
APP_KEY: "${APP_APP_KEY}"
APP_DEBUG: "${APP_APP_DEBUG}"
APP_URL: "https://www.${APP_DOMAIN}"
APP_URL: "https://${REDIRECT}${APP_DOMAIN}"
LOG_CHANNEL: "k8s"
DB_HOST: "mariadb-service.common-services"
DB_HOST: "${APP_DB_HOST}"
DB_DATABASE: "${APP_DB_DATABASE}"
DB_USERNAME: "${APP_DB_USERNAME}"
DB_PASSWORD: "${APP_DB_PASSWORD}"
@ -28,6 +28,12 @@ data:
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}"
MATOMO_SITE_ID: "${APP_MATOMO_SITE_ID}"
SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}"
SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}"
SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}"
SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}"
SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}"
UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}"
---
apiVersion: v1
@ -63,15 +69,22 @@ spec:
envFrom:
- configMapRef:
name: ${CI_PROJECT_PATH_SLUG}-config-map
resources:
limits:
cpu: "${APP_CI_CPU_LIMIT}"
memory: "${APP_CI_MEMORY_LIMIT}"
requests:
cpu: "${APP_CI_CPU_REQUEST}"
memory: "${APP_CI_MEMORY_REQUEST}"
volumeMounts:
- name: storage
mountPath: /app/srcs/storage
imagePullSecrets:
- name: docker-registry-credentials
securityContext:
runAsUser: 33
runAsGroup: 33
fsGroup: 33
runAsUser: 82
runAsGroup: 82
fsGroup: 82
fsGroupChangePolicy: "OnRootMismatch"
volumes:
- name: storage
@ -89,10 +102,10 @@ spec:
app: ${CI_PROJECT_PATH_SLUG}-app
ports:
- port: 80
targetPort: 443
targetPort: 80
---
apiVersion: traefik.containo.us/v1alpha1
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: ${CI_PROJECT_PATH_SLUG}-ingress-route
@ -101,30 +114,15 @@ spec:
- web
- websecure
routes:
- match: Host(`${APP_DOMAIN}`, `www.${APP_DOMAIN}`)
- match: Host(`${APP_DOMAIN}`) || Host(`www.${APP_DOMAIN}`)
kind: Rule
services:
- name: ${CI_PROJECT_PATH_SLUG}-service
port: 80
middlewares:
- name: redirect-no-www-to-www
namespace: common-services
tls:
secretName: $APP_DOMAIN
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: $APP_DOMAIN
spec:
dnsNames:
- $APP_DOMAIN
- www.${APP_DOMAIN}
secretName: $APP_DOMAIN
issuerRef:
name: $APP_CERT_ISSUER
kind: ClusterIssuer
secretName: ${CRT_TLS_NAME}
---
apiVersion: batch/v1
@ -133,6 +131,9 @@ metadata:
name: ${CI_PROJECT_PATH_SLUG}-cronjob
spec:
schedule: "* * * * *"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
@ -151,7 +152,7 @@ spec:
containers:
- name: ${CI_PROJECT_PATH_SLUG}-scheduled
image: $IMAGE_TAG
imagePullPolicy: Always
imagePullPolicy: IfNotPresent
command:
- "php"
args:
@ -170,8 +171,8 @@ spec:
persistentVolumeClaim:
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
securityContext:
runAsUser: 33
runAsGroup: 33
fsGroup: 33
runAsUser: 82
runAsGroup: 82
fsGroup: 82
fsGroupChangePolicy: "OnRootMismatch"
restartPolicy: OnFailure

307
package-lock.json generated
View file

@ -1,5 +1,5 @@
{
"name": "lionor",
"name": "srcs",
"lockfileVersion": 3,
"requires": true,
"packages": {
@ -11,7 +11,7 @@
"@fortawesome/free-regular-svg-icons": "latest",
"@fortawesome/free-solid-svg-icons": "latest",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@ldc-web/cookie-notice": "^3.0.8",
"@ldc-web/cookie-notice": "^6.0.3",
"@rushstack/eslint-patch": "^1.3.2",
"@sipec/vue3-tags-input": "^3.0.4",
"@types/bootstrap": "^5.2.7",
@ -31,10 +31,11 @@
"fslightbox": "^3.4.1",
"laravel-vite-plugin": "latest",
"leaflet": "^1.9.4",
"meow": "^9.0.0",
"postcss-discard-comments": "^5.1.2",
"prettier": "latest",
"print-js": "^1.6.0",
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"slugify": "^1.6.6",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",
@ -75,12 +76,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.13",
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
},
"engines": {
@ -127,12 +128,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.23.0",
"@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@ -406,9 +407,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -461,9 +462,9 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
@ -475,9 +476,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@ -1684,20 +1685,20 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz",
"integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==",
"version": "7.23.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz",
"integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"@babel/parser": "^7.23.6",
"@babel/types": "^7.23.6",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
@ -1705,12 +1706,12 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
@ -2435,9 +2436,9 @@
}
},
"node_modules/@ldc-web/cookie-notice": {
"version": "3.0.8",
"resolved": "https://lab.s-web.fr/api/v4/projects/50/packages/npm/@ldc-web/cookie-notice/-/@ldc-web/cookie-notice-3.0.8.tgz",
"integrity": "sha1-FOc6hEv1L9NOnRDOdyBnbIsd31A=",
"version": "6.0.3",
"resolved": "https://lab.s-web.fr/api/v4/projects/50/packages/npm/@ldc-web/cookie-notice/-/@ldc-web/cookie-notice-6.0.3.tgz",
"integrity": "sha1-mW2DD+8ipWkmvKhZ3m9h5wemAHI=",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
@ -2551,12 +2552,31 @@
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
"dev": true
},
"node_modules/@types/fs-extra": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz",
"integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.11",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.11.tgz",
"integrity": "sha512-L7A0AINMXQpVwxHJ4jxD6/XjZ4NDufaRlUJHjNIFKYUFBH1SvOW+neaqb0VTRSLW5suSrSu19ObFEFnfNcr+qg==",
"dev": true
},
"node_modules/@types/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
"dev": true,
"dependencies": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
@ -2572,19 +2592,32 @@
"@types/geojson": "*"
}
},
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"node_modules/@types/minimist": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz",
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==",
"dev": true
},
"node_modules/@types/node": {
"version": "22.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
"dev": true,
"peer": true
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.3",
@ -3270,7 +3303,6 @@
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
"integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -3292,9 +3324,9 @@
"dev": true
},
"node_modules/axios": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz",
"integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.0",
@ -3464,8 +3496,6 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
@ -3475,8 +3505,6 @@
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"camelcase": "^5.3.1",
"map-obj": "^4.0.0",
@ -3584,6 +3612,12 @@
"dev": true,
"peer": true
},
"node_modules/colorette": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -3755,7 +3789,6 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -3765,7 +3798,6 @@
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
"integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
"dev": true,
"peer": true,
"dependencies": {
"decamelize": "^1.1.0",
"map-obj": "^1.0.0"
@ -3782,7 +3814,6 @@
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -3853,7 +3884,6 @@
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"peer": true,
"dependencies": {
"is-arrayish": "^0.2.1"
}
@ -4558,7 +4588,6 @@
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
"integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
}
@ -4595,7 +4624,6 @@
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
"integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
"dev": true,
"peer": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -4608,7 +4636,6 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"peer": true,
"dependencies": {
"yallist": "^4.0.0"
},
@ -4620,8 +4647,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/html-tags": {
"version": "3.3.1",
@ -4697,8 +4723,6 @@
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -4730,8 +4754,7 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
@ -4811,7 +4834,6 @@
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4872,8 +4894,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@ -4925,7 +4946,6 @@
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4975,8 +4995,7 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/loader-utils": {
"version": "2.0.4",
@ -5064,7 +5083,6 @@
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
},
@ -5095,8 +5113,6 @@
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
"integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@types/minimist": "^1.2.0",
"camelcase-keys": "^6.2.2",
@ -5123,8 +5139,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
"integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=10"
},
@ -5180,7 +5194,6 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4"
}
@ -5202,7 +5215,6 @@
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
"dev": true,
"peer": true,
"dependencies": {
"arrify": "^1.0.1",
"is-plain-obj": "^1.1.0",
@ -5259,7 +5271,6 @@
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
"integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
"dev": true,
"peer": true,
"dependencies": {
"hosted-git-info": "^4.0.1",
"is-core-module": "^2.5.0",
@ -5275,7 +5286,6 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"peer": true,
"dependencies": {
"yallist": "^4.0.0"
},
@ -5288,7 +5298,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"peer": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -5303,8 +5312,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/normalize-path": {
"version": "3.0.0",
@ -5400,8 +5408,6 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
@ -5423,7 +5429,6 @@
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -5654,12 +5659,6 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/print-js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/print-js/-/print-js-1.6.0.tgz",
"integrity": "sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==",
"dev": true
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -5709,8 +5708,6 @@
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -5720,8 +5717,6 @@
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@types/normalize-package-data": "^2.4.0",
"normalize-package-data": "^2.5.0",
@ -5737,8 +5732,6 @@
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
"integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"find-up": "^4.1.0",
"read-pkg": "^5.2.0",
@ -5756,8 +5749,6 @@
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@ -5771,8 +5762,6 @@
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"p-locate": "^4.1.0"
},
@ -5785,8 +5774,6 @@
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"p-try": "^2.0.0"
},
@ -5802,8 +5789,6 @@
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"p-limit": "^2.2.0"
},
@ -5816,8 +5801,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -5826,17 +5809,13 @@
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true,
"optional": true,
"peer": true
"dev": true
},
"node_modules/read-pkg/node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
@ -5849,8 +5828,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver"
}
@ -5860,8 +5837,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -5883,8 +5858,6 @@
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
@ -6070,6 +6043,82 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup-plugin-copy": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz",
"integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==",
"dev": true,
"dependencies": {
"@types/fs-extra": "^8.0.1",
"colorette": "^1.1.0",
"fs-extra": "^8.1.0",
"globby": "10.0.1",
"is-plain-object": "^3.0.0"
},
"engines": {
"node": ">=8.3"
}
},
"node_modules/rollup-plugin-copy/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/rollup-plugin-copy/node_modules/globby": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
"integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
"dev": true,
"dependencies": {
"@types/glob": "^7.1.1",
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.0.3",
"glob": "^7.1.3",
"ignore": "^5.1.1",
"merge2": "^1.2.3",
"slash": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/rollup-plugin-copy/node_modules/is-plain-object": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rollup-plugin-copy/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/rollup-plugin-copy/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -6258,7 +6307,6 @@
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
"dev": true,
"peer": true,
"dependencies": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
@ -6268,15 +6316,13 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"peer": true,
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
@ -6286,8 +6332,7 @@
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/string-width": {
"version": "4.2.3",
@ -6321,8 +6366,6 @@
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"min-indent": "^1.0.0"
},
@ -6830,9 +6873,9 @@
"peer": true
},
"node_modules/sweetalert2": {
"version": "11.7.32",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.32.tgz",
"integrity": "sha512-44tNNe2oLe7T94mT6dus4hc9G7qg6jZU/K5qZzpNS6e5HGPrSF6Kie6oZ7B5puIJydB34V2h/8f5EhIFivYo4A==",
"version": "11.10.2",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz",
"integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==",
"dev": true,
"funding": {
"type": "individual",
@ -6948,8 +6991,6 @@
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -7003,6 +7044,12 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@ -7117,16 +7164,15 @@
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"peer": true,
"dependencies": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vite": {
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"dev": true,
"peer": true,
"dependencies": {
@ -7745,7 +7791,6 @@
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10"
}

View file

@ -18,7 +18,7 @@
"@fortawesome/free-regular-svg-icons": "latest",
"@fortawesome/free-solid-svg-icons": "latest",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@ldc-web/cookie-notice": "^3.0.8",
"@ldc-web/cookie-notice": "^6.0.3",
"@rushstack/eslint-patch": "^1.3.2",
"@sipec/vue3-tags-input": "^3.0.4",
"@types/bootstrap": "^5.2.7",
@ -38,10 +38,11 @@
"fslightbox": "^3.4.1",
"laravel-vite-plugin": "latest",
"leaflet": "^1.9.4",
"meow": "^9.0.0",
"postcss-discard-comments": "^5.1.2",
"prettier": "latest",
"print-js": "^1.6.0",
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"slugify": "^1.6.6",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",

10
phpstan.neon Normal file
View file

@ -0,0 +1,10 @@
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
# Level 9 is the highest level
level: 5

13
production-resources.yaml Normal file
View file

@ -0,0 +1,13 @@
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: $APP_DOMAIN
spec:
dnsNames:
- $APP_DOMAIN
- www.${APP_DOMAIN}
secretName: $APP_DOMAIN
issuerRef:
name: $APP_CERT_ISSUER
kind: ClusterIssuer

View file

@ -1,2 +1,13 @@
User-agent: *
Disallow:
Disallow: /bo
User-agent: GPTBot
Disallow: /
User-agent: Amazonbot
Disallow: /
User-agent: ClaudeBot
Disallow: /
Sitemap: https://www.lionor.fr/sitemap.xml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Some files were not shown because too many files have changed in this diff Show more