Compare commits

...
Sign in to create a new pull request.

383 commits

Author SHA1 Message Date
K.
c02cf6b6ae Merge branch '326-postcode-required' into 'develop'
feat: ✨ make postcode required in contact form

See merge request ldc-web/projets/le-gaulois-pro!242
2026-10-19 11:20:40 +00:00
K.
1be2a7f211 Merge branch 'issue-339' into 'develop'
fix: 🚸 move BEA banner into another section #339

See merge request ldc-web/projets/le-gaulois-pro!244
2026-10-19 11:20:23 +00:00
Alexis Gousseau
4d7e1a70f1
fix: 🚸 move BEA banner into another section #339 2026-10-15 16:18:53 +02:00
K Muller
1f758666c4 fix: 🐛 Docker image + alpine www data 82 2026-10-08 16:54:22 +02:00
Karl M'muller
9607cfd8d8 Merge branch 'issue-339' into 'develop'
feat: ✨ add BEA banner in homepage #339

See merge request ldc-web/projets/le-gaulois-pro!243
2026-10-08 14:43:50 +00:00
Alexis Gousseau
07bc082bb3
chore: ⬆️ install meow + npm update 2026-10-06 13:00:15 +02:00
Alexis Gousseau
8e590e46a1
feat: ✨ add BEA banner in homepage #339 2026-10-06 12:59:57 +02:00
Noah Lancien
c869ad71c0 feat: ✨ make postcode required in contact form 2026-07-02 11:31:23 +02:00
karl.muller
634962d93e Merge branch '316-bug-bo-products' into 'develop'
fix: 🐛 delete order column on products table

See merge request ldc-web/projets/le-gaulois-pro!239
2026-05-21 09:33:26 +00:00
Corentin Huet
247d57ef6b fix: 🚨 fix linters errors 2026-05-11 14:21:12 +02:00
Vincent BRIDIER
57d993a799 Merge branch '307-fix-newsletter' into 'develop'
fix: 🐛 increase title font size and set re7 to prod links

See merge request ldc-web/projets/le-gaulois-pro!240
2026-05-07 06:40:53 +00:00
Corentin Huet
395ba817bf fix: 🐛 increase title font size and set re7 to prod links 2026-05-06 15:54:58 +02:00
Noix
a43e775f4d Merge branch '307-newsletter' into 'develop'
feat: ✨ add newsletter 260416

See merge request ldc-web/projets/le-gaulois-pro!238
2026-05-04 15:26:12 +00:00
Corentin Huet
4bda1ee99b fix: 🐛 delete order column on products table 2026-04-29 16:23:41 +02:00
Corentin Huet
d90a7f6991 feat: ✨ add newsletter 260416 2026-04-29 12:45:01 +02:00
Noix
73920cf1fd Merge branch 'migrate-mysql-and-bump-php' into 'develop'
chore: ⬆️ Bump php8.5 + migrate to mysql

See merge request ldc-web/projets/le-gaulois-pro!237
2026-04-16 12:44:13 +00:00
Noah Lancien
a9a4d25525 chore: ⬆️ Bump php8.5 + migrate to mysql 2026-04-15 16:59:48 +02:00
Noix
c24d2f7360 Merge branch 'issue-235' into 'develop'
add order on front for product and recipes

See merge request ldc-web/projets/le-gaulois-pro!236
2026-04-14 09:52:35 +00:00
System User
571f1e4f1c add order on front for product and recipes 2026-04-13 15:35:33 +02:00
Noix
4d0138d635 Merge branch 'issue-299-and-300' into 'develop'
feat ✨ : refactor code for ordering list of products or recipes

See merge request ldc-web/projets/le-gaulois-pro!235
2026-04-09 06:56:13 +00:00
Noix
e70a6f8fc9 Merge branch 'issue-296' into 'develop'
feat ✨ : save draft data in contact form

See merge request ldc-web/projets/le-gaulois-pro!234
2026-04-09 06:53:15 +00:00
System User
117c64c5b7 feat ✨ refactor ordering logic for models 2026-03-25 10:45:26 +01:00
System User
b8f75d958b refacto: remove script from blade and add to ts 2026-03-24 18:28:25 +01:00
System User
4c26d1e505 feat ✨ : save draft contact form 2026-03-06 16:19:46 +01:00
noah.lancien
6286b3a335 Merge branch 'newsletter_mars_2026' into 'develop'
feat: align blocs text footer NL LGP

See merge request ldc-web/projets/le-gaulois-pro!233
2026-03-06 08:46:59 +00:00
Ophélie Girault
dbd2de3e9d feat: align blocs text footer NL LGP 2026-03-05 15:42:49 +01:00
noah.lancien
1115313c46 Merge branch 'newsletter_mars_2026' into 'develop'
feat: 🚚 add links to PROD + add picto fb LGP

See merge request ldc-web/projets/le-gaulois-pro!232
2026-03-05 09:18:22 +00:00
Ophélie Girault
7c64100629 feat: 🚚 add links to PROD + add picto fb LGP 2026-03-05 10:03:18 +01:00
noah.lancien
cfce95a923 Merge branch 'newsletter_mars_2026' into 'develop'
feat: 🚚 modif links picto footer to re7 NL LGP

See merge request ldc-web/projets/le-gaulois-pro!231
2026-03-02 14:58:42 +00:00
Ophélie Girault
e0b30b3481 feat: 🚚 modif links picto footer re7 NL LGP 2026-03-02 15:48:10 +01:00
noah.lancien
1a4302a7ca Merge branch 'newsletter_mars_2026' into 'develop'
🎉 create NL LGP mars 2026

See merge request ldc-web/projets/le-gaulois-pro!230
2026-03-02 11:14:57 +00:00
Ophélie Girault
6f834916ef feat: 🎉 add index.html NL LGP 2026-02-26 16:34:36 +01:00
Ophélie Girault
1918f08af5 feat: 🎉 create NL LGP mars 2026 2026-02-26 16:30:53 +01:00
noah.lancien
4a58fdeb26 Merge branch 'issue-295' into 'develop'
feat ✨ : change font and content for card s news on home

See merge request ldc-web/projets/le-gaulois-pro!229
2026-02-26 08:57:04 +00:00
noah.lancien
82558bd5df Merge branch 'issue-298' into 'develop'
feat ✨ : update sorting news DESC

See merge request ldc-web/projets/le-gaulois-pro!228
2026-02-26 08:50:12 +00:00
System User
49a74589b0 feat ✨ : change font and content for card s news on home 2026-02-25 17:48:14 +01:00
System User
a5f17cb108 feat ✨ : update sorting news DESC 2026-02-25 16:12:14 +01:00
Vincent BRIDIER
18ab7d63aa Merge branch 'issue-297' into 'develop'
Issue 297

See merge request ldc-web/projets/le-gaulois-pro!227
2026-02-09 11:42:46 +00:00
Vincent BRIDIER
ce646440fb Merge branch 'issue-294' into 'develop'
fix 🐛 : date created articles

See merge request ldc-web/projets/le-gaulois-pro!226
2026-02-09 11:42:32 +00:00
Vincent BRIDIER
84cea60760 Merge branch '286-new-species' into 'develop'
feat: ✨ add new species

See merge request ldc-web/projets/le-gaulois-pro!225
2026-02-09 11:42:20 +00:00
Vincent BRIDIER
68831c8de4 Merge branch '293-picto-fb' into 'develop'
feat: ✨ add facebook picto

See merge request ldc-web/projets/le-gaulois-pro!224
2026-02-09 11:42:10 +00:00
System User
826715f255 fix csfixer problems 2026-02-06 15:16:39 +01:00
System User
b70f6dfc0b feat ✨ : change style and image 2026-02-06 15:13:29 +01:00
System User
5456271586 fix 🐛: resolve issue vers.1 2026-02-05 17:23:46 +01:00
System User
d91ed18200 fix 🐛: csfixer problems 2026-02-05 12:26:20 +01:00
System User
76fca04789 fix 🐛 : date created articles 2026-02-05 11:46:08 +01:00
Corentin Huet
c082f55e4a feat: ✨ add new species 2026-01-30 17:07:47 +01:00
Corentin Huet
b486c9e318 feat: ✨ add facebook picto 2026-01-30 16:39:40 +01:00
Noah Lancien
12ab0f1da5 ci: limit jobs history to 1 2026-01-22 12:36:22 +01:00
noah.lancien
25d14f5bb4 Merge branch '290-brigade-page-bo' into 'develop'
feat: create models for brigade page - #290

See merge request ldc-web/projets/le-gaulois-pro!223
2026-01-22 11:31:47 +00:00
Noah Lancien
b4a1a3c3d2 feat: create models for brigade page - #290 2026-01-19 14:47:29 +01:00
noah.lancien
e719dc51cd Merge branch '288-remove-brigade-in-mail' into 'develop'
fix: remove brigande line in contact form mail

See merge request ldc-web/projets/le-gaulois-pro!221
2026-01-15 15:09:50 +00:00
noah.lancien
096f61faf4 Merge branch '287-ck-content-fix' into 'develop'
fix: wrap ck content in a ck-content div

See merge request ldc-web/projets/le-gaulois-pro!222
2026-01-15 15:08:45 +00:00
Noah Lancien
029f2c0c0a feat: updated admin-secure 2026-01-13 15:49:36 +01:00
Noah Lancien
eae876a911 ci: added ci variables to define pod resources 2026-01-09 08:58:44 +01:00
Noah Lancien
137d050989 fix: wrap ck content in a ck-content div 2026-01-07 15:57:39 +01:00
Noah Lancien
44793b3b62 fix: remove brigande line in contact form mail 2026-01-07 12:08:40 +01:00
noah.lancien
b81354b647 Merge branch '285-cookies' into 'develop'
fix: adjust cookie text color

See merge request ldc-web/projets/le-gaulois-pro!220
2025-11-12 15:13:20 +00:00
Corentin Huet
262fdf3363 fix: adjust cookie text color 2025-11-03 11:55:24 +01:00
noah.lancien
3986b32fea Merge branch 'newsletter-lgp-novembre' into 'develop'
feat : ✏  fix typos NL LGP

See merge request ldc-web/projets/le-gaulois-pro!219
2025-10-30 11:06:56 +00:00
Ophélie Girault
2d98fbf2b8 feat : ✏ fix typos NL LGP 2025-10-30 11:58:27 +01:00
noah.lancien
fe1194a6ab Merge branch 'newsletter-lgp-novembre' into 'develop'
feat : 🚚 rename RE7 links > to PROD NL LGP Nov 25

See merge request ldc-web/projets/le-gaulois-pro!218
2025-10-29 14:27:01 +00:00
Ophélie Girault
bc9cdee644 feat : 🚚 rename RE7 links > to PROD NL LGP Nov 25 2025-10-29 15:15:46 +01:00
noah.lancien
912e8bf317 Merge branch 'newsletter-lgp-novembre' into 'develop'
feat : ✏  typo in the newsletter: hivers > hiver, éffiloché > effiloché

See merge request ldc-web/projets/le-gaulois-pro!217
2025-10-28 10:13:00 +00:00
Ophélie Girault
8de3645ac9 feat : ✏ typo in the newsletter: hivers > hiver, éffiloché > effiloché 2025-10-28 11:07:40 +01:00
noah.lancien
2d91206ec8 Merge branch 'newsletter-lgp-novembre' into 'develop'
🎉 create newsletter lgp novembre

See merge request ldc-web/projets/le-gaulois-pro!216
2025-10-27 16:17:43 +00:00
Ophélie Girault
8ad5cfcd51 feat: 🎉 create newsletter lgp novembre 2025-10-27 16:20:19 +01:00
Ophélie Girault
befae186c1 feat : 🎉 create newsletter lgp novembre 2025-10-27 15:48:10 +01:00
noah.lancien
abf22f1f0e Merge branch 'tags-crud-improvement' into 'develop'
feat: added filter for tags type

See merge request ldc-web/projets/le-gaulois-pro!215
2025-10-14 08:43:03 +00:00
Noah Lancien
0bb38af214 feat: added filter for tags type 2025-10-14 09:36:43 +02:00
Noah Lancien
e9aa0720c5 ci: added staging-resources.yaml 2025-10-14 09:16:42 +02:00
noah.lancien
83418be011 Merge branch '275-story-page' into 'develop'
feat: remove story page and all related content

See merge request ldc-web/projets/le-gaulois-pro!212
2025-10-14 07:06:41 +00:00
noah.lancien
c6d14f55f3 Merge branch '282-product-tags' into 'develop'
feat: added tags to products

See merge request ldc-web/projets/le-gaulois-pro!214
2025-10-14 07:05:09 +00:00
noah.lancien
bbd49c4303 Merge branch '280-tag-crud' into 'develop'
feat: uncomment tags CRUD

See merge request ldc-web/projets/le-gaulois-pro!213
2025-10-14 07:03:05 +00:00
Noah Lancien
82461ec23f feat: added tags to products 2025-10-13 18:00:09 +02:00
Noah Lancien
43be1369ac feat: uncomment tags CRUD 2025-10-13 15:52:53 +02:00
Noah Lancien
1d56b74465 feat: remove story page and all related content 2025-10-13 15:38:30 +02:00
noah.lancien
764c8e499a Merge branch '281-brigade' into 'develop'
feat: remove CTA and update text on brigade and contact pages

See merge request ldc-web/projets/le-gaulois-pro!211
2025-10-13 10:51:17 +00:00
Corentin Huet
fca4257123 feat: remove CTA and update text on brigade and contact pages 2025-10-13 12:10:04 +02:00
Noah Lancien
b24d534329 fix: upated npm dependencies 2025-09-19 12:57:08 +02:00
noah.lancien
0db974d865 Merge branch '276-news' into 'develop'
feat: move breadcrumb above news title

See merge request ldc-web/projets/le-gaulois-pro!210
2025-09-18 07:22:18 +00:00
Corentin Huet
41353e8208 feat: move breadcrumb above news title 2025-09-17 15:17:39 +02:00
noah.lancien
1adf9d15f9 Merge branch 'sentry-fix' into 'develop'
fix: Make advice->picture required in request + manage null pictures on bo...

See merge request ldc-web/projets/le-gaulois-pro!209
2025-07-31 17:00:23 +00:00
Noah Lancien
d213e6d113 fix: make nullable sticker + sticker_sm for advices 2025-07-31 18:59:00 +02:00
Noah Lancien
f6a29ace4b fix: Make advice->picture required in request + manage null pictures on bo index to prevent crashes with actual datas 2025-07-31 18:52:42 +02:00
karl.muller
b5722a05f0 Merge branch 'issue-269-logo' into 'develop'
feat: update header/footer logo

See merge request ldc-web/projets/le-gaulois-pro!208
2025-07-31 07:35:28 +00:00
Corentin Huet
028aa9ccb2 feat: update header/footer logo 2025-07-28 11:06:19 +02:00
noah.lancien
a909b42ce9 Merge branch 'issue-243-redirections' into 'develop'
feat: add laravel-redirections package

See merge request ldc-web/projets/le-gaulois-pro!207
2025-07-09 10:04:25 +00:00
Corentin Huet
8aaf8fabdd feat: add laravel-redirections package 2025-07-09 12:00:00 +02:00
noah.lancien
5e16bc33c8 Merge branch 'issue-273' into 'develop'
fix last feedbacks for production

See merge request ldc-web/projets/le-gaulois-pro!206
2025-07-03 14:43:35 +00:00
Noah Lancien
1368c73271 clean: remove index.mjml 2025-07-03 16:41:26 +02:00
Vincent Crochard
44212dd81f fix last feedbacks for production 2025-07-03 16:29:21 +02:00
noah.lancien
39009f63f0 Merge branch 'issue-274' into 'develop'
fix: 🐛 clarifies the extension of product commercial sheet #274

See merge request ldc-web/projets/le-gaulois-pro!205
2025-07-03 13:28:48 +00:00
Alexis Gousseau
359b40f43c
fix: 🚨 update types for php 8.4 2025-07-03 15:22:50 +02:00
Alexis Gousseau
079d7c2636
fix: 🐛 clarifies the extension of product commercial sheet #274 2025-07-03 15:19:54 +02:00
karl.muller
3d504f0f99 Merge branch 'issue-273' into 'develop'
update urls for production

See merge request ldc-web/projets/le-gaulois-pro!204
2025-07-02 12:52:30 +00:00
Vincent Crochard
2b340f5593 update urls for production 2025-07-02 14:40:50 +02:00
karl.muller
653c1e4714 Merge branch 'issue-262-bis' into 'develop'
style: 💄 add ui improvment

See merge request ldc-web/projets/le-gaulois-pro!203
2025-07-01 10:16:20 +00:00
Karl
b62dfd64ed style: 💄 add ui improvment 2025-07-01 09:37:06 +02:00
karl.muller
ded8e85fd1 Merge branch 'issue-273' into 'develop'
🔥 update  newsletter Brigad Juillet last updates

See merge request ldc-web/projets/le-gaulois-pro!202
2025-06-30 14:33:00 +00:00
Vincent Crochard
165d9bb4f8 🔥 update newsletter Brigad Juillet last updates 2025-06-30 16:20:33 +02:00
noah.lancien
8c01eb8db0 Merge branch 'issue-262' into 'develop'
feat: ✨ add crm + newsletter subscription + unsubscribe page

See merge request ldc-web/projets/le-gaulois-pro!194
2025-06-26 09:49:40 +00:00
Noah Lancien
ee37488030 fix: feedbacks on contact form with newsletter subscription 2025-06-26 11:44:26 +02:00
noah.lancien
5c920f2b91 Merge branch 'markup' into 'develop'
fix: ⏪ replace component image markup on svg by img base tag #271

See merge request ldc-web/projets/le-gaulois-pro!201
2025-06-25 14:51:44 +00:00
Alexis Gousseau
9bb5132025
fix: ⏪ replace component image markup on svg by img base tag #271 2025-06-25 11:15:38 +02:00
noah.lancien
acf5d66c37 Merge branch 'issue-273' into 'develop'
🔥 update  newsletter Brigade Juin 2025

See merge request ldc-web/projets/le-gaulois-pro!200
2025-06-24 10:21:25 +00:00
Noah Lancien
5dbb494f10 fix: remove conditions to check if a customer exists + some feedbacks 2025-06-24 12:18:21 +02:00
Karl Muller
c6a9e2135f feat: ✨ add crm + newsletter subscription + unsubscribe page 2025-06-24 12:18:19 +02:00
Vincent Crochard
76022bac85 🔥 update newsletter Brigade Juin 2025 2025-06-24 11:56:56 +02:00
noah.lancien
a7a227a669 Merge branch 'issue-273' into 'develop'
🔥 Add files newsletter Brigade Juin 2025

See merge request ldc-web/projets/le-gaulois-pro!199
2025-06-24 08:41:07 +00:00
vincent.crochard
adc383d41c Update index.mjml 2025-06-23 18:15:00 +00:00
Vincent Crochard
623b39139a 🔥 Add files newsletter Brigade Juin 2025 2025-06-23 19:59:15 +02:00
Noah Lancien
7e75ef6696 fix: wording in brigade page 2025-06-20 12:01:46 +02:00
noah.lancien
8b075db73a Merge branch 'issue-268' into 'develop'
fix: resolve conflict and error on links

See merge request ldc-web/projets/le-gaulois-pro!198
2025-06-17 14:00:44 +00:00
Karl
f4d01656f0 fix: resolve conflict 2025-06-17 15:55:36 +02:00
Karl
c8971a00fd feat: add missing part to the brigade page + contact form 2025-06-17 15:46:30 +02:00
Noah Lancien
a295febbb7 ci: update image version + replace 443 port by 80 in manifest + remove index.php in urls 2025-06-17 15:16:41 +02:00
noah.lancien
999b1c3026 Merge branch 'issue-268' into 'develop'
feat: add missing part to the brigade page + contact form

See merge request ldc-web/projets/le-gaulois-pro!197
2025-06-17 13:04:05 +00:00
Karl
66744a261c feat: add missing part to the brigade page + contact form 2025-06-17 14:49:26 +02:00
Karl
2892900063 Merge branch 'develop' into issue-268 2025-06-17 09:34:08 +02:00
noah.lancien
f14edc9519 Merge branch 'issue-255' into 'develop'
fix: 💄 add a col-lg for the img container in post.blade

See merge request ldc-web/projets/le-gaulois-pro!191
2025-06-16 10:32:29 +00:00
Karl
7cae702384 wip: adding the missing part of the issue 2025-06-16 10:36:56 +02:00
Alexis Gousseau
4068cba624 fix: 🐛 set default empty array for product's slider_pictures value 2025-06-16 10:00:29 +02:00
Nicolas Demangeat
a4c6d80a92 fix: 💄 add a col-lg for the img container in post.blade 2025-06-16 10:00:28 +02:00
noah.lancien
0369a5c2ee Merge branch 'issue-268' into 'develop'
feat: ✨ add brigade page #268

See merge request ldc-web/projets/le-gaulois-pro!196
2025-06-10 06:58:54 +00:00
Karl
ed2d61af01 feat: ✨ add brigade page 2025-06-06 09:24:04 +02:00
Karl
e62ac1de10 Merge branch 'develop' into trunk 2025-05-20 10:47:46 +02:00
karl.muller
665244bb3a Merge branch 'issue-267' into 'develop'
feat: 💄 changes the structure of the news.show pages

See merge request ldc-web/projets/le-gaulois-pro!195
2025-05-20 08:47:07 +00:00
Nicolas Demangeat
d31916c376 fix: 🐛 remove unused comment + fix logo X in modules.socialShare 2025-05-20 10:19:38 +02:00
Nicolas Demangeat
638aaadfdf feat: 💄 changes the structure of the news.show pages 2025-05-20 09:45:08 +02:00
Karl Muller
80b74b8482 Merge branch 'develop' into trunk 2025-05-13 08:43:29 +00:00
karl.muller
d151aaf503 Merge branch 'issue-266' into 'develop'
feat: add multistage Docker build

See merge request ldc-web/projets/le-gaulois-pro!193
2025-05-13 08:42:46 +00:00
Karl Muller
5d2f00cd64 feat: add multistage Docker build 2025-05-13 08:29:27 +00:00
Vincent BRIDIER
f0aa2b9ebe
Merge branch 'develop' into trunk 2025-05-09 12:51:41 +02:00
Vincent BRIDIER
610c7fbc00
fix: Fix header search bar 2025-05-09 12:51:27 +02:00
Vincent BRIDIER
7918ea6b72
Merge branch 'develop' into trunk 2025-05-09 09:26:58 +02:00
Vincent BRIDIER
27c287b918 Merge branch 'issue-265' into 'develop'
fix: 🐛 change the storeAdviceRequest, the helper and some clean, phpcs phpstan OK

See merge request ldc-web/projets/le-gaulois-pro!192
2025-05-09 07:26:29 +00:00
Nicolas Demangeat
507b4e4aa0 fix: 🐛 change the storeAdviceRequest, the helper and some clean, phpcs phpstan OK 2025-05-07 16:07:02 +02:00
Noah Lancien
ddc602326c Merge branch 'develop' into trunk 2025-04-24 16:00:48 +02:00
noah.lancien
701377dc61 Merge branch 'issue-255' into 'develop'
fix: change the helper in news form.blade

See merge request ldc-web/projets/le-gaulois-pro!190
2025-04-24 14:00:13 +00:00
Nicolas Demangeat
6ca6c42926 fix: change the helper in news form.blade 2025-04-24 15:57:37 +02:00
Noah Lancien
0948260114 Merge branch 'develop' into trunk 2025-04-24 11:42:22 +02:00
noah.lancien
124a8b5cdb Merge branch 'issue-255' into 'develop'
feat: change the size for picture and picture 2 in news request and bo view

See merge request ldc-web/projets/le-gaulois-pro!189
2025-04-24 09:41:52 +00:00
Nicolas Demangeat
70f676cab2 feat: change the size for picture and picture 2 in news request and bo view 2025-04-23 15:59:42 +02:00
Noah Lancien
0b3baf7ca8 Merge branch 'develop' into trunk 2025-04-17 09:56:10 +02:00
noah.lancien
a2f6272fa6 Merge branch 'issue-253' into 'develop'
fix: 💄 remove col-lg-8 in view bo layout.blade

See merge request ldc-web/projets/le-gaulois-pro!188
2025-04-17 07:55:42 +00:00
Nicolas Demangeat
272b907d2e fix: 💄 remove col-lg-8 in view bo layout.blade 2025-04-17 09:17:14 +02:00
Noah Lancien
0aafabb61c fix: remove column creation/deletion for products order in the last migration 2025-04-15 11:34:00 +02:00
Noah Lancien
9cc63e1b42 Merge branch 'develop' into trunk 2025-04-15 10:35:01 +02:00
noah.lancien
21c0733d4d Merge branch 'issue-253-order' into 'develop'
feat: added order column on products

See merge request ldc-web/projets/le-gaulois-pro!186
2025-04-15 08:33:55 +00:00
noah.lancien
fb3efa1a52 Merge branch 'issue-261' into 'develop'
fix: 🐛 change the max length of wysiwyg to 2500 char

See merge request ldc-web/projets/le-gaulois-pro!187
2025-04-15 08:33:07 +00:00
noah.lancien
2d6ee4d662 Merge branch 'issue-260' into 'develop'
fix: 🐛 change CTA url in breeder.blade and eleverdurablement.blade

See merge request ldc-web/projets/le-gaulois-pro!185
2025-04-15 08:28:56 +00:00
Nicolas Demangeat
fc9132c104 fix: 🐛 change the max length of wysiwyg to 2500 char 2025-04-15 10:05:58 +02:00
Corentin Huet
a9dcdc1489 feat: added order column on products 2025-04-11 16:24:15 +02:00
Nicolas Demangeat
95ec1170de fix: 🎨 phpcs and phpstan 2025-04-09 09:49:15 +02:00
Nicolas Demangeat
627350d13c fix: 🐛 change CTA url in breeder.blade and eleverdurablement.blade 2025-04-09 09:40:03 +02:00
Noah Lancien
bab1bd2db1 Merge branch 'develop' into trunk 2025-04-03 09:18:54 +02:00
Noah Lancien
6181f7ea8d fix: wrong paramused in markup.img component 2025-04-03 09:18:42 +02:00
Noah Lancien
8c7842fd7d Merge branch 'develop' into trunk 2025-04-02 18:06:49 +02:00
Noah Lancien
8507e6abc6 fix: add closing '/' to markup.img includes 2025-04-02 18:06:28 +02:00
Karl Muller
8df241ec27 Merge branch 'develop' into trunk 2025-04-02 06:55:28 +00:00
karl.muller
67dfbb6c1d Merge branch 'issue-259' into 'develop'
feat: preparation recipe from 540->2500 caracters

See merge request ldc-web/projets/le-gaulois-pro!184
2025-04-02 06:55:06 +00:00
Karl Muller
d3365f8f9b Merge branch 'develop' into trunk 2025-04-02 06:54:27 +00:00
karl.muller
29c88d928d Merge branch 'issue-220' into 'develop'
feat: Replace img basic markup by laravel-common markup to handle webp generation

See merge request ldc-web/projets/le-gaulois-pro!164
2025-04-02 06:54:04 +00:00
Karl Muller
c9a45504f5 feat: preparation recipe from 540->2500 caracters 2025-04-01 10:01:12 +00:00
Corentin Huet
d2349934fd feat: changing img tags with a uwil component 2025-03-31 16:33:55 +02:00
Sebastien Hattry
7f0937e9bf feat: ⚡️ replace img by picture 2025-03-28 14:48:47 +01:00
Sebastien Hattry
7155c33f6e chore: update laravel-common 2025-03-28 14:43:40 +01:00
Sebastien Hattry
69b5472008 chore: auto formating 2025-03-28 14:43:36 +01:00
Vincent BRIDIER
9cd9cf43b8 feat: Replace img basic markup by laravel-common markup to handle webp generation 2025-03-28 14:41:32 +01:00
Noah Lancien
9d83ce7001 Merge branch 'develop' into trunk 2025-03-24 10:15:16 +01:00
noah.lancien
7af6bdabed Merge branch 'issue-224-sitemap' into 'develop'
feat: ✨ added static page sitemap !224

See merge request ldc-web/projets/le-gaulois-pro!178
2025-03-24 09:14:36 +00:00
Noah Lancien
84e2490e80 Merge branch 'develop' into trunk 2025-03-20 11:01:07 +01:00
noah.lancien
4ea88b1338 Merge branch 'issue-250' into 'develop'
feat: ✨ the button on a product page now directly display a pdf

See merge request ldc-web/projets/le-gaulois-pro!181
2025-03-20 09:57:00 +00:00
noah.lancien
b011a64858 Merge branch 'issue-251' into 'develop'
feat: ✏️ remove ingredient list for product

See merge request ldc-web/projets/le-gaulois-pro!182
2025-03-20 09:55:25 +00:00
noah.lancien
5c282f5346 Merge branch 'issue-256' into 'develop'
feat: ✨ size and packaging not required to send contact mail

See merge request ldc-web/projets/le-gaulois-pro!183
2025-03-20 09:55:10 +00:00
Nicolas Demangeat
1eb73f8716 fix: 🔥 remove product modal + resize pdf icon 2025-03-20 10:53:20 +01:00
Nicolas Demangeat
67449aad40 feat: ✨ size and packaging not required to send contact mail 2025-03-20 10:06:39 +01:00
Nicolas Demangeat
10165a260e feat: ✏️ remove ingredient list for product 2025-03-20 09:37:43 +01:00
Nicolas Demangeat
9eb1fc8eb8 fix: 🐛 the pdf now open in a new tab 2025-03-20 09:34:58 +01:00
Nicolas Demangeat
f25f79f09b feat: ✨ the button on a product page now directly display a pdf 2025-03-19 11:38:37 +01:00
Vincent BRIDIER
58f00f29dc Merge branch 'issue-258' into 'develop'
fix: ✏️ bad wording in engagement blade

See merge request ldc-web/projets/le-gaulois-pro!180
2025-03-17 14:45:19 +00:00
Vincent BRIDIER
476f6abbe1 Merge branch 'issue-257' into 'develop'
fix: ✏️ change twitter wording for X

See merge request ldc-web/projets/le-gaulois-pro!179
2025-03-17 14:44:53 +00:00
Nicolas Demangeat
2a701860f3 fix: ✏️ bad wording in engagement blade 2025-03-17 15:44:13 +01:00
Nicolas Demangeat
2cbce9537d fix: ✏️ change twitter wording for X 2025-03-17 15:39:12 +01:00
Corentin Huet
d0110bb939 refactor: using the balde directive for declare php code 2025-03-12 09:27:47 +01:00
Corentin Huet
08c3eee654 feat: ✨ added static page sitemap 2025-03-11 12:15:00 +01:00
Noah Lancien
59c3c69969 Merge branch 'develop' into trunk 2025-03-07 12:18:57 +01:00
Noah Lancien
b91ab37b57 fix: 💬 Wording in js error 2025-03-07 12:17:44 +01:00
Noah Lancien
ac4c0581bd Merge branch 'develop' into trunk 2025-03-05 17:32:42 +01:00
Vincent BRIDIER
d80ec5cef9 Merge branch 'issue-252' into 'develop'
feat: auto select packaging and caliber when only one option is available

See merge request ldc-web/projets/le-gaulois-pro!177
2025-03-05 16:31:50 +00:00
Vincent BRIDIER
604bf1f5e6 Merge branch 'issue-254' into 'develop'
feat: delete nutritionnal_value from bo and fo 🔥 one migration has to pass

See merge request ldc-web/projets/le-gaulois-pro!176
2025-03-05 16:31:45 +00:00
Karl Muller
b469ee3354 feat: auto select packaging and caliber when only one option is available 2025-03-05 12:52:43 +00:00
Karl Muller
5968541086 feat: delete nutritionnal_value from bo and fo 🔥 one migration has to pass 2025-03-05 09:52:19 +00:00
Noah Lancien
399b11fa08 Merge branch 'develop' into trunk 2025-02-27 09:50:43 +01:00
noah.lancien
fcdf1b85e8 Merge branch 'issue-249-responsive' into 'develop'
fix: responsive for toast error

See merge request ldc-web/projets/le-gaulois-pro!175
2025-02-27 08:33:52 +00:00
Noah Lancien
5d7f746bdb fix: responsive for toast error 2025-02-26 17:56:24 +01:00
Vincent BRIDIER
31430e2aa1
Merge branch 'develop' into trunk 2025-02-26 15:31:31 +01:00
Vincent BRIDIER
e2ac6046de
feat: Add .net redirect Close #219 2025-02-26 15:31:21 +01:00
Vincent BRIDIER
0a897f9655
Merge branch 'develop' into trunk 2025-02-18 13:16:36 +01:00
Vincent BRIDIER
0b728d6677 Merge branch 'issue-226' into 'develop'
wip: add events for matomo

See merge request ldc-web/projets/le-gaulois-pro!166
2025-02-18 12:15:56 +00:00
Noah Lancien
0211b5ef8b fix: updated the way we add matomo tracker 2025-02-18 13:06:24 +01:00
Karl Muller
cda0a5f461 Merge branch 'develop' into trunk 2025-02-05 11:14:28 +00:00
karl.muller
46e85fcb89 Merge branch 'issue-245-fix' into 'develop'
Issue 245 fix

See merge request ldc-web/projets/le-gaulois-pro!174
2025-02-05 11:12:23 +00:00
Sebastien Hattry
d54cb44c83 fix: helper + wysiwyg requests 2025-02-03 16:15:22 +01:00
Vincent BRIDIER
5a122d6257
Merge branch 'develop' into trunk 2025-02-03 15:32:59 +01:00
Vincent BRIDIER
21c586862b Merge branch 'issue-246-fix' into 'develop'
fix: take into account a second logo

See merge request ldc-web/projets/le-gaulois-pro!173
2025-02-03 14:32:32 +00:00
Sebastien Hattry
3488ac9342 fix: take into account a second logo 2025-02-03 10:18:51 +01:00
Vincent BRIDIER
1123bab565
Merge branch 'develop' into trunk 2025-01-31 16:02:31 +01:00
Vincent BRIDIER
30cd0daea4 Merge branch 'issue-248' into 'develop'
feat: 💄 update twitter x logo

See merge request ldc-web/projets/le-gaulois-pro!172
2025-01-31 15:01:09 +00:00
Vincent BRIDIER
4b72bb948b Merge branch 'issue-247' into 'develop'
feat: 💬 correct wording

See merge request ldc-web/projets/le-gaulois-pro!171
2025-01-31 15:00:32 +00:00
Vincent BRIDIER
7fb0fa6365 Merge branch 'issue-246' into 'develop'
Issue 246

See merge request ldc-web/projets/le-gaulois-pro!170
2025-01-31 14:59:46 +00:00
Vincent BRIDIER
c30e45f6a0 Merge branch 'issue-245' into 'develop'
fix: 🐛 Change so title helper min caracters

See merge request ldc-web/projets/le-gaulois-pro!169
2025-01-31 14:56:54 +00:00
Sebastien Hattry
4c25f6dee3 feat: 💄 update twitter x logo 2025-01-31 15:30:45 +01:00
Sebastien Hattry
39fc4f0998 feat: 💬 correct wording 2025-01-31 14:41:50 +01:00
Sebastien Hattry
8733732631 feat: 💄 increase logo + center in mobile 2025-01-30 16:34:28 +01:00
Sebastien Hattry
3bcdd9ad57 chore: auto formatting 2025-01-30 16:29:17 +01:00
Sebastien Hattry
39eea44f97 fix: 🐛 Change so title helper min caracters 2025-01-30 15:53:18 +01:00
Noah Lancien
1cf967fe5f Merge branch 'develop' into trunk 2025-01-14 14:51:17 +01:00
Noah Lancien
b14d153c77 ci: Added 'concurrencyPolicy: Forbid' in cron jobs 2025-01-14 14:51:02 +01:00
Noah Lancien
f567745476 Merge branch 'develop' into trunk 2025-01-07 10:11:11 +01:00
noah.lancien
078f346bd8 Merge branch 'issue-227-seo' into 'develop'
Issue 227 seo

See merge request ldc-web/projets/le-gaulois-pro!168
2025-01-07 09:10:34 +00:00
Noah Lancien
d434acf62f fix: take only published slides in Fo/HomeController 2025-01-07 10:10:14 +01:00
Noah Lancien
8084e356ea fix: wording 2025-01-07 09:53:56 +01:00
Noah Lancien
3c483d2a99 fix: h tags on homepage and product.show 2025-01-06 17:57:04 +01:00
Noah Lancien
cced4045d9 Merge branch 'develop' into trunk 2025-01-06 16:35:08 +01:00
Noah Lancien
6e378a4de7 fix: composer update to fix CVE 2025-01-06 16:30:52 +01:00
noah.lancien
d939c099b1 Merge branch 'issue-240-publish_slides' into 'develop'
feat: Added published column for slide Model - #240

See merge request ldc-web/projets/le-gaulois-pro!167
2025-01-06 15:28:58 +00:00
Noah Lancien
453385f58d feat: Added published column for slide Model - #240 2025-01-06 16:25:40 +01:00
noah.lancien
f696a475f8 Merge branch 'issue-242' into 'develop'
fix: 🐛 add removeFiles method in FileStorageHelper + condition in Product...

See merge request ldc-web/projets/le-gaulois-pro!165
2024-11-22 14:43:34 +00:00
Nicolas Demangeat
646d215891 wip: add events for matomo 2024-10-16 12:21:25 +02:00
Nicolas Demangeat
9603b744b1 fix: 🐛 change the required field to match the request 2024-10-15 15:04:27 +02:00
Nicolas Demangeat
2de5a273f5 fix: 🐛 add removeFiles method in FileStorageHelper + condition in Product model in updated and deleted method to check if the param is an array 2024-10-15 14:51:33 +02:00
Vincent BRIDIER
bd7dec73e0
feat: Remove useless file for mail testing 2024-09-27 18:35:57 +02:00
Vincent BRIDIER
036c2fc3eb
feat: Add new sitemap generator config 2024-09-27 18:35:42 +02:00
Vincent BRIDIER
4c744d2706
feat: Bump L11 2024-09-27 17:50:58 +02:00
Vincent BRIDIER
77f894a005
feat: Add laravel common package 2024-09-27 17:30:56 +02:00
Vincent BRIDIER
6262977fa5 Merge branch 'issue-228' into 'develop'
fix: 🐛 no h1 on product.show

See merge request ldc-web/projets/le-gaulois-pro!163
2024-09-27 15:23:57 +00:00
Vincent BRIDIER
15dd7f65dd
feat: Update robots.txt config 2024-09-27 17:20:05 +02:00
Vincent BRIDIER
c4a7c35137 Merge branch 'issue-221' into 'develop'
fix: 💄 improve CTA in error.blade

See merge request ldc-web/projets/le-gaulois-pro!162
2024-09-27 15:18:47 +00:00
Vincent BRIDIER
1ac5213601 Merge branch 'issue-223' into 'develop'
feat: 📦 add verschuur/laravel-robotstxt

See merge request ldc-web/projets/le-gaulois-pro!161
2024-09-27 15:17:11 +00:00
Nicolas Demangeat
e6c61cbfa9 fix: 🐛 no h1 on product.show 2024-09-25 16:28:30 +02:00
Nicolas Demangeat
a3f7862c91 fix: 🐛 public/robots.txt, wa have to do that or it will take precedence over Laravel's route and this package will not work 2024-09-25 15:19:25 +02:00
Nicolas Demangeat
eb1f84b9ae fix: 💄 change btn color to blue 2024-09-25 14:42:18 +02:00
Nicolas Demangeat
9c035becbd fix: 💄 improve CTA in error.blade 2024-09-25 14:27:50 +02:00
Nicolas Demangeat
74887bd8cc feat: 📦 add verschuur/laravel-robotstxt 2024-09-25 12:02:37 +02:00
Noah Lancien
b6d26101ba Merge branch 'develop' into trunk 2024-09-18 11:57:52 +02:00
noah.lancien
9ed4edec97 Merge branch 'issue-239-store-news-crash' into 'develop'
fix #239

See merge request ldc-web/projets/le-gaulois-pro!160
2024-09-18 09:57:23 +00:00
Noah Lancien
84a6909c78 chore: phpcs 2024-09-18 11:52:38 +02:00
Noah Lancien
afb3b0cc5e fix: Updated StoreNewsRequest validation for picture2 + store and update method in Controller + lazy loading in front Controller 2024-09-18 11:47:53 +02:00
Vincent BRIDIER
fce90c10fc
Merge branch 'develop' into trunk 2024-09-02 16:31:59 +02:00
Vincent BRIDIER
5e3382a433
fix: 💬 change claim text related to #232 2024-09-02 16:31:41 +02:00
Vincent BRIDIER
2a63141443
Merge branch 'develop' into trunk 2024-08-21 11:43:31 +02:00
Vincent BRIDIER
b618994aca Merge branch 'issue-232' into 'develop'
Update Header/Footer 40ans

See merge request ldc-web/projets/le-gaulois-pro!156
2024-08-21 09:42:48 +00:00
Vincent BRIDIER
c43ee7184a
Merge branch 'develop' into trunk 2024-08-14 13:32:00 +02:00
Vincent BRIDIER
08d2f3a60c Merge branch 'product-filter-mobile' into 'develop'
fix: Products filter now works on mobile

See merge request ldc-web/projets/le-gaulois-pro!159
2024-08-14 11:31:33 +00:00
Vincent BRIDIER
f963740439
Bump to PHP8.3 2024-08-14 13:31:03 +02:00
Noah Lancien
007061dbb7 fix: Products filter now works on mobile 2024-08-14 09:32:52 +02:00
Noah Lancien
8de8848c8a Merge branch 'develop' into trunk 2024-08-05 17:02:04 +02:00
noah.lancien
1031019198 Merge branch 'issue-230' into 'develop'
fix: made product pictures slider full size in desktop

See merge request ldc-web/projets/le-gaulois-pro!158
2024-08-05 15:01:11 +00:00
Noah Lancien
aff3549699 fix: made product pictures slider full size in desktop 2024-08-05 16:42:30 +02:00
noah.lancien
6ba7f69ee8 Merge branch 'issue-233' into 'develop'
fix: products update was broken because of saving images

See merge request ldc-web/projets/le-gaulois-pro!157
2024-08-05 14:31:55 +00:00
noah.lancien
d4547e8a8c Merge branch 'issue-218' into 'develop'
Issue 218

See merge request ldc-web/projets/le-gaulois-pro!155
2024-08-05 14:31:12 +00:00
Noah Lancien
e2d3f636a0 fix: products update was broken because of saving images 2024-08-05 16:23:15 +02:00
Noah Lancien
8943d00ae3 fix: replaced Math.round with Math.ceil in ingredients calculator 2024-08-05 15:47:11 +02:00
Noah Lancien
38814e4ebf fix: Added missing cookies 2024-08-05 15:46:33 +02:00
vincent.crochard
8bdf7e3ce3 Update Header/Footer 40ans 2024-07-31 12:35:50 +02:00
Vincent BRIDIER
fe217ff0bb
Merge branch 'develop' into trunk 2024-07-26 17:20:12 +02:00
Vincent BRIDIER
d9732dc9e7
fix navigation and slides table 2024-07-26 17:20:03 +02:00
Vincent BRIDIER
e99fa50402
Merge branch 'develop' into trunk 2024-07-26 15:36:36 +02:00
Vincent BRIDIER
347dee9752
Update wording on login page 2024-07-26 15:36:19 +02:00
Vincent BRIDIER
0d3c18765f
Merge branch 'develop' into trunk 2024-07-20 23:50:08 +02:00
Vincent BRIDIER
cb4ca5e644
Fix manfiest 2024-07-20 23:50:00 +02:00
Vincent BRIDIER
4ace5a3583
Merge branch 'develop' into trunk 2024-07-20 23:20:23 +02:00
Vincent BRIDIER
237e945262
SAML Auth compliant with SW 2024-07-20 22:31:13 +02:00
Sebastien Hattry
b77b7053a3 chore: ⬆️ cn to 6.0.3 2024-07-05 10:22:10 +02:00
Sebastien Hattry
00d7b44305 feat: Allow decimals for ing recipe 2024-07-05 10:05:26 +02:00
Louis Rossignol
c4f9acd1f2 Merge branch 'develop' into 'trunk'
Préparation migration

See merge request ldc-web/projets/le-gaulois-pro!153
2024-07-01 07:47:53 +00:00
LOR
4935a0726f Préparation migration 2024-07-01 09:38:54 +02:00
Vincent BRIDIER
18cd295bc6
Merge branch 'develop' into trunk 2024-06-11 15:14:41 +02:00
Vincent BRIDIER
0aa8f3395a Merge branch 'RGPD-maj' into 'develop'
fix cn recaptcha fallback

See merge request ldc-web/projets/le-gaulois-pro!152
2024-06-11 13:13:53 +00:00
Nicolas Demangeat
8cd02bc0fb fix cn recaptcha fallback 2024-06-11 15:02:49 +02:00
Vincent BRIDIER
0c961dbab3 Merge branch 'develop' into trunk 2024-06-11 12:15:11 +02:00
Vincent BRIDIER
35cbfc5c09 Merge branch 'RGPD-maj' into 'develop'
fix cookie form popup responsiv and color

See merge request ldc-web/projets/le-gaulois-pro!151
2024-06-11 09:56:51 +00:00
Nicolas Demangeat
4d8d9ee2a9 fix cookie form popup responsiv and color 2024-06-11 11:54:47 +02:00
Vincent BRIDIER
98e399c7a4
Merge branch 'develop' into trunk 2024-06-10 17:03:37 +02:00
Vincent BRIDIER
bbe37b89cc Merge branch 'RGPD-maj' into 'develop'
Rgpd maj

See merge request ldc-web/projets/le-gaulois-pro!150
2024-06-10 15:02:58 +00:00
nicolas.demangeat
fd3ebd60c6 Merge branch 'develop' into 'RGPD-maj'
# Conflicts:
#   resources/sass/fo/vendor/_ldcweb-cookies.scss
#   resources/views/fo/pages/contact.blade.php
2024-06-10 07:52:55 +00:00
Nicolas Demangeat
aa6c9b5086 fix cookie warning in form + phpcs + phpstan 2024-06-07 16:07:12 +02:00
Vincent BRIDIER
39fb9c4411
Merge branch 'develop' into trunk 2024-06-07 11:31:51 +02:00
Vincent BRIDIER
7f251e1310 Merge branch 'RGPD-maj' into 'develop'
fix-recipescontroller

See merge request ldc-web/projets/le-gaulois-pro!149
2024-06-07 09:31:01 +00:00
nicolas.demangeat
e77435bea3 fix-recipescontroller 2024-06-07 09:31:01 +00:00
Nicolas Demangeat
211cfcb965 fix lazy loading on recipe 2024-06-07 11:28:29 +02:00
Vincent BRIDIER
d064f35c2d
Merge branch 'develop' into trunk 2024-06-07 10:58:28 +02:00
Vincent BRIDIER
95fb5c7380 Merge branch 'RGPD-maj' into 'develop'
update cookie notice 5.0.7

See merge request ldc-web/projets/le-gaulois-pro!148
2024-06-07 08:57:59 +00:00
nicolas.demangeat
bc97ca6eef update cookie notice 5.0.7 2024-06-07 08:57:59 +00:00
Nicolas Demangeat
74e39c4d8a fix good version in comment and fix phpcs 2024-06-07 10:53:46 +02:00
Nicolas Demangeat
9f4b0d7984 delet a forgeten console.log 2024-06-07 09:41:05 +02:00
Nicolas Demangeat
a1d30e0ff3 add recaptacha popup in contact form 2024-06-07 09:20:42 +02:00
Nicolas Demangeat
5f58c2576e update cookie notice 5.0.7 2024-06-06 15:53:46 +02:00
Vincent BRIDIER
06601cc79d Merge branch 'rgpd-compliance-04-2024' into 'develop'
RGPD compliance (april 2024)

See merge request ldc-web/projets/le-gaulois-pro!147
2024-06-06 08:54:21 +00:00
Noah Lancien
df8c1509b9 RGPD compliance (april 2024) 2024-04-29 12:44:48 +02:00
Jérémy Munsch
dab651d673
Merge branch 'develop' into trunk 2024-04-22 11:22:54 +02:00
Jérémy Munsch
cb35fe0b5c Merge branch 'fix-search-crash' into 'develop'
Fixed search + news card display

See merge request ldc-web/projets/le-gaulois-pro!146
2024-04-22 09:20:36 +00:00
Noah Lancien
ea093ad995 Fixed search + news card display 2024-04-22 10:25:51 +02:00
Jérémy Munsch
f9c1efdc88
Merge branch 'develop' into trunk 2024-04-19 11:13:25 +02:00
Jérémy Munsch
e7ba234f17 Merge branch 'cookie-notice-4.1.1' into 'develop'
npm udpate cookie-notice

See merge request ldc-web/projets/le-gaulois-pro!144
2024-04-19 09:12:25 +00:00
Jérémy Munsch
1071f9ab32 Merge branch 'fix-static-pages-and-user-create-command' into 'develop'
Fixed static pages FO + user create command

See merge request ldc-web/projets/le-gaulois-pro!145
2024-04-19 09:12:12 +00:00
Noah Lancien
ee9715be57 Fixed static pages FO + user create command 2024-04-19 10:56:37 +02:00
Noah Lancien
654a45887c npm udpate cookie-notice 2024-04-19 09:10:27 +02:00
Jérémy Munsch
fc100ccfd2
Merge branch 'develop' into trunk 2024-04-18 16:22:11 +02:00
Jérémy Munsch
9732133d6a
PRefix for renderProductHtmlFormBladeString picutres ulrdasd qd qwd qd 2024-04-18 16:22:00 +02:00
Jérémy Munsch
092d551d40
Merge branch 'develop' into trunk 2024-04-05 09:34:22 +02:00
Jérémy Munsch
0b370c3c82 Merge branch 'phpstan-refactor' into 'develop'
A merge par dessus !137 et !141 PHPStan

See merge request ldc-web/projets/le-gaulois-pro!142
2024-04-05 07:31:26 +00:00
Jérémy Munsch
5f7df8a633 Merge branch 'issue-199' into 'develop'
Fix #199 - Updated 'mieux vivre ensemble' wording and icon section

See merge request ldc-web/projets/le-gaulois-pro!138
2024-04-05 07:31:13 +00:00
Jérémy Munsch
64bf2d266f Merge branch 'issue-200-static-pages' into 'develop'
Added staticpages crud for legals, policies and contact page + Updated seo title format

See merge request ldc-web/projets/le-gaulois-pro!137
2024-04-05 07:30:00 +00:00
Jérémy Munsch
25286fc03a
Expertise page lazylaod tags 2024-02-05 11:37:54 +01:00
Jérémy Munsch
c82f26e12f
phpcs fix 2024-02-05 11:26:17 +01:00
Jérémy Munsch
095305a9f1
phpcs update xml rules 2024-02-05 11:22:42 +01:00
Jérémy Munsch
a190a12198
phpstan fix 2024-02-05 11:21:03 +01:00
Jérémy Munsch
b62dec7442
Advice Type auto resolve 2024-02-05 11:15:46 +01:00
Jérémy Munsch
37b88e2e78
Add missing Static Pages policies 2024-02-05 10:45:02 +01:00
Jérémy Munsch
c2d9b3d4f0
Fixup Seeds 2024-02-05 10:36:57 +01:00
Jérémy Munsch
a64b6253c4
Fixup for news controller
- wrong query
- No text for empty filter results
- lazy load
2024-02-05 09:57:21 +01:00
Jérémy Munsch
49fb2a931c
Lazy load fix on Tips.show 2024-02-05 09:40:37 +01:00
Jérémy Munsch
abd8663f1c
Advice method call fix on blades 2024-02-05 09:39:13 +01:00
Jérémy Munsch
176d6ab503
lazy load fix on product controller 2024-02-05 09:36:25 +01:00
Jérémy Munsch
3a0d4e9d20
PHPstan OK 2024-02-05 09:33:08 +01:00
Alexis Gousseau
b747d482d4 add expertise pages to static pages - issue #195 2024-01-25 17:01:47 +01:00
Alexis Gousseau
82539c4a0d fix static pages integration 2024-01-25 15:55:42 +01:00
Alexis Gousseau
72a6fcd04b fix seo page 2024-01-25 15:24:17 +01:00
Noah Lancien
3721fe0570 Added staticpages crud for legals, policies and contact page + Updated seo title format 2024-01-25 15:21:58 +01:00
Vincent BRIDIER
0556ee5b2e Merge branch 'develop' into trunk 2023-12-18 11:06:21 +01:00
Noah Lancien
dc83b99cbf Fix #199 - Updated 'mieux vivre ensemble' wording and icon section 2023-11-27 17:14:16 +01:00
Vincent BRIDIER
3152860ae0
Merge branch 'develop' into trunk 2023-11-08 11:17:19 +01:00
Vincent BRIDIER
d55852891f
Merge branch 'develop' into trunk 2023-11-07 09:11:26 +01:00
Vincent BRIDIER
9eb55f8456
Merge branch 'develop' into trunk 2023-11-06 18:12:19 +01:00
Vincent BRIDIER
03b09f1cdf
Merge branch 'develop' into trunk 2023-11-06 12:25:01 +01:00
Vincent BRIDIER
ab9f2f76a3
Merge branch 'develop' into trunk 2023-11-06 12:03:24 +01:00
Vincent BRIDIER
44dcda52a3
Merge branch 'develop' into trunk 2023-10-10 15:16:05 +02:00
Vincent BRIDIER
5daaec57e8 Merge branch 'develop' into trunk 2023-08-24 11:25:36 +02:00
Vincent BRIDIER
173288ca10
Merge branch 'develop' into trunk 2023-08-22 15:12:48 +02:00
Vincent BRIDIER
fa58eedb8c
Merge branch 'develop' into trunk 2023-08-21 15:06:55 +02:00
Vincent BRIDIER
e6ab8c37dc
Merge branch 'develop' into trunk 2023-08-21 13:53:54 +02:00
Vincent BRIDIER
a89edc5c5f
Merge branch 'develop' into trunk 2023-02-27 12:17:08 +01:00
Vincent BRIDIER
71a9b3027e
Merge branch 'develop' into trunk 2023-02-23 11:58:19 +01:00
Vincent BRIDIER
7bce78a6e7
Merge branch 'develop' into trunk 2023-01-31 17:17:55 +01:00
Vincent BRIDIER
a1e5d4d8dd
Merge branch 'develop' into trunk 2023-01-31 12:36:05 +01:00
Vincent BRIDIER
eef78952ae
Merge branch 'develop' into trunk 2023-01-31 11:21:16 +01:00
Vincent BRIDIER
f539e19224
Merge branch 'develop' into trunk 2023-01-18 19:42:23 +01:00
Vincent BRIDIER
ac884c13fa
Merge branch 'develop' into trunk 2023-01-18 19:24:05 +01:00
Vincent BRIDIER
5f6b560dd2
Merge branch 'develop' into trunk 2023-01-18 19:04:04 +01:00
Vincent BRIDIER
891bb3c141
Merge branch 'develop' into trunk 2023-01-18 18:33:21 +01:00
Vincent BRIDIER
400ddc65b1
Merge branch 'develop' into trunk 2023-01-18 18:29:43 +01:00
Vincent BRIDIER
25d38801a5
Merge branch 'develop' into trunk 2023-01-18 15:35:54 +01:00
Vincent BRIDIER
53354e10f6
Merge branch 'develop' into trunk 2023-01-18 14:26:20 +01:00
Vincent BRIDIER
d53394153b
Merge branch 'develop' into trunk 2023-01-18 12:57:15 +01:00
Vincent BRIDIER
aa89a89ba6
Merge branch 'develop' into trunk 2023-01-18 11:48:25 +01:00
Vincent BRIDIER
8a03028b1f
Merge branch 'develop' into trunk 2023-01-18 10:41:09 +01:00
Vincent BRIDIER
04430cd884
Merge branch 'develop' into trunk 2023-01-17 20:18:18 +01:00
Vincent BRIDIER
c9fb9d098d
Merge branch 'develop' into trunk 2023-01-17 19:24:08 +01:00
Vincent BRIDIER
fb14497f07
Merge branch 'develop' into trunk 2023-01-17 19:10:25 +01:00
Vincent BRIDIER
f49e587138
Merge branch 'develop' into trunk 2023-01-17 19:07:42 +01:00
Vincent BRIDIER
5bb7e5e460
Merge branch 'develop' into trunk 2023-01-17 18:21:48 +01:00
Vincent BRIDIER
d961d1130f
Merge branch 'develop' into trunk 2023-01-16 16:44:10 +01:00
Vincent BRIDIER
ec3e429fca
Merge branch 'develop' into trunk 2023-01-13 18:28:29 +01:00
Vincent BRIDIER
136ef63dc9
Merge branch 'develop' into trunk 2023-01-13 17:54:09 +01:00
Vincent BRIDIER
942bc40258
Merge branch 'develop' into trunk 2023-01-13 17:08:10 +01:00
Vincent BRIDIER
9521f814a7
Merge branch 'develop' into trunk 2023-01-13 16:50:15 +01:00
Vincent BRIDIER
db7a4b93eb
Merge branch 'develop' into trunk 2023-01-13 15:19:50 +01:00
Vincent BRIDIER
ae500961ed
Merge branch 'develop' into trunk 2023-01-12 18:49:17 +01:00
Vincent BRIDIER
038832b07e
Merge branch 'develop' into trunk 2023-01-12 17:06:43 +01:00
Vincent BRIDIER
9ae964028d
Merge branch 'develop' into trunk 2023-01-09 12:11:35 +01:00
Vincent BRIDIER
ae4cc650b4
Merge develop into trunk 2023-01-09 11:38:48 +01:00
Vincent BRIDIER
efd9a6b717
Merge branch 'develop' into trunk 2023-01-09 10:59:56 +01:00
Vincent BRIDIER
5401c9c9b2
Merge branch 'develop' into trunk 2023-01-09 10:47:07 +01:00
Olivier Legat
7a69c459f4 Merge branch 'develop' into trunk 2022-03-01 15:50:33 +01:00
Olivier Legat
cc7d4c450e Merge branch 'develop' into trunk 2022-03-01 14:50:00 +01:00
Olivier Legat
1443d96cc0 Merge branch 'develop' into trunk 2022-03-01 14:34:42 +01:00
Olivier Legat
0cfc55621d Merge branch 'develop' into trunk 2022-01-17 13:21:26 +01:00
Olivier Legat
fe84558251 Merge branch 'develop' into trunk 2022-01-14 16:45:29 +01:00
Olivier Legat
c8dda01e20 Merge branch 'develop' into trunk 2022-01-14 16:31:56 +01:00
Olivier Legat
3dca11d46a Merge branch 'develop' into trunk 2022-01-14 15:42:29 +01:00
349 changed files with 20544 additions and 10533 deletions

View file

@ -43,5 +43,21 @@ GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE= GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION= GOOGLE_RECAPTCHA_VERSION=
# LDC Web Admin Secure # UWIL ADMIN SECURE
LDC_WEB_API_KEY="MY_KEY" # - 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=
# CRM
CRM_ENDPOINT=
CRM_PASSWORD=
CRM_TOKEN=

3
.gitignore vendored
View file

@ -23,6 +23,9 @@ Homestead.yaml
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
.npmrc .npmrc
.zshrc
.npm/
.composer/
# Ignore public favicon # Ignore public favicon
/public/android-chrome-192x192.png /public/android-chrome-192x192.png

View file

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

View file

@ -1,5 +1,47 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest ARG PHP_8_5_TAG=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
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 USER root
RUN chmod -R 755 /app/srcs/storage RUN chmod -R 755 /app/srcs/storage
USER www-data USER ${USER}

View file

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

View file

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

View file

@ -1,35 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class TestMail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:test';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send test email to equipe.web@ldc.fr';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg) {
$msg->to('equipe.web@ldc.fr')->subject('Test Email');
});
}
}

View file

@ -3,7 +3,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\User; use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -15,7 +14,7 @@ class UserCreate extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'user:create'; protected $signature = 'uwil:user:create';
/** /**
* The console command description. * The console command description.
@ -25,33 +24,26 @@ class UserCreate extends Command
protected $description = 'Create a backend admin user'; protected $description = 'Create a backend admin user';
/** /**
* The firstname of the new user. * The first name of the new user.
* *
* @var string * @var string|null
*/ */
protected $firstname; protected $firstname;
/** /**
* The lastname of the new user. * The last name of the new user.
* *
* @var string * @var string|null
*/ */
protected $lastname; protected $lastname;
/** /**
* The email of the new user. * The email of the new user.
* *
* @var string * @var string|null
*/ */
protected $email; protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/** /**
* Execute the console command. * Execute the console command.
* *
@ -60,23 +52,18 @@ class UserCreate extends Command
public function handle(): int public function handle(): int
{ {
$this->firstname = null; $this->firstname = null;
$this->addFirstname(); $this->addFirstName();
$this->lastname = null; $this->lastname = null;
$this->addLastname(); $this->addLastName();
$this->email = null; $this->email = null;
$this->addEmail(); $this->addEmail();
$this->password = null;
$this->addPassword();
$user = new User([ $user = new User([
'firstname' => $this->firstname, 'firstname' => $this->firstname,
'lastname' => $this->lastname, 'lastname' => $this->lastname,
'email' => $this->email, 'email' => $this->email
'password' => $this->password,
'last_connection' => Carbon::now(),
]); ]);
$user->saveOrFail(); $user->saveOrFail();
@ -86,15 +73,15 @@ class UserCreate extends Command
} }
/** /**
* Add a firstname to the user. * Add a first name to the user.
* *
* @return void * @return void
*/ */
private function addFirstname(): void private function addFirstName(): void
{ {
while (is_null($this->firstname)) { while (is_null($this->firstname)) {
try { try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin'); $tmp = $this->ask('Type the wanted user first name', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [ $validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255' 'firstname' => 'required|min:3|max:255'
]); ]);
@ -102,24 +89,24 @@ class UserCreate extends Command
$this->firstname = $tmp; $this->firstname = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please provide a valid firstname %s', 'Please provide a valid first name %s',
\implode(',', collect($e->errors())->flatten()->all()) \implode(',', collect($e->errors())->flatten()->all())
)); ));
continue; continue;
} //end try } //end try
}; //end while };
} }
/** /**
* Add a lastname to the user. * Add a last name to the user.
* *
* @return void * @return void
*/ */
private function addLastname(): void private function addLastName(): void
{ {
while (is_null($this->lastname)) { while (is_null($this->lastname)) {
try { try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama'); $tmp = $this->ask('Type the wanted user last name', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [ $validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255' 'lastname' => 'required|min:3|max:255'
]); ]);
@ -132,7 +119,7 @@ class UserCreate extends Command
)); ));
continue; continue;
} //end try } //end try
}; //end while };
} }
/** /**
@ -157,22 +144,6 @@ class UserCreate extends Command
)); ));
continue; continue;
} //end try } //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

@ -14,6 +14,8 @@ enum AdviceDifficultyEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::easy->name => 'Facile', self::easy->name => 'Facile',

View file

@ -15,6 +15,8 @@ enum AdviceTimeEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::first->name => '0 - 30min', self::first->name => '0 - 30min',

View file

@ -4,16 +4,18 @@ namespace App\Enums\Advices;
use App\Traits\Enums\BaseEnum; use App\Traits\Enums\BaseEnum;
enum AdviceTypeEnum: int enum AdviceTypeEnum: string
{ {
use BaseEnum; use BaseEnum;
case all = 0; case all = '0';
case recipes = 1; case recipes = '1';
case tips = 2; case tips = '2';
/** /**
* Optionnal labels definition * Optionnal labels definition
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::all->name => 'Recettes / Astuces', self::all->name => 'Recettes / Astuces',

View file

@ -17,6 +17,8 @@ enum ContactActivityEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::distributeur->name => 'Distributeur', self::distributeur->name => 'Distributeur',

View file

@ -14,6 +14,8 @@ enum ContactTimeEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::morning->name => 'Le matin', self::morning->name => 'Le matin',

View file

@ -16,6 +16,8 @@ enum ExpertiseProductPageEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::duck->name => 'Canard', self::duck->name => 'Canard',

View file

@ -17,6 +17,8 @@ enum NutriscoreEnum: int
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::noLetter->name => 'Ø', self::noLetter->name => 'Ø',

View file

@ -8,32 +8,36 @@ enum ProductSpeciesEnum: int
{ {
use BaseEnum; use BaseEnum;
case caille = 0; case caille = 0;
case canard = 1; case canard = 1;
case canette = 2; case canette = 2;
case coq = 3; case coq = 3;
case coquelet = 4; case coquelet = 4;
case dinde = 5; case dinde = 5;
case lapin = 6; case lapin = 6;
case pigeonneau = 7; case pigeonneau = 7;
case pintade = 8; case pintade = 8;
case poule = 9; case poule = 9;
case poulet = 10; case poulet = 10;
case volaille_festive = 11;
/** /**
* Optionnal labels definition. * Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/ */
private const LABELS = [ private const LABELS = [
self::caille->name => 'Caille', self::caille->name => 'Caille',
self::canard->name => 'Canard', self::canard->name => 'Canard',
self::canette->name => 'Canette', self::canette->name => 'Canette',
self::coq->name => 'Coq', self::coq->name => 'Coq',
self::coquelet->name => 'Coquelet', self::coquelet->name => 'Coquelet',
self::dinde->name => 'Dinde', self::dinde->name => 'Dinde',
self::lapin->name => 'Lapin', self::lapin->name => 'Lapin',
self::pigeonneau->name => 'Pigeonneau', self::pigeonneau->name => 'Pigeonneau',
self::pintade->name => 'Pintade', self::pintade->name => 'Pintade',
self::poule->name => 'Poule', self::poule->name => 'Poule',
self::poulet->name => 'Poulet' self::poulet->name => 'Poulet',
self::volaille_festive->name => 'Volaille festive'
]; ];
} }

View file

@ -0,0 +1,37 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum StaticPageType: int
{
use BaseEnum;
case contact = 1;
case legals = 2;
case confidentiality = 3;
case expertise_guineafowl = 4;
case expertise_turkey = 5;
case expertise_chicken = 6;
case expertise_duck = 7;
case expertise_rabbit = 8;
case sitemap = 9;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::contact->name => 'Contact',
self::legals->name => 'Mentions légales',
self::confidentiality->name => 'Confidentialité',
self::expertise_guineafowl->name => 'Expertise pintade',
self::expertise_turkey->name => 'Expertise dinde',
self::expertise_chicken->name => 'Expertise poulet',
self::expertise_duck->name => 'Expertise canard',
self::expertise_rabbit->name => 'Expertise lapin',
self::sitemap->name => 'Plan du site',
];
}

23
app/Enums/TagTypeEnum.php Normal file
View file

@ -0,0 +1,23 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum TagTypeEnum: int
{
use BaseEnum;
case recipes = 1;
case products = 2;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::recipes->name => 'Recettes / Astuces',
self::products->name => 'Produits',
];
}

View file

@ -3,42 +3,40 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\Advices\AdviceTypeEnum; use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Advice\StoreAdviceRequest; use App\Http\Requests\Bo\Advice\StoreAdviceRequest;
use App\Http\Requests\Bo\Advice\UpdateAdviceRequest; use App\Http\Requests\Bo\Advice\UpdateAdviceRequest;
use App\Models\Advice; use App\Models\Advice;
use App\Models\Product; use App\Models\Product;
use App\Models\Tag; use App\Models\Tag;
use App\Traits\Controllers\UpdateModelStatus; use App\Traits\Controllers\ManagesOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class AdviceController extends Controller class AdviceController extends Controller
{ {
use UpdateModelStatus; use UpdateModelPublished;
use ManagesOrder;
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \App\Enums\Advices\AdviceTypeEnum|null $type
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function index(Request $request): \Illuminate\Contracts\View\View public function index(Request $request, AdviceTypeEnum|null $type = null): \Illuminate\Contracts\View\View
{ {
/** @var \Illuminate\Database\Eloquent\Builder $advice */ $type = $type ?: AdviceTypeEnum::all;
/** @var \Illuminate\Database\Eloquent\Builder $adviceModels */
$adviceModels = Advice::query(); $adviceModels = Advice::query();
$type = $request->input('type');
if (empty($currentType = AdviceTypeEnum::make(\intval($type)))) { $adviceModels->when(!$type->equals(AdviceTypeEnum::all), fn (Builder $q) => $q->where('type', $type));
abort(404);
}
$adviceModels->when(isset($type), function ($adviceModels) use ($currentType) {
if ($currentType->value() !== AdviceTypeEnum::all->value()) {
$adviceModels->where('type', '=', $currentType->value());
}
});
/** @var string|null $search Search field */ /** @var string|null $search Search field */
$search = $request->input('search'); $search = $request->input('search');
@ -58,47 +56,44 @@ class AdviceController extends Controller
/** Custom pagination */ /** Custom pagination */
$adviceModels = $this->paginate($adviceModels); $adviceModels = $this->paginate($adviceModels);
return view('bo.pages.advice.index', compact('adviceModels', 'currentType', 'search', 'searchFields')); return view('bo.pages.advice.index', [
'adviceModels' => $adviceModels,
'currentType' => $type,
'search' => $search,
'searchFields' => $searchFields
]);
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param Request $request * @param \App\Enums\Advices\AdviceTypeEnum $type
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function create(Request $request): \Illuminate\Contracts\View\View public function create(AdviceTypeEnum $type): \Illuminate\Contracts\View\View
{ {
$advice = new Advice(); $advice = new Advice();
$type = $request->type; $adviceTags = Tag::query()->where('type', TagTypeEnum::recipes)->select(['id', 'label', 'slug'])->get();
$currentType = AdviceTypeEnum::make(\intval($type));
$adviceTags = Tag::query()->select(['id', 'label', 'slug'])->get();
$adviceProducts = Product::all(); $adviceProducts = Product::all();
$recipeList = Advice::where('type', '=', AdviceTypeEnum::recipes)->get(); $recipeList = Advice::query()->where('type', '=', AdviceTypeEnum::recipes)->get();
$tipList = Advice::where('type', '=', AdviceTypeEnum::tips)->get(); $tipList = Advice::query()->where('type', '=', AdviceTypeEnum::tips)->get();
$recipeList = collect($recipeList)->each(function (Advice &$value) {
$value->label = $value->title;
});
$tipList = collect($tipList)->each(function (Advice &$value) {
$value->label = $value->title;
});
$recipesSelected = collect([]); $recipesSelected = collect();
$tipsSelected = collect([]); $tipsSelected = collect();
collect($advice->advices)->each(function ($eltAdvice) use (&$recipesSelected, &$tipsSelected) { collect($advice->advices)
if ($eltAdvice->type->value == AdviceTypeEnum::recipes->value()) { // @phpstan-ignore-next-line
$eltAdvice->label = $eltAdvice->title; ->each(function (Advice $eltAdvice) use (&$recipesSelected, &$tipsSelected) {
$recipesSelected = $recipesSelected->concat([$eltAdvice]); if ($eltAdvice->type->equals(AdviceTypeEnum::recipes)) {
} elseif ($eltAdvice->type->value == AdviceTypeEnum::tips->value()) { $recipesSelected = $recipesSelected->concat([$eltAdvice]);
$eltAdvice->label = $eltAdvice->title; } elseif ($eltAdvice->type->equals(AdviceTypeEnum::tips)) {
$tipsSelected = $tipsSelected->concat([$eltAdvice]); $tipsSelected = $tipsSelected->concat([$eltAdvice]);
} }
}); });
return view('bo.pages.advice.create', [ return view('bo.pages.advice.create', [
'adviceModel' => $advice, 'adviceModel' => $advice,
'currentType' => $currentType, 'currentType' => $type,
'adviceTags' => $adviceTags, 'adviceTags' => $adviceTags,
'adviceProducts' => $adviceProducts, 'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList, 'recipeList' => $recipeList,
@ -118,14 +113,19 @@ class AdviceController extends Controller
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$advice = new Advice(); $advice = new Advice();
$advice->fill($request->validated()); $advice->fill(Arr::except($request->validated(), [
'tags',
'products',
'associated_tips_list',
'associated_recipes_list',
]));
$currentType = AdviceTypeEnum::make(\intval($request->type)); $currentType = AdviceTypeEnum::make(\intval($request->type));
if ($advice->saveOrFail()) { if ($advice->saveOrFail()) {
$advice->tags()->sync(collect($request->tags)->pluck('id')); $advice->tags()->sync(collect($request->input('tags'))->pluck('id'));
$advice->products()->sync($request->products_list); $advice->products()->sync($request->input('products_list'));
$advice->advices()->sync(array_merge($request->recipes_list, $request->tips_list)); $advice->advices()->sync(array_merge($request->input('recipes_list'), $request->input('tips_list')));
return redirect() return redirect()
->route('bo.advice.edit', $advice) ->route('bo.advice.edit', $advice)
@ -152,37 +152,18 @@ class AdviceController extends Controller
public function edit(Advice $advice): \Illuminate\Contracts\View\View public function edit(Advice $advice): \Illuminate\Contracts\View\View
{ {
$currentType = $advice->type; $currentType = $advice->type;
$adviceTags = Tag::whereNotIn('id', $advice->tags()->pluck('id')) $adviceTags = Tag::query()->where('type', TagTypeEnum::recipes)
->select(['id', 'label', 'slug'])->get(); ->select(['id', 'label', 'slug'])->get();
$adviceProducts = Product::all(); $adviceProducts = Product::all();
$recipeList = Advice::where([ $recipeList = Advice::query()->where([
['type', '=', AdviceTypeEnum::recipes], ['type', '=', AdviceTypeEnum::recipes],
['id', '!=', $advice->id], ['id', '!=', $advice->id],
])->get(); ])->get();
$tipList = Advice::where([ $tipList = Advice::query()->where([
['type', '=', AdviceTypeEnum::tips], ['type', '=', AdviceTypeEnum::tips],
['id', '!=', $advice->id], ['id', '!=', $advice->id],
])->get(); ])->get();
$recipeList = collect($recipeList)->each(function (Advice &$value) {
$value->label = $value->title;
});
$tipList = collect($tipList)->each(function (Advice &$value) {
$value->label = $value->title;
});
$recipesSelected = collect([]);
$tipsSelected = collect([]);
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */
$recipesSelected = $advice->recipes()->each(function (Advice &$recipe) {
$recipe->label = $recipe->title;
});
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */
$tipsSelected = $advice->tips()->each(function (Advice &$tip) {
$tip->label = $tip->title;
});
return view('bo.pages.advice.edit', [ return view('bo.pages.advice.edit', [
'adviceModel' => $advice, 'adviceModel' => $advice,
'currentType' => $currentType, 'currentType' => $currentType,
@ -190,8 +171,8 @@ class AdviceController extends Controller
'adviceProducts' => $adviceProducts, 'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList, 'recipeList' => $recipeList,
'tipList' => $tipList, 'tipList' => $tipList,
'recipesSelected' => $recipesSelected, 'recipesSelected' => $advice->recipes,
'tipsSelected' => $tipsSelected, 'tipsSelected' => $advice->tips,
]); ]);
} }
@ -205,11 +186,16 @@ class AdviceController extends Controller
public function update(UpdateAdviceRequest $request, Advice $advice): \Illuminate\Http\RedirectResponse public function update(UpdateAdviceRequest $request, Advice $advice): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $advice) { return DB::transaction(function () use ($request, $advice) {
$advice->tags()->sync(collect($request->tags)->pluck('id')); $advice->tags()->sync(collect($request->input('tags'))->pluck('id'));
$advice->products()->sync($request->products_list); $advice->products()->sync($request->input('products_list'));
$advice->advices()->sync(array_merge($request->recipes_list, $request->tips_list)); $advice->advices()->sync(array_merge($request->input('recipes_list'), $request->input('tips_list')));
$advice->fill($request->validated()); $advice->fill(Arr::except($request->validated(), [
'tags',
'products',
'associated_tips_list',
'associated_recipes_list',
]));
$currentType = AdviceTypeEnum::make(\intval($request->type)); $currentType = AdviceTypeEnum::make(\intval($request->type));

View file

@ -0,0 +1,152 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Requests\Bo\BrigadeSections\StoreBrigadeSectionsRequest;
use App\Http\Requests\Bo\BrigadeSections\UpdateBrigadeSectionsRequest;
use App\Models\BrigadeSection;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Uwil\Laravel\Common\Controllers\Controller;
class BrigadeSectionController extends Controller
{
use UpdateModelPublished;
use ChangesModelOrder;
/**
* 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 $brigadeSectionModels */
$brigadeSectionModels = BrigadeSection::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$brigadeSectionModels,
$search,
null,
'title',
'content',
'button_text',
'button_url'
);
}
$searchFields = trans('Titre, Texte, CTA, Lien');
/** Sort columns with a query */
$this->sortQuery($brigadeSectionModels);
/** Custom pagination */
$brigadeSectionModels = $this->paginate($brigadeSectionModels);
return view('bo.pages.brigade_sections.index', compact('brigadeSectionModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\BrigadeSection $brigadeSection
* @return \Illuminate\Contracts\View\View
*/
public function create(BrigadeSection $brigadeSection): \Illuminate\Contracts\View\View
{
$brigadeSection = $brigadeSection->exists() ? $brigadeSection : new BrigadeSection();
return view('bo.pages.brigade_sections.create', [
'brigadeSectionModel' => $brigadeSection,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\BrigadeSections\StoreBrigadeSectionsRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreBrigadeSectionsRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$brigadeSection = new BrigadeSection();
$brigadeSection->fill($request->validated());
if ($brigadeSection->saveOrFail()) {
return redirect()->route('bo.brigade_sections.edit', $brigadeSection->id)
->with('success', __('crud.messages.has_been_created', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\BrigadeSection $brigadeSection
* @return \Illuminate\Contracts\View\View
*/
public function edit(BrigadeSection $brigadeSection): \Illuminate\Contracts\View\View
{
return view('bo.pages.brigade_sections.edit', [
'brigadeSectionModel' => $brigadeSection,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\BrigadeSections\UpdateBrigadeSectionsRequest $request
* @param \App\Models\BrigadeSection $brigadeSection
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateBrigadeSectionsRequest $request, BrigadeSection $brigadeSection): RedirectResponse
{
return DB::transaction(function () use ($request, $brigadeSection) {
$brigadeSection->fill($request->validated());
if ($brigadeSection->saveOrFail()) {
return redirect()->route('bo.brigade_sections.edit', $brigadeSection->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
}
return redirect()->route('bo.brigade_sections.edit', $brigadeSection->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\BrigadeSection $brigadeSection
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(BrigadeSection $brigadeSection): \Illuminate\Http\RedirectResponse
{
if ($brigadeSection->deleteOrFail()) {
return redirect()->route('bo.brigade_sections.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => trans_choice('models.classes.brigade_section', 1)
]));
}
}

View file

@ -48,7 +48,7 @@ class ExpertiseProductController extends Controller
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param App\Models\ExpertiseProduct $vip * @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function create(ExpertiseProduct $vip): \Illuminate\Contracts\View\View public function create(ExpertiseProduct $vip): \Illuminate\Contracts\View\View
@ -95,8 +95,8 @@ class ExpertiseProductController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\ExpertiseProduct\UpdateExpertiseProductRequest $request * @param \App\Http\Requests\Bo\ExpertiseProducts\UpdateExpertiseProductRequest $request
* @param \App\Models\ExpertiseProduct $vip * @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function update( public function update(

View file

@ -8,14 +8,14 @@ use App\Http\Requests\Bo\News\UpdateNewsRequest;
use App\Models\News; use App\Models\News;
use App\Models\NewsCategory; use App\Models\NewsCategory;
use App\Models\Product; use App\Models\Product;
use App\Traits\Controllers\UpdateModelStatus; use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class NewsController extends Controller class NewsController extends Controller
{ {
use UpdateModelStatus; use UpdateModelPublished;
/** /**
* Display a listing of the resource. * Display a listing of the resource.
@ -60,18 +60,18 @@ class NewsController extends Controller
*/ */
public function create(News $news): \Illuminate\Contracts\View\View public function create(News $news): \Illuminate\Contracts\View\View
{ {
$news = $news ?? new News(); $news = $news->exists() ? $news : new News();
$newsCategories = NewsCategory::all(); $newsCategories = NewsCategory::all();
$newsProducts = Product::all(); $newsProducts = Product::all();
$newsList = News::all(); $newsList = News::all();
$newsList = collect($newsList)->each(function (News &$value) { $newsList = collect($newsList)->each(function (News $value) {
$value->label = $value->title; $value->setAttribute('label', $value->title);
}); });
$newsSelected = collect([]); $newsSelected = collect([]);
collect($news->news ?? [])->each(function (News &$value) use (&$newsSelected) { collect($news->news ?? [])->each(function (News $value) use (&$newsSelected) {
$value->label = $value->title; $value->setAttribute('label', $value->title);
$newsSelected = $newsSelected->concat([$value]); $newsSelected = $newsSelected->concat([$value]);
}); });
@ -93,7 +93,7 @@ class NewsController extends Controller
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$news = new News(); $news = new News();
$news->fill($request->validated()); $news->fill($request->safe()->except(['categories', 'products', 'associated_news_list']));
if ($news->saveOrFail()) { if ($news->saveOrFail()) {
$news->categories()->sync($request->categories_list); $news->categories()->sync($request->categories_list);
@ -124,13 +124,13 @@ class NewsController extends Controller
$newsProducts = Product::all(); $newsProducts = Product::all();
$newsList = News::where('id', '!=', $news->id)->get(); $newsList = News::where('id', '!=', $news->id)->get();
$newsList = collect($newsList)->each(function (\App\Models\News &$value) { $newsList = collect($newsList)->each(function (News $value) {
$value->label = $value->title; $value->setAttribute('label', $value->title);
}); });
$newsSelected = collect([]); $newsSelected = collect([]);
collect($news->news ?? [])->each(function (\App\Models\News &$value) use (&$newsSelected) { collect($news->news ?? [])->each(function (News $value) use (&$newsSelected) {
$value->label = $value->title; $value->setAttribute('label', $value->title);
$newsSelected = $newsSelected->concat([$value]); $newsSelected = $newsSelected->concat([$value]);
}); });
@ -156,17 +156,17 @@ class NewsController extends Controller
$news->products()->sync($request->products_list); $news->products()->sync($request->products_list);
$news->news()->sync($request->news_list); $news->news()->sync($request->news_list);
$news->fill($request->validated()); $news->fill($request->safe()->except(['categories', 'products', 'associated_news_list']));
if ($news->saveOrFail()) { if ($news->saveOrFail()) {
return redirect()->route('bo.news.edit', $news->id) return redirect()->route('bo.news.edit', $news->id)
->with('success', __('crud.messages.has_been_updated', [ ->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.news')) 'model' => Str::singular(__('models.classes.news'))
])); ]));
} }
return redirect()->route('bo.news.edit', $news->id) return redirect()->route('bo.news.edit', $news->id)
->with('error', __('crud.messages.cannot_be_updated', [ ->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.news')) 'model' => Str::singular(__('models.classes.news'))
])); ]));
}); });
} }

View file

@ -2,23 +2,25 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\StoreProductRequest; use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest; use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Product; use App\Models\Product;
use App\Models\Range;
use App\Models\Subrange; use App\Models\Subrange;
use App\Traits\Controllers\HasPicture; use App\Models\Tag;
use App\Traits\Controllers\UpdateModelStatus; use App\Traits\Controllers\UpdateModelPublished;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ManagesOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class ProductController extends Controller class ProductController extends Controller
{ {
use ChangesModelOrder; use UpdateModelPublished;
use UpdateModelStatus; use ManagesOrder;
use HasPicture;
/** /**
* Display a listing of the resource. * Display a listing of the resource.
@ -41,6 +43,13 @@ class ProductController extends Controller
'label' 'label'
); );
} }
if ($request->range) {
$productModels->whereHas('subrange', function ($q) use ($request) {
$q->where('subranges.range_id', '=', $request->range);
})->get();
}
$searchFields = trans('Label'); $searchFields = trans('Label');
/** Sort columns with a query */ /** Sort columns with a query */
@ -49,7 +58,9 @@ class ProductController extends Controller
/** Custom pagination */ /** Custom pagination */
$productModels = $this->paginate($productModels); $productModels = $this->paginate($productModels);
return view('bo.pages.products.index', compact('productModels', 'search', 'searchFields')); $ranges = Range::all();
return view('bo.pages.products.index', compact('productModels', 'search', 'searchFields', 'ranges'));
} }
/** /**
@ -62,10 +73,12 @@ class ProductController extends Controller
{ {
/** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */ /** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */
$subrangeModels = Subrange::get(); $subrangeModels = Subrange::get();
$productTags = Tag::query()->where('type', TagTypeEnum::products)->select(['id', 'label', 'slug'])->get();
return view('bo.pages.products.create', [ return view('bo.pages.products.create', [
'productModel' => $product, 'productModel' => $product,
'subrangeModels' => $subrangeModels 'subrangeModels' => $subrangeModels,
'productTags' => $productTags,
]); ]);
} }
@ -79,10 +92,11 @@ class ProductController extends Controller
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$product = new Product(); $product = new Product();
$product->fill($request->validated()); $product->fill($request->safe()->except('tags'));
$this->storePictures($request, $product); $this->storePictures($request, $product);
if ($product->saveOrFail()) { if ($product->saveOrFail()) {
$product->tags()->sync(collect($request->input('tags'))->pluck('id'));
return redirect()->route('bo.products.edit', $product->id) return redirect()->route('bo.products.edit', $product->id)
->with('success', __('crud.messages.has_been_created', [ ->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.products')) 'model' => Str::singular(__('models.classes.products'))
@ -106,9 +120,13 @@ class ProductController extends Controller
/** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */ /** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */
$subrangeModels = Subrange::get(); $subrangeModels = Subrange::get();
$productTags = Tag::query()->where('type', TagTypeEnum::products)
->select(['id', 'label', 'slug'])->get();
return view('bo.pages.products.edit', [ return view('bo.pages.products.edit', [
'productModel' => $product, 'productModel' => $product,
'subrangeModels' => $subrangeModels, 'subrangeModels' => $subrangeModels,
'productTags' => $productTags,
]); ]);
} }
@ -122,7 +140,8 @@ class ProductController extends Controller
public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $product) { return DB::transaction(function () use ($request, $product) {
$product->fill($request->validated()); $product->tags()->sync(collect($request->input('tags'))->pluck('id'));
$product->fill($request->safe()->except('tags'));
$this->storePictures($request, $product); $this->storePictures($request, $product);
if ($product->saveOrFail()) { if ($product->saveOrFail()) {
@ -148,6 +167,7 @@ class ProductController extends Controller
{ {
$product->advices()->sync([]); $product->advices()->sync([]);
$product->news()->sync([]); $product->news()->sync([]);
$product->tags()->sync([]);
if ($product->deleteOrFail()) { if ($product->deleteOrFail()) {
return redirect()->route('bo.products.index') return redirect()->route('bo.products.index')
@ -160,4 +180,22 @@ class ProductController extends Controller
'model' => Str::singular(__('models.classes.products')) 'model' => Str::singular(__('models.classes.products'))
])); ]));
} }
/**
* Save Model Images
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Product $product
* @return void
*/
private function storePictures(Request $request, Product $product)
{
if (is_array($request->input('slider_pictures'))) {
$product->slider_pictures = [];
$product->slider_pictures = collect($request->file('slider_pictures'))
->map(function ($picture) use ($product) {
return FileStorageHelper::storeFile($product, $picture, true);
})->all();
}
}
} }

View file

@ -7,6 +7,7 @@ use App\Http\Requests\Bo\Slides\StoreSlideRequest;
use App\Http\Requests\Bo\Slides\UpdateSlideRequest; use App\Http\Requests\Bo\Slides\UpdateSlideRequest;
use App\Models\Slide; use App\Models\Slide;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -14,6 +15,7 @@ use Illuminate\Support\Str;
class SlideController extends Controller class SlideController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
use UpdateModelPublished;
/** /**
* Display a listing of the resource. * Display a listing of the resource.
@ -51,7 +53,7 @@ class SlideController extends Controller
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @param App\Models\Slide $slide * @param \App\Models\Slide $slide
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function create(Slide $slide): \Illuminate\Contracts\View\View public function create(Slide $slide): \Illuminate\Contracts\View\View

View file

@ -0,0 +1,91 @@
<?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) {
$static_page->fill($request->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,6 +2,7 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tags\StoreTagRequest; use App\Http\Requests\Bo\Tags\StoreTagRequest;
use App\Http\Requests\Bo\Tags\UpdateTagRequest; use App\Http\Requests\Bo\Tags\UpdateTagRequest;
@ -22,6 +23,7 @@ class TagController extends Controller
{ {
/** @var \Illuminate\Database\Eloquent\Builder $tagModels */ /** @var \Illuminate\Database\Eloquent\Builder $tagModels */
$tagModels = Tag::query(); $tagModels = Tag::query();
$tagModels = $request->type ? $tagModels->where('type', TagTypeEnum::{$request->type}) : $tagModels;
/** @var string $search Search field */ /** @var string $search Search field */
$search = $request->search; $search = $request->search;
@ -30,10 +32,11 @@ class TagController extends Controller
$tagModels, $tagModels,
$search, $search,
null, null,
'label' 'label',
'type',
); );
} }
$searchFields = trans('Nom'); $searchFields = trans('Nom, Type');
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($tagModels); $this->sortQuery($tagModels);

View file

@ -9,7 +9,6 @@ use App\Lib\Helpers\ToolboxHelper;
use App\Models\Advice; use App\Models\Advice;
use App\Models\News; use App\Models\News;
use App\Models\Product; use App\Models\Product;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
@ -30,7 +29,7 @@ class Controller extends BaseController
* *
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder. * @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string. * @param string $search The query string.
* @param callable $searchQuery The query string. * @param callable|null $searchQuery The query string.
* @param string ...$fields Either a simple array or a string * @param string ...$fields Either a simple array or a string
* in case of a string the two fields * in case of a string the two fields
* would be concatenate to make the search. * would be concatenate to make the search.
@ -39,9 +38,9 @@ class Controller extends BaseController
protected function searchQuery( protected function searchQuery(
Builder $query, Builder $query,
string $search, string $search,
callable $searchQuery = null, callable|null $searchQuery = null,
string ...$fields string ...$fields
): Builder { ): \Illuminate\Database\Eloquent\Builder {
if (count($fields)) { if (count($fields)) {
// Search using words. // Search using words.
$words = explode(' ', $search); $words = explode(' ', $search);
@ -70,45 +69,97 @@ class Controller extends BaseController
* @param array $fields * @param array $fields
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
private function searchOnFields(Builder $query, string $search, array $fields): Builder private function searchOnFields(
{ Builder $query,
string $search,
array $fields
): \Illuminate\Database\Eloquent\Builder {
$table = $query->getModel()->getTable(); $table = $query->getModel()->getTable();
foreach ($fields as $f) { foreach ($fields as $field) {
$f = str_replace('?', '\?', $f); $field = str_replace('?', '\?', $field);
if (is_array($f)) { if (is_array($field)) {
$query->orWhereRaw( $query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) { sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($field)->map(function ($q) use ($table) {
return "`$table`.`$q`"; return "`$table`.`$q`";
})->implode(', \' \', ')), })->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%'] ['%' . htmlspecialchars($search) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
); );
return $query;
} }
} // Get models woth enum's label.
if (is_array($this->getSearchValue($query, $search, $field))) {
foreach ($this->getSearchValue($query, $search, $field) as $array) {
$query->orWhereRaw(
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
['%' . htmlspecialchars($array->value) . '%']
);
}
return $query;
}
// Get models with relation's name/label.
if (Str::endsWith($field, '_id')) {
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
$queryRelation->where('name', 'like', '%' . $search . '%');
} elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) {
$queryRelation->where('label', 'like', '%' . $search . '%');
}
});
return $query;
}
$query->orWhereRaw(
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
['%' . htmlspecialchars($this->getSearchValue($query, $search, $field)) . '%']
);
} //end foreach
return $query; return $query;
} }
/** /**
* Sort columns with a query * Get the search value (on string or Enum).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param string $field
* @return string|array
*/
protected function getSearchValue(Builder $query, string $search, string $field): string|array
{
$cast = $query->getModel()->getCasts()[$field] ?? null;
if (
count($query->getModel()->getCasts()) and // Check if the model has casts.
$cast and // Check if there is cast about specific field.
enum_exists($cast) // Check if the cast is an enum.
) {
/** @var \App\Traits\Enums $cast */
// @phpstan-ignore-next-line
$cases = collect($cast::toArray());
$enum = $cases->filter(function ($item) use ($search) {
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
});
return $enum->toArray();
} else {
return $search;
}
}
/**
* Sort columns with a query.
* *
* @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $query
* @param array $ignore * @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
protected function sortQuery(Builder $query, array $ignore = []): Builder protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder
{ {
$rName = request()->route()->getName(); $rName = request()->route()->getName();
$table = $query->getModel()->getTable(); $table = $query->getModel()->getTable();
if (request()->sort_col) { if (request()->input('sort_col')) {
Session::put("$rName.sort_col", request()->sort_col); Session::put("$rName.sort_col", request()->input('sort_col'));
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->sort_way) { if (request()->input('sort_way')) {
Session::put("$rName.sort_way", request()->sort_way); Session::put("$rName.sort_way", request()->input('sort_way'));
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->rst) { if (request()->rst) {
@ -116,17 +167,29 @@ class Controller extends BaseController
Session::remove("$rName.sort_col"); Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way"); Session::remove("$rName.sort_way");
} }
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) { if (
if ( request()->input('sort_col') and
!isset($ignore) or request()->input('sort_way') and
(isset($ignore) and !in_array(request()->sort_col, $ignore)) Schema::hasColumn($table, request()->input('sort_col'))
) { ) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way); if (!in_array(request()->input('sort_col'), $ignore)) {
$query = $query->orderBy(
\sprintf("$table.%s", request()->input('sort_col')),
request()->input('sort_way')
);
} }
} elseif (Schema::hasColumn($table, 'order')) { } elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC'); $query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order'); Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc'); Session::put("$rName.sort_way", 'asc');
} elseif (Schema::hasColumn($table, 'updated_at')) {
$query = $query->orderBy('updated_at', 'DESC');
Session::put("$rName.sort_col", 'updated_at');
Session::put("$rName.sort_way", 'desc');
} elseif (Schema::hasColumn($table, 'created_at')) {
$query = $query->orderBy('created_at', 'DESC');
Session::put("$rName.sort_col", 'created_at');
Session::put("$rName.sort_way", 'desc');
} }
return $query; return $query;
} }
@ -138,13 +201,17 @@ class Controller extends BaseController
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination * @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/ */
protected function paginate(Builder $query, ItemsPerPaginationEnum $pagination = null): LengthAwarePaginator protected function paginate(
{ Builder $query,
ItemsPerPaginationEnum|null $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName()); $currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}"; $cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve; $pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
$pagination = ToolboxHelper::getValidatedPaginate( $pagination = ToolboxHelper::getValidatedEnum(
Cache::get($cacheKey, $pagination->value) Cache::get($cacheKey, $pagination->value()),
'pagination',
'\App\Enums\Pagination\ItemsPerPaginationEnum',
); );
if (Cache::get($cacheKey) !== $pagination) { if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination); Cache::put($cacheKey, $pagination);
@ -170,7 +237,7 @@ class Controller extends BaseController
*/ */
protected function getProductQuery(string $search): Builder protected function getProductQuery(string $search): Builder
{ {
return Product::query()->where('status', true)->where(function ($query) use ($search) { return Product::query()->where('published', true)->where(function ($query) use ($search) {
// Par leur désignation. // Par leur désignation.
$query->where('label', 'like', "%{$search}%"); $query->where('label', 'like', "%{$search}%");
// Par ingrédients. // Par ingrédients.
@ -190,12 +257,12 @@ class Controller extends BaseController
// Par espèce. // Par espèce.
if (\strlen($search)) { if (\strlen($search)) {
/** @var \App\Enums\Products\ProductSpeciesEnum|null $specie */ /** @var \App\Enums\Products\ProductSpeciesEnum|null $specie */
$specie = \collect(ProductSpeciesEnum::toArray()) $specie = \collect(ProductSpeciesEnum::cases())
->filter(function (mixed $specie) use ($search) { ->filter(function (ProductSpeciesEnum $specie) use ($search) {
return \strpos(Str::lower($specie->label), Str::lower($search)) !== false; return \strpos(Str::lower($specie->label()), Str::lower($search)) !== false;
})->first(); })->first();
if ($specie) { if ($specie) {
$query->orWhere('species', $specie->value); $query->orWhere('species', $specie->value());
} }
} }
}); });
@ -212,7 +279,7 @@ class Controller extends BaseController
*/ */
protected function getNewsQuery(string $search): Builder protected function getNewsQuery(string $search): Builder
{ {
return News::query()->where('status', true)->where(function ($query) use ($search) { return News::query()->where('published', true)->where(function ($query) use ($search) {
$query->where('title', 'like', "%{$search}%"); $query->where('title', 'like', "%{$search}%");
$query->orWhere('subtitle', 'like', "%{$search}%"); $query->orWhere('subtitle', 'like', "%{$search}%");
$query->orWhere('content', 'like', "%{$search}%"); $query->orWhere('content', 'like', "%{$search}%");
@ -231,7 +298,7 @@ class Controller extends BaseController
*/ */
protected function getRecipeQuery(string $search): Builder protected function getRecipeQuery(string $search): Builder
{ {
return Advice::query()->where('status', true) return Advice::query()->where('published', true)
->where('type', '=', AdviceTypeEnum::recipes->value) ->where('type', '=', AdviceTypeEnum::recipes->value)
->where(function ($query) use ($search) { ->where(function ($query) use ($search) {
$query->where('title', 'like', "%{$search}%"); $query->where('title', 'like', "%{$search}%");

View file

@ -16,16 +16,6 @@ class AdnController extends Controller
return view('fo.pages.adn.adn'); return view('fo.pages.adn.adn');
} }
/**
* Undocumented function
*
* @return \Illuminate\Contracts\View\View
*/
public function story(): \Illuminate\Contracts\View\View
{
return view('fo.pages.adn.story');
}
/** /**
* Undocumented function * Undocumented function
* *

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\BrigadeSection;
class BrigadeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
{
$sections = BrigadeSection::query()
->where('published', true)
->orderBy('order')
->get();
return view('fo.pages.brigade', compact(
'sections',
));
}
}

View file

@ -2,11 +2,18 @@
namespace App\Http\Controllers\Fo; namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Contact\ContactRequest; use App\Http\Requests\Fo\Contact\ContactRequest;
use App\Mail\ContactFormMail; use App\Mail\ContactFormMail;
use App\Models\StaticPage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class ContactController extends Controller class ContactController extends Controller
{ {
@ -17,7 +24,9 @@ class ContactController extends Controller
*/ */
public function index(): \Illuminate\Contracts\View\View public function index(): \Illuminate\Contracts\View\View
{ {
return view('fo.pages.contact'); /** @var \App\Models\StaticPage */
$staticPageModel = StaticPage::query()->where('type', StaticPageType::contact)->firstOrFail();
return view('fo.pages.contact', compact('staticPageModel'));
} }
/** /**
@ -28,12 +37,63 @@ class ContactController extends Controller
*/ */
public function contactSubmit(ContactRequest $request): \Illuminate\Http\RedirectResponse public function contactSubmit(ContactRequest $request): \Illuminate\Http\RedirectResponse
{ {
Mail::to(config('mail.contactsList.contactform'))->send(new ContactFormMail($request)); $recipients = config('mail.contactsList.contactform');
if (empty($recipients)) {
Log::warning('Aucun destinataire configuré pour le formulaire de contact.');
return back()
->withErrors(['global' => __('Une erreur est survenue, veuillez réessayer plus tard.')])
->withInput();
}
$redirection = back()->with('success', trans(
'Votre message a bien été envoyé, merci de votre intérêt, nous répondrons dans les meilleurs délais.'
));
if ($request->opt_in) {
try {
SingleOptin::setStatus($request->email, true);
} catch (ApiErrorException $e) {
\report($e);
$redirection = $redirection->withErrors([
'opt_in' => trans('Une erreur est survenue,
votre inscription à la newsletter n\'a pas été prise en compte.')
]);
} catch (CrmException $e) {
\report($e);
$redirection = $redirection->withErrors([
'opt_in' => trans('Une erreur est survenue,
votre inscription à la newsletter n\'a pas été prise en compte.')
]);
}
} //end if
foreach ($recipients as $recipient) {
Mail::to($recipient)->send(new ContactFormMail($request));
}
Session::forget('saveProductInBasket'); Session::forget('saveProductInBasket');
return back()->with('success', trans( Session::forget('contact_draft');
'Votre message a bien été envoyé, merci de votre intérêt, nous répondrons dans les meilleurs délais.'
)); Session::flash('matomo_generate_lead');
return $redirection;
}
/**
* Save data form as draft
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function saveDraft(Request $request): \Illuminate\Http\JsonResponse
{
$request->session()->put('contact_draft', $request->only([
'firstname', 'lastname', 'email', 'phone',
'activity', 'society', 'postcode', 'city',
'message', 'callTime', 'opt_in',
]));
return response()->json(['success' => true]);
} }
} }

View file

@ -5,121 +5,125 @@ namespace App\Http\Controllers\Fo;
use App\Enums\Advices\AdviceTypeEnum; use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\ExpertiseProducts\ExpertiseProductPageEnum; use App\Enums\ExpertiseProducts\ExpertiseProductPageEnum;
use App\Enums\Products\ProductSpeciesEnum; use App\Enums\Products\ProductSpeciesEnum;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Advice; use App\Models\Advice;
use App\Models\ExpertiseProduct; use App\Models\ExpertiseProduct;
use App\Models\Product; use App\Models\Product;
use App\Models\StaticPage;
use Illuminate\Contracts\Database\Eloquent\Builder;
class ExpertiseController extends Controller class ExpertiseController extends Controller
{ {
/** /**
* Undocumented function * Show expertise page (duck).
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
* *
*/ */
public function canard(): \Illuminate\Contracts\View\View public function canard(): \Illuminate\Contracts\View\View
{ {
// Get VIP Products for this page. return view('fo.pages.expertise.canard', $this->getExpertiseData(
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::duck->value())->get(); StaticPageType::expertise_duck,
$products = Product::query()->where('status', true) ExpertiseProductPageEnum::duck,
->where('species', ProductSpeciesEnum::canard->value()) ProductSpeciesEnum::canard
->inRandomOrder() ));
->limit(3)
->with('subrange.range')
->get();
$recipes = Advice::query()->whereHas('products', function ($query) {
return $query->where('species', ProductSpeciesEnum::canard->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return view('fo.pages.expertise.canard', compact('vip', 'products', 'recipes'));
} }
/** /**
* Undocumented function * Show expertise page (turkey).
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function dinde(): \Illuminate\Contracts\View\View public function dinde(): \Illuminate\Contracts\View\View
{ {
// Get VIP Products for this page. return view('fo.pages.expertise.dinde', $this->getExpertiseData(
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::turkey->value())->get(); StaticPageType::expertise_turkey,
$products = Product::query()->where('status', true) ExpertiseProductPageEnum::turkey,
->where('species', ProductSpeciesEnum::dinde->value()) ProductSpeciesEnum::dinde
->inRandomOrder() ));
->limit(3)
->with('subrange.range')
->get();
$recipes = Advice::query()->whereHas('products', function ($query) {
return $query->where('species', ProductSpeciesEnum::dinde->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return view('fo.pages.expertise.dinde', compact('vip', 'products', 'recipes'));
} }
/** /**
* Undocumented function * Show expertise page (rabbit).
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function lapin(): \Illuminate\Contracts\View\View public function lapin(): \Illuminate\Contracts\View\View
{ {
// Get VIP Products for this page. return view('fo.pages.expertise.lapin', $this->getExpertiseData(
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::rabbit->value())->get(); StaticPageType::expertise_rabbit,
$products = Product::query()->where('status', true) ExpertiseProductPageEnum::rabbit,
->where('species', ProductSpeciesEnum::lapin->value()) ProductSpeciesEnum::lapin
->inRandomOrder() ));
->limit(3)
->with('subrange.range')
->get();
$recipes = Advice::query()->whereHas('products', function ($query) {
return $query->where('species', ProductSpeciesEnum::lapin->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return view('fo.pages.expertise.lapin', compact('vip', 'products', 'recipes'));
} }
/** /**
* Undocumented function * Show expertise page (guineafowl).
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function pintade(): \Illuminate\Contracts\View\View public function pintade(): \Illuminate\Contracts\View\View
{ {
// Get VIP Products for this page. return view('fo.pages.expertise.pintade', $this->getExpertiseData(
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::guineafowl->value())->get(); StaticPageType::expertise_guineafowl,
$products = Product::query()->where('status', true) ExpertiseProductPageEnum::guineafowl,
->where('species', ProductSpeciesEnum::pintade->value()) ProductSpeciesEnum::pintade
->inRandomOrder() ));
->limit(3)
->with('subrange.range')
->get();
$recipes = Advice::query()->whereHas('products', function ($query) {
return $query->where('species', ProductSpeciesEnum::pintade->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return view('fo.pages.expertise.pintade', compact('vip', 'products', 'recipes'));
} }
/** /**
* Undocumented function * Show expertise page (chicken).
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function poulet(): \Illuminate\Contracts\View\View public function poulet(): \Illuminate\Contracts\View\View
{ {
// Get VIP Products for this page. return view('fo.pages.expertise.poulet', $this->getExpertiseData(
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::chicken->value())->get(); StaticPageType::expertise_chicken,
$products = Product::query()->where('status', true) ExpertiseProductPageEnum::chicken,
->where('species', ProductSpeciesEnum::poulet->value()) ProductSpeciesEnum::poulet
));
}
/**
* Get all expertise data needed.
*
* @param \App\Enums\StaticPageType $enumStaticPageType
* @param \App\Enums\ExpertiseProducts\ExpertiseProductPageEnum $enumExpertiseProductPage
* @param \App\Enums\Products\ProductSpeciesEnum $enumProductSpecies
*
* @return array
*/
public function getExpertiseData(
StaticPageType $enumStaticPageType,
ExpertiseProductPageEnum $enumExpertiseProductPage,
ProductSpeciesEnum $enumProductSpecies
): array {
/** @var \App\Models\StaticPage $staticPageModel */
$staticPageModel = StaticPage::query()->where('type', $enumStaticPageType->value())->first();
/** @var \App\Models\ExpertiseProduct $productVipModels */
$productVipModels = ExpertiseProduct::query()->where('page', $enumExpertiseProductPage->value())->get();
/** @var \App\Models\Product $productModels */
$productModels = Product::query()->where('published', true)
->where('species', $enumProductSpecies->value())
->inRandomOrder() ->inRandomOrder()
->limit(3) ->limit(3)
->with('subrange.range') ->with('subrange.range')
->get(); ->get();
$recipes = Advice::query()->whereHas('products', function ($query) {
return $query->where('species', ProductSpeciesEnum::poulet->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return view('fo.pages.expertise.poulet', compact('vip', 'products', 'recipes')); $recipeModels = Advice::query()->with('tags')
->whereHas('products', function (Builder $q) use ($enumProductSpecies) {
return $q->where('species', $enumProductSpecies->value());
})->where('type', AdviceTypeEnum::recipes)->limit(3)->get();
return [
'staticPageModel' => $staticPageModel,
'productVipModels' => $productVipModels,
'productModels' => $productModels,
'recipeModels' => $recipeModels
];
} }
} }

View file

@ -3,12 +3,17 @@
namespace App\Http\Controllers\Fo; namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Newsletter\SingleOptinRequest;
use App\Http\Requests\Fo\Newsletter\UnsubscribeNewsletterRequest;
use App\Models\News; use App\Models\News;
use App\Models\Product; use App\Models\Product;
use App\Models\Range; use App\Models\Range;
use App\Models\Slide; use App\Models\Slide;
use Carbon\Carbon; use Illuminate\Support\Facades\Log;
use Spatie\CalendarLinks\Link; use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class HomeController extends Controller class HomeController extends Controller
{ {
@ -19,43 +24,104 @@ class HomeController extends Controller
*/ */
public function index(): \Illuminate\Contracts\View\View public function index(): \Illuminate\Contracts\View\View
{ {
// Get Slides. // Get Slides.
/** @var \Illuminate\Database\Eloquent\Builder $slides */ /** @var \Illuminate\Database\Eloquent\Builder $slides */
$slides = Slide::query(); $slides = Slide::query()
$slides->orderBy('order', 'DESC'); ->where('published', true)
$slides = $slides->get(); ->orderBy('order', 'DESC')
->get();
// Gammes. // Gammes.
$ranges = Range::all(); $ranges = Range::all();
// VIP Products. // VIP Products.
$threeproducts = Product::where('status', true) $threeproducts = Product::where('published', true)
->where('is_vip', true) ->where('is_vip', true)
->take(3) ->take(3)
->orderBy('order', 'DESC') ->orderBy('priority_order', 'DESC')
->with('subrange.range') ->with('subrange.range')
->get(); ->get();
// Get Agenda. // Get Agenda.
// Events starting in the future. // Events starting in the future.
$events = News::where('status', true) $events = News::query()->where('published', true)
->where('event_start', '>', Carbon::now()) ->where('event_start', '>', now())
->orderBy('event_start', 'desc') ->orderBy('event_start', 'desc')
->take(2)->get(); ->take(2)->get()->reverse();
$events = $events->reverse();
foreach ($events as $event) {
$event->cal_link = Link::create($event->title, $event->event_start, $event->event_end)
->description($event->event_text)
->ics();
}
// Get last 3 News. // Get last 3 News.
$news = News::latest()->where('status', true)->take(3)->with('categories')->get(); $news = News::query()->latest()->where('published', true)
->take(3)->with('categories')->get();
return view('fo.pages.home', compact('slides', 'ranges', 'threeproducts', 'events', 'news')); return view('fo.pages.home', compact(
'slides',
'ranges',
'threeproducts',
'events',
'news'
));
}
/**
* Call the CRM to add a single optin
*
* @param \App\Http\Requests\Fo\Newsletter\SingleOptinRequest $request
* @return string|array|null|\Illuminate\Contracts\Translation\Translator
* @throws \LDC\LDCCRM\Exceptions\ValidationException ValidationException.
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
*/
public function addOptIn(SingleOptinRequest $request)
{
try {
SingleOptin::setStatus($request->email, true);
return \response(trans("Votre inscription à la newsletter a été prise en compte"), 200);
} catch (ApiErrorException $e) {
\report($e);
return \response(trans('Une erreur est survenue, merci de réessayer ultérieurement'), 500);
} catch (CrmException $e) {
\report($e);
return \response(trans('Une erreur est survenue, merci de réessayer ultérieurement'), 500);
}
}
/**
* unsubscribeNewsletter method
*
* @param \App\Http\Requests\Fo\Newsletter\UnsubscribeNewsletterRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function unsubscribeNewsletter(UnsubscribeNewsletterRequest $request): \Illuminate\Contracts\View\View
{
$error = null;
$hashcode = $request->hashcode;
try {
SingleOptin::unsetStatusWithHashcode($hashcode);
} catch (ApiErrorException $e) {
Log::debug($e->getMessage());
if ($e->getApiError()->equals(ApiError::modelNotFound)) {
abort(404);
} else {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
$error = true;
}
} catch (CrmException $e) {
Log::debug($e->getMessage());
$error = true;
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
} //end try
if (!is_null($error)) {
return view('fo.pages.newsletter-unsubscribe')->with(
'error',
trans('Une erreur est survenue, merci de réessayer ultérieurement')
);
}
return view('fo.pages.newsletter-unsubscribe');
} }
} }

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\News; use App\Models\News;
use App\Models\NewsCategory; use App\Models\NewsCategory;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class NewsController extends Controller class NewsController extends Controller
@ -18,32 +19,31 @@ class NewsController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\View public function index(Request $request): \Illuminate\Contracts\View\View
{ {
// Get NewsCategories. // Get NewsCategories.
$categories = NewsCategory::orderBy('label', 'ASC')->get(); $categories = NewsCategory::query()->orderBy('label', 'ASC')->get();
// Get News. // Get News.
$filters_activated = $request->filtered_categorie; $filters_activated = collect($request->input('filtered_categorie'));
$news = News::when( $news = News::query()->with('categories')->when(
isset($filters_activated) and $filters_activated[0] != 'all', $filters_activated->first() !== 'all',
function ($query) use ($filters_activated) { fn(Builder $q) => $q->with('categories')
$query->with('categories') ->whereRelation('categories', fn(Builder $q) =>
->whereRelation('categories', function ($query) use ($filters_activated) { $q->where(function (Builder $q) use ($filters_activated) {
foreach ($filters_activated as $value) { foreach ($filters_activated as $value) {
$query->orWhere('id', 'like', $value); if (is_string($value)) {
$q->orWhere('id', $value);
} }
}); }
} }))
) )->where('published', true)->orderByDesc('created_at')->paginate(6);
->where('status', true)
->orderBy('created_at')
->paginate(6);
return view('fo.pages.news.index', compact('categories', 'news', 'filters_activated')); return view('fo.pages.news.index', compact('categories', 'news', 'filters_activated'));
} }
/** /**
* Undocumented function * Show resource
* @param News $news *
* @param \App\Models\News $news
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
@ -51,7 +51,7 @@ class NewsController extends Controller
{ {
$news->load('categories', 'news.categories', 'products'); $news->load('categories', 'news.categories', 'products');
$readingTime = $news->getReadingTime(); $readingTime = $news->getReadingTime();
$products = $news->products()->get(); $products = $news->products()->with(['subrange', 'subrange.range'])->get();
return view('fo.pages.news.post', compact('news', 'readingTime', 'products')); return view('fo.pages.news.post', compact('news', 'readingTime', 'products'));
} }

View file

@ -61,7 +61,8 @@ class ProductsController extends Controller
$search = htmlspecialchars($request->query('search') ?? ''); $search = htmlspecialchars($request->query('search') ?? '');
$productsQuery = !empty($search) ? $this->getProductQuery($search) : Product::query(); $productsQuery = !empty($search) ? $this->getProductQuery($search) : Product::query();
$productsQuery->with('subrange.range'); $productsQuery->with('subrange.range');
$productsQuery->where('status', true); $productsQuery->where('published', true);
$productsQuery->orderBy('priority_order', 'ASC');
// * Range filter (obligatoire). // * Range filter (obligatoire).
$productsQuery->whereHas('subrange', function (Builder $q) use ($range) { $productsQuery->whereHas('subrange', function (Builder $q) use ($range) {
@ -84,7 +85,6 @@ class ProductsController extends Controller
if ($request->vip) { if ($request->vip) {
$productsQuery->where('is_vip', $request->vip); $productsQuery->where('is_vip', $request->vip);
} }
return \response()->json(new ProductPaginatedCollectionResource($productsQuery->paginate(6))); return \response()->json(new ProductPaginatedCollectionResource($productsQuery->paginate(6)));
} }
@ -97,12 +97,12 @@ class ProductsController extends Controller
public function show(Product $publishedProduct): \Illuminate\Contracts\View\View public function show(Product $publishedProduct): \Illuminate\Contracts\View\View
{ {
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */ /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */
$recipes = $publishedProduct->recipesQuery()->where('status', true)->get(); $recipes = $publishedProduct->recipesQuery()->where('published', true)->with('tags')->get();
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */ /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */
$tips = $publishedProduct->tipsQuery()->where('status', true)->get(); $tips = $publishedProduct->tipsQuery()->where('published', true)->get();
// Force load subrange and range. // Force load subrange and range.
$publishedProduct->load('subrange.range'); $publishedProduct->load(['subrange.range', 'tags']);
/** @var \App\Models\Subrange */ /** @var \App\Models\Subrange */
$subrange = $publishedProduct->subrange; $subrange = $publishedProduct->subrange;
@ -111,8 +111,9 @@ class ProductsController extends Controller
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Product> */ /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Product> */
$randomProducts = Product::query() $randomProducts = Product::query()
->with('subrange.range')
->where('id', '!=', $publishedProduct->id) ->where('id', '!=', $publishedProduct->id)
->where('status', true) ->where('published', true)
->whereHas('subrange', function (Builder $q) use ($range) { ->whereHas('subrange', function (Builder $q) use ($range) {
$q->whereHas('range', function (Builder $q) use ($range) { $q->whereHas('range', function (Builder $q) use ($range) {
$q->where('id', $range->id); $q->where('id', $range->id);
@ -121,7 +122,12 @@ class ProductsController extends Controller
$product = $publishedProduct; $product = $publishedProduct;
return view('fo.pages.products.product', compact('product', 'recipes', 'tips', 'randomProducts')); return view('fo.pages.products.product', compact(
'product',
'recipes',
'tips',
'randomProducts'
));
} }
/** /**
@ -130,13 +136,13 @@ class ProductsController extends Controller
* @param \App\Models\Product $publishedProduct * @param \App\Models\Product $publishedProduct
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function printCommericalSheet(Product $publishedProduct): Response public function printCommercialSheet(Product $publishedProduct): Response
{ {
$title = trans('Fiche commerciale du produit : :label - :appname', [ $title = trans('Fiche commerciale du produit : :label - :appname', [
'label' => $publishedProduct->label, 'label' => $publishedProduct->label,
'appname' => \config('app.name', 'Le Gaulois Pro') 'appname' => \config('app.name', 'Le Gaulois Pro')
]); ]);
$filename = trans('fiche-commerciale-:slug-:date', [ $filename = trans('fiche-commerciale-:slug-:date.pdf', [
'slug' => Str::slug($publishedProduct->label), 'slug' => Str::slug($publishedProduct->label),
'date' => \now()->format('Y-m-d') 'date' => \now()->format('Y-m-d')
]); ]);
@ -147,17 +153,15 @@ class ProductsController extends Controller
* ! apres avoir header et echo dans le corps de la réponse php (Nasty trick) * ! apres avoir header et echo dans le corps de la réponse php (Nasty trick)
*/ */
return \response( return \response(
$this->generateProductCommercialPdf($publishedProduct, $title)->output(), $this->generateProductCommercialPdf($publishedProduct, $title)->download($filename),
200, 200,
[ [
'Content-Type' => 'application/pdf', 'Content-Type' => 'application/pdf',
'Content-disposition' => "inline; filename=\"{$filename}\"", 'Content-disposition' => "inline; filename=\"{$filename}\"",
'Cache-Control' => 'public, must-revalidate, max-age=0', 'Cache-Control' => 'public, must-revalidate, max-age=0',
'Pragma' => 'public', 'Pragma' => 'public',
// * For glory 'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'X-Generator' => 'mPDF', 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
] ]
); );
} }
@ -230,7 +234,7 @@ class ProductsController extends Controller
*/ */
private function generateProductCommercialPdf(Product $product, string $title): \Mccarlosen\LaravelMpdf\LaravelMpdf private function generateProductCommercialPdf(Product $product, string $title): \Mccarlosen\LaravelMpdf\LaravelMpdf
{ {
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */ /** @var \Mccarlosen\LaravelMpdf\LaravelMpdfWrapper */
$mpdf = app('laravel-mpdf'); $mpdf = app('laravel-mpdf');
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */ /** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
$pdf = $mpdf->loadView( $pdf = $mpdf->loadView(
@ -256,7 +260,6 @@ class ProductsController extends Controller
'margin_bottom' => 0, 'margin_bottom' => 0,
'margin_header' => 0, 'margin_header' => 0,
'margin_footer' => 0, 'margin_footer' => 0,
'margin_top' => 0,
'orientation' => 'P', 'orientation' => 'P',
'title' => $title, 'title' => $title,
'author' => \config('app.name', 'Le Gaulois Pro'), 'author' => \config('app.name', 'Le Gaulois Pro'),

View file

@ -43,39 +43,39 @@ class RecipesController extends Controller
/** @var \App\Enums\Advices\AdviceTimeEnum|null $cooking_time */ /** @var \App\Enums\Advices\AdviceTimeEnum|null $cooking_time */
/** @var \App\Enums\Advices\AdviceTimeEnum|null $preparation_time */ /** @var \App\Enums\Advices\AdviceTimeEnum|null $preparation_time */
$query = Advice::query()->where('type', 1)->where('status', true)->when( $query = Advice::query()->with('tags')->where('type', 1)->where('published', true)->when(
is_numeric($search_range), is_numeric($search_range),
fn (Builder $q) => fn(Builder $q) =>
$q->whereHas( $q->whereHas(
'products', 'products',
fn (Builder $q) => fn(Builder $q) =>
$q->whereHas( $q->whereHas(
'subrange', 'subrange',
fn (Builder $q) => fn(Builder $q) =>
$q->whereHas( $q->whereHas(
'range', 'range',
fn (Builder $q) => fn(Builder $q) =>
$q->where('id', 'LIKE', $search_range) $q->where('id', 'LIKE', $search_range)
) )
) )
) )
)->when( )->when(
$cooking_time, $cooking_time,
fn (Builder $q) => fn(Builder $q) =>
$q->whereBetween('cooking_time', [ $q->whereBetween('cooking_time', [
Arr::get($cooking_time->times(), 0), Arr::get($cooking_time->times(), 0),
Arr::get($cooking_time->times(), 1) Arr::get($cooking_time->times(), 1)
]) ])
)->when( )->when(
$preparation_time, $preparation_time,
fn (Builder $q) => fn(Builder $q) =>
$q->whereBetween('preparation_time', [ $q->whereBetween('preparation_time', [
Arr::get($preparation_time->times(), 0), Arr::get($preparation_time->times(), 0),
Arr::get($preparation_time->times(), 1) Arr::get($preparation_time->times(), 1)
]) ])
); );
$recipes = $query->orderBy('slug', 'ASC')->paginate(9); $recipes = $query->orderBy('priority_order', 'ASC')->paginate(9);
return view('fo.pages.ideas.recipes', compact( return view('fo.pages.ideas.recipes', compact(
'ranges', 'ranges',

View file

@ -12,7 +12,7 @@ class SearchController extends Controller
* Search input txt in products, news and recipes * Search input txt in products, news and recipes
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\Contracts\View\View
*/ */
public function search(Request $request): \Illuminate\Contracts\View\View public function search(Request $request): \Illuminate\Contracts\View\View
{ {
@ -25,11 +25,8 @@ class SearchController extends Controller
$recipes = []; $recipes = [];
$recipesTotal = 0; $recipesTotal = 0;
if (is_array($search)) {
abort(400);
}
if (is_string($model)) { if (is_string($model)) {
return $this->callAction('searchModel', [$model, $search]); return $this->searchModel($model, $search);
} }
if (!empty($search)) { if (!empty($search)) {
@ -63,7 +60,7 @@ class SearchController extends Controller
* *
* @param string $modelName * @param string $modelName
* @param string $search * @param string $search
* @return \Illuminate\Http\Response * @return \Illuminate\Contracts\View\View
*/ */
public function searchModel(string $modelName, string $search): \Illuminate\Contracts\View\View public function searchModel(string $modelName, string $search): \Illuminate\Contracts\View\View
{ {

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\StaticPage;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
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.legals', ['staticPageModel' => $policiesPage]);
}
/**
* Sitemap show
*
* @param \App\Models\StaticPage $sitemap
* @return \Illuminate\Contracts\View\View
*/
public function sitemap(StaticPage $sitemap)
{
$sitemap = StaticPage::query()->where('type', StaticPageType::sitemap)->first();
$singlePages = SitemapHelper::getSinglePages();
return view('fo.pages.sitemap', ['staticPageModel' => $sitemap, 'singlePages' => $singlePages]);
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
class StoryController extends Controller
{
/**
* Undocumented function
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
{
return view('fo.pages.story');
}
}

View file

@ -17,7 +17,7 @@ class TipsController extends Controller
{ {
$advices = Advice::query()->with('tags') $advices = Advice::query()->with('tags')
->where('type', AdviceTypeEnum::tips) ->where('type', AdviceTypeEnum::tips)
->where('status', true) ->where('published', true)
->paginate(9); ->paginate(9);
return view('fo.pages.ideas.tips', \compact('advices')); return view('fo.pages.ideas.tips', \compact('advices'));
} }
@ -31,7 +31,9 @@ class TipsController extends Controller
public function show(Advice $publishedAdvice): \Illuminate\Contracts\View\View public function show(Advice $publishedAdvice): \Illuminate\Contracts\View\View
{ {
$publishedAdvice->load([ $publishedAdvice->load([
'advices', 'products.subrange', 'tags' 'advices',
'products.subrange.range',
'tags'
]); ]);
return view('fo.pages.ideas.tip', [ return view('fo.pages.ideas.tip', [
'adviceModel' => $publishedAdvice 'adviceModel' => $publishedAdvice

View file

@ -33,12 +33,16 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class, \App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class \App\Http\Middleware\SecureHeaders::class
] ],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
]; ];
/** /**

View file

@ -33,7 +33,6 @@ class Authenticate extends Middleware
} }
return route('login'); return route('login');
} }
abort(401);
return null; return null;
} }

View file

@ -39,7 +39,6 @@ class SecureHeaders
View::share('nonce', $nonce = Vite::useCspNonce()); View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute // * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'"; $jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ? $reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : ''; \sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList); $this->removeUnwantedHeaders($this->unwantedHeaderList);
@ -61,6 +60,7 @@ class SecureHeaders
]) . ';', ]) . ';',
"img-src 'self' data: blob: " . implode(' ', [ "img-src 'self' data: blob: " . implode(' ', [
'https://yl.s-web.fr', 'https://yl.s-web.fr',
'https://i3.ytimg.com/',
// * VITE DEV // * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '', config('app.debug') ? "https://{$host}:4443" : '',
// * MESBR // * MESBR
@ -99,7 +99,6 @@ class SecureHeaders
'https: blob:', 'https: blob:',
'https://www.googletagmanager.com', 'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com', 'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
// * VITE DEV // * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '', config('app.debug') ? "https://{$host}:4443" : '',
]) . ';' ]) . ';'

View file

@ -4,6 +4,7 @@ namespace App\Http\Requests\Bo\Advice;
use App\Enums\Advices\AdviceDifficultyEnum; use App\Enums\Advices\AdviceDifficultyEnum;
use App\Enums\Advices\AdviceTypeEnum; use App\Enums\Advices\AdviceTypeEnum;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture; use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -34,23 +35,23 @@ class StoreAdviceRequest extends FormRequest
{ {
//phpcs:enable //phpcs:enable
$this->merge([ $this->merge([
'slug' => Str::slug(strip_tags($this->title)), 'slug' => Str::slug(strip_tags($this->input('title'))),
'status' => $this->status ? true : false, 'published' => $this->boolean('published'),
'tags' => $this->tags ?? [], 'tags' => $this->input('tags', []),
'products' => $this->products ?? [], 'products' => $this->input('products', []),
'associated_tips_list' => $this->associated_tips_list ?? [], 'associated_tips_list' => $this->input('associated_tips_list', []),
'associated_recipes_list' => $this->associated_recipes_list ?? [], 'associated_recipes_list' => $this->input('associated_recipes_list', []),
// * For sync relations // * For sync relations
'tags_list' => \collect($this->tags)->map(function ($value) { 'tags_list' => \collect($this->input('tags'))->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null; return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(), })->filter()->all(),
'products_list' => \collect($this->products)->map(function ($value) { 'products_list' => \collect($this->input('products'))->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null; return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(), })->filter()->all(),
'tips_list' => \collect($this->associated_tips_list)->map(function ($value) { 'tips_list' => \collect($this->input('tips'))->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null; return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(), })->filter()->all(),
'recipes_list' => \collect($this->associated_recipes_list)->map(function ($value) { 'recipes_list' => \collect($this->input('recipes'))->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null; return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(), })->filter()->all(),
]); ]);
@ -79,18 +80,28 @@ class StoreAdviceRequest extends FormRequest
'sticker_title' => 'required_with:sticker|nullable|min:3|max:255', 'sticker_title' => 'required_with:sticker|nullable|min:3|max:255',
'sticker_sm_alt' => 'required_with:sticker_sm|nullable|min:3|max:255', 'sticker_sm_alt' => 'required_with:sticker_sm|nullable|min:3|max:255',
'sticker_sm_title' => 'required_with:sticker_sm|nullable|min:3|max:255', 'sticker_sm_title' => 'required_with:sticker_sm|nullable|min:3|max:255',
'preparation' => 'required|string|min:15', 'preparation' => [
'advice' => 'nullable|string|min:15', 'required',
'string',
new WysiwygLength(15, 2500)
],
'advice' => [
'nullable',
'string',
new WysiwygLength(15, 2500)
],
'ingredients' => [ 'ingredients' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) < 1 || count($value) > 15) { if (count($value) < 1 || count($value) > 15) {
$fail(\trans('Vous devez associer 1 à 15 lignes d\'ingrédients.')); $fail(\trans('Vous devez associer 1 à 15 lignes d\'ingrédients.'));
} }
} }
], ],
'ingredients.*' => 'required|array', 'ingredients.*' => 'required|array',
'ingredients.*.value' => 'nullable|numeric|max:65535', 'ingredients.*.value' => 'nullable|regex:/^\d*\.?\d{0,1}$/|between:0,65535',
'ingredients.*.label' => [ 'ingredients.*.label' => [
Rule::requiredIf(function () { Rule::requiredIf(function () {
return $this->type === AdviceTypeEnum::recipes->value(); return $this->type === AdviceTypeEnum::recipes->value();
@ -118,16 +129,22 @@ class StoreAdviceRequest extends FormRequest
) { ) {
$fail(\trans('Le nombre de convives est obligatoire')); $fail(\trans('Le nombre de convives est obligatoire'));
} }
}, 'nullable', 'numeric', 'min:1', 'max:99' },
'nullable',
'numeric',
'min:1',
'max:99'
], ],
'youtube_title' => 'required_with:youtube_video_id|nullable|min:3|max:255', 'youtube_title' => 'required_with:youtube_video_id|nullable|min:3|max:255',
'youtube_video_id' => 'required_with:youtube_title|nullable|regex:%([^"&?\/\s]{11})%i', 'youtube_video_id' => 'required_with:youtube_title|nullable|min:3|max:50|regex:%([^"&?\/\s]{11})%i',
'cooking_time' => 'nullable|numeric|min:0|max:65535', 'cooking_time' => 'nullable|numeric|min:0|max:65535',
'preparation_time' => [ 'preparation_time' => [
Rule::requiredIf(function () { Rule::requiredIf(function () {
return $this->type === AdviceTypeEnum::recipes->value(); return $this->type === AdviceTypeEnum::recipes->value();
}), }),
'numeric', 'min:0', 'max:65535' 'numeric',
'min:0',
'max:65535'
], ],
'difficulty' => [ 'difficulty' => [
Rule::requiredIf(function () { Rule::requiredIf(function () {
@ -135,10 +152,12 @@ class StoreAdviceRequest extends FormRequest
}), }),
new Enum(AdviceDifficultyEnum::class) new Enum(AdviceDifficultyEnum::class)
], ],
'status' => 'required|boolean', 'published' => 'required|boolean',
'tags' => [ 'tags' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) > 2) { if (count($value) > 2) {
$fail(\trans('Vous devez associer au maximum 2 tags.')); $fail(\trans('Vous devez associer au maximum 2 tags.'));
} }
@ -148,8 +167,10 @@ class StoreAdviceRequest extends FormRequest
'tags.*.id' => 'required|numeric|exists:tags,id|distinct', 'tags.*.id' => 'required|numeric|exists:tags,id|distinct',
'tags.*.label' => 'required|string', 'tags.*.label' => 'required|string',
'products' => [ 'products' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if ($this->type == AdviceTypeEnum::recipes->value()) { if ($this->type == AdviceTypeEnum::recipes->value()) {
if (count($value) < 1 || count($value) > 3) { if (count($value) < 1 || count($value) > 3) {
$fail(\trans('Vous devez associer 1 à 3 produits.')); $fail(\trans('Vous devez associer 1 à 3 produits.'));
@ -165,8 +186,10 @@ class StoreAdviceRequest extends FormRequest
'products.*.value' => 'required|exists:products,id', 'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string', 'products.*.label' => 'required|string',
'associated_tips_list' => [ 'associated_tips_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) > 3) { if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 astuces.')); $fail(\trans('Vous devez associer au maximum 3 astuces.'));
} }
@ -176,8 +199,10 @@ class StoreAdviceRequest extends FormRequest
'associated_tips_list.*.value' => 'required|exists:advice,id', 'associated_tips_list.*.value' => 'required|exists:advice,id',
'associated_tips_list.*.label' => 'required|string', 'associated_tips_list.*.label' => 'required|string',
'associated_recipes_list' => [ 'associated_recipes_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) > 3) { if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 recettes.')); $fail(\trans('Vous devez associer au maximum 3 recettes.'));
} }
@ -189,9 +214,9 @@ class StoreAdviceRequest extends FormRequest
]; ];
return \array_merge( return \array_merge(
$rules, $rules,
$this->pictureRules('picture', true, 650, 690, 650, 690), $this->pictureRules('picture', 650, 690, 650, 690, false),
$this->pictureRules('sticker', true, 370, 300, 370, 300), $this->pictureRules('sticker', 370, 300, 370, 300, false),
$this->pictureRules('sticker_sm', true, 270, 145, 270, 145), $this->pictureRules('sticker_sm', 270, 145, 270, 145, false),
); );
} }

View file

@ -0,0 +1,84 @@
<?php
namespace App\Http\Requests\Bo\BrigadeSections;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
class StoreBrigadeSectionsRequest extends FormRequest
{
use HasPicture;
/**
* 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
*/
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->boolean('published'),
'button_display' => $this->boolean('button_display'),
'button_newtab' => $this->boolean('button_newtab'),
]);
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = [
'title' => 'required|string|min:3|max:500',
'content' => [
'required',
'string',
new WysiwygLength(15, 1500)
],
'picture_alt' => 'required|string|min:3|max:255',
'picture_title' => 'required|string|min:3|max:255',
'button_display' => 'required|boolean',
'button_text' => 'required_if_accepted:button_display|nullable|string|min:3|max:255',
'button_url' => 'required_if_accepted:button_display|nullable|url:https|min:3|max:255',
'button_newtab' => 'required|boolean',
'published' => 'required|boolean',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 800, 535, 800, 535, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'title' => trans('Titre'),
'picture' => trans('Image'),
'picture_alt' => trans('Attribut alt de l\'image'),
'picture_title' => trans('Titre de l\'image'),
'content' => trans('Contenu'),
'button_text' => trans('Texte du CTA'),
'button_url' => trans('URL du CTA'),
'published' => trans('Statut'),
];
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Http\Requests\Bo\BrigadeSections;
class UpdateBrigadeSectionsRequest extends StoreBrigadeSectionsRequest
{
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Requests\Bo\News; namespace App\Http\Requests\Bo\News;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture; use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -31,7 +32,7 @@ class StoreNewsRequest extends FormRequest
//phpcs:enable //phpcs:enable
$this->merge([ $this->merge([
'slug' => Str::slug($this->title), 'slug' => Str::slug($this->title),
'status' => $this->status ? true : false, 'published' => $this->published ? true : false,
'categories' => $this->categories ?? [], 'categories' => $this->categories ?? [],
'products' => $this->products ?? [], 'products' => $this->products ?? [],
'associated_news_list' => $this->associated_news_list ?? [], 'associated_news_list' => $this->associated_news_list ?? [],
@ -66,8 +67,18 @@ class StoreNewsRequest extends FormRequest
'picture_title' => 'required_with:picture|nullable|min:3|max:255', 'picture_title' => 'required_with:picture|nullable|min:3|max:255',
'picture2_alt' => 'required_with:picture2|nullable|min:3|max:255', 'picture2_alt' => 'required_with:picture2|nullable|min:3|max:255',
'picture2_title' => 'required_with:picture2|nullable|min:3|max:255', 'picture2_title' => 'required_with:picture2|nullable|min:3|max:255',
'content' => 'required_with:picture|nullable|string|min:15', 'content' => [
'content2' => 'required_with:picture2|nullable|string|min:15', 'required_with:picture',
'nullable',
'string',
new WysiwygLength(15, 2500)
],
'content2' => [
'required_with:picture2',
'nullable',
'string',
new WysiwygLength(15, 2500)
],
'event_start' => 'nullable|date_format:Y-m-d', 'event_start' => 'nullable|date_format:Y-m-d',
'event_end' => [ 'event_end' => [
'required_with:event_start', 'required_with:event_start',
@ -81,10 +92,12 @@ class StoreNewsRequest extends FormRequest
'min:3', 'min:3',
'max:255' 'max:255'
], ],
'status' => 'required|boolean', 'published' => 'required|boolean',
'categories' => [ 'categories' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) < 1 || count($value) > 2) { if (count($value) < 1 || count($value) > 2) {
$fail(\trans('Vous devez associer une actualité à 1 ou 2 catégories.')); $fail(\trans('Vous devez associer une actualité à 1 ou 2 catégories.'));
} }
@ -94,8 +107,10 @@ class StoreNewsRequest extends FormRequest
'categories.*.value' => 'required|exists:news_categories,id', 'categories.*.value' => 'required|exists:news_categories,id',
'categories.*.label' => 'required|string', 'categories.*.label' => 'required|string',
'products' => [ 'products' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) > 3) { if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 produits.')); $fail(\trans('Vous devez associer au maximum 3 produits.'));
} }
@ -105,8 +120,10 @@ class StoreNewsRequest extends FormRequest
'products.*.value' => 'required|exists:products,id', 'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string', 'products.*.label' => 'required|string',
'associated_news_list' => [ 'associated_news_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (count($value) > 3) { if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 actualités.')); $fail(\trans('Vous devez associer au maximum 3 actualités.'));
} }
@ -118,8 +135,8 @@ class StoreNewsRequest extends FormRequest
]; ];
return \array_merge( return \array_merge(
$rules, $rules,
$this->pictureRules('picture', true, 920, 1025, 920, 1025), $this->pictureRules('picture', 590, 645, 590, 645, false),
$this->pictureRules('picture2', false, 920, 1025, 920, 1025) $this->pictureRules('picture2', 590, 645, 590, 645, true, requiredWith: 'content2')
); );
} }
@ -147,7 +164,7 @@ class StoreNewsRequest extends FormRequest
'event_start' => trans('Date de début de l\'évènement'), 'event_start' => trans('Date de début de l\'évènement'),
'event_end' => trans('Date de fin de l\'évènement'), 'event_end' => trans('Date de fin de l\'évènement'),
'event_text' => trans('Description de l\'évènement'), 'event_text' => trans('Description de l\'évènement'),
'status' => trans('Status'), 'published' => trans('Status'),
]; ];
} }
} }

View file

@ -4,6 +4,7 @@ namespace App\Http\Requests\Bo\Products;
use App\Enums\NutriscoreEnum; use App\Enums\NutriscoreEnum;
use App\Enums\Products\ProductSpeciesEnum; use App\Enums\Products\ProductSpeciesEnum;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture; use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -35,9 +36,19 @@ class StoreProductRequest extends FormRequest
'is_frenchpoultry' => $this->is_frenchpoultry ? true : false, 'is_frenchpoultry' => $this->is_frenchpoultry ? true : false,
'is_vip' => $this->is_vip ? true : false, 'is_vip' => $this->is_vip ? true : false,
'is_egalim' => $this->is_egalim ? true : false, 'is_egalim' => $this->is_egalim ? true : false,
'status' => $this->status ? true : false 'published' => $this->published ? true : false,
'tags' => $this->input('tags', []),
// * For sync relations
'tags_list' => \collect($this->input('tags'))->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(),
]); ]);
$this->mergePicture('sticker_sm'); $this->mergePicture('sticker_sm');
($this->slider_pictures)
? $this->mergePictures('slider_pictures')
: $this->merge([
'slider_pictures' => [],
]);
} }
/** /**
@ -56,12 +67,26 @@ class StoreProductRequest extends FormRequest
'nutriscore' => ['required', new Enum(NutriscoreEnum::class)], 'nutriscore' => ['required', new Enum(NutriscoreEnum::class)],
'sticker_sm_alt' => 'required|min:3|max:255', 'sticker_sm_alt' => 'required|min:3|max:255',
'sticker_sm_title' => 'required|min:3|max:255', 'sticker_sm_title' => 'required|min:3|max:255',
'ingredients' => 'required|string|min:15', 'ingredients' => [
'advantage' => 'required|string|min:15', 'required',
'preparation' => 'required|string|min:15', 'string',
new WysiwygLength(15, 2500)
],
'advantage' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'preparation' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'packaging' => [ 'packaging' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (1 > count($value) || count($value) > 6) { if (1 > count($value) || count($value) > 6) {
$fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de conditionnement.')); $fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de conditionnement.'));
} }
@ -70,8 +95,10 @@ class StoreProductRequest extends FormRequest
'packaging.*' => 'required|array', 'packaging.*' => 'required|array',
'packaging.*.value' => 'required|string|min:2|max:255', 'packaging.*.value' => 'required|string|min:2|max:255',
'caliber' => [ 'caliber' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) { function ($attribute, array $value, $fail) {
if (1 > count($value) || count($value) > 6) { if (1 > count($value) || count($value) > 6) {
$fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de calibre.')); $fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de calibre.'));
} }
@ -79,27 +106,30 @@ class StoreProductRequest extends FormRequest
], ],
'caliber.*' => 'required|array', 'caliber.*' => 'required|array',
'caliber.*.value' => 'required|string|min:1|max:255', 'caliber.*.value' => 'required|string|min:1|max:255',
'nutritionnal_values' => 'required|array',
'nutritionnal_values' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (count($value) != 9) {
$fail(\trans('Vous devez remplir seulement 9 lignes de valeurs nutritionnelles.'));
}
}
],
'nutritionnal_values.*' => 'nullable|array',
'nutritionnal_values.*.label' => 'nullable|string|min:3|max:255',
'nutritionnal_values.*.value' => 'nullable|numeric|min:0',
'is_frenchpoultry' => 'required|boolean', 'is_frenchpoultry' => 'required|boolean',
'is_vip' => 'required|boolean', 'is_vip' => 'required|boolean',
'is_egalim' => 'required|boolean', 'is_egalim' => 'required|boolean',
'status' => 'required|boolean' 'published' => 'required|boolean',
// * TAGS
'tags' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) > 2) {
$fail(\trans('Vous devez associer au maximum 2 tags.'));
}
}
],
'tags.*' => 'required|array',
'tags.*.id' => 'required|numeric|exists:tags,id|distinct',
'tags.*.label' => 'required|string',
]; ];
return \array_merge( return \array_merge(
$rules, $rules,
$this->pictureRules('sticker_sm', true, 206, 159, 206, 159), $this->pictureRules('sticker_sm', 206, 159, 206, 159, false),
$this->picturesRules('slider_pictures', true, 396, 286, 396, 286, 1, 5), $this->picturesRules('slider_pictures', 396, 286, 396, 286, 1, 5, true),
); );
} }
@ -111,29 +141,30 @@ class StoreProductRequest extends FormRequest
public function attributes(): array public function attributes(): array
{ {
return [ return [
'label' => trans('title'), 'label' => trans('title'),
'slug' => trans('slug'), 'slug' => trans('slug'),
'seo_title' => trans('title') . ' seo', 'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo', 'seo_description' => trans('description') . ' seo',
'subrange_id' => trans('sous-gamme'), 'subrange_id' => trans('sous-gamme'),
'species' => trans('Espèce du produit'), 'species' => trans('Espèce du produit'),
'nutriscore' => trans('Label Nutriscore'), 'nutriscore' => trans('Label Nutriscore'),
'sticker_sm' => trans('miniature'), 'sticker_sm' => trans('miniature'),
'sticker_sm_alt' => trans('attribut alt de la miniature'), 'sticker_sm_alt' => trans('attribut alt de la miniature'),
'sticker_sm_title' => trans('titre de la miniature'), 'sticker_sm_title' => trans('titre de la miniature'),
'slider_pictures' => trans('images du slider'), 'slider_pictures' => trans('images du slider'),
'slider_pictures.*' => trans('image du slider'), 'slider_pictures.*' => trans('image du slider'),
'packaging' => trans('conditionnement'), 'packaging' => trans('conditionnement'),
'packaging.*.value' => trans('valeur du conditionnement'), 'packaging.*.value' => trans('valeur du conditionnement'),
'caliber' => trans('calibre'), 'caliber' => trans('calibre'),
'caliber.*.value' => trans('valeur du calibre'), 'caliber.*.value' => trans('valeur du calibre'),
'nutritionnal_values' => trans('valeurs nutritionnelles'), 'is_frenchpoultry' => trans('Label volaille française'),
'nutritionnal_values.*.label' => trans('libellé de la valeur nutritionnelle'), 'is_vip' => trans('Produit VIP'),
'nutritionnal_values.*.value' => trans('valeur de la valeur nutritionnelle'), 'is_egalim' => trans('Produit Égalim'),
'is_frenchpoultry' => trans('Label volaille française'), 'published' => trans('publish'),
'is_vip' => trans('Produit VIP'), 'tags' => trans('tag'),
'is_egalim' => trans('Produit Égalim'), 'tags.*' => trans('tag'),
'status' => trans('publish') 'tags.*.id' => trans('id du tag'),
'tags.*.label' => trans('label du tag'),
]; ];
} }
} }

View file

@ -53,8 +53,8 @@ class UpdateRangeRequest extends FormRequest
]; ];
return \array_merge( return \array_merge(
$rules, $rules,
$this->pictureRules('picture', true, 290, 232, 290, 232), $this->pictureRules('picture', 290, 232, 290, 232, true),
$this->pictureRules('picture_banner', true, 1440, 300, 1440, 300), $this->pictureRules('picture_banner', 1440, 300, 1440, 300, true),
); );
} }

View file

@ -2,6 +2,7 @@
namespace App\Http\Requests\Bo\Slides; namespace App\Http\Requests\Bo\Slides;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture; use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@ -27,8 +28,9 @@ class StoreSlideRequest extends FormRequest
protected function prepareForValidation(): void protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'button_display' => $this->button_display ? true : false, 'button_display' => $this->boolean('button_display'),
'button_targetblank' => $this->button_targetblank ? true : false, 'button_targetblank' => $this->boolean('button_targetblank'),
'published' => $this->boolean('published'),
]); ]);
$this->mergePicture('picture'); $this->mergePicture('picture');
$this->mergePicture('picture_thumbnail'); $this->mergePicture('picture_thumbnail');
@ -43,7 +45,11 @@ class StoreSlideRequest extends FormRequest
{ {
$rules = [ $rules = [
'title' => 'required|min:3|max:255', 'title' => 'required|min:3|max:255',
'content' => 'required|string|min:15|max:540', 'content' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'button_display' => 'required|boolean', 'button_display' => 'required|boolean',
'button_text' => 'nullable|min:3|max:45', 'button_text' => 'nullable|min:3|max:45',
'button_url' => 'nullable|min:3|max:255|url', 'button_url' => 'nullable|min:3|max:255|url',
@ -51,12 +57,13 @@ class StoreSlideRequest extends FormRequest
'picture_alt' => 'required|min:3|max:255', 'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255', 'picture_title' => 'required|min:3|max:255',
'picture_thumbnail_alt' => 'required|min:3|max:255', 'picture_thumbnail_alt' => 'required|min:3|max:255',
'picture_thumbnail_title' => 'required|min:3|max:255' 'picture_thumbnail_title' => 'required|min:3|max:255',
'published' => 'required|boolean',
]; ];
return \array_merge( return \array_merge(
$rules, $rules,
$this->pictureRules('picture', true, 950, 700, 950, 700), $this->pictureRules('picture', 950, 700, 950, 700, true),
$this->pictureRules('picture_thumbnail', true, 167, 70, 167, 70) $this->pictureRules('picture_thumbnail', 167, 70, 167, 70, true)
); );
} }

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\Bo\StaticPages;
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' => Str::of($this->post('content'))->value()
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'title' => 'required|min:3|max:255',
'content' => 'present',
];
}
}

View file

@ -2,8 +2,10 @@
namespace App\Http\Requests\Bo\Tags; namespace App\Http\Requests\Bo\Tags;
use App\Enums\TagTypeEnum;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class StoreTagRequest extends FormRequest class StoreTagRequest extends FormRequest
{ {
@ -38,6 +40,10 @@ class StoreTagRequest extends FormRequest
{ {
return [ return [
'label' => 'required|min:2|max:30', 'label' => 'required|min:2|max:30',
'type' => [
'required',
Rule::enum(TagTypeEnum::class),
]
]; ];
} }
@ -51,6 +57,7 @@ class StoreTagRequest extends FormRequest
return [ return [
'label' => trans('libellé'), 'label' => trans('libellé'),
'slug' => trans('slug'), 'slug' => trans('slug'),
'type' => trans('type'),
]; ];
} }
} }

View file

@ -0,0 +1,68 @@
<?php
namespace App\Http\Requests\Bo;
use App\Interfaces\OrderableInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Http\FormRequest;
class UpdateOrderRequest extends FormRequest
{
/**
* Attribute model needed.
*
* @var Model $orderable
*/
private Model $orderable;
/**
* 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
*/
protected function prepareForValidation(): void
{
$this->orderable = collect($this->route()->parameters())
->first(fn($param) => $param instanceof Model);
$groupField = $this->orderable->getOrderGroupField();
if ($groupField) {
$this->orderable::query()->where($groupField, $this->orderable->{$groupField});
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'priority_order' => [
'required',
'integer',
'min:1',
sprintf('max:%s', $this->orderable->max('priority_order')),
function ($value, $fail) {
// phpcs:ignore Generic.Files.LineLength.TooLong
if ($this->orderable instanceof OrderableInterface && intval($value) === $this->orderable->getPriorityOrder()) {
$fail(trans('validation.custom.same_priority'));
}
},
],
];
}
}

View file

@ -20,6 +20,18 @@ class ContactRequest extends FormRequest
return true; return true;
} }
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'opt_in' => $this->boolean('opt_in'),
]);
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
@ -36,11 +48,12 @@ class ContactRequest extends FormRequest
'phone' => 'required|max:255|phone:' . implode(',', $this->isoCodes()), 'phone' => 'required|max:255|phone:' . implode(',', $this->isoCodes()),
'activity' => ['required', new Enum(ContactActivityEnum::class)], 'activity' => ['required', new Enum(ContactActivityEnum::class)],
'society' => 'nullable|string|max:255', 'society' => 'nullable|string|max:255',
'postcode' => 'nullable|postal_code:' . implode(',', $this->isoCodes()), 'postcode' => 'required|postal_code:' . implode(',', $this->isoCodes()),
'city' => 'nullable|string|max:255', 'city' => 'nullable|string|max:255',
'message' => 'required|string|min:20|max:255', 'message' => 'required|string|min:20|max:255',
'callTime' => ['required', new Enum(ContactTimeEnum::class)], 'callTime' => ['required', new Enum(ContactTimeEnum::class)],
'rgpd' => 'accepted' 'brigade' => 'nullable|boolean',
'opt_in' => 'required|boolean',
]; ];
} }
@ -81,7 +94,8 @@ class ContactRequest extends FormRequest
'city' => trans('ville'), 'city' => trans('ville'),
'message' => trans('message'), 'message' => trans('message'),
'callTime' => trans('préférence de rappel'), 'callTime' => trans('préférence de rappel'),
'rgpd' => trans('condition d\'utilisation'), 'brigade' => trans('La Brigade'),
'opt_in' => trans('newsletter'),
]; ];
} }

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Fo\Newsletter;
use Illuminate\Foundation\Http\FormRequest;
class SingleOptinRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email:rfc,dns,spoof',
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Fo\Newsletter;
use Illuminate\Foundation\Http\FormRequest;
class UnsubscribeNewsletterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'hashcode' => 'required|uuid'
];
}
}

View file

@ -24,7 +24,7 @@ class AjaxAddProductBasket extends FormRequest
public function rules(): array public function rules(): array
{ {
return [ return [
'packaging' => 'required|array|min:1', 'packaging' => 'array',
'packaging.*' => [ 'packaging.*' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
@ -37,7 +37,7 @@ class AjaxAddProductBasket extends FormRequest
} }
} }
], ],
'caliber' => 'required|array|min:1', 'caliber' => 'array',
'caliber.*' => [ 'caliber.*' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
@ -56,7 +56,7 @@ class AjaxAddProductBasket extends FormRequest
/** /**
* Get custom attributes for validator errors. * Get custom attributes for validator errors.
* *
* @return array * @return array<string,string>
*/ */
public function attributes(): array public function attributes(): array
{ {

View file

@ -28,8 +28,8 @@ class AjaxFilterProductsRequest extends FormRequest
{ {
$this->merge([ $this->merge([
'vip' => $this->vip ? true : false, 'vip' => $this->vip ? true : false,
'subranges' => isset($this->subranges) ? explode(',', $this->subranges) : 'subranges' => isset($this->subranges) ?
(\is_array($this->subranges) ? $this->subranges : []) explode(',', $this->subranges) : (\is_array($this->subranges) ? $this->subranges : [])
]); ]);
} }
@ -47,7 +47,7 @@ class AjaxFilterProductsRequest extends FormRequest
$rules['subranges'] = 'present|array'; $rules['subranges'] = 'present|array';
$rules['subranges.*'] = [ $rules['subranges.*'] = [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'exists:subranges,id', function ($attribute, $value, $fail) { 'exists:subranges,id', function (string $attribute, $value, callable $fail) {
if (empty($this->range) or !\is_object($this->range) or \get_class($this->range) !== Range::class) { if (empty($this->range) or !\is_object($this->range) or \get_class($this->range) !== Range::class) {
$fail(trans('La gamme est obligatoire', ['attribute' => $attribute])); $fail(trans('La gamme est obligatoire', ['attribute' => $attribute]));
return; return;
@ -59,7 +59,7 @@ class AjaxFilterProductsRequest extends FormRequest
$hasSubrange = Range::query()->whereHas('subranges', function (Builder $q) use ($subrange) { $hasSubrange = Range::query()->whereHas('subranges', function (Builder $q) use ($subrange) {
$q->where('id', $subrange->id); $q->where('id', $subrange->id);
})->where('id', $this->range->id)->exists(); })->where('id', $this->range->id)->exists();
if (empty($value) or !$hasSubrange) { if (!$hasSubrange) {
$fail(trans(':attribute doit appartenir à la bonne gamme', ['attribute' => $attribute])); $fail(trans(':attribute doit appartenir à la bonne gamme', ['attribute' => $attribute]));
return; return;
} }

View file

@ -28,7 +28,6 @@ class ProductCommercialSheetResource extends JsonResource
'ingredients' => '', 'ingredients' => '',
'preparation' => '', 'preparation' => '',
'sticker_sm' => '', 'sticker_sm' => '',
'nutritionnal_values' => [],
'slider_pictures' => [], 'slider_pictures' => [],
'subrange' => null, 'subrange' => null,
]; ];
@ -65,7 +64,6 @@ class ProductCommercialSheetResource extends JsonResource
'caliber' => $this->product->caliber, 'caliber' => $this->product->caliber,
'ingredients' => $this->product->ingredients, 'ingredients' => $this->product->ingredients,
'preparation' => $this->product->preparation, 'preparation' => $this->product->preparation,
'nutritionnal_values' => $this->product->nutritionnal_values,
'sticker_sm' => $this->product->sticker_sm, 'sticker_sm' => $this->product->sticker_sm,
'slider_pictures' => $this->product->slider_pictures, 'slider_pictures' => $this->product->slider_pictures,
'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request), 'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request),

View file

@ -0,0 +1,21 @@
<?php
namespace App\Interfaces;
interface OrderableInterface
{
/**
* Returns the priority order value.
*
* @return integer
*/
public function getPriorityOrder(): int;
/**
* Returns the grouping field used for ordering.
* Returns null if the order is global.
*
* @return string|null
*/
public function getOrderGroupField(): ?string;
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Lib;
use App\Enums\Advices\AdviceTypeEnum;
use App\Models\Advice;
use App\Models\Product;
use LDC\SitemapGenerator\Lib\Sitemap;
class CompleteSitemapWith
{
/**
* Use this method to append a list of links to the sitemap
*
* * Please call $sitemap->mergeUrls to append url for any specific route name.
*
* @param \LDC\SitemapGenerator\Lib\Sitemap $sitemap
* @return void
*/
public static function append(Sitemap $sitemap): void
{
// * Ajouts des éléments qui sont affichés en random
$sitemap->mergeUrls(Product::query()->where('status', true)->pluck('slug')->transform(function (string $slug) {
return \route('fo.product.show', $slug);
}));
$sitemap->mergeUrls(Advice::query()
->where('status', true)->where('type', AdviceTypeEnum::recipes)
->pluck('slug')->transform(function (string $slug) {
return \route('fo.recipes.show', $slug);
}));
$sitemap->mergeUrls(Advice::query()
->where('status', true)->where('type', AdviceTypeEnum::tips)
->pluck('slug')->transform(function (string $slug) {
return \route('fo.tips.show', $slug);
}));
}
}

View file

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

View file

@ -30,6 +30,20 @@ use Illuminate\Support\Str;
*/ */
class FileStorageHelper class FileStorageHelper
{ {
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $publicStorageFilePath The file path from public directory.
* @return string The local storage path.
*/
public static function getFileFullPath(string $publicStorageFilePath): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
return $disk->path($path);
}
/** /**
* Destroy the file associate with the attribute. * Destroy the file associate with the attribute.
* *
@ -49,6 +63,20 @@ class FileStorageHelper
} }
} }
/**
* Destroy files associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param array $array
* @return void
*/
public static function removeFiles(Model $model, array $array): void
{
foreach ($array as $item) {
self::removeFile($model, $item);
}
}
/** /**
* Destroy the file associate with the attribute old value (before updated to DB). * Destroy the file associate with the attribute old value (before updated to DB).
* *
@ -91,7 +119,7 @@ class FileStorageHelper
Model $model, Model $model,
$file, $file,
bool $slugify = false, bool $slugify = false,
string $filename = null, string|null $filename = null,
bool $private = false bool $private = false
): string { ): string {
// phpcs:enable // phpcs:enable
@ -152,7 +180,7 @@ class FileStorageHelper
public static function storeModelFilesFromHtml( public static function storeModelFilesFromHtml(
Model $model, Model $model,
string $htmlString, string $htmlString,
string $oldHtmlString = null string|null $oldHtmlString = null
): string { ): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString))) \collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) { ->each(function (string $path) use ($model, &$htmlString) {
@ -308,8 +336,7 @@ class FileStorageHelper
$folderPath = self::getStoragePath( $folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles', $private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName, $tableName,
$filename, $filename
$private
); );
$filename = self::getFileUniqueName($folderPath, $filename); $filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}"; return "{$folderPath}/{$filename}";
@ -404,7 +431,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-'); $filename = Str::of($filename)->ltrim('.-');
// * Optional beautification // * Optional beautification
if ($beautify) { if ($beautify) {
$filename = static::beautifyFilename($filename); $filename = self::beautifyFilename($filename);
} }
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086 // * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */ /** @var string */

View file

@ -22,6 +22,36 @@ use Symfony\Component\Finder\SplFileInfo;
class ToolboxHelper class ToolboxHelper
{ {
/**
* Run a terminal command and gives the output
*
* @param array|string $command The command to execute on terminal.
* @param boolean $throw Wether to throw en esception on failure or not.
* @return string|false Return false on failure only if $throw is false.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @throws \Illuminate\Process\Exceptions\ProcessTimedOutException
* @throws \Symfony\Component\Process\Exception\LogicException
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException
* @throws \Symfony\Component\Process\Exception\RuntimeException
* @throws \LogicException
* @throws \RuntimeException If the command fails.
*/
public static function runConsoleCommand(array|string $command, bool $throw = true): string | false
{
// phpcs:enable
$process = \Illuminate\Support\Facades\Process::command($command)->run();
if ($process->failed()) {
$code = $process->exitCode();
$ouput = $process->errorOutput();
if ($throw) {
throw new \RuntimeException("Failed to run command exit code {$code} with output \n{$ouput}");
}
return false;
}
return $process->output();
}
/** /**
* @param array<int|string, mixed> $array1 * @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2 * @param array<int|string, mixed> $array2
@ -76,7 +106,7 @@ class ToolboxHelper
throw new \RuntimeException(); throw new \RuntimeException();
} }
if (\floatval($response->json('score')) < 0.8) { if (\floatval($response->json('score')) < 0.8) {
$fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha"); $fail(trans('recaptcha.not_enough_score'));
} }
return; return;
} }
@ -89,7 +119,7 @@ class ToolboxHelper
throw new \RuntimeException(); throw new \RuntimeException();
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$fail('Erreur lors de la validation du formulaire veuillez réssayer'); $fail(trans('recaptcha.error'));
} //end try } //end try
} }
] ]
@ -176,11 +206,10 @@ class ToolboxHelper
$url = $match; $url = $match;
try { try {
$request = Request::create($url); $request = Request::create($url);
// If a route is match then it do not throws exception. // Assert route matches existing routes.
if ( $routes->match($request);
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) && // ? Is url internal ?
$routes->match($request) if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
) {
// Removes the matched url. // Removes the matched url.
throw ValidationException::withMessages([ throw ValidationException::withMessages([
$field => trans( $field => trans(
@ -206,14 +235,14 @@ class ToolboxHelper
* @param array|string $replace * @param array|string $replace
* @param array|string $subject * @param array|string $subject
* @param integer $count * @param integer $count
* @return array|string * @return array|string|false
*/ */
public static function mbReplace( public static function mbReplace(
array|string $search, array|string $search,
array|string $replace, array|string $replace,
array|string $subject, array|string $subject,
int &$count = 0 int &$count = 0
): array|string { ): array|string|false {
if (!is_array($search) && is_array($replace)) { if (!is_array($search) && is_array($replace)) {
return false; return false;
} }
@ -289,10 +318,17 @@ class ToolboxHelper
*/ */
public static function getOriginal(Model $model, string $attribute): mixed public static function getOriginal(Model $model, string $attribute): mixed
{ {
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) { if (
\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model)) and
// @phpstan-ignore-next-line
$model->isTranslatableAttribute($attribute)
) {
// @phpstan-ignore-next-line
return $model->getTranslation( return $model->getTranslation(
$attribute, $attribute,
// @phpstan-ignore-next-line
$model->getLocale(), $model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale() $model->useFallbackLocale()
); );
} }
@ -362,47 +398,45 @@ class ToolboxHelper
/** /**
* Convert deeply an object to array * Convert deeply an object to array
* *
* @param array|object $input * @param mixed $input
* @return array * @return array
* @throws \RuntimeException If $input var is a scalar value. * @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. // Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) { 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 {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($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 * Convert deeply an array to object
* *
* @param array|object $input * @param mixed $input
* @return object * @return object
* @throws \RuntimeException If $input var is a scalar value. * @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. // Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) { 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 {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($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;
} }
/** /**
@ -410,11 +444,11 @@ class ToolboxHelper
* *
* @param string $attribute * @param string $attribute
* @param string|null $value * @param string|null $value
* @param callback $fail * @param callable $fail
* @param object $sizes * @param object $sizes
* @return void * @return void
*/ */
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes) public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
{ {
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) { if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [ $fail(trans(':attribute n\'existe pas dans le système de fichier', [
@ -422,7 +456,6 @@ class ToolboxHelper
])); ]));
return; return;
} }
/** @var string */
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage); $fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if ( if (
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [ !\in_array(self::getUploadedFileMimeType($value, $useStorage), [
@ -433,7 +466,7 @@ class ToolboxHelper
'attribute' => $attribute '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', [ $fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute 'attribute' => $attribute
])); ]));
@ -442,19 +475,19 @@ class ToolboxHelper
$height = intval($dimensions[1]); $height = intval($dimensions[1]);
$xMessage = trans( $xMessage = trans(
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px" ":attribute la largeur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
); );
if ($sizes->minWith === $sizes->maxWith) { if ($sizes->minWidth === $sizes->maxWidth) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px"); $xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
} }
$yMessage = trans( $yMessage = trans(
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px" ":attribute la hauteur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
); );
if ($sizes->minHeight === $sizes->maxHeight) { if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px"); $yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
} }
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) { if (($width < $sizes->minWidth) or ($width > $sizes->maxWidth)) {
$fail($xMessage); $fail($xMessage);
} }
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) { if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
@ -519,6 +552,26 @@ class ToolboxHelper
); );
} }
/**
* 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;
}
}
/** /**
* Get validated pagination from request. * Get validated pagination from request.
* *

View file

@ -30,7 +30,7 @@ class ContactFormMail extends Mailable
*/ */
public function build() public function build()
{ {
return $this->from(config('mail.contactsList.contactform')) return $this->from(config('mail.from.address'))
->subject(sprintf("%s : %s", config('app.name'), trans("Nouveau contact"))) ->subject(sprintf("%s : %s", config('app.name'), trans("Nouveau contact")))
->markdown('fo.emails.contact-form', $this->request->toArray()); ->markdown('fo.emails.contact-form', $this->request->toArray());
} }

View file

@ -2,48 +2,60 @@
namespace App\Models; namespace App\Models;
use App\Enums\Advices\AdviceTypeEnum; use App\Interfaces\OrderableInterface;
use App\Enums\Advices\AdviceDifficultyEnum; use App\Enums\Advices\AdviceDifficultyEnum;
use App\Enums\Advices\AdviceTypeEnum;
use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper; use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\Orderable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\Laravel\Redirections\Interfaces\RedirectableInterface;
use Uwil\Laravel\Redirections\Traits\IsRedirectable;
use Uwil\Seo\Laravel\Interfaces\SeoInterface; use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Traits\SeoTrait;
use Uwil\Seo\Laravel\Objects\SeoItem; use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
/** /**
* @property integer $id Id. * @property integer $id Id.
* @property string $title Title. * @property string $title Title.
* @property string $slug Slug of the title. * @property string $slug Slug of the title.
* @property string $seo_title SEO Title. * @property string $seo_title SEO Title.
* @property string $seo_description SEO description. * @property string $seo_description SEO description.
* @property \App\Enums\Advices\AdviceTypeEnum $type Type of the advice. * @property \App\Enums\Advices\AdviceTypeEnum $type Type of the advice.
* @property string $picture Path of a picture. * @property string $picture Path of a picture.
* @property string $picture_alt Picture's attribute alt. * @property string $picture_alt Picture's attribute alt.
* @property string $picture_title Picture's attribute title. * @property string $picture_title Picture's attribute title.
* @property string $sticker Path of a sticker. * @property string $sticker Path of a sticker.
* @property string $sticker_alt Sticker's attribute alt. * @property string $sticker_alt Sticker's attribute alt.
* @property string $sticker_title Sticker's attribute title. * @property string $sticker_title Sticker's attribute title.
* @property string $sticker_sm Path of a small sticker. * @property string $sticker_sm Path of a small sticker.
* @property string $sticker_sm_alt small sticker's attribute alt. * @property string $sticker_sm_alt small sticker's attribute alt.
* @property string $sticker_sm_title Small sticker's attribute title. * @property string $sticker_sm_title Small sticker's attribute title.
* @property string $preparation Preparation tips. * @property string $preparation Preparation tips.
* @property string $advice Advice. * @property string|null $advice Advice.
* @property \Illuminate\Support\Carbon $ingredients Ingredients. * @property array<array<string,string|integer>> $ingredients Ingredients.
* @property \Illuminate\Support\Carbon $guests_nb Number of guests. * @property integer|null $guests_nb Number of guests.
* @property \Illuminate\Support\Carbon $youtube_title Youtube title. * @property string|null $youtube_title Youtube title.
* @property \Illuminate\Support\Carbon $youtube_video_id Youtube video id. * @property string|null $youtube_video_id Youtube video id.
* @property \Illuminate\Support\Carbon $cooking_time Time of cooking. * @property integer|null $cooking_time Time of cooking.
* @property \Illuminate\Support\Carbon $preparation_time Time of preparation. * @property integer|null $preparation_time Time of preparation.
* @property \App\Enums\Advices\AdviceDifficultyEnum $difficulty Difficulty. * @property \App\Enums\Advices\AdviceDifficultyEnum|null $difficulty Difficulty.
* @property boolean $status Status. * @property boolean $published Status.
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property integer $priority_order Priority Order.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
* *
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $recipes
* HasMany Recipes.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $tips
* HasMany Tips.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $advices * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $advices
* Advices Belongs-To-Many relationship. * Advices Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products
@ -51,10 +63,12 @@ use Uwil\Seo\Laravel\Objects\SeoItem;
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tag> $tags * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tag> $tags
* Tags Morph-To-Many relationship. * Tags Morph-To-Many relationship.
*/ */
class Advice extends Authenticatable implements SeoInterface class Advice extends Authenticatable implements SeoInterface, Sitemapable, RedirectableInterface, OrderableInterface
{ {
use HasFactory; use HasFactory;
use SeoTrait; use SeoTrait;
use IsRedirectable;
use Orderable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -85,17 +99,18 @@ class Advice extends Authenticatable implements SeoInterface
'cooking_time', 'cooking_time',
'preparation_time', 'preparation_time',
'difficulty', 'difficulty',
'status' 'published',
'priority_order'
]; ];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'ingredients' => 'array', 'ingredients' => 'array',
'status' => 'bool', 'published' => 'bool',
'type' => AdviceTypeEnum::class, 'type' => AdviceTypeEnum::class,
'difficulty' => AdviceDifficultyEnum::class, 'difficulty' => AdviceDifficultyEnum::class,
]; ];
@ -109,6 +124,7 @@ class Advice extends Authenticatable implements SeoInterface
{ {
static::creating(function (self $model) { static::creating(function (self $model) {
self::manageFields($model); self::manageFields($model);
self::bootOrderable();
}); });
static::updating(function (self $model) { static::updating(function (self $model) {
self::manageFields($model); self::manageFields($model);
@ -123,6 +139,7 @@ class Advice extends Authenticatable implements SeoInterface
FileStorageHelper::removeFile($model, 'picture'); FileStorageHelper::removeFile($model, 'picture');
FileStorageHelper::removeFile($model, 'sticker'); FileStorageHelper::removeFile($model, 'sticker');
FileStorageHelper::removeFile($model, 'sticker_sm'); FileStorageHelper::removeFile($model, 'sticker_sm');
self::setOrder($model);
}); });
} }
@ -131,10 +148,10 @@ class Advice extends Authenticatable implements SeoInterface
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
self::assertTitleIsUnique($model->title, $model->id); self::assertTitleIsUnique($model->title, $model->id);
$model->slug = Str::slug($model->title); $model->slug = Str::slug($model->title);
@ -196,11 +213,11 @@ class Advice extends Authenticatable implements SeoInterface
/** /**
* Limit products associated. * Limit products associated.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
* @throws \Illuminate\Validation\ValidationException Error exception. * @throws \Illuminate\Validation\ValidationException Error exception.
*/ */
private static function assertLimitProducts(Model $model): void private static function assertLimitProducts(self $model): void
{ {
$nbProductsInBdd = $model->products()->count(); $nbProductsInBdd = $model->products()->count();
if ($model->type->value == AdviceTypeEnum::recipes->value()) { if ($model->type->value == AdviceTypeEnum::recipes->value()) {
@ -221,10 +238,10 @@ class Advice extends Authenticatable implements SeoInterface
/** /**
* Asserts using validation that the title is unique. * Asserts using validation that the title is unique.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function setCookingtimePreparationtimeValues(Model $model): void private static function setCookingtimePreparationtimeValues(self $model): void
{ {
if ($model->type->value != AdviceTypeEnum::recipes->value()) { if ($model->type->value != AdviceTypeEnum::recipes->value()) {
$model->cooking_time = null; $model->cooking_time = null;
@ -251,21 +268,59 @@ class Advice extends Authenticatable implements SeoInterface
* *
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> * @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice>
*/ */
public function recipes(): \Illuminate\Database\Eloquent\Collection public function recipesQuery(): \Illuminate\Database\Eloquent\Collection
{ {
return $this->advices()->where('type', AdviceTypeEnum::recipes)->get(); return $this->advices()->where('type', AdviceTypeEnum::recipes)->get();
} }
/**
* Get the advice recipes
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function recipes(): Attribute
{
return Attribute::make(
get: fn() => $this->recipesQuery(),
);
}
/** /**
* Get associated tips * Get associated tips
* *
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> * @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice>
*/ */
public function tips(): \Illuminate\Database\Eloquent\Collection public function tipsQuery(): \Illuminate\Database\Eloquent\Collection
{ {
return $this->advices()->where('type', AdviceTypeEnum::tips)->get(); return $this->advices()->where('type', AdviceTypeEnum::tips)->get();
} }
/**
* Get the advice tips
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function tips(): Attribute
{
return Attribute::make(
get: fn() => $this->tipsQuery(),
);
}
// * REDIRECTIONS.
/**
* Get the front url of this model.
* Should return the model.show route, with all binded elements if needed.
*
* @param string $modelKey
* @return string
*/
public function getFrontUrl(string $modelKey): string
{
return route('fo.tips.show', $modelKey);
}
// * RELATIONSHIPS // * RELATIONSHIPS
/** /**
@ -301,7 +356,7 @@ class Advice extends Authenticatable implements SeoInterface
/** /**
* Transfotrm to seoModel * Transfotrm to seoModel
* *
* @return \App\Objects\SeoItem * @return \Uwil\Seo\Laravel\Objects\SeoItem
*/ */
public function toSeoItem(): SeoItem public function toSeoItem(): SeoItem
{ {
@ -309,9 +364,33 @@ class Advice extends Authenticatable implements SeoInterface
\trans($this->getTable()), \trans($this->getTable()),
$this->seo_title, $this->seo_title,
$this->seo_description, $this->seo_description,
$this->status ?? false, $this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this), \route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
$this->type === AdviceTypeEnum::recipes ? route('fo.recipes.show', $this) : route('fo.tips.show', $this) $this->type === AdviceTypeEnum::recipes ? route('fo.recipes.show', $this) : route('fo.tips.show', $this)
); );
} }
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this;
$route = $this->type === AdviceTypeEnum::tips ? 'fo.tips.show' : 'fo.recipes.show';
$params = ['publishedAdvice' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
/**
* Group ordering by type.
*
* @return string
*/
public function getOrderGroupField(): string
{
return 'type';
}
} }

View file

@ -0,0 +1,124 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException;
use Uwil\Laravel\Common\Helpers\ToolboxHelper;
class BrigadeSection extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'title',
'content',
'picture',
'picture_alt',
'picture_title',
'button_display',
'button_text',
'button_url',
'button_newtab',
'published',
'order'
];
/**
* The attributes that should be cast.
*
* @var array<string,string>
*/
protected $casts = [
'button_display' => 'bool',
'button_newtab' => 'bool',
'published' => 'bool',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
self::setOrder($model);
self::manageFields($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::deleting(function (self $model) {
self::assertLimitPublishedSections($model, true);
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * METHODS
/**
* Common methods function.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
self::assertLimitPublishedSections($model);
ToolboxHelper::assertNoInternalBoUrls('content', $model->content);
ToolboxHelper::assertNoInternalBoUrls('button_url', $model->content);
$model->content = clean($model->content);
$model->picture = FileStorageHelper::storeFile($model, $model->picture);
}
/**
* Set order after the last element of the list.
*
* @param self $model
* @return void
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Check if the limit of published element is reached.
*
* @param self $model
* @param boolean $isDeletion
* @return void
* @throws \Illuminate\Validation\ValidationException Returns if fails.
*/
private static function assertLimitPublishedSections(self $model, bool $isDeletion = false)
{
$count = self::query()->where('published', true)->count();
if (
(!$model->published && $count <= 1) ||
($model->published && $count >= 5) ||
($isDeletion && $count === 1)
) {
throw ValidationException::withMessages([
'Il doit y avoir entre 1 et 5 sections publiées simultanément.'
]);
}
}
}

View file

@ -30,7 +30,7 @@ class ExpertiseProduct extends Model
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'page', 'page',
@ -46,7 +46,7 @@ class ExpertiseProduct extends Model
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'points' => 'array', 'points' => 'array',
@ -84,10 +84,10 @@ class ExpertiseProduct extends Model
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
self::assertFieldIsUnique('label', $model->label, $model->id); self::assertFieldIsUnique('label', $model->label, $model->id);
self::assertFieldIsUnique('title', $model->title, $model->id); self::assertFieldIsUnique('title', $model->title, $model->id);

View file

@ -2,15 +2,22 @@
namespace App\Models; namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException; use Spatie\Sitemap\Tags\Url;
use Uwil\Seo\Laravel\Interfaces\SeoInterface; use Spatie\CalendarLinks\Link;
use App\Lib\Helpers\ToolboxHelper;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait; use Uwil\Seo\Laravel\Traits\SeoTrait;
use App\Lib\Helpers\FileStorageHelper;
use Spatie\Sitemap\Contracts\Sitemapable;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Uwil\Laravel\Redirections\Traits\IsRedirectable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Uwil\Laravel\Redirections\Interfaces\RedirectableInterface;
/** /**
*/ */
@ -24,19 +31,21 @@ use Uwil\Seo\Laravel\Traits\SeoTrait;
* @property string $picture Path of a picture. * @property string $picture Path of a picture.
* @property string $picture_alt Picture's attribute alt. * @property string $picture_alt Picture's attribute alt.
* @property string $picture_title Picture's attribute title. * @property string $picture_title Picture's attribute title.
* @property string $picture2 Path of a second picture. * @property string|null $picture2 Path of a second picture.
* @property string $picture2_alt Second picture's attribute alt. * @property string|null $picture2_alt Second picture's attribute alt.
* @property string $picture2_title Second picture's attribute title. * @property string|null $picture2_title Second picture's attribute title.
* @property string $content First WYSWYG content. * @property string $content First WYSWYG content.
* @property string $content2 Second WYSWYG content. * @property string|null $content2 Second WYSWYG content.
* @property \Illuminate\Support\Carbon $event_start Start of the event. * @property \Illuminate\Support\Carbon|null $event_start Start of the event.
* @property \Illuminate\Support\Carbon $event_end End of the event. * @property \Illuminate\Support\Carbon|null $event_end End of the event.
* @property string $event_text Text of the event. * @property string|null $event_text Text of the event.
* @property boolean $status Status. * @property boolean $published Status.
* @property-read string $reading_time Time reading of a news' page * @property-read string $reading_time Time reading of a news' page
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date. * @property-read \Illuminate\Support\Carbon $updated_at Updated date.
* *
* @property-read \Spatie\CalendarLinks\Link|null $cal_link
*
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news
* News Belongs-To-Many relationship. * News Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products
@ -44,15 +53,16 @@ use Uwil\Seo\Laravel\Traits\SeoTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tags> $categories * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tags> $categories
* Categories Morph-To-Many relationship. * Categories Morph-To-Many relationship.
*/ */
class News extends Authenticatable implements SeoInterface class News extends Authenticatable implements SeoInterface, Sitemapable, RedirectableInterface
{ {
use HasFactory; use HasFactory;
use SeoTrait; use SeoTrait;
use IsRedirectable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array<int, string> * @var array<int,string>
*/ */
protected $fillable = [ protected $fillable = [
'title', 'title',
@ -71,18 +81,18 @@ class News extends Authenticatable implements SeoInterface
'event_start', 'event_start',
'event_end', 'event_end',
'event_text', 'event_text',
'status', 'published',
]; ];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'event_start' => 'datetime', 'event_start' => 'datetime',
'event_end' => 'datetime', 'event_end' => 'datetime',
'status' => 'bool', 'published' => 'bool',
]; ];
/** /**
@ -111,15 +121,36 @@ class News extends Authenticatable implements SeoInterface
}); });
} }
// * ATTRIBUTES
/**
* Get cal_link attribute
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function calLink(): Attribute
{
return Attribute::make(
get: fn() => (
$this->exists() and
($this->event_start && $this->event_end && $this->event_text)
) ? Link::create(
$this->title,
$this->event_start,
$this->event_end
)->description($this->event_text)->ics() : null
);
}
// * METHODS // * METHODS
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
self::assertTitleIsUnique($model->title, $model->id); self::assertTitleIsUnique($model->title, $model->id);
$model->slug = Str::slug($model->title); $model->slug = Str::slug($model->title);
@ -152,6 +183,7 @@ class News extends Authenticatable implements SeoInterface
/** /**
* Limit news associated. * Limit news associated.
*
* @param integer $nbNewsInBdd * @param integer $nbNewsInBdd
* @return void * @return void
* @throws \Illuminate\Validation\ValidationException Error exception. * @throws \Illuminate\Validation\ValidationException Error exception.
@ -167,6 +199,7 @@ class News extends Authenticatable implements SeoInterface
/** /**
* Limit news categories associated. * Limit news categories associated.
*
* @param integer $nbNewsCategoriesInBdd * @param integer $nbNewsCategoriesInBdd
* @return void * @return void
* @throws \Illuminate\Validation\ValidationException Error exception. * @throws \Illuminate\Validation\ValidationException Error exception.
@ -216,15 +249,31 @@ class News extends Authenticatable implements SeoInterface
return ($time < 1) ? round(($allWords / 140) * 60, 0) . " seconde(s)" : $time . " minute(s)"; return ($time < 1) ? round(($allWords / 140) * 60, 0) . " seconde(s)" : $time . " minute(s)";
} }
// * REDIRECTIONS.
/**
* Get the front url of this model.
* Should return the model.show route, with all binded elements if needed.
*
* @param string $modelKey
* @return string
*/
public function getFrontUrl(string $modelKey): string
{
return route('fo.news.show', $modelKey);
}
// * RELATIONSHIPS // * RELATIONSHIPS
/** /**
* News Morph-To-Many relationship. * News Morph-To-Many relationship.
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
* @phpcs:disable Generic.NamingConventions.ConstructorName.OldStyle
*/ */
public function news(): \Illuminate\Database\Eloquent\Relations\BelongsToMany public function news(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{ {
// phpcs:enable
return $this->belongsToMany(News::class, 'news_news', 'news_id', 'news_related_id'); return $this->belongsToMany(News::class, 'news_news', 'news_id', 'news_related_id');
} }
@ -247,4 +296,34 @@ class News extends Authenticatable implements SeoInterface
{ {
return $this->belongsToMany(NewsCategory::class); return $this->belongsToMany(NewsCategory::class);
} }
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this;
$route = 'fo.news.show';
$params = ['actu' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans($this->getTable()),
$this->seo_title,
$this->seo_description,
$this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
route('fo.news.show', $this)
);
}
} }

View file

@ -24,7 +24,7 @@ class NewsCategory extends Authenticatable
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array<int, string> * @var array<int,string>
*/ */
protected $fillable = [ protected $fillable = [
'label', 'label',

View file

@ -2,16 +2,24 @@
namespace App\Models; namespace App\Models;
use App\Interfaces\OrderableInterface;
use App\Enums\Advices\AdviceTypeEnum; use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\NutriscoreEnum; use App\Enums\NutriscoreEnum;
use App\Enums\Products\ProductSpeciesEnum; use App\Enums\Products\ProductSpeciesEnum;
use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper; use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\Orderable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\Laravel\Redirections\Interfaces\RedirectableInterface;
use Uwil\Laravel\Redirections\Traits\IsRedirectable;
use Uwil\Seo\Laravel\Interfaces\SeoInterface; use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait; use Uwil\Seo\Laravel\Traits\SeoTrait;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
/** /**
* @property integer $id Id. * @property integer $id Id.
@ -25,17 +33,18 @@ use Uwil\Seo\Laravel\Traits\SeoTrait;
* @property string $sticker_sm Path of a small sticker. * @property string $sticker_sm Path of a small sticker.
* @property string $sticker_sm_alt small sticker's attribute alt. * @property string $sticker_sm_alt small sticker's attribute alt.
* @property string $sticker_sm_title Small sticker's attribute title. * @property string $sticker_sm_title Small sticker's attribute title.
* @property string $slider_pictures Array of paths of pictures. * @property array<string> $slider_pictures Array of paths of pictures.
* @property string $packaging Packaging. * @property array<array<string,string>> $packaging Packaging.
* @property \Illuminate\Support\Carbon $caliber Caliber. * @property array<array<string,string>> $caliber Caliber.
* @property \Illuminate\Support\Carbon $nutritionnal_values Nutritionnal values. * @property string $ingredients Ingredients.
* @property \Illuminate\Support\Carbon $ingredients Ingredients. * @property string $advantage Advantage.
* @property \Illuminate\Support\Carbon $advantage Advantage. * @property string $preparation Preparation.
* @property \Illuminate\Support\Carbon $preparation Preparation. * @property boolean $is_frenchpoultry Is frenchpoultry or not.
* @property \Illuminate\Support\Carbon $is_frenchpoultry Is frenchpoultry or not. * @property boolean $is_vip Is vip or not.
* @property \App\Enums\Advices\AdviceDifficultyEnum $is_vip Is vip or not. * @property boolean $is_egalim Is egalim or not.
* @property \App\Enums\Advices\AdviceDifficultyEnum $is_egalim Is egalim or not. * @property boolean $published Status.
* @property boolean $status Status. * @property integer $order Order.
* @property integer $priority_order Priority Order.
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date. * @property-read \Illuminate\Support\Carbon $updated_at Updated date.
* *
@ -45,15 +54,17 @@ use Uwil\Seo\Laravel\Traits\SeoTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news
* News Belongs-To-Many relation. * News Belongs-To-Many relation.
*/ */
class Product extends Model implements SeoInterface class Product extends Model implements SeoInterface, Sitemapable, RedirectableInterface, OrderableInterface
{ {
use HasFactory; use HasFactory;
use SeoTrait; use SeoTrait;
use IsRedirectable;
use Orderable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'label', 'label',
@ -72,29 +83,28 @@ class Product extends Model implements SeoInterface
'slider_pictures', 'slider_pictures',
'packaging', 'packaging',
'caliber', 'caliber',
'nutritionnal_values',
'is_frenchpoultry', 'is_frenchpoultry',
'is_vip', 'is_vip',
'is_egalim', 'is_egalim',
'status' 'published',
'priority_order',
]; ];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'slider_pictures' => 'array', 'slider_pictures' => 'array',
'packaging' => 'array', 'packaging' => 'array',
'caliber' => 'array', 'caliber' => 'array',
'nutritionnal_values' => 'array', 'is_frenchpoultry' => 'bool',
'is_frenchpoultry' => 'bool', 'is_vip' => 'bool',
'is_vip' => 'bool', 'is_egalim' => 'bool',
'is_egalim' => 'bool', 'published' => 'bool',
'status' => 'bool', 'species' => ProductSpeciesEnum::class,
'species' => ProductSpeciesEnum::class, 'nutriscore' => NutriscoreEnum::class
'nutriscore' => NutriscoreEnum::class
]; ];
/** /**
@ -107,16 +117,25 @@ class Product extends Model implements SeoInterface
// * EVENT CLOSURES. // * EVENT CLOSURES.
static::creating(function (self $model) { static::creating(function (self $model) {
self::manageFields($model); self::manageFields($model);
$model->order = \intval(self::query()->max('order')) + 1; self::bootOrderable();
}); });
static::updating(function (self $model) { static::updating(function (self $model) {
self::manageFields($model); self::manageFields($model);
}); });
static::updated(function (self $model) { static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'sticker_sm'); FileStorageHelper::removeOldFile($model, 'sticker_sm');
FileStorageHelper::removeOldFiles(
!is_array($model->getOriginal('slider_pictures')) ? [] : $model->getOriginal('slider_pictures'),
$model->slider_pictures
);
}); });
static::deleted(function (self $model) { static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'sticker_sm'); FileStorageHelper::removeFile($model, 'sticker_sm');
FileStorageHelper::removeFiles(
$model,
!is_array($model->getOriginal('slider_pictures')) ? [] : $model->getOriginal('slider_pictures'),
);
self::setOrder($model);
}); });
} }
@ -125,13 +144,13 @@ class Product extends Model implements SeoInterface
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
// * ASSERTIONS // * ASSERTIONS
static::assertLabelIsUnique($model->label, $model->id); self::assertLabelIsUnique($model->label, $model->id);
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients); ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
ToolboxHelper::assertNoInternalBoUrls('advantage', $model->advantage); ToolboxHelper::assertNoInternalBoUrls('advantage', $model->advantage);
ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation); ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation);
@ -141,22 +160,16 @@ class Product extends Model implements SeoInterface
foreach ($model->caliber as $calib) { foreach ($model->caliber as $calib) {
ToolboxHelper::assertNoInternalBoUrls('caliber', $calib['value']); ToolboxHelper::assertNoInternalBoUrls('caliber', $calib['value']);
} }
foreach ($model->nutritionnal_values as $nutritionnal_value) {
if ($model->nutritionnal_value) {
ToolboxHelper::assertNoInternalBoUrls('nutritionnal_values', $nutritionnal_value['value']);
}
}
// * TRANSFORMATIONS // * TRANSFORMATIONS
$model->slug = Str::slug($model->label); $model->slug = Str::slug($model->label);
$model->sticker_sm = FileStorageHelper::storeFile($model, $model->sticker_sm, true); $model->sticker_sm = FileStorageHelper::storeFile($model, $model->sticker_sm, true);
$model->ingredients = clean($model->ingredients); $model->ingredients = clean($model->ingredients);
$model->advantage = clean($model->advantage); $model->advantage = clean($model->advantage);
$model->preparation = clean($model->preparation); $model->preparation = clean($model->preparation);
$model->slider_pictures = clean($model->slider_pictures); $model->slider_pictures = clean($model->slider_pictures);
$model->packaging = clean($model->packaging); $model->packaging = clean($model->packaging);
$model->caliber = clean($model->caliber); $model->caliber = clean($model->caliber);
$model->nutritionnal_values = clean($model->nutritionnal_values);
} }
/** /**
@ -196,31 +209,47 @@ class Product extends Model implements SeoInterface
/** /**
* Get associated tips. * Get associated tips.
* *
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> * @return \Illuminate\Database\Eloquent\Collection<int,\App\Models\Advice>
*/ */
public function tips(): \Illuminate\Database\Eloquent\Collection public function tips(): \Illuminate\Database\Eloquent\Collection
{ {
// @phpstan-ignore-next-line
return $this->tipsQuery()->get(); return $this->tipsQuery()->get();
} }
/** /**
* Get associated recipes. * Get associated recipes.
* *
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> * @return \Illuminate\Database\Eloquent\Collection<int,\App\Models\Advice>
*/ */
public function recipes(): \Illuminate\Database\Eloquent\Collection public function recipes(): \Illuminate\Database\Eloquent\Collection
{ {
// @phpstan-ignore-next-line
return $this->recipesQuery()->get(); return $this->recipesQuery()->get();
} }
// * REDIRECTIONS.
/**
* Get the front url of this model.
* Should return the model.show route, with all binded elements if needed.
*
* @param string $modelKey
* @return string
*/
public function getFrontUrl(string $modelKey): string
{
return route('fo.product.show', $modelKey);
}
// * RELATIONSHIPS // * RELATIONSHIPS
/** /**
* Subrange Has-One relation. * Subrange Has-One relation.
* *
* @return \Illuminate\Database\Eloquent\Relations\belongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function subrange(): \Illuminate\Database\Eloquent\Relations\belongsTo public function subrange(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{ {
return $this->belongsTo(Subrange::class); return $this->belongsTo(Subrange::class);
} }
@ -244,4 +273,44 @@ class Product extends Model implements SeoInterface
{ {
return $this->belongsToMany(News::class); return $this->belongsToMany(News::class);
} }
/**
* Tags Morph-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function tags(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this;
$route = 'fo.product.show';
$params = ['publishedProduct' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
/**
* Transform to seoModel
*
* @return \Uwil\Seo\Laravel\Objects\SeoItem
*/
public function toSeoItem(): SeoItem
{
return new SeoItem(
\trans($this->getTable()),
$this->seo_title,
$this->seo_description,
$this->published ?? false,
\route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this),
route('fo.product.show', $this)
);
}
} }

View file

@ -11,6 +11,11 @@ use Illuminate\Support\Str;
use Uwil\Seo\Laravel\Interfaces\SeoInterface; use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Seo\Laravel\Objects\SeoItem; use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait; use Uwil\Seo\Laravel\Traits\SeoTrait;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Uwil\Laravel\Redirections\Interfaces\RedirectableInterface;
use Uwil\Laravel\Redirections\Traits\IsRedirectable;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
/** /**
* @property integer $id Id. * @property integer $id Id.
@ -32,15 +37,16 @@ use Uwil\Seo\Laravel\Traits\SeoTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Subrange> $subranges * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Subrange> $subranges
* Subranges Has-Many relationship. * Subranges Has-Many relationship.
*/ */
class Range extends Model implements SeoInterface class Range extends Model implements SeoInterface, Sitemapable, RedirectableInterface
{ {
use HasFactory; use HasFactory;
use SeoTrait; use SeoTrait;
use IsRedirectable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'label', 'label',
@ -60,7 +66,7 @@ class Range extends Model implements SeoInterface
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'color' => RgbaColor::class, 'color' => RgbaColor::class,
@ -77,14 +83,14 @@ class Range extends Model implements SeoInterface
throw new \RuntimeException('Impossible de créer un range'); throw new \RuntimeException('Impossible de créer un range');
}); });
static::updating(function (self $model) { static::updating(function (self $model) {
static::assertLabelIsUnique($model->label, $model->id); self::assertLabelIsUnique($model->label, $model->id);
$model->slug = Str::slug($model->label); $model->slug = Str::slug($model->label);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
$model->picture_banner = FileStorageHelper::storeFile($model, $model->picture_banner, true); $model->picture_banner = FileStorageHelper::storeFile($model, $model->picture_banner, true);
}); });
static::updated(function (self $model) { static::updated(function (self $model) {
$model->picture = FileStorageHelper::removeOldFile($model, 'picture'); FileStorageHelper::removeOldFile($model, 'picture');
$model->picture_banner = FileStorageHelper::removeOldFile($model, 'picture_banner'); FileStorageHelper::removeOldFile($model, 'picture_banner');
}); });
static::deleting(function () { static::deleting(function () {
throw new \RuntimeException('Impossible de supprimer un range'); throw new \RuntimeException('Impossible de supprimer un range');
@ -108,14 +114,38 @@ class Range extends Model implements SeoInterface
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($label), $id); ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($label), $id);
} }
// * REDIRECTIONS.
/**
* All models that belongs to it.
*
* @return array<string>
*/
protected function getRouteChildrenModels(): array
{
return ['App\Models\Subrange'];
}
/**
* Get the front url of this model.
* Should return the model.show route, with all binded elements if needed.
*
* @param string $modelKey
* @return string
*/
public function getFrontUrl(string $modelKey): string
{
return route('fo.products.range-products', $modelKey);
}
// * RELATIONSHIPS // * RELATIONSHIPS
/** /**
* Subranges Has-Many relationship. * Subranges Has-Many relationship.
* *
* @return \Illuminate\Database\Eloquent\Relations\hasMany * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
public function subranges(): \Illuminate\Database\Eloquent\Relations\hasMany public function subranges(): \Illuminate\Database\Eloquent\Relations\HasMany
{ {
return $this->hasMany(Subrange::class); return $this->hasMany(Subrange::class);
} }
@ -123,9 +153,9 @@ class Range extends Model implements SeoInterface
// * SEO // * SEO
/** /**
* Transfotrm to seoModel * Transform to seoModel
* *
* @return \App\Objects\SeoItem * @return \Uwil\Seo\Laravel\Objects\SeoItem
*/ */
public function toSeoItem(): SeoItem public function toSeoItem(): SeoItem
{ {
@ -138,4 +168,17 @@ class Range extends Model implements SeoInterface
\route('fo.products.range-products', $this) \route('fo.products.range-products', $this)
); );
} }
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this;
$route = 'fo.products.range-products';
$params = ['range' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
} }

View file

@ -11,11 +11,11 @@ use Illuminate\Validation\ValidationException;
/** /**
* @property integer $id Id. * @property integer $id Id.
* @property string $title Title. * @property string $title Title.
* @property integer $content WYSWYG content. * @property string $content WYSWYG content.
* @property boolean $button_display Display the button. * @property boolean $button_display Display the button.
* @property string $button_text Title of the button. * @property string|null $button_text Title of the button.
* @property string $button_url Link of the button. * @property string|null $button_url Link of the button.
* @property boolean $button_targetblank Open in new window. * @property boolean|null $button_targetblank Open in new window.
* @property string $picture Path of a picture. * @property string $picture Path of a picture.
* @property string $picture_alt Picture's attribute alt. * @property string $picture_alt Picture's attribute alt.
* @property string $picture_title Picture's attribute title. * @property string $picture_title Picture's attribute title.
@ -23,6 +23,7 @@ use Illuminate\Validation\ValidationException;
* @property string $picture_thumbnail_alt Thumbnail picture's attribute alt. * @property string $picture_thumbnail_alt Thumbnail picture's attribute alt.
* @property string $picture_thumbnail_title Thumbnail picture's attribute title. * @property string $picture_thumbnail_title Thumbnail picture's attribute title.
* @property integer $order Order. * @property integer $order Order.
* @property boolean $published Is the slide published ?
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date. * @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/ */
@ -33,7 +34,7 @@ class Slide extends Model
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'title', 'title',
@ -48,17 +49,19 @@ class Slide extends Model
'picture_thumbnail', 'picture_thumbnail',
'picture_thumbnail_alt', 'picture_thumbnail_alt',
'picture_thumbnail_title', 'picture_thumbnail_title',
'order' 'order',
'published'
]; ];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
* @var array * @var array<string,string>
*/ */
protected $casts = [ protected $casts = [
'button_display' => 'bool', 'button_display' => 'bool',
'button_targetblank' => 'bool' 'button_targetblank' => 'bool',
'published' => 'bool'
]; ];
/** /**
@ -69,9 +72,6 @@ class Slide extends Model
protected static function booted(): void protected static function booted(): void
{ {
static::creating(function (self $model) { static::creating(function (self $model) {
if (count(self::get()) >= 4) {
self::assertNumberOfSlides();
}
self::manageFields($model); self::manageFields($model);
$model->order = \intval(self::query()->max('order')) + 1; $model->order = \intval(self::query()->max('order')) + 1;
}); });
@ -82,10 +82,8 @@ class Slide extends Model
FileStorageHelper::removeOldFile($model, 'picture'); FileStorageHelper::removeOldFile($model, 'picture');
FileStorageHelper::removeOldFile($model, 'picture_thumbnail'); FileStorageHelper::removeOldFile($model, 'picture_thumbnail');
}); });
static::deleting(function () { static::deleting(function (self $model) {
if (1 >= count(self::get())) { self::assertNumberOfSlides($model, true);
self::assertNumberOfSlides();
}
}); });
static::deleted(function (self $model) { static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture'); FileStorageHelper::removeFile($model, 'picture');
@ -98,12 +96,13 @@ class Slide extends Model
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
self::assertTitleIsUnique($model->title, $model->id); self::assertTitleIsUnique($model->title, $model->id);
self::assertNumberOfSlides($model);
ToolboxHelper::assertNoInternalBoUrls($model->content, 'content'); ToolboxHelper::assertNoInternalBoUrls($model->content, 'content');
$model->content = clean($model->content); $model->content = clean($model->content);
if ($model->button_url) { if ($model->button_url) {
@ -117,14 +116,31 @@ class Slide extends Model
/** /**
* Asserts the number of slides in the slider. * Asserts the number of slides in the slider.
* *
* @param self $model
* @param boolean $isDeleting
* @return void * @return void
* @throws \Illuminate\Validation\ValidationException If there is less 1 or more than 4 slides. * @throws \Illuminate\Validation\ValidationException If there is less 1 or more than 4 slides.
*/ */
private static function assertNumberOfSlides(): void private static function assertNumberOfSlides(self $model, bool $isDeleting = false): void
{ {
throw ValidationException::withMessages([ if (
'Opération impossible, minimum un et maximum quatre slides doivent être présentes.' $isDeleting &&
]); $model->published &&
self::query()->where('published', true)->count() <= 1
) {
throw ValidationException::withMessages(
[trans('Minimum 1 slide doit être publiée, la slide n\'a pas été supprimée.')]
);
}
if ($model->published !== $model->getOriginal('published')) {
if (self::query()->where('published', true)->count() + 1 > 4 && $model->published) {
$model->published = false;
\session()->flash('info', \trans('Maximum 4 slides peuvent être publiées, la slide a été dépubliée.'));
} elseif (self::query()->where('published', true)->count() <= 1 && !$model->published) {
$model->published = true;
\session()->flash('info', \trans('Minimum 1 slide doit être publiée, la slide a été publiée.'));
}
}
} }
/** /**

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

@ -0,0 +1,85 @@
<?php
namespace App\Models;
use App\Enums\StaticPageType;
use App\Lib\Helpers\ToolboxHelper;
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-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/
class StaticPage extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'seo_title',
'seo_description',
'title',
'content',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'type' => StaticPageType::class,
];
/**
* 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);
});
}
// * 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

@ -26,7 +26,7 @@ class Subrange extends Model
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'label', 'label',
@ -55,12 +55,12 @@ class Subrange extends Model
/** /**
* Common methods function. * Common methods function.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param self $model
* @return void * @return void
*/ */
private static function manageFields(Model $model): void private static function manageFields(self $model): void
{ {
static::assertLabelIsUnique($model->label, $model->id); self::assertLabelIsUnique($model->label, $model->id);
$model->slug = Str::slug($model->label); $model->slug = Str::slug($model->label);
} }
@ -83,9 +83,9 @@ class Subrange extends Model
/** /**
* Products Belongs-To-Many relationship. * Products Belongs-To-Many relationship.
* *
* @return \Illuminate\Database\Eloquent\Relations\hasMany * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
public function products() public function products(): \Illuminate\Database\Eloquent\Relations\HasMany
{ {
return $this->hasMany(Product::class); return $this->hasMany(Product::class);
} }
@ -93,9 +93,9 @@ class Subrange extends Model
/** /**
* Range Belongs-To relationship. * Range Belongs-To relationship.
* *
* @return \Illuminate\Database\Eloquent\Relations\belongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function range() public function range(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{ {
return $this->belongsTo(Range::class); return $this->belongsTo(Range::class);
} }

View file

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\TagTypeEnum;
use App\Lib\Helpers\ToolboxHelper; use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -9,30 +10,9 @@ use Illuminate\Support\Str;
/** /**
* @property integer $id Id. * @property integer $id Id.
* @property string $title Title. * @property string $slug Slug.
* @property string $slug Slug of the title. * @property string $label Label.
* @property string $seo_title SEO Title. * @property \App\Enums\TagTypeEnum $type Type (ENUM).
* @property string $seo_description SEO description.
* @property \App\Enums\Advices\AdviceTypeEnum $type Type of the advice.
* @property string $picture Path of a picture.
* @property string $picture_alt Picture's attribute alt.
* @property string $picture_title Picture's attribute title.
* @property string $sticker Path of a sticker.
* @property string $sticker_alt Sticker's attribute alt.
* @property string $sticker_title Sticker's attribute title.
* @property string $sticker_sm Path of a small sticker.
* @property string $sticker_sm_alt small sticker's attribute alt.
* @property string $sticker_sm_title Small sticker's attribute title.
* @property string $preparation Preparation tips.
* @property string $advice Advice.
* @property \Illuminate\Support\Carbon $ingredients Ingredients.
* @property \Illuminate\Support\Carbon $guests_nb Number of guests.
* @property \Illuminate\Support\Carbon $youtube_title Youtube title.
* @property \Illuminate\Support\Carbon $youtube_video_id Youtube video id.
* @property \Illuminate\Support\Carbon $cooking_time Time of cooking.
* @property \Illuminate\Support\Carbon $preparation_time Time of preparation.
* @property \App\Enums\Advices\AdviceDifficultyEnum $difficulty Difficulty.
* @property boolean $status Status.
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date. * @property-read \Illuminate\Support\Carbon $updated_at Updated date.
* *
@ -46,10 +26,20 @@ class Tag extends Model
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'label' 'label',
'type',
];
/**
* The attributes that should be cast.
*
* @var array<string,string>
*/
protected $casts = [
'type' => TagTypeEnum::class,
]; ];
/** /**
@ -59,16 +49,16 @@ class Tag extends Model
*/ */
protected static function booted(): void protected static function booted(): void
{ {
static::creating(function (Tag $tag) { static::creating(function (self $model) {
static::assertLabelIsUnique($tag->label); static::assertLabelIsUnique($model->label);
$tag->slug = Str::slug($tag->label); $model->slug = Str::slug($model->label);
}); });
static::updating(function (Tag $tag) { static::updating(function (self $model) {
static::assertLabelIsUnique($tag->label, $tag->id); static::assertLabelIsUnique($model->label, $model->id);
$tag->slug = Str::slug($tag->label); $model->slug = Str::slug($model->label);
}); });
static::deleting(function (Tag $tag) { static::deleting(function (self $model) {
$tag->advice()->detach(); $model->advice()->detach();
}); });
} }
@ -82,7 +72,7 @@ class Tag extends Model
* @return void * @return void
* @throws \Illuminate\Validation\ValidationException If a tag label already exists. * @throws \Illuminate\Validation\ValidationException If a tag label already exists.
*/ */
private static function assertLabelIsUnique(string $label, ?int $id = null): void public static function assertLabelIsUnique(string $label, ?int $id = null): void
{ {
$table = (new self())->getTable(); $table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id); ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id);
@ -100,4 +90,14 @@ class Tag extends Model
{ {
return $this->morphedByMany(Advice::class, 'taggable'); return $this->morphedByMany(Advice::class, 'taggable');
} }
/**
* Product Morphed-By-Many relation.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function products(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(Product::class, 'taggable');
}
} }

View file

@ -6,16 +6,12 @@ use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/** /**
* @property integer $id Id. * @property integer $id Id.
* @property string $firstname Firstname. * @property string $firstname Firstname.
* @property string $lastname Lastname. * @property string $lastname Lastname.
* @property string $email Email. * @property string $email Email.
* @property \Illuminate\Support\Carbon $email_verified_at Email_verified_at.
* @property string $password Password.
* @property string $remember_token Remember_token. * @property string $remember_token Remember_token.
* @property \Carbon\Carbon $last_connection Last_connection. * @property \Carbon\Carbon $last_connection Last_connection.
* @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $created_at Created date.
@ -34,8 +30,7 @@ class User extends Authenticatable
protected $fillable = [ protected $fillable = [
'firstname', 'firstname',
'lastname', 'lastname',
'email', 'email'
'password',
]; ];
/** /**
@ -44,8 +39,7 @@ class User extends Authenticatable
* @var array<int, string> * @var array<int, string>
*/ */
protected $hidden = [ protected $hidden = [
'password', 'remember_token'
'remember_token',
]; ];
/** /**
@ -54,8 +48,7 @@ class User extends Authenticatable
* @var array<string, string> * @var array<string, string>
*/ */
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'last_connection' => 'date',
'last_connection' => 'datetime',
]; ];
/** /**
@ -67,18 +60,10 @@ class User extends Authenticatable
{ {
// * EVENT CLOSURES. // * EVENT CLOSURES.
static::creating(function (self $user) { static::creating(function (self $user) {
static::assertEmailIsUnique($user->email); self::assertEmailIsUnique($user->email);
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
$user->remember_token = Str::random(10);
}); });
static::updating(function (self $user) { static::updating(function (self $user) {
static::assertEmailIsUnique($user->email, $user->id); self::assertEmailIsUnique($user->email, $user->id);
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
$user->remember_token = Str::random(10);
}); });
} }

View file

@ -0,0 +1,31 @@
<?php
namespace App\Policies\Rules;
/**
* Userlevel Policiy Static Rules
*
* @codeCoverageIgnore
*/
abstract class UserStaticRules
{
/**
* Allow everyone.
*
* @return boolean
*/
public static function everyOne(): bool
{
return true;
}
/**
* Allow no one.
*
* @return boolean
*/
public static function noOne(): bool
{
return false;
}
}

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,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
@ -16,6 +17,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
Model::shouldBeStrict(!$this->app->environment('production'));
// * HTTPS FIX // * HTTPS FIX
URL::forceScheme('https'); URL::forceScheme('https');

View file

@ -18,7 +18,7 @@ class AuthServiceProvider extends ServiceProvider
* *
* @return void * @return void
*/ */
public function boot() public function boot(): void
{ {
$this->registerPolicies(); $this->registerPolicies();
} }

View file

@ -0,0 +1,26 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
use LDC\LDCCRM\CrmApiV4;
class CrmApiServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot(): void
{
App::singleton('crmAPI', function () {
return new CrmApiV4(
config('crm.endpoint'),
config('crm.token'),
config('crm.password')
);
});
}
}

View file

@ -3,6 +3,13 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; 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 class EventServiceProvider extends ServiceProvider
{ {
@ -20,5 +27,41 @@ class EventServiceProvider extends ServiceProvider
*/ */
public function boot() 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

@ -3,6 +3,7 @@
namespace App\Providers; namespace App\Providers;
use App\Models\Advice; use App\Models\Advice;
use App\Models\BrigadeSection;
use App\Models\ExpertiseProduct; use App\Models\ExpertiseProduct;
use App\Models\News; use App\Models\News;
use App\Models\NewsCategory; use App\Models\NewsCategory;
@ -57,6 +58,7 @@ class RouteServiceProvider extends ServiceProvider
Route::model('range', Range::class); Route::model('range', Range::class);
Route::model('product', Product::class); Route::model('product', Product::class);
Route::model('advice', Advice::class); Route::model('advice', Advice::class);
Route::model('brigade_section', BrigadeSection::class);
} }
Route::bind('actu', function ($value) { Route::bind('actu', function ($value) {
@ -65,7 +67,7 @@ class RouteServiceProvider extends ServiceProvider
// * Front binded Models // * Front binded Models
Route::bind('publishedAdvice', function ($value) { Route::bind('publishedAdvice', function ($value) {
return Advice::where('slug', $value)->where('status', true)->firstOrFail(); return Advice::where('slug', $value)->where('published', true)->firstOrFail();
}); });
} }
@ -110,8 +112,11 @@ class RouteServiceProvider extends ServiceProvider
})->all(); })->all();
// * Filter array keys // * Filter array keys
$cspReport = collect(array_intersect_key($cspReport, [ $cspReport = collect(array_intersect_key($cspReport, [
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '', 'blocked-uri' => '',
'referrer' => '', 'violated-directive' => '' 'document-uri' => '',
'original-policy' => '',
'referrer' => '',
'violated-directive' => ''
])); ]));
\Illuminate\Support\Facades\Log::warning(sprintf( \Illuminate\Support\Facades\Log::warning(sprintf(
"CSP REPORT for %s : \n %s", "CSP REPORT for %s : \n %s",

View file

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -20,9 +21,19 @@ class TranslationServiceProvider extends ServiceProvider
foreach ($this->collectLocalesStrings() as $locale) { foreach ($this->collectLocalesStrings() as $locale) {
// * Suported locales // * Suported locales
Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) { Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
$translations = [ $phpTransFallback = $this->phpTranslations(config('app.fallback_locale'));
'php' => $this->phpTranslations($locale), $phpTransLocale = $this->phpTranslations($locale);
'json' => $this->jsonTranslations($locale), $jsonTransFallback = $this->jsonTranslations(config('app.fallback_locale'));
$jsonTransLocale = $this->jsonTranslations($locale);
$translations = [
'php' => ToolboxHelper::arrayMergeRecursiveDistinct(
$phpTransLocale,
$phpTransFallback,
),
'json' => ToolboxHelper::arrayMergeRecursiveDistinct(
$jsonTransLocale,
$jsonTransFallback,
),
]; ];
return $translations; return $translations;
}); });
@ -36,11 +47,13 @@ class TranslationServiceProvider extends ServiceProvider
*/ */
private function collectLocalesStrings(): Collection private function collectLocalesStrings(): Collection
{ {
// * En dur dans ->keys array<int,string>.
// @phpstan-ignore-next-line
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) { return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) { if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
return [$file->getRelativePath() => '']; return [$file->getRelativePath() => ''];
} }
})->keys(); })->keys()->merge(config('app.locales', []));
} }
/** /**
@ -53,6 +66,10 @@ class TranslationServiceProvider extends ServiceProvider
{ {
$path = resource_path("lang/$locale"); $path = resource_path("lang/$locale");
if (!File::exists($path) or !File::isReadable($path)) {
return [];
}
return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) { return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
$translation = $file->getBasename('.php'); $translation = $file->getBasename('.php');
return [$translation => trans($translation, [], $locale)]; return [$translation => trans($translation, [], $locale)];
@ -69,10 +86,10 @@ class TranslationServiceProvider extends ServiceProvider
{ {
$path = resource_path("lang/$locale.json"); $path = resource_path("lang/$locale.json");
if (is_string($path) && is_readable($path)) { if (!File::exists($path) or !File::isReadable($path)) {
return json_decode(file_get_contents($path), true) ?? []; return [];
} }
return []; return json_decode(File::get($path), true) ?? [];
} }
} }

View file

@ -32,7 +32,7 @@ class ViewServiceProvider extends ServiceProvider
if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') { if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') {
Paginator::defaultView('bo.modules.pagination'); Paginator::defaultView('bo.modules.pagination');
} else { } else {
Paginator::useBootstrap(); Paginator::useBootstrapFive();
} }
} }
} }

View file

@ -0,0 +1,83 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Str;
/**
* Check the length of a Wysiwyg field, excluding tags and end spaces.
*
* @package App\Rules
*/
class WysiwygLength implements ValidationRule
{
/**
* Construct method.
*
* @param integer $min The minimum length of the content (excluding tags). Default is 15.
* @param integer|null $max The maximum length of the content (excluding tags).
* Default is null (max will be defined by the type of the column in DB).
* @return void
* @throws \InvalidArgumentException If $max < $min.
*/
public function __construct(private int $min = 15, private int|null $max = null)
{
if ($max !== null && $max < $min && $min < 0) {
throw new \InvalidArgumentException(sprintf(
'$max should be higher than $min. $max = %d. $min = %d.',
$max,
$min
));
}
}
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure $fail
*
* @return void
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
$length = Str::length(\strip_tags($value));
$translatedAttribute = self::getTranslatedAttribute($attribute);
if (!\is_null($this->max) and !($length >= $this->min && $length <= $this->max)) {
$fail(trans('validation.between.string', [
'attribute' => $translatedAttribute,
'min' => $this->min,
'max' => $this->max
]));
return;
}
if ($length < $this->min) {
$fail(trans('validation.min.string', [
'attribute' => $translatedAttribute,
'min' => $this->min
]));
}
}
/**
* Get the correct translation for the given attrribute.
*
* @param string $attribute
* @return string
*/
private static function getTranslatedAttribute(string $attribute): string
{
/** @var \Illuminate\Translation\Translator $trans */
$trans = trans();
if ($trans->has('validation.attributes.' . $attribute)) {
return trans('validation.attributes.' . $attribute);
}
if ($trans->has('validation.custom.' . $attribute)) {
return trans('validation.custom.' . $attribute);
}
return $attribute;
}
}

View file

@ -3,7 +3,7 @@
namespace App\Traits\Controllers; namespace App\Traits\Controllers;
use App\Http\Requests\Bo\UpdateChangeOrderRequest; use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -14,44 +14,43 @@ trait ChangesModelOrder
* Change order. * Change order.
* *
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request * @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
* @param \Illuminate\Database\Eloquent\Model $model * @param mixed ...$params
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé. * @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé.
*/ */
public function changeOrder(UpdateChangeOrderRequest $request, Model $model): \Illuminate\Http\RedirectResponse public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
{ {
$params = collect(request()->route()->parameterNames); $paramNames = collect(request()->route()->parameterNames);
$className = Str::ucfirst(Str::lower($params->get($params->count() - 2))); $className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\$className"; $className = "\\App\\Models\\$className";
$model = Arr::get($params, $paramNames->count() - 2);
if (!class_exists($className)) { if (!class_exists($className)) {
throw new \RuntimeException("The $className class doesn't exist."); throw new \RuntimeException("The $className class doesn't exist.");
} }
/** @var \Illuminate\Database\Eloquent\Model|null */ /** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::where('id', $model->id)->first(); $model = $className::where($model->getRouteKeyName(), $model->getRouteKey())->first();
if (!$model) { if (!$model) {
return redirect()->back()->with('error', trans('crud.messages.order_not_changed')); return back()->with('error', trans('crud.messages.order_not_changed'));
} }
return DB::transaction(function () use ($request, $className, $model) { return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */ /** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->order) $tmp = $className::where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first(); ->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) { if (!$tmp) {
return redirect()->back()->with('error', trans('crud.messages.order_not_changed')); return back()->with('error', trans('crud.messages.order_not_changed'));
} }
$newOrder = $tmp->order; $newOrder = $tmp->getOriginal('order');
$tmp->order = $model->order; $tmp->update(['order' => $model->getOriginal('order')]);
$model->order = $newOrder; $model->update(['order' => $newOrder]);
$tmp->saveOrFail();
$model->saveOrFail();
Session::flash('success', trans('crud.messages.order_changed')); Session::flash('success', trans('crud.messages.order_changed'));
return redirect()->back(); return back();
}); });
} }
} }

View file

@ -1,31 +0,0 @@
<?php
namespace App\Traits\Controllers;
use App\Lib\Helpers\FileStorageHelper;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
trait HasPicture
{
/**
* Save Model Images
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*
* @phpcs:disable Generic.Commenting.Fixme.TaskFound
* FIXME: Rename trait as IT Handled "Slider pictures" not only static "picture".
*/
private function storePictures(Request $request, Model $model)
{
if (is_array($request->slider_pictures)) {
$model->slider_pictures = [];
$model->slider_pictures = collect($request->slider_pictures)
->map(function ($picture) use ($model) {
return FileStorageHelper::storeFile($model, $picture, true);
})->all();
}
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Traits\Controllers;
use App\Http\Requests\Bo\UpdateOrderRequest;
use App\Interfaces\OrderableInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
trait ManagesOrder
{
/**
* Updates the order of a model and reorders all impacted elements.
*
* Shifts by +1 or -1 all elements located between the old
* and new position of the model, then assigns the new position
* to the target model. The whole operation runs inside a
* database transaction to ensure data consistency.
*
* @param UpdateOrderRequest $request The validated request containing the 'priority' field.
* @param Model&OrderableInterface $model The model to reorder (must use the Orderable trait).
* @return RedirectResponse
*
* @throws \Throwable Returns if save fail.
*/
public function changeOrderPriority(UpdateOrderRequest $request, Model&OrderableInterface $model): RedirectResponse
{
$newPriority = (int) $request->validated('priority_order');
return DB::transaction(function () use ($model, $newPriority): RedirectResponse {
$query = $model::query()
->whereKeyNot($model)
->whereBetween('priority_order', [
min($model->getPriorityOrder(), $newPriority),
max($model->getPriorityOrder(), $newPriority),
]);
$groupField = $model->getOrderGroupField();
if ($groupField) {
$query->where($groupField, $model->{$groupField});
}
$model->getPriorityOrder() < $newPriority
? $query->decrement('priority_order', 1)
: $query->increment('priority_order', 1);
$model->setAttribute('priority_order', $newPriority);
$model->saveOrFail();
return redirect()->back()->with('success', trans('crud.messages.order_changed'));
});
}
}

View file

@ -9,18 +9,17 @@ use Illuminate\Database\Eloquent\Model;
* to resolve the model and the DB row * to resolve the model and the DB row
* It does not work with binder Service * It does not work with binder Service
*/ */
trait UpdateModelStatus trait UpdateModelPublished
{ {
/** /**
* Change model published * Change model published.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function changePublished(Model $model) public function changePublished(Model $model)
{ {
$model->status = !$model->status; $model->update(['published' => !$model->getOriginal('published')]);
$model->saveOrFail(); return redirect()->back()->with('success', trans('crud.messages.publish_status_saved'));
return redirect()->back();
} }
} }

View file

@ -89,7 +89,7 @@ trait BaseEnum
/** /**
* Get enums. * Get enums.
* *
* @return array * @return array<self>
*/ */
public static function toArray(): array public static function toArray(): array
{ {
@ -118,9 +118,9 @@ trait BaseEnum
/** /**
* Fetch enum using value. * Fetch enum using value.
* *
* @param \App\Traits\Enums\BaseEnum|integer|string $value * @param self|integer|string $value
* @param string $field * @param string $field
* @return \App\Traits\Enums\BaseEnum|null * @return self|null
*/ */
public static function make(self|int|string $value, string $field = 'value'): self|null public static function make(self|int|string $value, string $field = 'value'): self|null
{ {

View file

@ -0,0 +1,78 @@
<?php
namespace App\Traits\Models;
use App\Interfaces\OrderableInterface;
use Illuminate\Database\Eloquent\Model;
trait Orderable
{
/**
* @property int $priority_order
*/
/**
* Returns the grouping field used for ordering.
* Returns null if the order is global
*
* @return string|null
*/
public function getOrderGroupField(): ?string
{
return null;
}
/**
* Returns the priority order value of the model.
*
* @return integer
*/
public function getPriorityOrder(): int
{
return $this->priority_order ?? 0;
}
/**
* shifts down by 1 all elements positioned after the delete.
*
* @param Model&OrderableInterface $model
* @return void
*/
public static function setOrder(OrderableInterface $model): void
{
$groupField = $model->getOrderGroupField();
$query = $model::query()->where('priority_order', '>', $model->getPriorityOrder());
if ($groupField) {
$query->where($groupField, $model->{$groupField});
}
$query->each(function (Model $item): void {
/** @var Model&OrderableInterface $item */
$item->setAttribute('priority_order', $item->getPriorityOrder() - 1);
$item->saveOrFail();
});
}
/**
* Assigns the next order value on creation.
*
* @return void
*/
public static function bootOrderable(): void
{
static::creating(function (Model $model): void {
/** @var Model&OrderableInterface $model */
if (!$model->getPriorityOrder()) {
$groupField = $model->getOrderGroupField();
$query = static::query();
if ($groupField) {
$query->where($groupField, $model->{$groupField});
}
$model->setAttribute('priority_order', ($query->max('priority_order') ?? 0) + 1);
}
});
}
}

View file

@ -2,8 +2,11 @@
namespace App\Traits\Requests; namespace App\Traits\Requests;
use Illuminate\Support\Facades\File; use App\Lib\Helpers\ToolboxHelper;
/**
* ! This assumes file system is using local storage
*/
trait HasPicture trait HasPicture
{ {
/** /**
@ -11,14 +14,14 @@ trait HasPicture
* *
* @var integer * @var integer
*/ */
private $minWith = 400; private $minWidth = 400;
/** /**
* Maximum width * Maximum width
* *
* @var integer * @var integer
*/ */
private $maxWith = 1800; private $maxWidth = 1800;
/** /**
* Minimum height * Minimum height
@ -38,32 +41,41 @@ trait HasPicture
* Image rules * Image rules
* *
* @param string $name * @param string $name
* @param boolean $required * @param integer|null $minWidth
* @param integer|null $minWith
* @param integer|null $minHeight * @param integer|null $minHeight
* @param integer|null $maxWith * @param integer|null $maxWidth
* @param integer|null $maxHeight * @param integer|null $maxHeight
* @param boolean $nullable
* @param integer|null $size
* @param string|null $requiredWith
* @param string|null $requiredWithout
* @return array * @return array
*/ */
public function pictureRules( public function pictureRules(
string $name = 'picture', string $name = 'picture',
bool $required = true, ?int $minWidth = null,
?int $minWith = null,
?int $minHeight = null, ?int $minHeight = null,
?int $maxWith = null, ?int $maxWidth = null,
?int $maxHeight = null ?int $maxHeight = null,
bool $nullable = false,
?int $size = null,
?string $requiredWith = null,
?string $requiredWithout = null,
): array { ): array {
$sizes = (object)[ $dimensions = (object)[
'minWith' => $minWith ?? $this->minWith, 'minWidth' => $minWidth ?? $this->minWidth,
'minHeight' => $minHeight ?? $this->minHeight, 'minHeight' => $minHeight ?? $this->minHeight,
'maxWith' => $maxWith ?? $this->maxWith, 'maxWidth' => $maxWidth ?? $this->maxWidth,
'maxHeight' => $maxHeight ?? $this->maxHeight 'maxHeight' => $maxHeight ?? $this->maxHeight
]; ];
return [ return [
$name => [ $name => [
$required ? 'required' : 'nullable', !$nullable ? 'required' : 'nullable',
function (string $attribute, $value, callable $fail) use ($sizes) { (strlen($requiredWith)) ? 'required_with:' . $requiredWith : '',
return $this->validatePicture($attribute, $value, $fail, $sizes); (strlen($requiredWithout)) ? 'required_without:' . $requiredWithout : '',
($size) ? 'max:' . $size : '',
function (string $attribute, $value, callable $fail) use ($dimensions) {
ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
} }
] ]
]; ];
@ -73,92 +85,42 @@ trait HasPicture
* Images rules * Images rules
* *
* @param string $name * @param string $name
* @param boolean $required * @param integer|null $minWidth
* @param integer|null $minWith
* @param integer|null $minHeight * @param integer|null $minHeight
* @param integer|null $maxWith * @param integer|null $maxWidth
* @param integer|null $maxHeight * @param integer|null $maxHeight
* @param integer|null $limitMin * @param integer $limitMin
* @param integer|null $limitMax * @param integer $limitMax
* @param boolean $nullable
* @return array * @return array
*/ */
public function picturesRules( public function picturesRules(
string $name = 'pictures', string $name = 'pictures',
bool $required = true, ?int $minWidth = null,
?int $minWith = null,
?int $minHeight = null, ?int $minHeight = null,
?int $maxWith = null, ?int $maxWidth = null,
?int $maxHeight = null, ?int $maxHeight = null,
?int $limitMin = null, int $limitMin = 1,
?int $limitMax = null int $limitMax = 5,
bool $nullable = false
): array { ): array {
$sizes = (object)[ $sizes = (object)[
'required' => $required ?? $this->required, 'minWidth' => $minWidth ?? $this->minWidth,
'minWith' => $minWith ?? $this->minWith,
'minHeight' => $minHeight ?? $this->minHeight, 'minHeight' => $minHeight ?? $this->minHeight,
'maxWith' => $maxWith ?? $this->maxWith, 'maxWidth' => $maxWidth ?? $this->maxWidth,
'maxHeight' => $maxHeight ?? $this->maxHeight, 'maxHeight' => $maxHeight ?? $this->maxHeight,
'limitMin' => $limitMin ?? $this->limitMin,
'limitMax' => $limitMax ?? $this->limitMax
]; ];
return [ return [
"{$name}" => ($required ? 'required' : 'nullable') . "|array|between:$limitMin,$limitMax", $name => [!$nullable ? 'required' : 'nullable', 'array', "between:{$limitMin},{$limitMax}"],
"{$name}.*" => [ "{$name}.*" => [
'required', 'required',
function (string $attribute, $value, callable $fail) use ($sizes) { function (string $attribute, $value, callable $fail) use ($sizes) {
return $this->validatePicture($attribute, $value, $fail, $sizes); ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
} }
] ]
]; ];
} }
/**
* Validate picture
*
* @param string $attribute
* @param mixed $value
* @param callback $fail
* @param object $sizes
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
*/
private function validatePicture(string $attribute, mixed $value, callable $fail, object $sizes)
{
if (!File::exists($value)) {
$fail(trans(":attribute n'existe pas dans le système de fichier"));
return;
}
if (!\in_array(File::mimeType($value), ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
$fail(trans(":attribute doit être une image de type 'image/jpeg,image/jpg,image/png,image/gif'"));
}
if (!($dimensions = \getimagesize($value)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
$fail(trans(":attribute impossible de valider les dimensions"));
} else {
$width = intval($dimensions[0]);
$height = intval($dimensions[1]);
$xMessage = trans(
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
);
if ($sizes->minWith === $sizes->maxWith) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px");
}
$yMessage = trans(
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
);
if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px");
}
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
$fail($yMessage);
}
} //end if
}
/** /**
* Merges products pictures fields with request data * Merges products pictures fields with request data
* *
@ -189,4 +151,38 @@ trait HasPicture
]); ]);
} //end if } //end if
} }
/**
* Merges products pictures fields with request data
*
* @param string $name
* @return void
*/
protected function mergePictures(string $name = 'pictures'): void
{
$pictures = $this->{$name};
if (is_array($pictures)) {
$this->merge(["{$name}Validate" => []]);
foreach ($pictures as $key => $picturePath) {
// ! Empty picture.
if ($picturePath === 'false') {
// * Empty picture and exit
$this->merge([$name => []]);
return;
}
$decodedPath = \urldecode($picturePath);
$this->merge([
"{$name}Validate" => array_merge(
$this->{"{$name}Validate"},
[\public_path(ltrim($decodedPath, '/'))]
)
]);
$changedPicture = $this->{$name};
$changedPicture[$key] = ltrim($decodedPath, '/');
$this->merge([
$name => $changedPicture
]);
}
} //end if
}
} }

View file

@ -12,33 +12,38 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.5",
"24slides/laravel-saml2": "^2.4",
"axlon/laravel-postal-code-validation": "^3.4", "axlon/laravel-postal-code-validation": "^3.4",
"carlos-meneses/laravel-mpdf": "^2.1.13", "carlos-meneses/laravel-mpdf": "^2.1.13",
"diglactic/laravel-breadcrumbs": "^8.1.1", "diglactic/laravel-breadcrumbs": "^9.0",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"kaishiyoku/laravel-color-validation": "^2.1.1", "kaishiyoku/laravel-color-validation": "*",
"kwaadpepper/image-resizer": "^2.1.0", "laravel/framework": "^11.0",
"laravel/framework": "^10.0",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
"ldc/sitemap-generator": "^2.0", "ldc/ldccrmv4client": "^4.0",
"ldc/web-admin-secure-laravel": "^2.0.0",
"mews/purifier": "^3.4", "mews/purifier": "^3.4",
"propaganistas/laravel-phone": "^5.0.3", "propaganistas/laravel-phone": "^5.0.3",
"sentry/sentry-laravel": "^3.8", "sentry/sentry-laravel": "^4.9",
"spatie/calendar-links": "^1.8.5", "spatie/calendar-links": "^1.8.5",
"uwil/admin-secure-laravel": "^5.0",
"uwil/k8s-laravel": "^1.2", "uwil/k8s-laravel": "^1.2",
"uwil/seo-laravel": "^1.0" "uwil/laravel-common": "^2.0",
"uwil/laravel-redirections": "^2.0",
"uwil/seo-laravel": "^3.0",
"uwil/sitemap-generator": "^3.0",
"verschuur/laravel-robotstxt": "^5.1"
}, },
"require-dev": { "require-dev": {
"doctrine/dbal": "^3.3", "doctrine/dbal": "^3.3",
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
"larastan/larastan": "^2.8",
"laravel-lang/lang": "10.0", "laravel-lang/lang": "10.0",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.18", "laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0", "nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^10.0", "phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "^8.10", "slevomat/coding-standard": "^8.10",
"spatie/laravel-ignition": "^2.3" "spatie/laravel-ignition": "^2.3"
@ -89,7 +94,10 @@
"pestphp/pest-plugin": true, "pestphp/pest-plugin": true,
"php-http/discovery": true, "php-http/discovery": true,
"dealerdirect/phpcodesniffer-composer-installer": true "dealerdirect/phpcodesniffer-composer-installer": true
} },
"gitlab-domains": [
"lab.s-web.fr"
]
}, },
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true "prefer-stable": true

6080
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -179,6 +179,7 @@ return [
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
App\Providers\ViewServiceProvider::class, App\Providers\ViewServiceProvider::class,
App\Providers\CrmApiServiceProvider::class,
], ],
/* /*

7
config/crm.php Normal file
View file

@ -0,0 +1,7 @@
<?php
return [
'endpoint' => env('CRM_ENDPOINT'),
'token' => env('CRM_TOKEN'),
'password' => env('CRM_PASSWORD')
];

View file

@ -5,7 +5,7 @@ return [
* The php image driver to use * The php image driver to use
* this can be 'imagick' or 'gd' * this can be 'imagick' or 'gd'
*/ */
'driver' => 'gd', 'driver' => 'imagick',
/** /**
* The cache driver to use as defined * The cache driver to use as defined
@ -57,6 +57,9 @@ return [
* ] * ]
*/ */
'templates' => [ 'templates' => [
'toWebp' => [
'format' => 'webp'
],
'tipsBigCard' => [ 'tipsBigCard' => [
'height' => 300, 'height' => 300,
'width' => 370, 'width' => 370,

View file

@ -120,6 +120,6 @@ return [
*/ */
'contactsList' => [ 'contactsList' => [
'contactform' => env('MAIL_LIST_CONTACTFORM') 'contactform' => array_filter(explode(',', env('MAIL_LIST_CONTACTFORM', '') ?? '')),
] ]
]; ];

20
config/robots-txt.php Normal file
View file

@ -0,0 +1,20 @@
<?php
return [
'environments' => [
'production' => [
'paths' => [
'*' => [
'disallow' => [
'/bo',
'*.pdf$'
],
'allow' => []
],
],
'sitemaps' => [
'sitemap.xml'
]
]
]
];

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