Compare commits

..

489 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
Alexis Gousseau
13090b9bfa fix wrong path sass file 2024-01-09 16:21:41 +01:00
Jérémy Munsch
b040a44766
Fo head update 2023-12-22 09:19:11 +01:00
Jérémy Munsch
931fe103f0
Bo head blade update with base project 2023-12-22 09:12:36 +01:00
Jérémy Munsch
a2c8efea03
bump cookie notice to 4.0.0 + fix npm deps 2023-12-21 18:26:07 +01:00
Jérémy Munsch
221149d31d
Ts assets module fix 2023-12-21 18:01:56 +01:00
Jérémy Munsch
f6639544b1
toastify import fix 2023-12-21 17:54:46 +01:00
Jérémy Munsch
8babf9d3ad
layout blade formatter fix 2023-12-21 17:54:36 +01:00
Jérémy Munsch
b2ff5914f7
Fo tooltip fix 2023-12-21 17:41:54 +01:00
Jérémy Munsch
6a98fe85fc
Colors Input Switch fix on front 2023-12-21 16:56:12 +01:00
Jérémy Munsch
9929f8f101
Fix home slider on mobile height 2023-12-21 16:22:42 +01:00
Jérémy Munsch
b959cbf271
partial fix for swiper slider height 2023-12-21 16:15:55 +01:00
Jérémy Munsch
e2ca73d69c
front compilation fix 2023-12-21 16:15:38 +01:00
Jérémy Munsch
54f6719715
SecureHeaders Update 2023-12-21 16:09:56 +01:00
Alexis Gousseau
23e6a69c40 fix phpcs 2023-12-20 15:15:18 +01:00
Alexis Gousseau
e6db9fa239 fix front iso prod 2023-12-20 15:09:43 +01:00
Alexis Gousseau
9fb6a5981a fix bump cookies v3.1.2 (matomo + recaptcha) 2023-12-20 11:05:25 +01:00
Alexis Gousseau
29790c49c0 update favicon le-gaulois-pro 2023-12-19 16:11:11 +01:00
Jérémy Munsch
dd167f1d04
Fix bg-default class 2023-12-18 17:58:36 +01:00
Jérémy Munsch
f61ed0267d
Some bootstrap fixes 2023-12-18 17:54:35 +01:00
Jérémy Munsch
b6d47a51c6
Fixes #204 2023-12-18 17:30:25 +01:00
Jérémy Munsch
238c1c9e20
Fixes #205 REcipes search broken 2023-12-18 17:09:46 +01:00
Jérémy Munsch
780717b856
Closes #206 AdviceController::index fix request param usage 2023-12-18 16:43:39 +01:00
Jérémy Munsch
34985e1f2b
composer update 2023-12-18 16:40:33 +01:00
Jérémy Munsch
b16ae1f9e2
Fix favicon to gitignore on public 2023-12-18 16:37:51 +01:00
Jérémy Munsch
3be08fdeb2
Bump front deps + add sass mail overload + fixup favicons 2023-12-18 16:33:26 +01:00
Vincent BRIDIER
0556ee5b2e Merge branch 'develop' into trunk 2023-12-18 11:06:21 +01:00
Vincent BRIDIER
3a6fb1fe62 Merge branch 'issue-202-recipe-type' into 'develop'
Type was not provided in the form

See merge request ldc-web/projets/le-gaulois-pro!139
2023-12-18 10:05:37 +00:00
Vincent BRIDIER
bf614a1399 Merge branch 'issue-203' into 'develop'
Add SEO description in ideas page

See merge request ldc-web/projets/le-gaulois-pro!140
2023-12-15 14:21:50 +00:00
Sebastien Hattry
1968d8bc88 Add SEO description in ideas page 2023-12-15 15:15:33 +01:00
Vincent BRIDIER
044916aa46 Merge branch 'issue-201' into 'develop'
Issue 201

See merge request ldc-web/projets/le-gaulois-pro!136
2023-12-12 16:57:36 +00:00
sebastien.hattry
ed25599d4a Update Cookie Notice - Close #201 2023-12-12 16:57:36 +00:00
Vincent BRIDIER
e575e44999 Update manifest 2023-12-11 11:15:31 +00:00
Noah Lancien
19f5efc08d Type was not provided in the form 2023-12-06 13:09:25 +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
d9fb453315
Fix access to search page with empty query 2023-11-08 10:58:49 +01:00
Vincent BRIDIER
d55852891f
Merge branch 'develop' into trunk 2023-11-07 09:11:26 +01:00
Vincent BRIDIER
5a5ea2a4bf
Fix PHPCS ProductAjaxFilter 2023-11-07 09:11:17 +01:00
Vincent BRIDIER
133a2c7f61
Fix contact form 2023-11-07 09:11:01 +01:00
Vincent BRIDIER
9eb55f8456
Merge branch 'develop' into trunk 2023-11-06 18:12:19 +01:00
Vincent BRIDIER
0337dfb10f
Add pagination translations 2023-11-06 18:12:11 +01:00
Vincent BRIDIER
c68c70d65f
Fix #197 2023-11-06 18:11:57 +01:00
Vincent BRIDIER
927e305b93
Fix #196 2023-11-06 17:29:27 +01:00
Vincent BRIDIER
5333945267
Fix #198 2023-11-06 17:19:21 +01:00
Vincent BRIDIER
a2401de135
Fix service config 2023-11-06 17:13:39 +01:00
Vincent BRIDIER
03b09f1cdf
Merge branch 'develop' into trunk 2023-11-06 12:25:01 +01:00
Vincent BRIDIER
26678571d5
Bump SEO Laravel 2023-11-06 12:24:53 +01:00
Vincent BRIDIER
ab9f2f76a3
Merge branch 'develop' into trunk 2023-11-06 12:03:24 +01:00
Vincent BRIDIER
9fef87ee91
Fix SeoItem PHPCS 2023-11-06 12:03:17 +01:00
Vincent BRIDIER
12c3fed7d1 Merge branch '165-ajouter-le-package-seo-laravel' into 'develop'
add seo-laravel

See merge request ldc-web/projets/le-gaulois-pro!135
2023-11-06 11:01:02 +00:00
nicolas.demangeat
ae3b098366 Close #165 - Add SEO Laravel package 2023-11-06 11:01:02 +00:00
Vincent BRIDIER
3a2d8e30ed Merge branch 'issue-36' into 'develop'
Add new sitemap

See merge request ldc-web/projets/le-gaulois-pro!118
2023-11-06 10:01:59 +00:00
Jérémy Munsch
a3e795d797
Bump sitemap generator
- Remove kwaadpepper/refresh-sitemap
- Adds ldc/sitemap-generator to 2.0.0
2023-10-11 16:48:03 +02:00
Vincent BRIDIER
44dcda52a3
Merge branch 'develop' into trunk 2023-10-10 15:16:05 +02:00
Vincent BRIDIER
b15b180ea5 Merge branch 'bump-laravel-10' into 'develop'
Bump laravel 10

See merge request ldc-web/projets/le-gaulois-pro!129
2023-10-10 13:03:44 +00:00
Vincent BRIDIER
168ff226f5
Bump PHP version into Dockerfile 2023-10-10 14:59:53 +02:00
Vincent BRIDIER
960649d627
Fix timezone 2023-10-10 14:58:51 +02:00
Vincent BRIDIER
199409f226
Update footer infos 2023-10-10 14:58:39 +02:00
Vincent BRIDIER
ec8878f0e8
PHPCS 2023-10-10 09:48:14 +02:00
Vincent BRIDIER
dbbc73cb57
Fix CollectionHelper namespace 2023-10-10 09:41:25 +02:00
Vincent BRIDIER
9ba3be0c5e Merge branch 'issue-187' into 'bump-laravel-10'
Changed display when there's no result in the front search

See merge request ldc-web/projets/le-gaulois-pro!133
2023-10-04 15:26:43 +00:00
noah.lancien
5d84bb5e03 Changed display when there's no result in the front search 2023-10-04 15:26:42 +00:00
Vincent BRIDIER
f61bea54cd Merge branch 'issue-192' into 'bump-laravel-10'
Products associed recipes are now displayed in the product page

See merge request ldc-web/projets/le-gaulois-pro!134
2023-10-04 06:20:32 +00:00
Vincent BRIDIER
e6bf28355b Merge branch 'issue-191-product-card' into 'bump-laravel-10'
Limit product title to 25 chars in cards

See merge request ldc-web/projets/le-gaulois-pro!132
2023-10-04 06:20:04 +00:00
noah.lancien
d7e463006f Limit product title to 25 chars in cards 2023-10-04 06:20:04 +00:00
Noah Lancien
eb21bebe4b Products associed recipes are now displayed in the product page 2023-10-03 17:23:23 +02:00
Vincent BRIDIER
897669024e Merge branch 'issue-190' into 'bump-laravel-10'
All the recipe/advice card is now clickable

See merge request ldc-web/projets/le-gaulois-pro!131
2023-10-03 12:58:43 +00:00
Noah Lancien
74c1988f57 All the recipe/advice card is now clickable 2023-10-03 13:01:52 +02:00
Vincent BRIDIER
d46b21e822 Merge branch 'issue-189' into 'bump-laravel-10'
Hide news description for mobiles

See merge request ldc-web/projets/le-gaulois-pro!130
2023-10-03 10:47:22 +00:00
Noah Lancien
249552186b Hide news description for mobiles 2023-10-03 12:22:04 +02:00
Alexis Gousseau
bca2b01bff bump laravel 10 + update with base project 2023-09-27 10:46:07 +02:00
Vincent BRIDIER
b76403ea74 Merge branch 'issue-171' into 'develop'
Removed subtitle for mobiles in ideas.ideas blade

See merge request ldc-web/projets/le-gaulois-pro!127
2023-09-15 14:28:32 +00:00
Noah Lancien
3f1b0c0d03 Removed subtitle for mobiles in ideas.ideas blade 2023-09-04 10:17:59 +02:00
Vincent BRIDIER
5daaec57e8 Merge branch 'develop' into trunk 2023-08-24 11:25:36 +02:00
Vincent BRIDIER
4f224adeeb Merge branch '185-contact-page-success-msg-nok' into 'develop'
Resolve "Contact page success msg NOK"

See merge request ldc-web/projets/le-gaulois-pro!125
2023-08-24 08:46:37 +00:00
nicolas.demangeat
3d922d3f10 Resolve "Contact page success msg NOK" 2023-08-24 08:46:37 +00:00
Vincent BRIDIER
fff8fc8a62 Merge branch '184-contact-delete-item-msg-nok' into 'develop'
update delete item message

See merge request ldc-web/projets/le-gaulois-pro!126
2023-08-24 08:43:44 +00:00
Nicolas Demangeat
5647bb9e0c update delete item message 2023-08-24 09:24:20 +02:00
Vincent BRIDIER
d0a1b5440f Merge branch '183-recipes-card-p' into 'develop'
Resolve "recipes card <p>"

See merge request ldc-web/projets/le-gaulois-pro!124
2023-08-23 14:25:58 +00:00
nicolas.demangeat
fc7bdf2c99 Resolve "recipes card <p>" 2023-08-23 14:25:58 +00:00
Vincent BRIDIER
34d49de8d5 Merge branch '181-respecter-la-terre-br' into 'develop'
Resolve "Respecter la terre <br>"

See merge request ldc-web/projets/le-gaulois-pro!123
2023-08-23 09:08:39 +00:00
nicolas.demangeat
09a7c038f4 Resolve "Respecter la terre <br>" 2023-08-23 09:08:38 +00:00
Vincent BRIDIER
173288ca10
Merge branch 'develop' into trunk 2023-08-22 15:12:48 +02:00
Vincent BRIDIER
c4ffea2b18
fix respecterlaterre blade 2023-08-21 17:41:40 +02:00
Vincent BRIDIER
fa58eedb8c
Merge branch 'develop' into trunk 2023-08-21 15:06:55 +02:00
Vincent BRIDIER
918c60a2ca
fix front 2023-08-21 15:06:44 +02:00
Vincent BRIDIER
e6ab8c37dc
Merge branch 'develop' into trunk 2023-08-21 13:53:54 +02:00
Vincent BRIDIER
f467f9742b
Remove status checker 2023-08-21 13:03:11 +02:00
Vincent BRIDIER
e9a4c43e28
update for CICD 2023-08-21 12:58:38 +02:00
Vincent BRIDIER
5c1fa2273c Merge branch 'issue-176' into 'develop'
fix wording - issue #176

See merge request ldc-web/projets/le-gaulois-pro!119
2023-08-18 15:41:27 +00:00
Alexis Gousseau
fb00c49c47 fix wording - issue #176 2023-08-18 15:41:27 +00:00
Vincent BRIDIER
e0855e8850 Merge branch 'safari-issues' into 'develop'
Safari issues

See merge request ldc-web/projets/le-gaulois-pro!116
2023-08-18 15:29:37 +00:00
Vincent BRIDIER
8532a7e6d4 Merge branch 'issue-159' into 'develop'
fix ckeditor class - issue #159

See merge request ldc-web/projets/le-gaulois-pro!108
2023-08-18 15:25:46 +00:00
Alexis Gousseau
72cf44275d fix ckeditor class - issue #159 2023-08-18 15:25:46 +00:00
Vincent BRIDIER
8283cfcce5 Merge branch 'issue-178' into 'develop'
fix wording - issue #178

See merge request ldc-web/projets/le-gaulois-pro!120
2023-08-18 07:57:32 +00:00
Alexis Gousseau
f3ec6eba60 fix wording - issue #178 2023-08-18 07:57:32 +00:00
Vincent BRIDIER
dca0e5ef2e Merge branch 'issue-177' into 'develop'
fix wording - issue #177

See merge request ldc-web/projets/le-gaulois-pro!121
2023-08-18 06:59:56 +00:00
Alexis Gousseau
0f6266f897 fix wording - issue #177 2023-08-18 06:59:56 +00:00
Vincent BRIDIER
80a674b025 Merge branch 'issue-179' into 'develop'
fix wording - issue #179

See merge request ldc-web/projets/le-gaulois-pro!122
2023-08-18 06:52:07 +00:00
Alexis Gousseau
7667da6e82 fix wording - issue #179 2023-08-17 15:56:49 +02:00
Jérémy Munsch
1d5d6f2775
Fixes #166 Front slider bad rending on Big screen Safari 2023-08-14 10:00:08 +02:00
Jérémy Munsch
455ac7db3d
Fix Home swiper max height 2023-08-14 09:52:42 +02:00
Jérémy Munsch
705625662d
composer http discovery 2023-08-14 09:26:50 +02:00
Jérémy Munsch
7bd109b90c
Fixes #167 footer popins translation on IOS 2023-08-14 09:26:35 +02:00
Vincent BRIDIER
90cd142a90 Merge branch 'fix-legals' into 'develop'
Fix for missing link on legals blade

See merge request ldc-web/projets/le-gaulois-pro!117
2023-08-11 08:00:17 +00:00
Jérémy Munsch
659beafc3c
Fix for missing link on legals blade 2023-08-10 15:14:24 +02:00
Vincent BRIDIER
7521904d00 Merge branch 'issue-171' into 'develop'
Issue 171

See merge request ldc-web/projets/le-gaulois-pro!111
2023-08-09 15:03:46 +00:00
Jérémy Munsch
c5f27ef20a
Add other product navigation element 2023-08-09 14:36:23 +02:00
Vincent BRIDIER
d6104941bd Merge branch 'issue-168' into 'develop'
Issue 168

See merge request ldc-web/projets/le-gaulois-pro!115
2023-08-09 12:21:24 +00:00
Jérémy Munsch
5008555bc8 Fixes #168 Fix typo in commercial sheets 2023-08-09 12:21:24 +00:00
Vincent BRIDIER
0968185b91 Merge branch 'fix-product-show-404' into 'develop'
Fix product show 404

See merge request ldc-web/projets/le-gaulois-pro!113
2023-08-09 12:15:23 +00:00
Vincent BRIDIER
5c532d0b03 Merge branch 'slider-font' into 'develop'
Fix slide font size in vw <(O_o)> Broooooo

See merge request ldc-web/projets/le-gaulois-pro!112
2023-08-09 12:11:50 +00:00
Jérémy Munsch
703dc3f57d
Add frontend*minimal for error pages in front 2023-08-09 12:35:14 +02:00
Jérémy Munsch
0ee3a25e63
Fix slide font size in vw <(O_o)> Broooooo 2023-08-09 12:27:17 +02:00
Jérémy Munsch
e3c344188b
Fixes #171 link products cards are not responsive /notre-expertise-volaillere/poulet 2023-08-09 12:19:01 +02:00
Vincent BRIDIER
186bcf4d5b Merge branch 'issue-172' into 'develop'
Issue 172

See merge request ldc-web/projets/le-gaulois-pro!110
2023-08-09 09:42:15 +00:00
Jérémy Munsch
a1ff80a16b
Fixes #172 Add slider max content length + remove slider max height breaking front slider display 2023-08-09 11:39:04 +02:00
Jérémy Munsch
27458eae63
npm audit fix 2023-08-09 11:39:04 +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
553 changed files with 40364 additions and 37782 deletions

5
.browserslistrc Normal file
View file

@ -0,0 +1,5 @@
# Browsers that we support
last 3 version
> 0.3%
not dead # no browsers without security updates

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
.git
*.md

View file

@ -3,9 +3,9 @@ root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
@ -14,8 +14,8 @@ trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[*.vue]
indent_size = 2
[docker-compose.yml]
indent_size = 4
[*.vue]
indent_size = 2

View file

@ -43,9 +43,21 @@ GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=
# LDC Web Status Checker
LDC_STATUS_CHECKER_API_KEY="MY_KEY"
LDC_STATUS_CHECKER_UUID="UUID_PROJECT"
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
# LDC Web Admin Secure
LDC_WEB_API_KEY="MY_KEY"
# 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=

View file

@ -29,7 +29,14 @@
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"ignorePatterns": [
"webpack.mix.js",
"**/*.php",
"**/*.svg",
"**/vendor/*.js",
"**/vendor/**/*.js",
"**/vendor/**/*.scss"
],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
@ -37,5 +44,19 @@
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".vue", ".ts", ".d.ts"]
},
"alias": {
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
"map": [
["~", "./resources/assets"],
["@", "./node_modules"]
]
}
}
}
}

3
.gitattributes vendored
View file

@ -1,4 +1,4 @@
* text=auto
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
@ -8,3 +8,4 @@
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

46
.gitignore vendored
View file

@ -1,21 +1,41 @@
/node_modules
/public/hot
/public/storage
/public/build
/public/medias
/public/uploads
/public/cache
/public/sitemap.xml
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
.stylelintcache
/.fleet
/.idea
/.phpunit.cache
/.vscode
/node_modules
/bootstrap
/public/build
/public/cache
/public/hot
/public/sitemap.xml
/public/storage
/storage/*.key
/vendor
auth.json
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode
auth.json
.npmrc
.zshrc
.npm/
.composer/
# Ignore public favicon
/public/android-chrome-192x192.png
/public/android-chrome-512x512.png
/public/apple-touch-icon.png
/public/browserconfig.xml
/public/browserconfig.xml
/public/favicon-16x16.png
/public/favicon-32x32.png
/public/favicon.ico
/public/mstile-150x150.png
/public/safari-pinned-tab.svg
/public/site.webmanifest

View file

@ -1,13 +1,13 @@
include:
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'lint/lint7.3.yml'
file: 'lint/lint.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'security/security7.3.yml'
file: 'security/security.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'build/build7.3.yml'
file: 'build/docker-multistage.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'test/test.yml'
@ -21,4 +21,4 @@ stages:
- security
- build
- test
- deploy
- deploy

View file

@ -4,8 +4,13 @@
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": ["stylelint-scss"],
"files": ["**/*.css", "**/*.scss"],
"plugins": [
"stylelint-scss"
],
"files": [
"**/*.css",
"**/*.scss"
],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
@ -18,13 +23,14 @@
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"declaration-property-value-no-unknown": true,
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"declaration-colon-newline-after": null,
"value-list-comma-newline-after": null,
"scss/double-slash-comment-empty-line-before": [
"always",
{

View file

@ -1,5 +1,4 @@
{
"peacock.remoteColor": "#006ea3",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#0090d6",
"activityBar.background": "#0090d6",
@ -18,5 +17,9 @@
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#006ea399",
"titleBar.inactiveForeground": "#e7e7e799"
}
},
"peacock.remoteColor": "#006ea3",
"intelephense.environment.phpVersion": "8.2.0",
"intelephense.completion.fullyQualifyGlobalConstantsAndFunctions": true,
"intelephense.phpdoc.useFullyQualifiedNames": true
}

View file

@ -1,10 +1,22 @@
# 01/08/2022
init
# Changelog
## 18/12/23
Fixes + bump front stack
## 18/09/2023
bump laravel 10 + update with base project
## 01/08/2022
init
with packages :
- kwaadpepper/exception-handler
- kwaadpepper/refresh-sitemap
- ldc/web-admin-secure-laravel
- ldc/web-status-checker-laravel
- ldc/web-status-checker-laravel
with Google Services keys
with gitlab-ci config file
with Google Services keys
with gitlab-ci config file

View file

@ -1,4 +1,47 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-7.3:latest
COPY . /app/srcs
COPY ./public/build /app/srcs/public/build
COPY ./vendor /app/srcs/vendor
ARG PHP_8_5_TAG=latest
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
RUN chmod -R 755 /app/srcs/storage
USER ${USER}

View file

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

View file

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

View file

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

View file

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

View file

@ -2,40 +2,24 @@
namespace App\Enums\Advices;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self easy()
* @method static self medium()
* @method static self hard()
*/
final class AdviceDifficultyEnum extends BaseEnum
enum AdviceDifficultyEnum: int
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'easy' => 0,
'medium' => 1,
'hard' => 2
];
}
use BaseEnum;
case easy = 0;
case medium = 1;
case hard = 2;
/**
* Enum labels
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'easy' => trans('Facile'),
'medium' => trans('Moyen'),
'hard' => trans('Difficile')
];
}
private const LABELS = [
self::easy->name => 'Facile',
self::medium->name => 'Moyen',
self::hard->name => 'Difficile',
];
}

View file

@ -2,70 +2,46 @@
namespace App\Enums\Advices;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self first()
* @method static self second()
* @method static self third()
* @method static self fourth()
*/
final class AdviceTimeEnum extends BaseEnum
enum AdviceTimeEnum: int
{
use BaseEnum;
case first = 1;
case second = 2;
case third = 3;
case fourth = 4;
/**
* Enum values
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::first->name => '0 - 30min',
self::second->name => '31min - 2h',
self::third->name => '2h - 4h',
self::fourth->name => '> 4h',
];
/**
* Optionnal labels definition.
*/
private const TIMES = [
'first' => [0, 30],
'second' => [31, 120],
'third' => [120, 240],
'fourth' => [240, 1440]
];
/**
* Get time.
*
* @return array
*/
protected static function values(): array
public function times(): array
{
return [
'first' => 1,
'second' => 2,
'third' => 3,
'fourth' => 4,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'first' => trans('0 - 30min'),
'second' => trans('31min - 2h'),
'third' => trans('2h - 4h'),
'fourth' => trans('> 4h'),
];
}
/**
* Enum times
*
* @return array
*/
protected static function times(): array
{
return [
'first' => [0, 30],
'second' => [31, 120],
'third' => [120, 240],
'fourth' => [240, 1440]
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'times') {
return $this->times()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
return self::TIMES[$this->name];
}
}

View file

@ -2,40 +2,24 @@
namespace App\Enums\Advices;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self all()
* @method static self recipes()
* @method static self tips()
*/
final class AdviceTypeEnum extends BaseEnum
enum AdviceTypeEnum: string
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'all' => 0,
'recipes' => 1,
'tips' => 2,
];
}
use BaseEnum;
case all = '0';
case recipes = '1';
case tips = '2';
/**
* Enum labels
* Optionnal labels definition
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'all' => trans('Recettes / Astuces'),
'recipes' => trans('Recettes'),
'tips' => trans('Astuces'),
];
}
private const LABELS = [
self::all->name => 'Recettes / Astuces',
self::recipes->name => 'Recettes',
self::tips->name => 'Astuces',
];
}

View file

@ -2,49 +2,30 @@
namespace App\Enums\Contact;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self distributeur()
* @method static self restaurationSociale()
* @method static self restaurationCommerciale()
* @method static self boulangeriePatisserie()
* @method static self journaliste()
* @method static self autre()
*/
final class ContactActivityEnum extends BaseEnum
enum ContactActivityEnum: int
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'distributeur' => 1,
'restaurationSociale' => 2,
'restaurationCommerciale' => 3,
'boulangeriePatisserie' => 4,
'journaliste' => 5,
'autre' => 6
];
}
use BaseEnum;
case distributeur = 1;
case restaurationSociale = 2;
case restaurationCommerciale = 3;
case boulangeriePatisserie = 4;
case journaliste = 5;
case autre = 6;
/**
* Enum labels
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'distributeur' => trans('Distributeur'),
'restaurationSociale' => trans('Restauration sociale'),
'restaurationCommerciale' => trans('Restauration commerciale'),
'boulangeriePatisserie' => trans('Boulangerie-pâtisserie'),
'journaliste' => trans('Journaliste'),
'autre' => trans('Autre'),
];
}
private const LABELS = [
self::distributeur->name => 'Distributeur',
self::restaurationSociale->name => 'Restauration sociale',
self::restaurationCommerciale->name => 'Restauration commerciale',
self::boulangeriePatisserie->name => 'Boulangerie-pâtisserie',
self::journaliste->name => 'Journaliste',
self::autre->name => 'Autre',
];
}

View file

@ -2,40 +2,24 @@
namespace App\Enums\Contact;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self morning()
* @method static self afternoon()
* @method static self anytime()
*/
final class ContactTimeEnum extends BaseEnum
enum ContactTimeEnum: int
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'morning' => 1,
'afternoon' => 2,
'anytime' => 3,
];
}
use BaseEnum;
case morning = 1;
case afternoon = 2;
case anytime = 3;
/**
* Enum labels
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'morning' => trans('Le matin'),
'afternoon' => trans('L\'après-midi'),
'anytime' => trans('À tout moment'),
];
}
private const LABELS = [
self::morning->name => 'Le matin',
self::afternoon->name => 'L\'après-midi',
self::anytime->name => 'À tout moment',
];
}

View file

@ -2,46 +2,28 @@
namespace App\Enums\ExpertiseProducts;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self duck()
* @method static self turkey()
* @method static self rabbit()
* @method static self guineafowl()
* @method static self chicken()
*/
final class ExpertiseProductPageEnum extends BaseEnum
enum ExpertiseProductPageEnum: int
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'duck' => 0,
'turkey' => 1,
'rabbit' => 2,
'guineafowl' => 3,
'chicken' => 4,
];
}
use BaseEnum;
case duck = 0;
case turkey = 1;
case rabbit = 2;
case guineafowl = 3;
case chicken = 4;
/**
* Enum labels
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'duck' => trans('Canard'),
'turkey' => trans('Dinde'),
'rabbit' => trans('Lapin'),
'guineafowl' => trans('Pintade'),
'chicken' => trans('Poulet'),
];
}
private const LABELS = [
self::duck->name => 'Canard',
self::turkey->name => 'Dinde',
self::rabbit->name => 'Lapin',
self::guineafowl->name => 'Pintade',
self::chicken->name => 'Poulet',
];
}

View file

@ -2,52 +2,30 @@
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self noLetter()
* @method static self aLetter()
* @method static self bLetter()
* @method static self cLetter()
* @method static self dLetter()
* @method static self eLetter()
*/
final class NutriscoreEnum extends BaseEnum
enum NutriscoreEnum: int
{
// @phpcs:ignore Generic.Commenting.Fixme.TaskFound
// FIXME: faire en sorte de ne pas afficher le nutriscore en front si sa valeur est de 0.
use BaseEnum;
case noLetter = 0;
case aLetter = 1;
case bLetter = 2;
case cLetter = 3;
case dLetter = 4;
case eLetter = 5;
/**
* Enum values
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function values(): array
{
return [
'noLetter' => 0,
'aLetter' => 1,
'bLetter' => 2,
'cLetter' => 3,
'dLetter' => 4,
'eLetter' => 5
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'noLetter' => 'Ø',
'aLetter' => 'A',
'bLetter' => 'B',
'cLetter' => 'C',
'dLetter' => 'D',
'eLetter' => 'E'
];
}
private const LABELS = [
self::noLetter->name => 'Ø',
self::aLetter->name => 'A',
self::bLetter->name => 'B',
self::cLetter->name => 'C',
self::dLetter->name => 'D',
self::eLetter->name => 'E'
];
}

View file

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

View file

@ -2,64 +2,42 @@
namespace App\Enums\Products;
use Kwaadpepper\Enum\BaseEnum;
use App\Traits\Enums\BaseEnum;
/**
* @method static self caille()
* @method static self canard()
* @method static self canette()
* @method static self coq()
* @method static self coquelet()
* @method static self dinde()
* @method static self lapin()
* @method static self pigeonneau()
* @method static self pintade()
* @method static self poule()
* @method static self poulet()
*/
final class ProductSpeciesEnum extends BaseEnum
enum ProductSpeciesEnum: int
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'caille' => 0,
'canard' => 1,
'canette' => 2,
'coq' => 3,
'coquelet' => 4,
'dinde' => 5,
'lapin' => 6,
'pigeonneau' => 7,
'pintade' => 8,
'poule' => 9,
'poulet' => 10,
];
}
use BaseEnum;
case caille = 0;
case canard = 1;
case canette = 2;
case coq = 3;
case coquelet = 4;
case dinde = 5;
case lapin = 6;
case pigeonneau = 7;
case pintade = 8;
case poule = 9;
case poulet = 10;
case volaille_festive = 11;
/**
* Enum labels
* Optionnal labels definition.
*
* @return array
* @phpstan-ignore-next-line
*/
protected static function labels(): array
{
return [
'caille' => trans('Caille'),
'canard' => trans('Canard'),
'canette' => trans('Canette'),
'coq' => trans('Coq'),
'coquelet' => trans('Coquelet'),
'dinde' => trans('Dinde'),
'lapin' => trans('Lapin'),
'pigeonneau' => trans('Pigeonneau'),
'pintade' => trans('Pintade'),
'poule' => trans('Poule'),
'poulet' => trans('Poulet')
];
}
private const LABELS = [
self::caille->name => 'Caille',
self::canard->name => 'Canard',
self::canette->name => 'Canette',
self::coq->name => 'Coq',
self::coquelet->name => 'Coquelet',
self::dinde->name => 'Dinde',
self::lapin->name => 'Lapin',
self::pigeonneau->name => 'Pigeonneau',
self::pintade->name => 'Pintade',
self::poule->name => 'Poule',
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,18 +3,26 @@
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [];
/**
* A list of the inputs that are never flashed for validation exceptions.
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
@ -31,7 +39,7 @@ class Handler extends ExceptionHandler
*/
public function register()
{
$this->reportable(function (\Throwable $e) {
$this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}

View file

@ -3,134 +3,142 @@
namespace App\Http\Controllers\Bo;
use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Advice\StoreAdviceRequest;
use App\Http\Requests\Bo\Advice\UpdateAdviceRequest;
use App\Models\Advice;
use App\Models\Product;
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\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AdviceController extends Controller
{
use UpdateModelStatus;
use UpdateModelPublished;
use ManagesOrder;
/**
* Display a listing of the resource.
*
* @param Request $request
* @return \Illuminate\Http\Response
* @param \Illuminate\Http\Request $request
* @param \App\Enums\Advices\AdviceTypeEnum|null $type
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request, AdviceTypeEnum|null $type = null): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $advice */
$advice = Advice::query();
$type = $request->type;
$currentType = AdviceTypeEnum::make(\intval($type));
$type = $type ?: AdviceTypeEnum::all;
/** @var \Illuminate\Database\Eloquent\Builder $adviceModels */
$adviceModels = Advice::query();
$advice->when(isset($type), function ($advice) use ($currentType) {
$advice->where('type', '=', $currentType->value);
});
$adviceModels->when(!$type->equals(AdviceTypeEnum::all), fn (Builder $q) => $q->where('type', $type));
if ($request->search) {
/** @var string|null $search Search field */
$search = $request->input('search');
if ($search) {
$this->searchQuery(
$advice,
$request->search,
$adviceModels,
$search,
null,
'title'
);
}
$this->sortQuery($advice);
$searchFields = trans('Titre');
$advice = $advice->paginate(10);
/** Sort columns with a query */
$this->sortQuery($adviceModels);
return view('bo.pages.advice.index', compact('advice', 'currentType', 'searchFields'));
/** Custom pagination */
$adviceModels = $this->paginate($adviceModels);
return view('bo.pages.advice.index', [
'adviceModels' => $adviceModels,
'currentType' => $type,
'search' => $search,
'searchFields' => $searchFields
]);
}
/**
* Show the form for creating a new resource.
*
* @param Request $request
* @return \Illuminate\Http\Response
* @param \App\Enums\Advices\AdviceTypeEnum $type
* @return \Illuminate\Contracts\View\View
*/
public function create(Request $request)
public function create(AdviceTypeEnum $type): \Illuminate\Contracts\View\View
{
$advice = new Advice();
$type = $request->type;
$currentType = AdviceTypeEnum::make(\intval($type));
$adviceTags = Tag::query()->select(['id', 'label', 'slug'])->get();
$adviceTags = Tag::query()->where('type', TagTypeEnum::recipes)->select(['id', 'label', 'slug'])->get();
$adviceProducts = Product::all();
$recipeList = Advice::where('type', '=', AdviceTypeEnum::recipes())->get();
$tipList = Advice::where('type', '=', AdviceTypeEnum::tips())->get();
$recipeList = Advice::query()->where('type', '=', AdviceTypeEnum::recipes)->get();
$tipList = Advice::query()->where('type', '=', AdviceTypeEnum::tips)->get();
$recipeList = collect($recipeList)->each(function (\App\Models\Advice &$value) {
$value->label = $value->title;
});
$tipList = collect($tipList)->each(function (\App\Models\Advice &$value) {
$value->label = $value->title;
});
$recipesSelected = collect([]);
$tipsSelected = collect([]);
collect($advice->advices)->each(function ($eltAdvice) use (&$recipesSelected, &$tipsSelected) {
if ($eltAdvice->type->value == AdviceTypeEnum::recipes()->value) {
$eltAdvice->label = $eltAdvice->title;
$recipesSelected = $recipesSelected->concat([$eltAdvice]);
} elseif ($eltAdvice->type->value == AdviceTypeEnum::tips()->value) {
$eltAdvice->label = $eltAdvice->title;
$tipsSelected = $tipsSelected->concat([$eltAdvice]);
}
});
$recipesSelected = collect();
$tipsSelected = collect();
collect($advice->advices)
// @phpstan-ignore-next-line
->each(function (Advice $eltAdvice) use (&$recipesSelected, &$tipsSelected) {
if ($eltAdvice->type->equals(AdviceTypeEnum::recipes)) {
$recipesSelected = $recipesSelected->concat([$eltAdvice]);
} elseif ($eltAdvice->type->equals(AdviceTypeEnum::tips)) {
$tipsSelected = $tipsSelected->concat([$eltAdvice]);
}
});
return view('bo.pages.advice.create', [
'advice' => $advice,
'currentType' => $currentType,
'adviceTags' => $adviceTags,
'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList,
'tipList' => $tipList,
'adviceModel' => $advice,
'currentType' => $type,
'adviceTags' => $adviceTags,
'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList,
'tipList' => $tipList,
'recipesSelected' => $recipesSelected,
'tipsSelected' => $tipsSelected,
'tipsSelected' => $tipsSelected,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Advice\StoreAdviceRequest $request
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\Advice\StoreAdviceRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreAdviceRequest $request)
public function store(StoreAdviceRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$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));
if ($advice->saveOrFail()) {
$advice->tags()->sync(collect($request->tags)->pluck('id'));
$advice->products()->sync($request->products_list);
$advice->advices()->sync(array_merge($request->recipes_list, $request->tips_list));
$advice->tags()->sync(collect($request->input('tags'))->pluck('id'));
$advice->products()->sync($request->input('products_list'));
$advice->advices()->sync(array_merge($request->input('recipes_list'), $request->input('tips_list')));
return redirect()
->route('bo.advice.edit', $advice)
->with('success', trans(
':model a été créé',
['model' => Str::singular($currentType->label)]
->with('success', __(
'crud.messages.has_been_created',
['model' => Str::singular($currentType->label())]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular($currentType->label)]
->with('error', __(
'crud.messages.cannot_be_created',
['model' => Str::singular($currentType->label())]
));
});
}
@ -139,69 +147,55 @@ class AdviceController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Advice $advice
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Advice $advice)
public function edit(Advice $advice): \Illuminate\Contracts\View\View
{
$currentType = $advice->type;
$adviceTags = Tag::whereNotIn('id', $advice->tags()->pluck('id'))
$adviceTags = Tag::query()->where('type', TagTypeEnum::recipes)
->select(['id', 'label', 'slug'])->get();
$adviceProducts = Product::all();
$recipeList = Advice::where([
['type', '=', AdviceTypeEnum::recipes()],
$recipeList = Advice::query()->where([
['type', '=', AdviceTypeEnum::recipes],
['id', '!=', $advice->id],
])->get();
$tipList = Advice::where([
['type', '=', AdviceTypeEnum::tips()],
$tipList = Advice::query()->where([
['type', '=', AdviceTypeEnum::tips],
['id', '!=', $advice->id],
])->get();
$recipeList = collect($recipeList)->each(function (\App\Models\Advice &$value) {
$value->label = $value->title;
});
$tipList = collect($tipList)->each(function (\App\Models\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', [
'advice' => $advice,
'currentType' => $currentType,
'adviceTags' => $adviceTags,
'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList,
'tipList' => $tipList,
'recipesSelected' => $recipesSelected,
'tipsSelected' => $tipsSelected,
'adviceModel' => $advice,
'currentType' => $currentType,
'adviceTags' => $adviceTags,
'adviceProducts' => $adviceProducts,
'recipeList' => $recipeList,
'tipList' => $tipList,
'recipesSelected' => $advice->recipes,
'tipsSelected' => $advice->tips,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Advice\UpdateAdviceRequest $request
* @param \App\Models\Advice $advice
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\Advice\UpdateAdviceRequest $request
* @param \App\Models\Advice $advice
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateAdviceRequest $request, Advice $advice)
public function update(UpdateAdviceRequest $request, Advice $advice): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $advice) {
$advice->tags()->sync(collect($request->tags)->pluck('id'));
$advice->products()->sync($request->products_list);
$advice->advices()->sync(array_merge($request->recipes_list, $request->tips_list));
$advice->tags()->sync(collect($request->input('tags'))->pluck('id'));
$advice->products()->sync($request->input('products_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));
@ -209,16 +203,16 @@ class AdviceController extends Controller
return redirect()
->route('bo.advice.edit', $advice)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular($currentType->label)]
'crud.messages.has_been_updated',
['model' => Str::singular($currentType->label())]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular($currentType->label)]
'crud.messages.cannot_be_updated',
['model' => Str::singular($currentType->label())]
));
});
}
@ -226,32 +220,32 @@ class AdviceController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Advice $advice
* @return \Illuminate\Http\Response
* @param \App\Models\Advice $advice
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Advice $advice)
public function destroy(Advice $advice): \Illuminate\Http\RedirectResponse
{
DB::table('advice_advice')->where($advice->advices()->getRelatedPivotKeyName(), $advice->id)->delete();
$advice->tags()->sync([]);
$advice->products()->sync([]);
$advice->advices()->sync([]);
$currentType = AdviceTypeEnum::make(\intval($advice->type->value));
$currentType = AdviceTypeEnum::make(\intval($advice->type->value()));
if ($advice->deleteOrFail()) {
return redirect()
->route('bo.advice.index', ['type' => $currentType])
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular($currentType->label)]
'crud.messages.has_been_deleted',
['model' => Str::singular($currentType->label())]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular($currentType->label)]
'crud.messages.cannot_be_deleted',
['model' => Str::singular($currentType->label())]
));
}
}

View file

@ -3,24 +3,14 @@
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class LoginController extends Controller
{
use AuthenticatesUsers;
use ThrottlesLogins;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
@ -32,68 +22,33 @@ class LoginController extends Controller
$this->middleware('guest:backend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard('backend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
* @return \Illuminate\Contracts\View\View
*/
public function showLoginForm()
public function showLoginForm(): View
{
return view('bo.auth.login');
}
/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
* @return boolean
*/
protected function attemptLogin(Request $request)
{
return Auth::guard('backend')->attempt(
$this->credentials($request),
$request->filled('remember')
);
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param null $user
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
protected function authenticated(Request $request, $user)
{
/** @var \App\Models\User|null */
$user = Auth::guard('backend')->user();
if ($user) {
$user->last_connection = now();
$user->saveOrFail();
Session::flash('info', trans('Vous êtes à présent connecté'));
}
}
/**
* The user has logged out of the application.
*
* @param \Illuminate\Http\Request $request
* @return mixed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function loggedOut(Request $request)
{
Session::flash('info', trans('Vous êtes à présent déconnecté'));
}
/**
* Get the post register / login redirect path.
* Where to redirect users after login.
*
* @return string
*/
public function redirectPath()
protected function redirectTo(): string
{
return response()->redirectToRoute('bo.dashboard')->getTargetUrl();
return response()->redirectToIntended()->getTargetUrl();
}
}

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

@ -16,62 +16,68 @@ class ExpertiseProductController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $vips */
$vips = ExpertiseProduct::query();
/** @var \Illuminate\Database\Eloquent\Builder $vipModels */
$vipModels = ExpertiseProduct::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$vips,
$request->search,
$vipModels,
$search,
null,
'label',
'title'
);
}
$this->sortQuery($vips);
$searchFields = trans('Libellé, Titre');
$vips = $vips->paginate(10);
/** Sort columns with a query */
$this->sortQuery($vipModels);
return view('bo.pages.vips.index', compact('vips', 'searchFields'));
/** Custom pagination */
$vipModels = $this->paginate($vipModels);
return view('bo.pages.vips.index', compact('vipModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(ExpertiseProduct $vip): \Illuminate\Contracts\View\View
{
return view('bo.pages.vips.create');
return view('bo.pages.vips.create', ['vipModel' => $vip]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\ExpertiseProducts\StoreExpertiseProductRequest $request
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\ExpertiseProducts\StoreExpertiseProductRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreExpertiseProductRequest $request)
public function store(StoreExpertiseProductRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$vip = new ExpertiseProduct();
$vip->fill($request->validated());
if ($vip->saveOrFail()) {
return redirect()
->route('bo.vips.edit', $vip)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.vips'))]
));
return redirect()->route('bo.vips.edit', $vip->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
}
return back();
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
});
}
@ -79,55 +85,57 @@ class ExpertiseProductController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(ExpertiseProduct $vip)
public function edit(ExpertiseProduct $vip): \Illuminate\Contracts\View\View
{
return view('bo.pages.vips.edit', [
'vip' => $vip
]);
return view('bo.pages.vips.edit', ['vipModel' => $vip]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\ExpertiseProduct\UpdateExpertiseProductRequest $request
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\ExpertiseProducts\UpdateExpertiseProductRequest $request
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateExpertiseProductRequest $request, ExpertiseProduct $vip)
{
public function update(
UpdateExpertiseProductRequest $request,
ExpertiseProduct $vip
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $vip) {
$vip->fill($request->validated());
if ($vip->saveOrFail()) {
return redirect()
->route('bo.vips.edit', $vip)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.vips'))]
));
return redirect()->route('bo.vips.edit', $vip->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
}
return back();
return redirect()->route('bo.vips.edit', $vip->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\Response
* @param \App\Models\ExpertiseProduct $vip
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(ExpertiseProduct $vip)
public function destroy(ExpertiseProduct $vip): \Illuminate\Http\RedirectResponse
{
if ($vip->deleteOrFail()) {
return redirect()
->route('bo.vips.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.vips'))]
));
return redirect()->route('bo.vips.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
}
return back();
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::singular(trans_choice('models.classes.vips', 2))
]));
}
}

View file

@ -7,12 +7,12 @@ use App\Http\Controllers\Controller;
class HomeController extends Controller
{
/**
* Dashboard view
* Dashboard view.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Contracts\View\View
*/
public function dashboard()
public function dashboard(): \Illuminate\Contracts\View\View
{
return view('bo.pages.home');
return view('bo.pages.homepage');
}
}

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\NewsCategories\UpdateNewsCategoryRequest;
use App\Models\NewsCategory;
use App\Traits\Models\ChangesModelOrder;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -18,40 +18,44 @@ class NewsCategoryController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $newscategories */
$newscategories = NewsCategory::query();
/** @var \Illuminate\Database\Eloquent\Builder $newscategoryModels */
$newscategoryModels = NewsCategory::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$newscategories,
$request->search,
$newscategoryModels,
$search,
null,
'label'
);
}
$this->sortQuery($newscategories);
$searchFields = trans('Libellé');
$newscategories = $newscategories->get();
/** Sort columns with a query */
$this->sortQuery($newscategoryModels);
return view('bo.pages.newscategories.index', compact('newscategories', 'searchFields'));
/** Custom pagination */
$newscategoryModels = $this->paginate($newscategoryModels);
return view('bo.pages.newscategories.index', compact('newscategoryModels', 'searchFields'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\NewsCategory $newscategory
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(NewsCategory $newscategory)
public function edit(NewsCategory $newscategory): \Illuminate\Contracts\View\View
{
return view('bo.pages.newscategories.edit', [
'newscategory' => $newscategory
'newscategoryModel' => $newscategory
]);
}
@ -60,22 +64,25 @@ class NewsCategoryController extends Controller
*
* @param \App\Http\Requests\Bo\NewsCategories\UpdateNewsCategoryRequest $request
* @param \App\Models\NewsCategory $newscategory
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateNewsCategoryRequest $request, NewsCategory $newscategory)
{
public function update(
UpdateNewsCategoryRequest $request,
NewsCategory $newscategory
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $newscategory) {
$newscategory->fill($request->validated());
if ($newscategory->saveOrFail()) {
return redirect()
->route('bo.newscategories.edit', $newscategory)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.newscategories'))]
));
return redirect()->route('bo.newscategories.edit', $newscategory->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.newscategories'))
]));
}
return redirect()->back();
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(__('models.classes.newscategories'))
]));
});
}
}

View file

@ -8,30 +8,32 @@ use App\Http\Requests\Bo\News\UpdateNewsRequest;
use App\Models\News;
use App\Models\NewsCategory;
use App\Models\Product;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class NewsController extends Controller
{
use UpdateModelStatus;
use UpdateModelPublished;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $news */
$news = News::query();
/** @var \Illuminate\Database\Eloquent\Builder $newsModels */
$newsModels = News::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$news,
$request->search,
$newsModels,
$search,
null,
'title',
'subtitle',
@ -39,71 +41,74 @@ class NewsController extends Controller
'content2'
);
}
$this->sortQuery($news);
$searchFields = trans('Titre, Sous-titre, Contenus');
$news = $news->paginate(10);
/** Sort columns with a query */
$this->sortQuery($newsModels);
return view('bo.pages.news.index', compact('news', 'searchFields'));
/** Custom pagination */
$newsModels = $this->paginate($newsModels);
return view('bo.pages.news.index', compact('newsModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @param \App\Models\News $news
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(News $news): \Illuminate\Contracts\View\View
{
$news = new News();
$news = $news->exists() ? $news : new News();
$newsCategories = NewsCategory::all();
$newsProducts = Product::all();
$newsList = News::all();
$newsList = collect($newsList)->each(function (\App\Models\News &$value) {
$value->label = $value->title;
$newsList = collect($newsList)->each(function (News $value) {
$value->setAttribute('label', $value->title);
});
$newsSelected = collect([]);
collect($news->news ?? [])->each(function (\App\Models\News &$value) use (&$newsSelected) {
$value->label = $value->title;
collect($news->news ?? [])->each(function (News $value) use (&$newsSelected) {
$value->setAttribute('label', $value->title);
$newsSelected = $newsSelected->concat([$value]);
});
return view('bo.pages.news.create', compact('newsCategories', 'newsProducts', 'newsList'));
return view('bo.pages.news.create', [
'newsModel' => $news,
'newsCategories' => $newsCategories,
'newsProducts' => $newsProducts,
'newsList' => $newsList,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\News\StoreNewsRequest $request
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\News\StoreNewsRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreNewsRequest $request)
public function store(StoreNewsRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$news = new News();
$news->fill($request->validated());
$news->fill($request->safe()->except(['categories', 'products', 'associated_news_list']));
if ($news->saveOrFail()) {
$news->categories()->sync($request->categories_list);
$news->products()->sync($request->products_list);
$news->news()->sync($request->news_list);
return redirect()
->route('bo.news.edit', $news)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->route('bo.news.edit', $news->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.news'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(__('models.classes.news'))
]));
});
}
@ -111,73 +116,68 @@ class NewsController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\News $news
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(News $news)
public function edit(News $news): \Illuminate\Contracts\View\View
{
$newsCategories = NewsCategory::all();
$newsProducts = Product::all();
$newsList = News::where('id', '!=', $news->id)->get();
$newsList = collect($newsList)->each(function (\App\Models\News &$value) {
$value->label = $value->title;
$newsList = collect($newsList)->each(function (News $value) {
$value->setAttribute('label', $value->title);
});
$newsSelected = collect([]);
collect($news->news ?? [])->each(function (\App\Models\News &$value) use (&$newsSelected) {
$value->label = $value->title;
collect($news->news ?? [])->each(function (News $value) use (&$newsSelected) {
$value->setAttribute('label', $value->title);
$newsSelected = $newsSelected->concat([$value]);
});
return view('bo.pages.news.edit', [
'news' => $news,
'newsModel' => $news,
'newsCategories' => $newsCategories,
'newsProducts' => $newsProducts,
'newsList' => $newsList,
'newsProducts' => $newsProducts,
'newsList' => $newsList,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\News\UpdateNewsRequest $request
* @param \App\Models\News $news
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\News\UpdateNewsRequest $request
* @param \App\Models\News $news
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateNewsRequest $request, News $news)
public function update(UpdateNewsRequest $request, News $news): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $news) {
$news->categories()->sync($request->categories_list);
$news->products()->sync($request->products_list);
$news->news()->sync($request->news_list);
$news->fill($request->validated());
$news->fill($request->safe()->except(['categories', 'products', 'associated_news_list']));
if ($news->saveOrFail()) {
return redirect()
->route('bo.news.edit', $news)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->route('bo.news.edit', $news->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.news'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->route('bo.news.edit', $news->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.news'))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\News $news
* @return \Illuminate\Http\Response
* @param \App\Models\News $news
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(News $news)
public function destroy(News $news): \Illuminate\Http\RedirectResponse
{
DB::table('news_news')->where($news->news()->getRelatedPivotKeyName(), $news->id)->delete();
$news->categories()->sync([]);
@ -185,19 +185,14 @@ class NewsController extends Controller
$news->news()->sync([]);
if ($news->deleteOrFail()) {
return redirect()
->route('bo.news.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->route('bo.news.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::singular(__('models.classes.news'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.news'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::singular(__('models.classes.news'))
]));
}
}

View file

@ -2,92 +2,110 @@
namespace App\Http\Controllers\Bo;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Product;
use App\Models\Range;
use App\Models\Subrange;
use App\Traits\Controllers\HasPicture;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Models\ChangesModelOrder;
use App\Models\Tag;
use App\Traits\Controllers\UpdateModelPublished;
use App\Traits\Controllers\ManagesOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ProductController extends Controller
{
use ChangesModelOrder;
use UpdateModelStatus;
use HasPicture;
use UpdateModelPublished;
use ManagesOrder;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $products */
$products = Product::query();
/** @var \Illuminate\Database\Eloquent\Builder $productModels */
$productModels = Product::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$products,
$request->search,
$productModels,
$search,
null,
'label'
);
}
$this->sortQuery($products);
if ($request->range) {
$productModels->whereHas('subrange', function ($q) use ($request) {
$q->where('subranges.range_id', '=', $request->range);
})->get();
}
$searchFields = trans('Label');
$products = $products->paginate(10);
/** Sort columns with a query */
$this->sortQuery($productModels);
return view('bo.pages.products.index', compact('products', 'searchFields'));
/** Custom pagination */
$productModels = $this->paginate($productModels);
$ranges = Range::all();
return view('bo.pages.products.index', compact('productModels', 'search', 'searchFields', 'ranges'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @param \App\Models\Product $product
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(Product $product): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $subranges */
$subranges = Subrange::get();
/** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */
$subrangeModels = Subrange::get();
$productTags = Tag::query()->where('type', TagTypeEnum::products)->select(['id', 'label', 'slug'])->get();
return view('bo.pages.products.create', compact('subranges'));
return view('bo.pages.products.create', [
'productModel' => $product,
'subrangeModels' => $subrangeModels,
'productTags' => $productTags,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreProductRequest $request)
public function store(StoreProductRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$product = new Product();
$product->fill($request->validated());
$product->fill($request->safe()->except('tags'));
$this->storePictures($request, $product);
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.products'))]
));
$product->tags()->sync(collect($request->input('tags'))->pluck('id'));
return redirect()->route('bo.products.edit', $product->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.products'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.products'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(__('models.classes.products'))
]));
});
}
@ -95,14 +113,21 @@ class ProductController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Product $product)
public function edit(Product $product): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $subranges */
$subranges = Subrange::get();
/** @var \Illuminate\Database\Eloquent\Builder $subrangeModels */
$subrangeModels = Subrange::get();
return view('bo.pages.products.edit', ['product' => $product], compact('subranges'));
$productTags = Tag::query()->where('type', TagTypeEnum::products)
->select(['id', 'label', 'slug'])->get();
return view('bo.pages.products.edit', [
'productModel' => $product,
'subrangeModels' => $subrangeModels,
'productTags' => $productTags,
]);
}
/**
@ -110,55 +135,67 @@ class ProductController extends Controller
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateProductRequest $request, Product $product)
public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $product) {
$product->fill($request->validated());
$product->tags()->sync(collect($request->input('tags'))->pluck('id'));
$product->fill($request->safe()->except('tags'));
$this->storePictures($request, $product);
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.products'))]
));
return redirect()->route('bo.products.edit', $product->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.products'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.products'))]
));
return redirect()->route('bo.products.edit', $product->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.products'))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
* @param \App\Models\Product $product
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Product $product)
public function destroy(Product $product): \Illuminate\Http\RedirectResponse
{
$product->advices()->sync([]);
$product->news()->sync([]);
$product->tags()->sync([]);
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.products'))]
));
return redirect()->route('bo.products.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::singular(__('models.classes.products'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'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();
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.products'))]
));
}
}

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Range;
use App\Traits\Models\ChangesModelOrder;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -18,41 +18,43 @@ class RangeController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $ranges */
$ranges = Range::query();
/** @var \Illuminate\Database\Eloquent\Builder $rangeModels */
$rangeModels = Range::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$ranges,
$request->search,
$rangeModels,
$search,
null,
'label'
);
}
$this->sortQuery($ranges);
$searchFields = trans('Libellé');
$ranges = $ranges->paginate(10);
/** Sort columns with a query */
$this->sortQuery($rangeModels);
return view('bo.pages.ranges.index', compact('ranges', 'searchFields'));
/** Custom pagination */
$rangeModels = $this->paginate($rangeModels);
return view('bo.pages.ranges.index', compact('rangeModels', 'search', 'searchFields'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Range $range)
public function edit(Range $range): \Illuminate\Contracts\View\View
{
return view('bo.pages.ranges.edit', [
'range' => $range
]);
return view('bo.pages.ranges.edit', ['rangeModel' => $range]);
}
/**
@ -60,22 +62,23 @@ class RangeController extends Controller
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateRangeRequest $request, Range $range)
public function update(UpdateRangeRequest $request, Range $range): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.ranges'))]
));
return redirect()->route('bo.ranges.edit', $range->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.ranges'))
]));
}
return redirect()->back();
return redirect()->route('bo.ranges.edit', $range->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.ranges'))
]));
});
}
}

View file

@ -6,7 +6,8 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Slides\StoreSlideRequest;
use App\Http\Requests\Bo\Slides\UpdateSlideRequest;
use App\Models\Slide;
use App\Traits\Models\ChangesModelOrder;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -14,67 +15,74 @@ use Illuminate\Support\Str;
class SlideController extends Controller
{
use ChangesModelOrder;
use UpdateModelPublished;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $slides */
$slides = Slide::query();
/** @var \Illuminate\Database\Eloquent\Builder $slideModels */
$slideModels = Slide::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$slides,
$request->search,
$slideModels,
$search,
null,
'title',
'content',
);
}
$this->sortQuery($slides);
$searchFields = trans('Titre, Contenu');
$slides = $slides->paginate(10);
/** Sort columns with a query */
$this->sortQuery($slideModels);
return view('bo.pages.slides.index', compact('slides', 'searchFields'));
/** Custom pagination */
$slideModels = $this->paginate($slideModels);
return view('bo.pages.slides.index', compact('slideModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @param \App\Models\Slide $slide
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(Slide $slide): \Illuminate\Contracts\View\View
{
return view('bo.pages.slides.create');
return view('bo.pages.slides.create', ['slideModel' => $slide]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Slides\StoreSlideRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreSlideRequest $request)
public function store(StoreSlideRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$slide = new Slide();
$slide->fill($request->validated());
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.slides'))]
));
return redirect()->route('bo.slides.edit', $slide->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.slides'))
]));
}
return back();
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(__('models.classes.slides'))
]));
});
}
@ -82,13 +90,11 @@ class SlideController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Slide $slide)
public function edit(Slide $slide): \Illuminate\Contracts\View\View
{
return view('bo.pages.slides.edit', [
'slide' => $slide
]);
return view('bo.pages.slides.edit', ['slideModel' => $slide]);
}
/**
@ -96,22 +102,23 @@ class SlideController extends Controller
*
* @param \App\Http\Requests\Bo\Slides\UpdateSlideRequest $request
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateSlideRequest $request, Slide $slide)
public function update(UpdateSlideRequest $request, Slide $slide): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $slide) {
$slide->fill($request->validated());
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.slides'))]
));
return redirect()->route('bo.slides.edit', $slide->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.slides'))
]));
}
return redirect()->back();
return redirect()->route('bo.slides.edit', $slide->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.slides'))
]));
});
}
@ -119,18 +126,19 @@ class SlideController extends Controller
* Remove the specified resource from storage.
*
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Slide $slide)
public function destroy(Slide $slide): \Illuminate\Http\RedirectResponse
{
if ($slide->deleteOrFail()) {
return redirect()
->route('bo.slides.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.slides'))]
));
return redirect()->route('bo.slides.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::singular(__('models.classes.slides'))
]));
}
return redirect()->back()->with('error', trans('Suppression impossible'));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::singular(__('models.classes.slides'))
]));
}
}

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;
use App\Enums\TagTypeEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tags\StoreTagRequest;
use App\Http\Requests\Bo\Tags\UpdateTagRequest;
@ -16,67 +17,69 @@ class TagController extends Controller
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $tags */
$tags = Tag::query();
/** @var \Illuminate\Database\Eloquent\Builder $tagModels */
$tagModels = Tag::query();
$tagModels = $request->type ? $tagModels->where('type', TagTypeEnum::{$request->type}) : $tagModels;
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$tags,
$request->search,
$tagModels,
$search,
null,
'label'
'label',
'type',
);
}
$searchFields = trans('Nom, Type');
$this->sortQuery($tags);
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($tagModels);
$tags = $tags->paginate(10);
/** Custom pagination */
$tagModels = $this->paginate($tagModels);
return view('bo.pages.tags.index', compact('tags', 'searchFields'));
return view('bo.pages.tags.index', compact('tagModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
* @param \App\Models\Tag $tag
* @return \Illuminate\Contracts\View\View
*/
public function create()
public function create(Tag $tag): \Illuminate\Contracts\View\View
{
return view('bo.pages.tags.create');
return view('bo.pages.tags.create', ['tagModel' => $tag]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTagRequest $request)
public function store(StoreTagRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$tag = new Tag();
$tag->fill($request->validated());
if ($tag->saveOrFail()) {
return redirect()
->route('bo.tags.edit', $tag)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->route('bo.tags.edit', $tag->id)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::singular(__('models.classes.tags'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::singular(__('models.classes.tags'))
]));
});
}
@ -84,9 +87,9 @@ class TagController extends Controller
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*/
public function jsonStore(StoreTagRequest $request)
public function jsonStore(StoreTagRequest $request): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request) {
/** @var \App\Models\Tag */
@ -101,66 +104,57 @@ class TagController extends Controller
* Show the form for editing the specified resource.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
* @return \Illuminate\Contracts\View\View
*/
public function edit(Tag $tag)
public function edit(Tag $tag): \Illuminate\Contracts\View\View
{
return view('bo.pages.tags.edit', [
'tag' => $tag,
'tagModel' => $tag,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
* @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTagRequest $request, Tag $tag)
public function update(UpdateTagRequest $request, Tag $tag): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $tag) {
$tag->fill($request->validated());
if ($tag->saveOrFail()) {
return redirect()
->route('bo.tags.edit', $tag)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->route('bo.tags.edit', $tag->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::singular(__('models.classes.tags'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->route('bo.tags.edit', $tag->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::singular(__('models.classes.tags'))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Tag $tag)
public function destroy(Tag $tag): \Illuminate\Http\RedirectResponse
{
if ($tag->deleteOrFail()) {
return redirect()
->route('bo.tags.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->route('bo.tags.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::singular(__('models.classes.tags'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimée',
['model' => Str::singular(trans('models.classes.tags'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::singular(__('models.classes.tags'))
]));
}
}

View file

@ -3,15 +3,17 @@
namespace App\Http\Controllers;
use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Enums\Products\ProductSpeciesEnum;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Advice;
use App\Models\News;
use App\Models\Product;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
@ -19,28 +21,26 @@ use Illuminate\Support\Str;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
// Back search methods.
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields would be concatenate to make the search.
* @return Builder
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable|null $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
callable|null $searchQuery = null,
string ...$fields
): Builder {
): \Illuminate\Database\Eloquent\Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
@ -64,69 +64,162 @@ class Controller extends BaseController
/**
* Build query search on all fields
*
* @param Builder $query
* @param string $search
* @param array $fields
* @return Builder
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @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();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
foreach ($fields as $field) {
$field = str_replace('?', '\?', $field);
if (is_array($field)) {
$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`";
})->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
['%' . htmlspecialchars($search) . '%']
);
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;
}
/**
* Sort columns with a query
* Get the search value (on string or Enum).
*
* @param Builder $query
* @param array $ignore
* @return Builder
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param string $field
* @return string|array
*/
protected function sortQuery(Builder $query, array $ignore = []): Builder
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 array $ignore
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
if (request()->input('sort_col')) {
Session::put("$rName.sort_col", request()->input('sort_col'));
Session::put("$rName.sorted", true);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
if (request()->input('sort_way')) {
Session::put("$rName.sort_way", request()->input('sort_way'));
Session::put("$rName.sorted", true);
}
if (request()->rst) {
Session::remove("$rName.sorted");
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
if (
request()->input('sort_col') and
request()->input('sort_way') and
Schema::hasColumn($table, request()->input('sort_col'))
) {
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')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
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;
}
/**
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
protected function paginate(
Builder $query,
ItemsPerPaginationEnum|null $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
$pagination = ToolboxHelper::getValidatedEnum(
Cache::get($cacheKey, $pagination->value()),
'pagination',
'\App\Enums\Pagination\ItemsPerPaginationEnum',
);
if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination);
}
$query = $query->paginate($pagination);
return $query;
}
// Front Search methods.
/**
@ -144,7 +237,7 @@ class Controller extends BaseController
*/
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.
$query->where('label', 'like', "%{$search}%");
// Par ingrédients.
@ -164,12 +257,12 @@ class Controller extends BaseController
// Par espèce.
if (\strlen($search)) {
/** @var \App\Enums\Products\ProductSpeciesEnum|null $specie */
$specie = \collect(ProductSpeciesEnum::toArray())
$specie = \collect(ProductSpeciesEnum::cases())
->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();
if ($specie) {
$query->orWhere('species', $specie);
$query->orWhere('species', $specie->value());
}
}
});
@ -186,7 +279,7 @@ class Controller extends BaseController
*/
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->orWhere('subtitle', 'like', "%{$search}%");
$query->orWhere('content', 'like', "%{$search}%");
@ -205,8 +298,8 @@ class Controller extends BaseController
*/
protected function getRecipeQuery(string $search): Builder
{
return Advice::query()->where('status', true)
->where('type', '=', AdviceTypeEnum::recipes()->value)
return Advice::query()->where('published', true)
->where('type', '=', AdviceTypeEnum::recipes->value)
->where(function ($query) use ($search) {
$query->where('title', 'like', "%{$search}%");
$query->orWhere('preparation', 'like', "%{$search}%");

View file

@ -13,7 +13,7 @@ class ActivitiesController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function sociale()
public function sociale(): \Illuminate\Contracts\View\View
{
// phpcs:disable
$activity_title = "Restauration sociale";
@ -43,7 +43,7 @@ class ActivitiesController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function commerciale()
public function commerciale(): \Illuminate\Contracts\View\View
{
// phpcs:disable
$activity_title = "Restauration commerciale";
@ -55,7 +55,7 @@ class ActivitiesController extends Controller
La pintade et le canard pour des repas gourmands. <br><br>
Des produits cuits pour plus de praticité et des services rapides. <br><br>
Une large gamme de pané pour la création de burger.<br><br>
Ou encore de nombreux produits pour la réalisation des menu enfants.";
Ou encore de nombreux produits pour la réalisation des menu enfants.";
// phpcs:enable
$activity_image = "restau-commerciale.jpg";
@ -77,7 +77,7 @@ class ActivitiesController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function distributeur()
public function distributeur(): \Illuminate\Contracts\View\View
{
// phpcs:disable
$activity_title = "Distributeur";
@ -108,7 +108,7 @@ class ActivitiesController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function boulangerie()
public function boulangerie(): \Illuminate\Contracts\View\View
{
// phpcs:disable
$activity_title = "Boul Pat";

View file

@ -11,7 +11,7 @@ class AdnController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
return view('fo.pages.adn.adn');
}
@ -21,17 +21,7 @@ class AdnController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function story()
{
return view('fo.pages.adn.story');
}
/**
* Undocumented function
*
* @return \Illuminate\Contracts\View\View
*/
public function btob()
public function btob(): \Illuminate\Contracts\View\View
{
return view('fo.pages.adn.btob');
}
@ -41,7 +31,7 @@ class AdnController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function breeder()
public function breeder(): \Illuminate\Contracts\View\View
{
return view('fo.pages.adn.breeder');
}

View file

@ -11,7 +11,7 @@ class BreederController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
return view('fo.pages.breeder');
}

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

@ -11,7 +11,7 @@ class BusinessController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
return view('fo.pages.btob');
}

View file

@ -2,13 +2,18 @@
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Contact\ContactRequest;
use App\Mail\ContactFormMail;
use App\Models\StaticPage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use App\Enums\Contact\ContactActivityEnum;
use App\Enums\Contact\ContactTimeEnum;
use Illuminate\Support\Facades\Session;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class ContactController extends Controller
{
@ -17,9 +22,11 @@ class ContactController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
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'));
}
/**
@ -30,17 +37,63 @@ class ContactController extends Controller
*/
public function contactSubmit(ContactRequest $request): \Illuminate\Http\RedirectResponse
{
$request->merge([
'activityEnum' => ContactActivityEnum::toArray()[$request->activity - 1],
'callTimeEnum' => ContactTimeEnum::toArray()[$request->callTime - 1]
]);
$recipients = config('mail.contactsList.contactform');
Mail::to(config('mail.contactsList.contactform'))->send(new ContactFormMail($request));
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');
return back()->with('success', trans(
'Votre message a bien été envoyé, merci de votre intérêt, nous répondrons dans les meilleurs délais.'
));
Session::forget('contact_draft');
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

@ -11,7 +11,7 @@ class EngagmentsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function francais()
public function francais(): \Illuminate\Contracts\View\View
{
return view('fo.pages.engagments.francais');
}
@ -21,7 +21,7 @@ class EngagmentsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function eleverdurablement()
public function eleverdurablement(): \Illuminate\Contracts\View\View
{
return view('fo.pages.engagments.eleverdurablement');
}
@ -31,7 +31,7 @@ class EngagmentsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function biennourrir()
public function biennourrir(): \Illuminate\Contracts\View\View
{
return view('fo.pages.engagments.biennourrir');
}
@ -41,7 +41,7 @@ class EngagmentsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function respecterlaterre()
public function respecterlaterre(): \Illuminate\Contracts\View\View
{
return view('fo.pages.engagments.respecterlaterre');
}
@ -51,7 +51,7 @@ class EngagmentsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function vivreensemble()
public function vivreensemble(): \Illuminate\Contracts\View\View
{
return view('fo.pages.engagments.vivreensemble');
}

View file

@ -5,121 +5,125 @@ namespace App\Http\Controllers\Fo;
use App\Enums\Advices\AdviceTypeEnum;
use App\Enums\ExpertiseProducts\ExpertiseProductPageEnum;
use App\Enums\Products\ProductSpeciesEnum;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\Advice;
use App\Models\ExpertiseProduct;
use App\Models\Product;
use App\Models\StaticPage;
use Illuminate\Contracts\Database\Eloquent\Builder;
class ExpertiseController extends Controller
{
/**
* Undocumented function
* Show expertise page (duck).
*
* @return \Illuminate\Contracts\View\View
*
*/
public function canard()
public function canard(): \Illuminate\Contracts\View\View
{
// Get VIP Products for this page.
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::duck()->value)->get();
$products = Product::query()->where('status', true)
->where('species', ProductSpeciesEnum::canard()->value)
->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'));
return view('fo.pages.expertise.canard', $this->getExpertiseData(
StaticPageType::expertise_duck,
ExpertiseProductPageEnum::duck,
ProductSpeciesEnum::canard
));
}
/**
* Undocumented function
* Show expertise page (turkey).
*
* @return \Illuminate\Contracts\View\View
*/
public function dinde()
public function dinde(): \Illuminate\Contracts\View\View
{
// Get VIP Products for this page.
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::turkey()->value)->get();
$products = Product::query()->where('status', true)
->where('species', ProductSpeciesEnum::dinde()->value)
->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'));
return view('fo.pages.expertise.dinde', $this->getExpertiseData(
StaticPageType::expertise_turkey,
ExpertiseProductPageEnum::turkey,
ProductSpeciesEnum::dinde
));
}
/**
* Undocumented function
* Show expertise page (rabbit).
*
* @return \Illuminate\Contracts\View\View
*/
public function lapin()
public function lapin(): \Illuminate\Contracts\View\View
{
// Get VIP Products for this page.
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::rabbit()->value)->get();
$products = Product::query()->where('status', true)
->where('species', ProductSpeciesEnum::lapin()->value)
->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'));
return view('fo.pages.expertise.lapin', $this->getExpertiseData(
StaticPageType::expertise_rabbit,
ExpertiseProductPageEnum::rabbit,
ProductSpeciesEnum::lapin
));
}
/**
* Undocumented function
* Show expertise page (guineafowl).
*
* @return \Illuminate\Contracts\View\View
*/
public function pintade()
public function pintade(): \Illuminate\Contracts\View\View
{
// Get VIP Products for this page.
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::guineafowl()->value)->get();
$products = Product::query()->where('status', true)
->where('species', ProductSpeciesEnum::pintade()->value)
->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'));
return view('fo.pages.expertise.pintade', $this->getExpertiseData(
StaticPageType::expertise_guineafowl,
ExpertiseProductPageEnum::guineafowl,
ProductSpeciesEnum::pintade
));
}
/**
* Undocumented function
* Show expertise page (chicken).
*
* @return \Illuminate\Contracts\View\View
*/
public function poulet()
public function poulet(): \Illuminate\Contracts\View\View
{
// Get VIP Products for this page.
$vip = ExpertiseProduct::query()->where('page', ExpertiseProductPageEnum::chicken()->value)->get();
$products = Product::query()->where('status', true)
->where('species', ProductSpeciesEnum::poulet()->value)
return view('fo.pages.expertise.poulet', $this->getExpertiseData(
StaticPageType::expertise_chicken,
ExpertiseProductPageEnum::chicken,
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()
->limit(3)
->with('subrange.range')
->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;
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\Product;
use App\Models\Range;
use App\Models\Slide;
use Carbon\Carbon;
use Spatie\CalendarLinks\Link;
use Illuminate\Support\Facades\Log;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class HomeController extends Controller
{
@ -17,46 +22,106 @@ class HomeController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
// Get Slides.
/** @var \Illuminate\Database\Eloquent\Builder $slides */
$slides = Slide::query();
$this->sortQuery($slides);
$slides->orderBy('order', 'DESC');
$slides = $slides->get();
$slides = Slide::query()
->where('published', true)
->orderBy('order', 'DESC')
->get();
// Gammes.
$ranges = Range::all();
// VIP Products.
$threeproducts = Product::where('status', true)
->where('is_vip', true)
$threeproducts = Product::where('published', true)
->where('is_vip', true)
->take(3)
->orderBy('order', 'DESC')
->orderBy('priority_order', 'DESC')
->with('subrange.range')
->get();
->get();
// Get Agenda.
// Events starting in the future.
$events = News::where('status', true)
->where('event_start', '>', Carbon::now())
->orderBy('event_start', 'desc')
->take(2)->get();
$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();
}
$events = News::query()->where('published', true)
->where('event_start', '>', now())
->orderBy('event_start', 'desc')
->take(2)->get()->reverse();
// 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

@ -11,7 +11,7 @@ class IdeasController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
return view('fo.pages.ideas.ideas');
}

View file

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

View file

@ -11,11 +11,10 @@ use App\Http\Resources\Fo\Products\ProductResource;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Mccarlosen\LaravelMpdf\LaravelMpdf;
class ProductsController extends Controller
{
@ -24,7 +23,7 @@ class ProductsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
public function index(): \Illuminate\Contracts\View\View
{
// Gammes.
$ranges = Range::all();
@ -36,9 +35,9 @@ class ProductsController extends Controller
* View a specific range products
*
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Contracts\View\View
*/
public function products(Range $range)
public function products(Range $range): \Illuminate\Contracts\View\View
{
$ranges = Range::query()->with('subranges')->get();
$range->load('subranges');
@ -54,15 +53,16 @@ class ProductsController extends Controller
*
* @param \App\Http\Requests\Fo\Products\AjaxFilterProductsRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Http\JsonResponse
*/
public function productsAjax(AjaxFilterProductsRequest $request, Range $range)
public function productsAjax(AjaxFilterProductsRequest $request, Range $range): \Illuminate\Http\JsonResponse
{
// * Init product query with search and status.
$search = htmlspecialchars($request->query('search') ?? '');
$productsQuery = !empty($search) ? $this->getProductQuery($search) : Product::query();
$productsQuery->with('subrange.range');
$productsQuery->where('status', true);
$productsQuery->where('published', true);
$productsQuery->orderBy('priority_order', 'ASC');
// * Range filter (obligatoire).
$productsQuery->whereHas('subrange', function (Builder $q) use ($range) {
@ -72,7 +72,7 @@ class ProductsController extends Controller
});
// * Subrange filter.
if (count($request->subranges)) {
if (count($request->subranges) > 0) {
$productsQuery->whereHas('subrange', function (Builder $q) use ($request, $range) {
$q->whereIn('id', $request->subranges);
$q->whereHas('range', function (Builder $q) use ($range) {
@ -85,7 +85,6 @@ class ProductsController extends Controller
if ($request->vip) {
$productsQuery->where('is_vip', $request->vip);
}
return \response()->json(new ProductPaginatedCollectionResource($productsQuery->paginate(6)));
}
@ -93,17 +92,17 @@ class ProductsController extends Controller
* View a specific range products
*
* @param \App\Models\Product $publishedProduct
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Contracts\View\View
*/
public function show(Product $publishedProduct)
public function show(Product $publishedProduct): \Illuminate\Contracts\View\View
{
/** @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> */
$tips = $publishedProduct->tipsQuery()->where('status', true)->get();
$tips = $publishedProduct->tipsQuery()->where('published', true)->get();
// Force load subrange and range.
$publishedProduct->load('subrange.range');
$publishedProduct->load(['subrange.range', 'tags']);
/** @var \App\Models\Subrange */
$subrange = $publishedProduct->subrange;
@ -112,8 +111,9 @@ class ProductsController extends Controller
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Product> */
$randomProducts = Product::query()
->with('subrange.range')
->where('id', '!=', $publishedProduct->id)
->where('status', true)
->where('published', true)
->whereHas('subrange', function (Builder $q) use ($range) {
$q->whereHas('range', function (Builder $q) use ($range) {
$q->where('id', $range->id);
@ -122,27 +122,48 @@ class ProductsController extends Controller
$product = $publishedProduct;
return view('fo.pages.products.product', compact('product', 'recipes', 'tips', 'randomProducts'));
return view('fo.pages.products.product', compact(
'product',
'recipes',
'tips',
'randomProducts'
));
}
/**
* Print product commercial sheet
*
* @param \App\Models\Product $publishedProduct
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Http\Response
*/
public function printCommericalSheet(Product $publishedProduct)
public function printCommercialSheet(Product $publishedProduct): Response
{
$title = trans('Fiche commerciale du produit : :label - :appname', [
'label' => $publishedProduct->label,
'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),
'date' => \now()->format('Y-m-d')
]);
return $this->generateProductCommercialPdf($publishedProduct, $title)
->stream("{$filename}.pdf");
/**
* ! Réponse mano car enfait \Mccarlosen\LaravelMpdf\LaravelMpdf
* ! ne retourne pas vraiemnt une http response, mais provoque une 500
* ! apres avoir header et echo dans le corps de la réponse php (Nasty trick)
*/
return \response(
$this->generateProductCommercialPdf($publishedProduct, $title)->download($filename),
200,
[
'Content-Type' => 'application/pdf',
'Content-disposition' => "inline; filename=\"{$filename}\"",
'Cache-Control' => 'public, must-revalidate, max-age=0',
'Pragma' => 'public',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
]
);
}
/**
@ -152,8 +173,10 @@ class ProductsController extends Controller
* @param \App\Models\Product $publishedProduct
* @return \Illuminate\Http\JsonResponse
*/
public function addProductToBasket(AjaxAddProductBasket $request, Product $publishedProduct): JsonResponse
{
public function addProductToBasket(
AjaxAddProductBasket $request,
Product $publishedProduct
): \Illuminate\Http\JsonResponse {
$sessionProducts = $this->getBasketProducts();
$sessionProducts->put(
$publishedProduct->id,
@ -175,7 +198,7 @@ class ProductsController extends Controller
* @param string $slug
* @return \Illuminate\Http\JsonResponse
*/
public function removeProductFromBasket(string $slug): JsonResponse
public function removeProductFromBasket(string $slug): \Illuminate\Http\JsonResponse
{
$sessionProducts = $this->getBasketProducts()->filter(function (array $basketElement) use ($slug) {
return $basketElement['product']->slug !== $slug and !empty($slug);
@ -209,13 +232,13 @@ class ProductsController extends Controller
* @param string $title
* @return \Mccarlosen\LaravelMpdf\LaravelMpdf
*/
private function generateProductCommercialPdf(Product $product, string $title): LaravelMpdf
private function generateProductCommercialPdf(Product $product, string $title): \Mccarlosen\LaravelMpdf\LaravelMpdf
{
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdfWrapper */
$mpdf = app('laravel-mpdf');
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
$pdf = $mpdf->loadView(
'documents.product-commercial-sheet',
'fo.documents.product-commercial-sheet',
// * Dirty convert to object.
[
'title' => $title,
@ -237,7 +260,6 @@ class ProductsController extends Controller
'margin_bottom' => 0,
'margin_header' => 0,
'margin_footer' => 0,
'margin_top' => 0,
'orientation' => 'P',
'title' => $title,
'author' => \config('app.name', 'Le Gaulois Pro'),

View file

@ -7,7 +7,9 @@ use App\Enums\Advices\AdviceTypeEnum;
use App\Http\Controllers\Controller;
use App\Models\Advice;
use App\Models\Range;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class RecipesController extends Controller
{
@ -17,37 +19,63 @@ class RecipesController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function indexRecipes(Request $request)
public function indexRecipes(Request $request): \Illuminate\Contracts\View\View
{
$search_range = $request->search_range;
$search_cooking_time = $request->search_cooking_time;
$search_preparation_time = $request->search_preparation_time;
/** @var string|null */
$search_range = $request->input('search_range');
/** @var string|null */
$search_cooking_time = $request->input('search_cooking_time');
/** @var string|null */
$search_preparation_time = $request->input('search_preparation_time');
$ranges = Range::all();
$query = Advice::where('type', 1)->where('status', true)
->when(is_numeric($search_range), function ($query) use ($search_range) {
$query->whereHas('products', function ($query) use ($search_range) {
$query->whereHas('subrange', function ($query) use ($search_range) {
$query->whereHas('range', function ($query) use ($search_range) {
$query->where('id', 'LIKE', $search_range);
});
});
});
})
->when(is_numeric($search_cooking_time), function ($query) use ($search_cooking_time) {
$query->whereBetween('cooking_time', [
AdviceTimeEnum::toArray()[$search_cooking_time - 1]->times[0],
AdviceTimeEnum::toArray()[$search_cooking_time - 1]->times[1]
]);
})
->when(is_numeric($search_preparation_time), function ($query) use ($search_preparation_time) {
$query->whereBetween('preparation_time', [
AdviceTimeEnum::toArray()[$search_preparation_time - 1]->times[0],
AdviceTimeEnum::toArray()[$search_preparation_time - 1]->times[1]
]);
});
/** @var array<\App\Enums\Advices\AdviceTimeEnum> */
$adviceEnumList = AdviceTimeEnum::all();
$ranges = Range::all();
$recipes = $query->orderBy('slug', 'ASC')->paginate(9);
$cooking_time = null;
if (is_numeric($search_cooking_time)) {
$cooking_time = Arr::get($adviceEnumList, $search_cooking_time - 1);
}
$preparation_time = null;
if (is_numeric($search_preparation_time)) {
$preparation_time = Arr::get($adviceEnumList, $search_preparation_time - 1);
}
/** @var \App\Enums\Advices\AdviceTimeEnum|null $cooking_time */
/** @var \App\Enums\Advices\AdviceTimeEnum|null $preparation_time */
$query = Advice::query()->with('tags')->where('type', 1)->where('published', true)->when(
is_numeric($search_range),
fn(Builder $q) =>
$q->whereHas(
'products',
fn(Builder $q) =>
$q->whereHas(
'subrange',
fn(Builder $q) =>
$q->whereHas(
'range',
fn(Builder $q) =>
$q->where('id', 'LIKE', $search_range)
)
)
)
)->when(
$cooking_time,
fn(Builder $q) =>
$q->whereBetween('cooking_time', [
Arr::get($cooking_time->times(), 0),
Arr::get($cooking_time->times(), 1)
])
)->when(
$preparation_time,
fn(Builder $q) =>
$q->whereBetween('preparation_time', [
Arr::get($preparation_time->times(), 0),
Arr::get($preparation_time->times(), 1)
])
);
$recipes = $query->orderBy('priority_order', 'ASC')->paginate(9);
return view('fo.pages.ideas.recipes', compact(
'ranges',
@ -64,11 +92,11 @@ class RecipesController extends Controller
* @param \App\Models\Advice $publishedAdvice
* @return \Illuminate\Contracts\View\View
*/
public function show(Advice $publishedAdvice)
public function show(Advice $publishedAdvice): \Illuminate\Contracts\View\View
{
$recipe = $publishedAdvice;
$relatedRecipes = $publishedAdvice->advices()->where('type', '=', AdviceTypeEnum::recipes()->value)->get();
$relatedTips = $publishedAdvice->advices()->where('type', '=', AdviceTypeEnum::tips()->value)->get();
$relatedRecipes = $publishedAdvice->advices()->where('type', '=', AdviceTypeEnum::recipes->value())->get();
$relatedTips = $publishedAdvice->advices()->where('type', '=', AdviceTypeEnum::tips->value())->get();
return view('fo.pages.ideas.recipe', compact('recipe', 'relatedRecipes', 'relatedTips'));
}

View file

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

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()
{
return view('fo.pages.story');
}
}

View file

@ -13,11 +13,11 @@ class TipsController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function indexTips()
public function indexTips(): \Illuminate\Contracts\View\View
{
$advices = Advice::query()->with('tags')
->where('type', AdviceTypeEnum::tips())
->where('status', true)
->where('type', AdviceTypeEnum::tips)
->where('published', true)
->paginate(9);
return view('fo.pages.ideas.tips', \compact('advices'));
}
@ -26,12 +26,14 @@ class TipsController extends Controller
* Display an advice
*
* @param \App\Models\Advice $publishedAdvice
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @return \Illuminate\Contracts\View\View
*/
public function show(Advice $publishedAdvice)
public function show(Advice $publishedAdvice): \Illuminate\Contracts\View\View
{
$publishedAdvice->load([
'advices', 'products.subrange', 'tags'
'advices',
'products.subrange.range',
'tags'
]);
return view('fo.pages.ideas.tip', [
'adviceModel' => $publishedAdvice

View file

@ -16,7 +16,6 @@ class Kernel extends HttpKernel
protected $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
@ -34,12 +33,16 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class
]
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];
/**

View file

@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class Authenticate extends Middleware
@ -12,9 +13,8 @@ class Authenticate extends Middleware
*
* @param \Illuminate\Http\Request $request
* @return string|null
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
protected function redirectTo($request)
protected function redirectTo(Request $request): ?string
{
$logins = [
'backend' => route('bo.login')
@ -31,8 +31,9 @@ class Authenticate extends Middleware
}
}
}
return collect($logins)->first() ?? '/';
return route('login');
}
abort(401);
return null;
}
}

View file

@ -11,6 +11,5 @@ class EncryptCookies extends Middleware
*
* @var array<int, string>
*/
protected $except = [
];
protected $except = [];
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
@ -12,21 +13,34 @@ class HtmlSanitizer
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param mixed ...$params
* @return mixed
* @param string|array ...$params
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, \Closure $next, ...$params)
public function handle(Request $request, \Closure $next, string|array ...$params): Response
{
$inputs = $request->all();
array_walk_recursive($inputs, function (&$input, $name) use ($params) {
/** @var callable|null */
$cleanInput = null;
$cleanInput = function (
null|string|array &$input,
string $name,
?string $rootName = null
) use (
$params,
&$cleanInput
): void {
// * Nested element array
if (is_array($input)) {
array_walk_recursive($input, $cleanInput, $name);
}
if (!is_string($input)) {
return;
}
if (in_array($name, $params)) {
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
// ? Is a json input ?
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
// ! Decode each json field recursive.
\array_walk_recursive($isJson, function (&$item) {
\array_walk_recursive($isJson, function (string|array &$item) {
if (\is_string($item)) {
$item = clean($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
@ -39,7 +53,8 @@ class HtmlSanitizer
} else {
$input = strip_tags($input);
}
});
};
\array_walk($inputs, $cleanInput);
$request->merge($inputs);
return $next($request);
}

View file

@ -11,6 +11,5 @@ class PreventRequestsDuringMaintenance extends Middleware
*
* @var array<int, string>
*/
protected $except = [
];
protected $except = [];
}

View file

@ -4,27 +4,38 @@ namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null ...$guards
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null ...$guards
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If guard is unhandled.
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, \Closure $next, ...$guards)
public function handle(Request $request, \Closure $next, ...$guards): Response
{
$logins = [
'backend' => route('bo.dashboard')
];
// phpcs:enable phpcs:disable
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return \redirect()->to($logins[$guard]);
switch ($guard) {
case 'frontend':
return \redirect()->route('fo.home');
case 'backend':
if (!$request->routeIs('bo.*')) {
continue;
}
return \redirect()->route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
}
}
}

View file

@ -6,6 +6,8 @@ use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
@ -24,17 +26,21 @@ class SecureHeaders
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next)
public function handle(Request $request, Closure $next): Response
{
View::share('nonce', $nonce = \uniqid());
// phpcs:enable
// * For Vite DEV
$host = \parse_url(\config('app.url'), \PHP_URL_HOST);
View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$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);
$response = $next($request);
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
@ -46,29 +52,58 @@ class SecureHeaders
"default-src 'none';",
"manifest-src 'self';",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' https://yl.s-web.fr blob:;",
"img-src * https://yl.s-web.fr data: blob:;",
// ! Blob required for printjs
'frame-src ' . implode(" ", [
"connect-src 'self' blob: " . implode(' ', [
// * VITE DEV
config('app.debug') ? "wss://{$host}:4443" : '',
config('app.debug') ? "https://{$host}:4443" : '',
'https://yl.s-web.fr'
]) . ';',
"img-src 'self' data: blob: " . implode(' ', [
'https://yl.s-web.fr',
'https://i3.ytimg.com/',
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
// * MESBR
'https://dev.mesbr.eu',
'https://www.mesbr.eu'
]) . ';',
"object-src 'self' " . implode(' ', []) . ';',
"frame-src 'self' " . implode(' ', [
'https://www.youtube-nocookie.com',
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/',
'blob:'
]) . ';',
"font-src 'self' data:;",
"font-src 'self' data: " . \implode(' ', [
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
'https://fonts.gstatic.com;'
]) . ';',
// ! Using CKEditor will must have unsafe-inline style
// phpcs:ignore Generic.Files.LineLength.TooLong
"style-src 'self' 'unsafe-inline';",
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
'https://cdnjs.cloudflare.com',
// * File Manager
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
'https://use.fontawesome.com',
'https://fonts.googleapis.com',
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
]) . ';',
// ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';",
// phpcs:ignore Generic.Files.LineLength.TooLong
'script-src ' . implode(" ", [
'\'self\'',
'https://www.google.com/recaptcha/',
'https://www.gstatic.com/recaptcha/',
'https://yl.s-web.fr',
]) . ' ' . $jsDev . ';'
"script-src 'self' {$jsDev} " . \implode(' ', [
'https: blob:',
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
]) . ';'
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies) . $reportUri
@ -83,7 +118,7 @@ class SecureHeaders
* @param array $headerList
* @return void
*/
private function removeUnwantedHeaders(array $headerList)
private function removeUnwantedHeaders(array $headerList): void
{
foreach ($headerList as $header) {
header_remove($header);

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [];
}

View file

@ -12,5 +12,6 @@ class VerifyCsrfToken extends Middleware
* @var array<int, string>
*/
protected $except = [
];
'/csp-report'
];
}

View file

@ -4,11 +4,12 @@ namespace App\Http\Requests\Bo\Advice;
use App\Enums\Advices\AdviceDifficultyEnum;
use App\Enums\Advices\AdviceTypeEnum;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Validation\Rules\Enum;
class StoreAdviceRequest extends FormRequest
{
@ -19,7 +20,7 @@ class StoreAdviceRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -28,28 +29,29 @@ class StoreAdviceRequest extends FormRequest
* Prepare the data for validation.
*
* @return void
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
protected function prepareForValidation()
protected function prepareForValidation(): void
{
//phpcs:enable
$this->merge([
'slug' => Str::slug(strip_tags($this->title)),
'status' => $this->status ? true : false,
'tags' => $this->tags ?? [],
'products' => $this->products ?? [],
'associated_tips_list' => $this->associated_tips_list ?? [],
'associated_recipes_list' => $this->associated_recipes_list ?? [],
'slug' => Str::slug(strip_tags($this->input('title'))),
'published' => $this->boolean('published'),
'tags' => $this->input('tags', []),
'products' => $this->input('products', []),
'associated_tips_list' => $this->input('associated_tips_list', []),
'associated_recipes_list' => $this->input('associated_recipes_list', []),
// * 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;
})->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;
})->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;
})->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;
})->filter()->all(),
]);
@ -62,40 +64,51 @@ class StoreAdviceRequest extends FormRequest
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
public function rules()
public function rules(): array
{
//phpcs:enable
$rules = [
'title' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'type' => ['required', new EnumIsValidRule(AdviceTypeEnum::class)],
'picture_alt' => 'required_with:picture|nullable|min:3|max:255',
'picture_title' => 'required_with:picture|nullable|min:3|max:255',
'sticker_alt' => '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_title' => 'required_with:sticker_sm|nullable|min:3|max:255',
'preparation' => 'required|string|min:15',
'advice' => 'nullable|string|min:15',
'ingredients' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'title' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'type' => ['required', new Enum(AdviceTypeEnum::class)],
'picture_alt' => 'required_with:picture|nullable|min:3|max:255',
'picture_title' => 'required_with:picture|nullable|min:3|max:255',
'sticker_alt' => '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_title' => 'required_with:sticker_sm|nullable|min:3|max:255',
'preparation' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'advice' => [
'nullable',
'string',
new WysiwygLength(15, 2500)
],
'ingredients' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) < 1 || count($value) > 15) {
$fail(\trans('Vous devez associer 1 à 15 lignes d\'ingrédients.'));
}
}
],
'ingredients.*' => 'required|array',
'ingredients.*.value' => 'nullable|numeric|max:65535',
'ingredients.*.label' => [
'ingredients.*' => 'required|array',
'ingredients.*.value' => 'nullable|regex:/^\d*\.?\d{0,1}$/|between:0,65535',
'ingredients.*.label' => [
Rule::requiredIf(function () {
return $this->type === AdviceTypeEnum::recipes()->value;
return $this->type === AdviceTypeEnum::recipes->value();
}),
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if ($this->type == AdviceTypeEnum::recipes()->value) {
if ($this->type == AdviceTypeEnum::recipes->value()) {
if (!$value) {
$fail(\trans('Le champ description de l\'ingrédient doit être une chaîne de caractères.'));
}
@ -106,8 +119,8 @@ class StoreAdviceRequest extends FormRequest
},
'max:255'
],
'guests_nb' => [
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'guests_nb' => [
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if (
\is_array($this->ingredients) and
@ -116,39 +129,49 @@ class StoreAdviceRequest extends FormRequest
) {
$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_video_id' => 'required_with:youtube_title|nullable|regex:%([^"&?\/\s]{11})%i',
'cooking_time' => 'nullable|numeric|min:0|max:65535',
'youtube_title' => 'required_with:youtube_video_id|nullable|min:3|max:255',
'youtube_video_id' => 'required_with:youtube_title|nullable|min:3|max:50|regex:%([^"&?\/\s]{11})%i',
'cooking_time' => 'nullable|numeric|min:0|max:65535',
'preparation_time' => [
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 () {
return $this->type === AdviceTypeEnum::recipes()->value;
return $this->type === AdviceTypeEnum::recipes->value();
}),
new EnumIsValidRule(AdviceDifficultyEnum::class)
new Enum(AdviceDifficultyEnum::class)
],
'status' => 'required|boolean',
'tags' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'published' => 'required|boolean',
'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',
'products' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if ($this->type == AdviceTypeEnum::recipes()->value) {
'tags.*' => 'required|array',
'tags.*.id' => 'required|numeric|exists:tags,id|distinct',
'tags.*.label' => 'required|string',
'products' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if ($this->type == AdviceTypeEnum::recipes->value()) {
if (count($value) < 1 || count($value) > 3) {
$fail(\trans('Vous devez associer 1 à 3 produits.'));
}
@ -159,37 +182,41 @@ class StoreAdviceRequest extends FormRequest
}
}
],
'products.*' => 'required|array',
'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string',
'associated_tips_list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'products.*' => 'required|array',
'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string',
'associated_tips_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 astuces.'));
}
}
],
'associated_tips_list.*' => 'required|array',
'associated_tips_list.*.value' => 'required|exists:advice,id',
'associated_tips_list.*.label' => 'required|string',
'associated_recipes_list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'associated_tips_list.*' => 'required|array',
'associated_tips_list.*.value' => 'required|exists:advice,id',
'associated_tips_list.*.label' => 'required|string',
'associated_recipes_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 recettes.'));
}
}
],
'associated_recipes_list.*' => 'required|array',
'associated_recipes_list.*' => 'required|array',
'associated_recipes_list.*.value' => 'required|exists:advice,id',
'associated_recipes_list.*.label' => 'required|string',
];
return \array_merge(
$rules,
$this->pictureRules('picture', true, 650, 690, 650, 690),
$this->pictureRules('sticker', true, 370, 300, 370, 300),
$this->pictureRules('sticker_sm', true, 270, 145, 270, 145),
$this->pictureRules('picture', 650, 690, 650, 690, false),
$this->pictureRules('sticker', 370, 300, 370, 300, false),
$this->pictureRules('sticker_sm', 270, 145, 270, 145, false),
);
}
@ -201,30 +228,30 @@ class StoreAdviceRequest extends FormRequest
public function attributes(): array
{
return [
'title' => trans('Titre'),
'slug' => trans('Slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'tags' => trans('tag'),
'tags.*' => trans('tag'),
'tags.*.id' => trans('id du tag'),
'tags.*.label' => trans('label du tag'),
'difficulty' => trans('Difficulté'),
'picture' => trans('Visuel'),
'picture_alt' => trans('Attribut alt du visuel'),
'picture_title' => trans('Titre du visuel'),
'sticker' => trans('Vignette'),
'sticker_alt' => trans('Attribut alt de la vignette'),
'sticker_title' => trans('Titre de la vignette'),
'sticker_sm' => trans('Petite vignette'),
'sticker_sm_alt' => trans('Attribut alt de la petite vignette'),
'sticker_sm_title' => trans('Titre de la petite vignette'),
'title' => trans('Titre'),
'slug' => trans('Slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'tags' => trans('tag'),
'tags.*' => trans('tag'),
'tags.*.id' => trans('id du tag'),
'tags.*.label' => trans('label du tag'),
'difficulty' => trans('Difficulté'),
'picture' => trans('Visuel'),
'picture_alt' => trans('Attribut alt du visuel'),
'picture_title' => trans('Titre du visuel'),
'sticker' => trans('Vignette'),
'sticker_alt' => trans('Attribut alt de la vignette'),
'sticker_title' => trans('Titre de la vignette'),
'sticker_sm' => trans('Petite vignette'),
'sticker_sm_alt' => trans('Attribut alt de la petite vignette'),
'sticker_sm_title' => trans('Titre de la petite vignette'),
'ingredients.*.value' => trans('quantité de l\'ingrédient'),
'ingredients.*.label' => trans('description de l\'ingrédient'),
'guests_nb' => trans('Nombre de convives'),
'youtube_title' => trans('Titre de la vidéo Youtube'),
'youtube_video_id' => trans('ID de la vidéo Youtube'),
'advice' => trans('Conseil LGP'),
'guests_nb' => trans('Nombre de convives'),
'youtube_title' => trans('Titre de la vidéo Youtube'),
'youtube_video_id' => trans('ID de la vidéo Youtube'),
'advice' => trans('Conseil LGP'),
];
}
}

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

@ -5,7 +5,7 @@ namespace App\Http\Requests\Bo\ExpertiseProducts;
use App\Enums\ExpertiseProducts\ExpertiseProductPageEnum;
use App\Enums\NutriscoreEnum;
use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Validation\Rules\Enum;
class StoreExpertiseProductRequest extends FormRequest
{
@ -14,7 +14,7 @@ class StoreExpertiseProductRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -24,12 +24,12 @@ class StoreExpertiseProductRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'is_frenchpoultry' => $this->is_frenchpoultry ? true : false,
'is_frenchpoultry' => $this->is_frenchpoultry ? true : false,
'is_natureeleveurs' => $this->is_natureeleveurs ? true : false,
'points' => $this->points ?? []
'points' => $this->points ?? []
]);
}
@ -38,19 +38,19 @@ class StoreExpertiseProductRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'page' => ['required', new EnumIsValidRule(ExpertiseProductPageEnum::class)],
'label' => 'required|min:3|max:255',
'title' => 'required|min:3|max:255',
'subtitle' => 'required|min:3|max:255',
'points' => 'required|array',
'points.*' => 'required|array',
'points.*.value' => 'required|min:3|max:255',
'is_frenchpoultry' => 'required|boolean',
'page' => ['required', new Enum(ExpertiseProductPageEnum::class)],
'label' => 'required|min:3|max:255',
'title' => 'required|min:3|max:255',
'subtitle' => 'required|min:3|max:255',
'points' => 'required|array',
'points.*' => 'required|array',
'points.*.value' => 'required|min:3|max:255',
'is_frenchpoultry' => 'required|boolean',
'is_natureeleveurs' => 'required|boolean',
'nutriscore' => ['required', new EnumIsValidRule(NutriscoreEnum::class)]
'nutriscore' => ['required', new Enum(NutriscoreEnum::class)]
];
}
@ -62,16 +62,16 @@ class StoreExpertiseProductRequest extends FormRequest
public function attributes(): array
{
return [
'page' => trans('Espèce'),
'label' => trans('Libellé du bloc'),
'title' => trans('Titre dans le bloc'),
'subtitle' => trans('Sous-titre'),
'points' => trans('Bloc des points forts'),
'points.*' => trans('Point fort'),
'points.*.value' => trans('Valeur Point fort'),
'is_frenchpoultry' => trans('Label volaille française'),
'page' => trans('Espèce'),
'label' => trans('Libellé du bloc'),
'title' => trans('Titre dans le bloc'),
'subtitle' => trans('Sous-titre'),
'points' => trans('Bloc des points forts'),
'points.*' => trans('Point fort'),
'points.*.value' => trans('Valeur Point fort'),
'is_frenchpoultry' => trans('Label volaille française'),
'is_natureeleveurs' => trans('Label nature d\'éleveur'),
'nutriscore' => trans('Label Nutriscore')
'nutriscore' => trans('Label Nutriscore')
];
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Requests\Bo\News;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
@ -15,7 +16,7 @@ class StoreNewsRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -24,28 +25,28 @@ class StoreNewsRequest extends FormRequest
* Prepare the data for validation.
*
* @return void
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
protected function prepareForValidation()
protected function prepareForValidation(): void
{
//phpcs:enable
$this->merge([
'slug' => Str::slug($this->title),
'status' => $this->status ? true : false,
'categories' => $this->categories ?? [],
'products' => $this->products ?? [],
'slug' => Str::slug($this->title),
'published' => $this->published ? true : false,
'categories' => $this->categories ?? [],
'products' => $this->products ?? [],
'associated_news_list' => $this->associated_news_list ?? [],
// * For sync relations
'news_list' => \collect($this->associated_news_list)->map(function ($value) {
'news_list' => \collect($this->associated_news_list)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(),
'products_list' => \collect($this->products)->map(function ($value) {
'products_list' => \collect($this->products)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all(),
'categories_list' => \collect($this->categories)->map(function ($value) {
'categories_list' => \collect($this->categories)->map(function ($value) {
return \is_array($value) ? ($value['value'] ?? null) : null;
})->filter()->all()
]);
$this->mergePicture('picture');
$this->mergePicture('picture2');
}
@ -55,71 +56,87 @@ class StoreNewsRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'title' => 'required|min:3|max:255',
'subtitle' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'picture_alt' => '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_title' => 'required_with:picture2|nullable|min:3|max:255',
'content' => 'required_with:picture|nullable|string|min:15',
'content2' => 'required_with:picture2|nullable|string|min:15',
'event_start' => 'nullable|date_format:Y-m-d',
'event_end' => [
'title' => 'required|min:3|max:255',
'subtitle' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'picture_alt' => '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_title' => 'required_with:picture2|nullable|min:3|max:255',
'content' => [
'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_end' => [
'required_with:event_start',
'after_or_equal:event_start',
'nullable',
'date_format:Y-m-d'
],
'event_text' => [
'event_text' => [
'required_with:event_start',
'nullable',
'min:3',
'max:255'
],
'status' => 'required|boolean',
'categories' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'published' => 'required|boolean',
'categories' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) < 1 || count($value) > 2) {
$fail(\trans('Vous devez associer une actualité à 1 ou 2 catégories.'));
}
}
],
'categories.*' => 'required|array',
'categories.*.value' => 'required|exists:news_categories,id',
'categories.*.label' => 'required|string',
'products' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'categories.*' => 'required|array',
'categories.*.value' => 'required|exists:news_categories,id',
'categories.*.label' => 'required|string',
'products' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 produits.'));
}
}
],
'products.*' => 'required|array',
'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string',
'associated_news_list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'products.*' => 'required|array',
'products.*.value' => 'required|exists:products,id',
'products.*.label' => 'required|string',
'associated_news_list' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (count($value) > 3) {
$fail(\trans('Vous devez associer au maximum 3 actualités.'));
}
}
],
'associated_news_list.*' => 'required|array',
'associated_news_list.*' => 'required|array',
'associated_news_list.*.value' => 'required|exists:news,id',
'associated_news_list.*.label' => 'required|string',
];
return \array_merge(
$rules,
$this->pictureRules('picture', true, 920, 1025, 920, 1025),
$this->pictureRules('picture2', false, 920, 1025, 920, 1025)
$this->pictureRules('picture', 590, 645, 590, 645, false),
$this->pictureRules('picture2', 590, 645, 590, 645, true, requiredWith: 'content2')
);
}
@ -131,23 +148,23 @@ class StoreNewsRequest extends FormRequest
public function attributes(): array
{
return [
'title' => trans('Titre'),
'slug' => trans('Slug'),
'seo_title' => trans('title') . ' seo',
'title' => trans('Titre'),
'slug' => trans('Slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'subtitle' => trans('Sous-titre'),
'picture' => trans('Image'),
'picture_alt' => trans('Attribut alt de l\'image'),
'picture_title' => trans('Titre de l\'image'),
'picture2' => trans('Image 2'),
'picture2_alt' => trans('Attribut alt de l\'image 2'),
'picture2_title' => trans('Titre de l\'image 2'),
'content' => trans('Contenu'),
'content2' => trans('Contenu 2'),
'event_start' => trans('Date de début de l\'évènement'),
'event_end' => trans('Date de fin de l\'évènement'),
'event_text' => trans('Description de l\'évènement'),
'status' => trans('Status'),
'subtitle' => trans('Sous-titre'),
'picture' => trans('Image'),
'picture_alt' => trans('Attribut alt de l\'image'),
'picture_title' => trans('Titre de l\'image'),
'picture2' => trans('Image 2'),
'picture2_alt' => trans('Attribut alt de l\'image 2'),
'picture2_title' => trans('Titre de l\'image 2'),
'content' => trans('Contenu'),
'content2' => trans('Contenu 2'),
'event_start' => trans('Date de début de l\'évènement'),
'event_end' => trans('Date de fin de l\'évènement'),
'event_text' => trans('Description de l\'évènement'),
'published' => trans('Status'),
];
}
}

View file

@ -12,7 +12,7 @@ class UpdateNewsCategoryRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -22,7 +22,7 @@ class UpdateNewsCategoryRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug(strip_tags($this->label))
@ -34,7 +34,7 @@ class UpdateNewsCategoryRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'label' => 'required|min:3|max:30',
@ -50,7 +50,7 @@ class UpdateNewsCategoryRequest extends FormRequest
{
return [
'label' => trans('label'),
'slug' => trans('slug'),
'slug' => trans('slug'),
];
}
}

View file

@ -4,10 +4,11 @@ namespace App\Http\Requests\Bo\Products;
use App\Enums\NutriscoreEnum;
use App\Enums\Products\ProductSpeciesEnum;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Validation\Rules\Enum;
class StoreProductRequest extends FormRequest
{
@ -18,7 +19,7 @@ class StoreProductRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -28,16 +29,26 @@ class StoreProductRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug(strip_tags($this->name)),
'slug' => Str::slug(strip_tags($this->name)),
'is_frenchpoultry' => $this->is_frenchpoultry ? true : false,
'is_vip' => $this->is_vip ? true : false,
'is_egalim' => $this->is_egalim ? true : false,
'status' => $this->status ? true : false
'is_vip' => $this->is_vip ? true : false,
'is_egalim' => $this->is_egalim ? 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->slider_pictures)
? $this->mergePictures('slider_pictures')
: $this->merge([
'slider_pictures' => [],
]);
}
/**
@ -45,61 +56,80 @@ class StoreProductRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'label' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'subrange_id' => 'required',
'species' => ['required', new EnumIsValidRule(ProductSpeciesEnum::class)],
'nutriscore' => ['required', new EnumIsValidRule(NutriscoreEnum::class)],
'sticker_sm_alt' => 'required|min:3|max:255',
'sticker_sm_title' => 'required|min:3|max:255',
'ingredients' => 'required|string|min:15',
'advantage' => 'required|string|min:15',
'preparation' => 'required|string|min:15',
'packaging' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'label' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'subrange_id' => 'required',
'species' => ['required', new Enum(ProductSpeciesEnum::class)],
'nutriscore' => ['required', new Enum(NutriscoreEnum::class)],
'sticker_sm_alt' => 'required|min:3|max:255',
'sticker_sm_title' => 'required|min:3|max:255',
'ingredients' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'advantage' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'preparation' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'packaging' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (1 > count($value) || count($value) > 6) {
$fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de conditionnement.'));
}
}
],
'packaging.*' => 'required|array',
'packaging.*.value' => 'required|string|min:2|max:255',
'caliber' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
'packaging.*' => 'required|array',
'packaging.*.value' => 'required|string|min:2|max:255',
'caliber' => [
'present',
'array',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, array $value, $fail) {
if (1 > count($value) || count($value) > 6) {
$fail(\trans('Vous devez remplir au moins 1 ligne et au plus 6 lignes de calibre.'));
}
}
],
'caliber.*' => 'required|array',
'caliber.*.value' => 'required|string|min:1|max:255',
'nutritionnal_values' => 'required|array',
'nutritionnal_values' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (count($value) != 9) {
$fail(\trans('Vous devez remplir seulement 9 lignes de valeurs nutritionnelles.'));
'caliber.*' => 'required|array',
'caliber.*.value' => 'required|string|min:1|max:255',
'is_frenchpoultry' => 'required|boolean',
'is_vip' => 'required|boolean',
'is_egalim' => '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.'));
}
}
],
'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_vip' => 'required|boolean',
'is_egalim' => 'required|boolean',
'status' => 'required|boolean'
'tags.*' => 'required|array',
'tags.*.id' => 'required|numeric|exists:tags,id|distinct',
'tags.*.label' => 'required|string',
];
return \array_merge(
$rules,
$this->pictureRules('sticker_sm', true, 206, 159, 206, 159),
$this->picturesRules('slider_pictures', true, 396, 286, 396, 286, 1, 5),
$this->pictureRules('sticker_sm', 206, 159, 206, 159, false),
$this->picturesRules('slider_pictures', 396, 286, 396, 286, 1, 5, true),
);
}
@ -111,29 +141,30 @@ class StoreProductRequest extends FormRequest
public function attributes(): array
{
return [
'label' => trans('title'),
'slug' => trans('slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'subrange_id' => trans('sous-gamme'),
'species' => trans('Espèce du produit'),
'nutriscore' => trans('Label Nutriscore'),
'sticker_sm' => trans('miniature'),
'sticker_sm_alt' => trans('attribut alt de la miniature'),
'sticker_sm_title' => trans('titre de la miniature'),
'slider_pictures' => trans('images du slider'),
'label' => trans('title'),
'slug' => trans('slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'subrange_id' => trans('sous-gamme'),
'species' => trans('Espèce du produit'),
'nutriscore' => trans('Label Nutriscore'),
'sticker_sm' => trans('miniature'),
'sticker_sm_alt' => trans('attribut alt de la miniature'),
'sticker_sm_title' => trans('titre de la miniature'),
'slider_pictures' => trans('images du slider'),
'slider_pictures.*' => trans('image du slider'),
'packaging' => trans('conditionnement'),
'packaging' => trans('conditionnement'),
'packaging.*.value' => trans('valeur du conditionnement'),
'caliber' => trans('calibre'),
'caliber.*.value' => trans('valeur du calibre'),
'nutritionnal_values' => trans('valeurs nutritionnelles'),
'nutritionnal_values.*.label' => trans('libellé de la valeur nutritionnelle'),
'nutritionnal_values.*.value' => trans('valeur de la valeur nutritionnelle'),
'is_frenchpoultry' => trans('Label volaille française'),
'is_vip' => trans('Produit VIP'),
'is_egalim' => trans('Produit Égalim'),
'status' => trans('publish')
'caliber' => trans('calibre'),
'caliber.*.value' => trans('valeur du calibre'),
'is_frenchpoultry' => trans('Label volaille française'),
'is_vip' => trans('Produit VIP'),
'is_egalim' => trans('Produit Égalim'),
'published' => trans('publish'),
'tags' => trans('tag'),
'tags.*' => trans('tag'),
'tags.*.id' => trans('id du tag'),
'tags.*.label' => trans('label du tag'),
];
}
}

View file

@ -15,7 +15,7 @@ class UpdateRangeRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -25,7 +25,7 @@ class UpdateRangeRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug(strip_tags($this->label))
@ -39,22 +39,22 @@ class UpdateRangeRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'label' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'color' => 'required|color_rgba',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'picture_banner_alt' => 'required|min:3|max:255',
'label' => 'required|min:3|max:255',
'seo_title' => 'required|min:45|max:70',
'seo_description' => 'required|min:50|max:160',
'color' => 'required|color_rgba',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'picture_banner_alt' => 'required|min:3|max:255',
'picture_banner_title' => 'required|min:3|max:255',
];
return \array_merge(
$rules,
$this->pictureRules('picture', true, 290, 232, 290, 232),
$this->pictureRules('picture_banner', true, 1440, 300, 1440, 300),
$this->pictureRules('picture', 290, 232, 290, 232, true),
$this->pictureRules('picture_banner', 1440, 300, 1440, 300, true),
);
}
@ -66,16 +66,16 @@ class UpdateRangeRequest extends FormRequest
public function attributes(): array
{
return [
'label' => trans('label'),
'slug' => trans('slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'color' => trans('couleur'),
'picture' => trans('Illustration'),
'picture_alt' => trans('attribut alt de l\'image'),
'picture_title' => trans('titre de l\'image'),
'picture_banner' => trans('bannière'),
'picture_banner_alt' => trans('attribut alt de la bannière'),
'label' => trans('label'),
'slug' => trans('slug'),
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'color' => trans('couleur'),
'picture' => trans('Illustration'),
'picture_alt' => trans('attribut alt de l\'image'),
'picture_title' => trans('titre de l\'image'),
'picture_banner' => trans('bannière'),
'picture_banner_alt' => trans('attribut alt de la bannière'),
'picture_banner_title' => trans('titre de la bannière'),
];
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Requests\Bo\Slides;
use App\Rules\WysiwygLength;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
@ -14,7 +15,7 @@ class StoreSlideRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -24,11 +25,12 @@ class StoreSlideRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'button_display' => $this->button_display ? true : false,
'button_targetblank' => $this->button_targetblank ? true : false,
'button_display' => $this->boolean('button_display'),
'button_targetblank' => $this->boolean('button_targetblank'),
'published' => $this->boolean('published'),
]);
$this->mergePicture('picture');
$this->mergePicture('picture_thumbnail');
@ -39,24 +41,29 @@ class StoreSlideRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'title' => 'required|min:3|max:255',
'content' => 'required|string|min:15',
'button_display' => 'required|boolean',
'button_text' => 'nullable|min:3|max:45',
'button_url' => 'nullable|min:3|max:255|url',
'button_targetblank' => 'required|boolean',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'picture_thumbnail_alt' => 'required|min:3|max:255',
'picture_thumbnail_title' => 'required|min:3|max:255'
'title' => 'required|min:3|max:255',
'content' => [
'required',
'string',
new WysiwygLength(15, 2500)
],
'button_display' => 'required|boolean',
'button_text' => 'nullable|min:3|max:45',
'button_url' => 'nullable|min:3|max:255|url',
'button_targetblank' => 'required|boolean',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'picture_thumbnail_alt' => 'required|min:3|max:255',
'picture_thumbnail_title' => 'required|min:3|max:255',
'published' => 'required|boolean',
];
return \array_merge(
$rules,
$this->pictureRules('picture', true, 950, 700, 950, 700),
$this->pictureRules('picture_thumbnail', true, 167, 70, 167, 70)
$this->pictureRules('picture', 950, 700, 950, 700, true),
$this->pictureRules('picture_thumbnail', 167, 70, 167, 70, true)
);
}
@ -68,17 +75,17 @@ class StoreSlideRequest extends FormRequest
public function attributes(): array
{
return [
'title' => trans('title'),
'content' => trans('contenu de la slide'),
'button_display' => trans('afficher le bouton'),
'button_text' => trans('texte du bouton'),
'button_url' => trans('url du bouton'),
'button_targetblank' => trans('ouvrir dans un nouvel onglet'),
'picture' => trans('Illustration'),
'picture_alt' => trans('attribut alt de l\'image'),
'picture_title' => trans('titre de l\'image'),
'picture_thumbnail' => trans('vignette'),
'picture_thumbnail_alt' => trans('attribut alt de la vignette'),
'title' => trans('title'),
'content' => trans('contenu de la slide'),
'button_display' => trans('afficher le bouton'),
'button_text' => trans('texte du bouton'),
'button_url' => trans('url du bouton'),
'button_targetblank' => trans('ouvrir dans un nouvel onglet'),
'picture' => trans('Illustration'),
'picture_alt' => trans('attribut alt de l\'image'),
'picture_title' => trans('titre de l\'image'),
'picture_thumbnail' => trans('vignette'),
'picture_thumbnail_alt' => trans('attribut alt de la vignette'),
'picture_thumbnail_title' => trans('titre de la vignette')
];
}

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;
use App\Enums\TagTypeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class StoreTagRequest extends FormRequest
{
@ -12,7 +14,7 @@ class StoreTagRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -22,7 +24,7 @@ class StoreTagRequest extends FormRequest
*
* @return void
*/
public function prepareForValidation()
public function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->label),
@ -34,10 +36,14 @@ class StoreTagRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'label' => 'required|min:2|max:30',
'type' => [
'required',
Rule::enum(TagTypeEnum::class),
]
];
}
@ -50,7 +56,8 @@ class StoreTagRequest extends FormRequest
{
return [
'label' => trans('libellé'),
'slug' => trans('slug'),
'slug' => trans('slug'),
'type' => trans('type'),
];
}
}

View file

@ -4,14 +4,14 @@ namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class ChangeOrderRequest extends FormRequest
class UpdateChangeOrderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return auth('backend')->check();
}
@ -21,10 +21,10 @@ class ChangeOrderRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'direction' => $this->action === 'up' ? true : false
'direction' => $this->direction === 'up' ? true : false
]);
}
@ -33,10 +33,9 @@ class ChangeOrderRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'action' => 'required|string|in:up,down',
'direction' => 'required|boolean',
];
}

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

@ -6,7 +6,7 @@ use App\Enums\Contact\ContactActivityEnum;
use App\Enums\Contact\ContactTimeEnum;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Illuminate\Validation\Rules\Enum;
class ContactRequest extends FormRequest
{
@ -15,32 +15,45 @@ class ContactRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
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.
*
* @return array
*/
public function rules()
public function rules(): array
{
ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version'));
return [
'firstname' => 'nullable|min:2|max:255',
'lastname' => 'required|min:2|max:255',
'email' => 'required|email:rfc,dns,spoof|max:255',
'phone' => 'required|max:255|phone:' . implode(',', $this->isoCodes()),
'activity' => ['required', new EnumIsValidRule(ContactActivityEnum::class)],
'society' => 'nullable|string|max:255',
'postcode' => 'nullable|postal_code:' . implode(',', $this->isoCodes()),
'city' => 'nullable|string|max:255',
'message' => 'required|string|min:20|max:255',
'callTime' => ['required', new EnumIsValidRule(ContactTimeEnum::class)],
'rgpd' => 'accepted'
'lastname' => 'required|min:2|max:255',
'email' => 'required|email:rfc,dns,spoof|max:255',
'phone' => 'required|max:255|phone:' . implode(',', $this->isoCodes()),
'activity' => ['required', new Enum(ContactActivityEnum::class)],
'society' => 'nullable|string|max:255',
'postcode' => 'required|postal_code:' . implode(',', $this->isoCodes()),
'city' => 'nullable|string|max:255',
'message' => 'required|string|min:20|max:255',
'callTime' => ['required', new Enum(ContactTimeEnum::class)],
'brigade' => 'nullable|boolean',
'opt_in' => 'required|boolean',
];
}
@ -49,17 +62,17 @@ class ContactRequest extends FormRequest
*
* @return array
*/
public function messages()
public function messages(): array
{
return [
// phpcs:ignore Generic.Files.LineLength.TooLong
'phone.phone' => trans('Le champ :attribute est invalide, il accepte les numéros des pays suivants :countries.', [
'countries' => implode(', ', array_keys($this->isoCodes()))
]),
// phpcs:ignore Generic.Files.LineLength.TooLong
'postcode.postal_code' => trans('Le champ :attribute est invalide, il accepte les codes postaux des pays suivants :countries.', [
'countries' => implode(', ', array_keys($this->isoCodes()))
]),
'phone.phone' => trans(
'Le champ :attribute est invalide, il accepte les numéros des pays suivants :countries.',
['countries' => implode(', ', array_keys($this->isoCodes()))]
),
'postcode.postal_code' => trans(
'Le champ :attribute est invalide, il accepte les codes postaux des pays suivants :countries.',
['countries' => implode(', ', array_keys($this->isoCodes()))]
),
];
}
@ -72,16 +85,17 @@ class ContactRequest extends FormRequest
{
return [
'firstname' => trans('prénom'),
'lastname' => trans('nom'),
'email' => trans('email'),
'phone' => trans('téléphone'),
'activity' => trans('activité'),
'society' => trans('société'),
'postcode' => trans('code postal'),
'city' => trans('ville'),
'message' => trans('message'),
'callTime' => trans('préférence de rappel'),
'rgpd' => trans('condition d\'utilisation'),
'lastname' => trans('nom'),
'email' => trans('email'),
'phone' => trans('téléphone'),
'activity' => trans('activité'),
'society' => trans('société'),
'postcode' => trans('code postal'),
'city' => trans('ville'),
'message' => trans('message'),
'callTime' => trans('préférence de rappel'),
'brigade' => trans('La Brigade'),
'opt_in' => trans('newsletter'),
];
}
@ -93,20 +107,20 @@ class ContactRequest extends FormRequest
private function isoCodes(): array
{
return [
'france' => 'FR',
'belgium' => 'BE',
'luxembourg' => 'LU',
'guadeloupe' => 'GP',
'martinique' => 'MQ',
'frenchGuiana' => 'GF',
'reunion' => 'RE',
'france' => 'FR',
'belgium' => 'BE',
'luxembourg' => 'LU',
'guadeloupe' => 'GP',
'martinique' => 'MQ',
'frenchGuiana' => 'GF',
'reunion' => 'RE',
'saintPierreAndMiquelon' => 'PM',
'mayotte' => 'YT',
'saintBarthelemy' => 'BL',
'saintMartinFrenchPart' => 'MF',
'wallisAndFutuna' => 'WF',
'frenchPolynesia' => 'PF',
'newCaledonia' => 'NC',
'mayotte' => 'YT',
'saintBarthelemy' => 'BL',
'saintMartinFrenchPart' => 'MF',
'wallisAndFutuna' => 'WF',
'frenchPolynesia' => 'PF',
'newCaledonia' => 'NC',
];
}
}

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

@ -11,7 +11,7 @@ class AjaxAddProductBasket extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return true;
}
@ -21,12 +21,12 @@ class AjaxAddProductBasket extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'packaging' => 'required|array|min:1',
'packaging' => 'array',
'packaging.*' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
$containsAttribute = (bool)collect($this->publishedProduct->packaging)
->filter(function (array $package) use ($value) {
@ -37,9 +37,9 @@ class AjaxAddProductBasket extends FormRequest
}
}
],
'caliber' => 'required|array|min:1',
'caliber.*' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'caliber' => 'array',
'caliber.*' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
$containsAttribute = (bool)collect($this->publishedProduct->caliber)
->filter(function (array $calibe) use ($value) {
@ -56,13 +56,13 @@ class AjaxAddProductBasket extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
* @return array<string,string>
*/
public function attributes(): array
{
return [
'packaging' => trans('conditionnement'),
'caliber' => trans('calibre'),
'caliber' => trans('calibre'),
];
}
}

View file

@ -14,7 +14,7 @@ class AjaxFilterProductsRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return true;
}
@ -24,11 +24,12 @@ class AjaxFilterProductsRequest extends FormRequest
*
* @return void
*/
public function prepareForValidation()
public function prepareForValidation(): void
{
$this->merge([
'vip' => $this->vip ? true : false,
'subranges' => \is_array($this->subranges) ? $this->subranges : []
'vip' => $this->vip ? true : false,
'subranges' => isset($this->subranges) ?
explode(',', $this->subranges) : (\is_array($this->subranges) ? $this->subranges : [])
]);
}
@ -37,7 +38,7 @@ class AjaxFilterProductsRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [];
if ($this->ajax()) {
@ -46,7 +47,7 @@ class AjaxFilterProductsRequest extends FormRequest
$rules['subranges'] = 'present|array';
$rules['subranges.*'] = [
// 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) {
$fail(trans('La gamme est obligatoire', ['attribute' => $attribute]));
return;
@ -58,7 +59,7 @@ class AjaxFilterProductsRequest extends FormRequest
$hasSubrange = Range::query()->whereHas('subranges', function (Builder $q) use ($subrange) {
$q->where('id', $subrange->id);
})->where('id', $this->range->id)->exists();
if (empty($value) or !$hasSubrange) {
if (!$hasSubrange) {
$fail(trans(':attribute doit appartenir à la bonne gamme', ['attribute' => $attribute]));
return;
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Resources\Fo\Products;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductCommercialSheetResource extends JsonResource
@ -12,24 +13,23 @@ class ProductCommercialSheetResource extends JsonResource
/** @var array */
private static $skeleton = [
'id' => '',
'label' => '',
'slug' => '',
'species' => null,
'is_frenchpoultry' => false,
'nutriscore' => [
'id' => '',
'label' => '',
'slug' => '',
'species' => null,
'is_frenchpoultry' => false,
'nutriscore' => [
'label' => 'none',
'value' => 0
],
'advantage' => '',
'packaging' => [],
'caliber' => [],
'ingredients' => '',
'preparation' => '',
'sticker_sm' => '',
'nutritionnal_values' => [],
'slider_pictures' => [],
'subrange' => null,
'advantage' => '',
'packaging' => [],
'caliber' => [],
'ingredients' => '',
'preparation' => '',
'sticker_sm' => '',
'slider_pictures' => [],
'subrange' => null,
];
/**
@ -48,27 +48,25 @@ class ProductCommercialSheetResource extends JsonResource
*
* @param \Illuminate\Http\Request|null $request
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request = null)
public function toArray(?Request $request = null): array
{
return array_intersect_key(
[
'id' => $this->product->id,
'label' => $this->product->label,
'slug' => $this->product->slug,
'species' => $this->product->species,
'is_frenchpoultry' => $this->product->is_frenchpoultry,
'nutriscore' => $this->product->nutriscore,
'advantage' => $this->product->advantage,
'packaging' => $this->product->packaging,
'caliber' => $this->product->caliber,
'ingredients' => $this->product->ingredients,
'preparation' => $this->product->preparation,
'nutritionnal_values' => $this->product->nutritionnal_values,
'sticker_sm' => $this->product->sticker_sm,
'slider_pictures' => $this->product->slider_pictures,
'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request),
'id' => $this->product->id,
'label' => $this->product->label,
'slug' => $this->product->slug,
'species' => $this->product->species,
'is_frenchpoultry' => $this->product->is_frenchpoultry,
'nutriscore' => $this->product->nutriscore,
'advantage' => $this->product->advantage,
'packaging' => $this->product->packaging,
'caliber' => $this->product->caliber,
'ingredients' => $this->product->ingredients,
'preparation' => $this->product->preparation,
'sticker_sm' => $this->product->sticker_sm,
'slider_pictures' => $this->product->slider_pictures,
'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request),
],
$this->getSkeleton()
);

View file

@ -3,6 +3,7 @@
namespace App\Http\Resources\Fo\Products;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ProductPaginatedCollectionResource extends ResourceCollection
@ -13,27 +14,26 @@ class ProductPaginatedCollectionResource extends ResourceCollection
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function toArray($request)
public function toArray(Request $request): array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
{
// phpcs:enable
//phpcs:enable
/** @var \Illuminate\Pagination\LengthAwarePaginator */
$paginator = $this->resource;
return [
'first_page_url' => $paginator->url(0),
'current_page' => $paginator->currentPage(),
'next_page_url' => $paginator->nextPageUrl(),
'links' => $paginator->linkCollection(),
'prev_page_url' => $paginator->previousPageUrl(),
'last_page_url' => $paginator->url($paginator->lastPage()),
'last_page' => $paginator->lastPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
'path' => $paginator->path(),
'from' => $paginator->firstItem(),
'to' => $paginator->lastItem(),
'data' => $this->collection->map(function (Product $product) {
'current_page' => $paginator->currentPage(),
'next_page_url' => $paginator->nextPageUrl(),
'links' => $paginator->linkCollection(),
'prev_page_url' => $paginator->previousPageUrl(),
'last_page_url' => $paginator->url($paginator->lastPage()),
'last_page' => $paginator->lastPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
'path' => $paginator->path(),
'from' => $paginator->firstItem(),
'to' => $paginator->lastItem(),
'data' => $this->collection->map(function (Product $product) {
return new ProductResource($product);
})
];

View file

@ -3,6 +3,7 @@
namespace App\Http\Resources\Fo\Products;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
@ -12,14 +13,14 @@ class ProductResource extends JsonResource
/** @var array */
private static $skeleton = [
'id' => '',
'label' => '',
'slug' => '',
'species' => null,
'sticker_sm' => '',
'sticker_sm_alt' => '',
'id' => '',
'label' => '',
'slug' => '',
'species' => null,
'sticker_sm' => '',
'sticker_sm_alt' => '',
'sticker_sm_title' => '',
'subrange' => null,
'subrange' => null,
];
/**
@ -38,20 +39,19 @@ class ProductResource extends JsonResource
*
* @param \Illuminate\Http\Request|null $request
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request = null)
public function toArray(?Request $request = null): array
{
return array_intersect_key(
[
'id' => $this->product->id,
'label' => $this->product->label,
'slug' => $this->product->slug,
'species' => $this->product->species,
'sticker_sm' => $this->product->sticker_sm,
'sticker_sm_alt' => $this->product->sticker_sm_alt,
'id' => $this->product->id,
'label' => $this->product->label,
'slug' => $this->product->slug,
'species' => $this->product->species,
'sticker_sm' => $this->product->sticker_sm,
'sticker_sm_alt' => $this->product->sticker_sm_alt,
'sticker_sm_title' => $this->product->sticker_sm_title,
'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request),
'subrange' => (new SubrangeResource($this->product->subrange))->toArray($request),
],
$this->getSkeleton()
);

View file

@ -3,6 +3,7 @@
namespace App\Http\Resources\Fo\Products;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class RangeResource extends JsonResource
@ -12,9 +13,9 @@ class RangeResource extends JsonResource
/** @var array */
private static $skeleton = [
'id' => '',
'id' => '',
'label' => '',
'slug' => '',
'slug' => '',
'color' => ''
];
@ -36,13 +37,14 @@ class RangeResource extends JsonResource
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request = null)
public function toArray(?Request $request = null): array
{
//phpcs:enable
return array_intersect_key(
[
'id' => $this->range->id,
'id' => $this->range->id,
'label' => $this->range->label,
'slug' => $this->range->slug,
'slug' => $this->range->slug,
'color' => $this->range->color
],
$this->getSkeleton()

View file

@ -3,6 +3,7 @@
namespace App\Http\Resources\Fo\Products;
use App\Models\Subrange;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class SubrangeResource extends JsonResource
@ -12,9 +13,9 @@ class SubrangeResource extends JsonResource
/** @var array */
private static $skeleton = [
'id' => '',
'id' => '',
'label' => '',
'slug' => '',
'slug' => '',
'range' => null
];
@ -34,9 +35,8 @@ class SubrangeResource extends JsonResource
*
* @param \Illuminate\Http\Request|null $request
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request = null)
public function toArray(?Request $request = null): array
{
return array_intersect_key(
[
@ -50,7 +50,7 @@ class SubrangeResource extends JsonResource
}
/**
* Returns the resource skeletton
* Returns the resource skeletton.
*
* @return array
*/

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

@ -0,0 +1,64 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Container\Container;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
class CollectionHelper
{
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
{
$page = Paginator::resolveCurrentPage('page');
return self::paginateManually($results, $pageSize, $page);
}
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @param integer $page
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
{
$total = $results->count();
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
}
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param integer $total
* @param integer $perPage
* @param integer $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
'items' => $items,
'total' => $total,
'perPage' => $perPage,
'currentPage' => $currentPage,
'options' => $options
]);
}
}

View file

@ -80,13 +80,13 @@ class ExceptionsHelper
foreach ($params as &$param) {
$type = \strtolower(\gettype($param));
if (\is_string($param)) {
$type = \explode(' ', \trim($param))[0] ?? null;
$type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
}
switch ($type) {
case 'Lambda':
case 'Object':
switch (true) {
case $type === 'lambda':
case $type === 'object':
continue 2;
case 'array':
case $type === 'array' and \is_array($param):
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
@ -145,7 +145,7 @@ class ExceptionsHelper
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
} else {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);

View file

@ -30,6 +30,20 @@ use Illuminate\Support\Str;
*/
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.
*
@ -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).
*
@ -60,10 +88,10 @@ class FileStorageHelper
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated and $model->{$attribute} === $model->getOriginal($attribute)) {
if ($onlyIfUpdated and $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
return;
}
$originalPath = \ltrim(\urldecode($model->getOriginal($attribute)), '/');
$originalPath = \ltrim(\urldecode(ToolboxHelper::getOriginal($model, $attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
return;
}
@ -82,29 +110,48 @@ class FileStorageHelper
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param boolean $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param boolean $private To store the file in a private space.
* @return string
* @throws \RuntimeException If file copy fails.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public static function storeFile(Model $model, $file, bool $slugify = false, string $filename = null): string
{
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string|null $filename = null,
bool $private = false
): string {
// phpcs:enable
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
/** @var \Illuminate\Http\UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename);
$newPath = self::prepareMoveFile($tableName, $filename, $private);
$newPath = str_replace(storage_path('app'), '', $newPath);
$file->storePubliclyAs(
if (!$private) {
// * Store public file.
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
$file->storeAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
return str_replace('/private/', '', $newPath);
} //end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename);
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
'Failed to copy file %s to %s',
@ -112,7 +159,9 @@ class FileStorageHelper
$newPath
));
}
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
return $private ?
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return strval($file);
}
@ -122,7 +171,6 @@ class FileStorageHelper
* If oldHtmlString is passed, a diff is done with new and old files
* to keep only files that are present in the new html string.
*
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
@ -132,7 +180,7 @@ class FileStorageHelper
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
string|null $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
@ -208,6 +256,7 @@ class FileStorageHelper
/**
* Find all occurence of files in html
*
* @param string $html
* @return array
*/
@ -268,22 +317,27 @@ class FileStorageHelper
* Prepare storage path and return
* the file full path
*
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param boolean $private May the file be stored in a private dir.
* @return string
*/
private static function prepareMoveFile(string $tableName, string $filename): string
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
{
// * Security filter
$filename = \htmlspecialchars($filename);
// * Security filter + beautifier
$filename = self::sanitizeFileName($filename);
// * Security filter
$filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
// * Slugify filename
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
(\strlen($ext) ? ".{$ext}" : '');
$folderPath = self::getStoragePath($tableName, $filename);
$folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
@ -293,15 +347,17 @@ class FileStorageHelper
* storage/modelfiles/posts/10/04/19/5f
* year/month/day/md5 first 2 letters
*
* @param string $storage_dir
* @param string $table
* @param string $filename
* @return string
*/
private static function getStoragePath(string $table, string $filename): string
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
$filenameHash = \md5($filename);
$pathFolder = \storage_path(sprintf(
'app/public/modelfiles/%s/%s/%s/%s/%s',
'%s/%s/%s/%s/%s/%s',
$storage_dir,
$table,
gmdate('y'),
gmdate('m'),
@ -328,7 +384,7 @@ class FileStorageHelper
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to has a unique filename for {$folderPath}/{$filename}");
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
}
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
@ -375,7 +431,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
$filename = self::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
@ -422,39 +478,4 @@ class FileStorageHelper
$filename = trim($filename, '.-');
return $filename;
}
/**
* Store picture array
* @param \Illuminate\Database\Eloquent\Model $model
* @param array $files
* @return array Containing stored Files using storage strategy.
*/
public static function storePictureArray(Model $model, array $files): array
{
return \collect($files)->map(function ($file) use ($model) {
if (!\is_string($file) and !(\is_object($file) and $file instanceof \SplFileInfo)) {
throw new \RuntimeException('picture array does not support anything else than string or SplfFileInfo');
}
return FileStorageHelper::storeFile($model, $file, true);
})->all();
}
/**
* Remove picture array
* @param \Illuminate\Database\Eloquent\Model $model
* @param array $files
* @return void
*/
public static function removePictureArray(Model $model, array $files): void
{
\collect($files)->map(function ($file) use ($model) {
if (!\is_string($file)) {
throw new \RuntimeException('picture array does not support anything else than string');
}
// * Use temp prop te remove file.
$model->tempPicture = $file;
FileStorageHelper::removeFile($model, 'tempPicture');
$model->tempPicture = null;
})->all();
}
}

View file

@ -2,44 +2,128 @@
namespace App\Lib\Helpers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Finder\SplFileInfo;
class ToolboxHelper
{
/**
* Cut string words
* Run a terminal command and gives the output
*
* @param string $string
* @param integer $max
* @param string $hyphen
* @return string
* @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 cutWords(string $string, int $max = 20, string $hyphen = '[.]')
public static function runConsoleCommand(array|string $command, bool $throw = true): string | false
{
$originalString = $string;
$tok = strtok($string, ' ');
$string = '';
while ($tok !== false && mb_strlen($string) < $max) {
if (
mb_strlen($string) + mb_strlen($tok) <= $max
) {
$string .= $tok . ' ';
} else {
break;
// 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}");
}
$tok = strtok(' ');
return false;
}
return trim($string) . (strlen(trim($string)) !== strlen(trim($originalString)) ? $hyphen : '');
return $process->output();
}
/**
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
*
* @return array<int|string, mixed>
*/
public static function arrayMergeRecursiveDistinct(array &$array1, array &$array2): array
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
/**
* Validate recaptcha
*
* @param \Illuminate\Http\Request $request
* @param boolean $isV3
* @return void
*/
public static function validateRecaptcha(Request $request, bool $isV3 = true)
{
Validator::validate($request->all(), [
'g-recaptcha-response' => [
'required', 'string',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3) {
try {
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.google.recaptcha.private'),
'response' => \strval($value),
'remoteip' => app('clientIp')
]);
if ($e = $response->toException()) {
throw $e;
}
if ($isV3) {
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
!$response->json('score')
) {
throw new \RuntimeException();
}
if (\floatval($response->json('score')) < 0.8) {
$fail(trans('recaptcha.not_enough_score'));
}
return;
}
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
) {
throw new \RuntimeException();
}
} catch (\Exception $e) {
$fail(trans('recaptcha.error'));
} //end try
}
]
], [], ['g-recaptcha-response' => 'reCaptcha']);
}
/**
@ -85,7 +169,10 @@ class ToolboxHelper
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => ':attribute : Il est interdit de coller une url du back office dans ce champ'
$field => trans(
'crud.libs.toolbox.assertNoInternalBoUrls',
['attribute' => $field]
)
]);
}
return $match;
@ -119,16 +206,15 @@ class ToolboxHelper
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Assert route matches existing routes.
$routes->match($request);
// ? Is url internal ?
if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => sprintf(
':attribute : Il est interdit de coller une url de %s dans ce champ',
config('app.name')
$field => trans(
'crud.libs.toolbox.assertNoInternalUrls',
['attribute' => $field, 'appname' => config('app.name')]
)
]);
}
@ -142,57 +228,21 @@ class ToolboxHelper
return $text;
}
/**
* Generate url using laravel route passing our group query if present
* This is used to keep ou group tracking
* Curd-policies is only used for root users as it cannot keep track of our group
* and validate things
*
* @param array|string $name
* @param mixed $parameters
* @param boolean $absolute
* @return string
* @throws \ErrorException If an object given as parameter is not a Model.
*/
public static function routeWithGroup($name, $parameters = [], bool $absolute = true): string
{
// * Handle if parameter if is an object.
if (\is_object($parameters)) {
$parents = \class_parents($parameters);
if ($parents === false or !\in_array(Model::class, $parents)) {
throw new \ErrorException(\sprintf(
'%s only support object of type %s',
__FUNCTION__,
Model::class
));
}
/** @var \Illuminate\Database\Eloquent\Model $model */
$model = $parameters;
$parameters = [Str::singular($model->getTable()) => $model];
}
// * Handle if parameter isnt and array like slug or else.
if (!is_array($parameters)) {
// * Best effort, result is not garantee.
$parameters = [$parameters];
}
// * Pass our group.
if ($group = Session::get('activeGroup', request()->group)) {
$parameters = array_merge($parameters, ['group' => $group]);
}
// * Call laravel route helper.
return route($name, $parameters, $absolute);
}
/**
* Mutltibyte string replace
* @param mixed $search
* @param mixed $replace
* @param mixed $subject
* @param integer $count
* @return mixed
*
* @param array|string $search
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string|false
*/
public static function mbReplace($search, $replace, $subject, int &$count = 0)
{
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string|false {
if (!is_array($search) && is_array($replace)) {
return false;
}
@ -225,142 +275,318 @@ class ToolboxHelper
/**
* Asserts that the field is unique
*
* @param string $table
* @param string $field
* @param mixed $value
* @param integer|null $id
* @param callable|null $query
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*/
public static function assertFieldIsUnique(
string $table,
string $field,
$value,
string|int|float|bool|null|array $value,
?int $id = null,
?callable $query = null
) {
if (!$query) {
Validator::make([$field => $value], [
$field => Rule::unique($table, $field)->ignore($id),
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
$field . '.unique' => trans('La valeur :value pour le champ :attribute est déjà utilisé', [
'attribute' => $field,
'value' => $value
])
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
])->validate();
return;
}
Validator::make([$field => $value], [
$field => Rule::unique($table)->where($query)->ignore($id),
], [
$field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [
'attribute' => $field
])
$field . '.unique' => trans(
'crud.libs.toolbox.assertFieldIsUnique',
['attribute' => $field, 'value' => $value]
)
])->validate();
}
/**
* Check that two arrays have the same content (1 level only)
* Get original polyfill with autotranslate behavior
*
* @param array $array1
* @param array $array2
* @return boolean
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return mixed
*/
public static function arrayHaveSameContent(array $array1, array $array2): bool
public static function getOriginal(Model $model, string $attribute): mixed
{
return (array_diff($array1, $array2) == [] && array_diff($array2, $array1) == []);
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(
$attribute,
// @phpstan-ignore-next-line
$model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale()
);
}
return $model->getOriginal($attribute);
}
/**
* * Register CSP report URI
* Dynamic model query
*
* Put this function inside RouteServiceProvider,
* and call it in RouteServiceProvider::boot
*
* ! Use this in conjonction with kwaadpepper/exception-handler
*
* @return void
* @param string $defaultLocale
* @param \Illuminate\Contracts\Database\Eloquent\Builder $q
* @param string $column
* @param string $value
* @return \Illuminate\Contracts\Database\Eloquent\Builder
*/
public static function cspReportRoute()
{
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
abort(401);
public static function queryColumnWithLocales(
string $defaultLocale,
Builder $q,
string $column,
string $value
): Builder {
$currentLocale = app()->currentLocale();
$locales = collect(config('app.locales'))
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
if (!$locales->has($defaultLocale)) {
// * Remove default locale.
$locales->pull($defaultLocale);
}
if (!$locales->has($currentLocale)) {
// * Remove current locale.
$locales->pull($currentLocale);
}
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
// * Query default locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
if ($defaultLocale !== $currentLocale) {
// * Query current locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$currentLocale}') = '{$value}'");
}
// * Flatten if needed
$cspReport = collect(request()->json('csp-report'))
->flatMap(function ($v, $k) {
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
})->all();
// * Filter array keys
$cspReport = collect(array_intersect_key($cspReport, [
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
'referrer' => '', 'violated-directive' => ''
]));
report(new \Illuminate\Http\Client\HttpClientException(sprintf(
"CSP REPORT for %s : \n %s",
$cspReport->get('document-uri'),
$cspReport->implode("\n")
)));
})->middleware('throttle:5,2')
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
->name('cspReportUri');
// * Query any other locale
$locales->each(fn ($locale) => $q->orWhereRaw(
"JSON_EXTRACT({$column}, '$.{$locale}') = '{$value}'"
));
});
}
/**
* Validate recaptcha
* Generates an array from a dot separated string
*
* @param Request $request
* @param boolean $isV3
* @param string $path
* @param mixed $value
* @param string $separator
* @return array
*/
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
{
$arr = $value;
$keys = array_reverse(explode($separator, $path));
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Convert deeply an object to array
*
* @param mixed $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray(mixed $input): array
{
// Only process if it's an object or array being passed to the function.
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
*
* @param mixed $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject(mixed $input): object
{
// Only process if it's an object or array being passed to the function.
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
}
/**
* Validate picture
*
* @param string $attribute
* @param string|null $value
* @param callable $fail
* @param object $sizes
* @return void
*/
public static function validateRecaptcha(Request $request, bool $isV3 = true)
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
{
Validator::validate($request->all(), [
'g-recaptcha-response' => [
'required', 'string',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3) {
try {
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.google.recaptcha.private'),
'response' => \strval($value),
'remoteip' => app('clientIp')
]);
if ($e = $response->toException()) {
throw $e;
}
if ($isV3) {
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
!$response->json('score')
) {
throw new \RuntimeException();
}
if (\floatval($response->json('score')) < 0.8) {
$fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha");
}
return;
}
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
) {
throw new \RuntimeException();
}
} catch (\Exception $e) {
$fail('Erreur lors de la validation du formulaire veuillez réssayer');
} //end try
}
]
], [], ['g-recaptcha-response' => 'reCaptcha']);
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
'attribute' => $attribute
]));
return;
}
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if (
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
'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\'', [
'attribute' => $attribute
]));
}
if (!($dimensions = \getimagesize($fileFullPath))) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
]));
} else {
$width = intval($dimensions[0]);
$height = intval($dimensions[1]);
$xMessage = trans(
":attribute la largeur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
);
if ($sizes->minWidth === $sizes->maxWidth) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
}
$yMessage = trans(
":attribute la hauteur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
);
if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
}
if (($width < $sizes->minWidth) or ($width > $sizes->maxWidth)) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
$fail($yMessage);
}
} //end if
}
/**
* Get the file full path
*
* @param string $value
* @param boolean $useStorage
* @return string
*/
private static function getUploadedFileFullPath(string $value, bool $useStorage): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk();
return !$useStorage ? (realpath($value) ?: $value) : $disk->path($value);
}
/**
* Get the file mimetype
*
* @param string $value
* @param boolean $useStorage
* @return string|false
*/
private static function getUploadedFileMimeType(string $value, bool $useStorage)
{
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
}
/**
* Fetch class list from a specific namespace
*
* @param string $namespace
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingNamespace(string $namespace): array
{
return collect(File::files(\app_path(Str::of($namespace)->replaceFirst('\App', ''))))
->filter(fn (SplFileInfo $splFileInfo) => $splFileInfo->getExtension() === 'php')
->map(fn (SplFileInfo $splFileInfo) => Str::of($splFileInfo->getBasename())
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\"))->all();
}
/**
* Get all classes from a specific namespace that implement a specific interface
*
* @param string $namespace
* @param string $interface
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingInterface(string $namespace, string $interface): array
{
return Arr::where(
static::getClassesUsingNamespace($namespace),
fn (string $class_namespace) => \collect(class_implements($class_namespace, true))->filter()
->contains(fn (string $class_interface) => $class_interface === $interface)
);
}
/**
* 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.
*
* @param integer $default
* @return integer
*/
public static function getValidatedPaginate(int $default): int
{
try {
return \intval(Validator::make(
['pagination' => \request()->get('pagination')],
['pagination' => new Enum(ItemsPerPaginationEnum::class)]
)->validated()['pagination']);
} catch (ValidationException $e) {
return $default;
}
}
}

View file

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

View file

@ -2,22 +2,73 @@
namespace App\Models;
use App\Enums\Advices\AdviceTypeEnum;
use App\Interfaces\OrderableInterface;
use App\Enums\Advices\AdviceDifficultyEnum;
use App\Enums\Advices\AdviceTypeEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Collection;
use App\Traits\Models\Orderable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Kwaadpepper\Enum\Traits\CastsEnums;
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\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
class Advice extends Authenticatable
/**
* @property integer $id Id.
* @property string $title Title.
* @property string $slug Slug of the title.
* @property string $seo_title SEO Title.
* @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|null $advice Advice.
* @property array<array<string,string|integer>> $ingredients Ingredients.
* @property integer|null $guests_nb Number of guests.
* @property string|null $youtube_title Youtube title.
* @property string|null $youtube_video_id Youtube video id.
* @property integer|null $cooking_time Time of cooking.
* @property integer|null $preparation_time Time of preparation.
* @property \App\Enums\Advices\AdviceDifficultyEnum|null $difficulty Difficulty.
* @property boolean $published Status.
* @property integer $priority_order Priority Order.
* @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
* Advices Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products
* Products Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tag> $tags
* Tags Morph-To-Many relationship.
*/
class Advice extends Authenticatable implements SeoInterface, Sitemapable, RedirectableInterface, OrderableInterface
{
use CastsEnums;
use HasFactory;
use SeoTrait;
use IsRedirectable;
use Orderable;
/**
* The attributes that are mass assignable.
@ -48,27 +99,20 @@ class Advice extends Authenticatable
'cooking_time',
'preparation_time',
'difficulty',
'status'
];
/**
* Map attribute names to enum classes.
*
* @var array
*/
protected $enumCasts = [
'type' => AdviceTypeEnum::class,
'difficulty' => AdviceDifficultyEnum::class
'published',
'priority_order'
];
/**
* The attributes that should be cast.
*
* @var array
* @var array<string,string>
*/
protected $casts = [
'ingredients' => 'array',
'status' => 'bool'
'published' => 'bool',
'type' => AdviceTypeEnum::class,
'difficulty' => AdviceDifficultyEnum::class,
];
/**
@ -76,10 +120,11 @@ class Advice extends Authenticatable
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
self::bootOrderable();
});
static::updating(function (self $model) {
self::manageFields($model);
@ -94,18 +139,19 @@ class Advice extends Authenticatable
FileStorageHelper::removeFile($model, 'picture');
FileStorageHelper::removeFile($model, 'sticker');
FileStorageHelper::removeFile($model, 'sticker_sm');
self::setOrder($model);
});
}
// * METHODS
/**
* Manage fields on this model event.
* Common methods function.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param self $model
* @return void
*/
private static function manageFields(Model $model)
private static function manageFields(self $model): void
{
self::assertTitleIsUnique($model->title, $model->id);
$model->slug = Str::slug($model->title);
@ -139,7 +185,7 @@ class Advice extends Authenticatable
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitTags(int $nbTagsInBdd)
private static function assertLimitTags(int $nbTagsInBdd): void
{
if ($nbTagsInBdd > 2) {
throw ValidationException::withMessages([
@ -155,7 +201,7 @@ class Advice extends Authenticatable
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitAdvices(int $nbAdvicesInBdd)
private static function assertLimitAdvices(int $nbAdvicesInBdd): void
{
if ($nbAdvicesInBdd > 6) {
throw ValidationException::withMessages([
@ -167,20 +213,20 @@ class Advice extends Authenticatable
/**
* Limit products associated.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param self $model
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitProducts(Model $model)
private static function assertLimitProducts(self $model): void
{
$nbProductsInBdd = $model->products()->count();
if ($model->type->value == AdviceTypeEnum::recipes()->value) {
if ($model->type->value == AdviceTypeEnum::recipes->value()) {
if ($nbProductsInBdd < 1 || $nbProductsInBdd > 3) {
throw ValidationException::withMessages([
'Vous devez associer 1 à 3 produits.'
]);
}
} elseif ($model->type->value == AdviceTypeEnum::tips()->value) {
} elseif ($model->type->value == AdviceTypeEnum::tips->value()) {
if ($nbProductsInBdd > 3) {
throw ValidationException::withMessages([
'Vous devez associer au maximum 3 produits.'
@ -192,12 +238,12 @@ class Advice extends Authenticatable
/**
* Asserts using validation that the title is unique.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param self $model
* @return void
*/
private static function setCookingtimePreparationtimeValues(Model $model)
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->preparation_time = null;
}
@ -206,11 +252,11 @@ class Advice extends Authenticatable
/**
* Asserts using validation that the title is unique.
*
* @param mixed $title
* @param string $title
* @param integer|null $id
* @return void
*/
private static function assertTitleIsUnique($title, ?int $id = null)
private static function assertTitleIsUnique(string $title, ?int $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
@ -222,9 +268,21 @@ class Advice extends Authenticatable
*
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice>
*/
public function recipes(): 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(),
);
}
/**
@ -232,40 +290,107 @@ class Advice extends Authenticatable
*
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice>
*/
public function tips(): 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
/**
* BelongsToMany advices
* Advices Belongs-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function advices()
public function advices(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Advice::class, 'advice_advice', 'advice_id', 'advice_related_id');
}
/**
* BelongsToMany products
* Products Belongs-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products()
public function products(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Product::class);
}
/**
* MorphToMany Tags
* Tags Morph-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function tags()
public function tags(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
/**
* Transfotrm 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),
$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

@ -9,17 +9,28 @@ use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException;
use Kwaadpepper\Enum\Traits\CastsEnums;
/**
* @property integer $id Id.
* @property \App\Enums\ExpertiseProducts\ExpertiseProductPageEnum $page Title.
* @property string $label Label of the title.
* @property string $title Title.
* @property string $subtitle Subtitle.
* @property array $points Type of the advice.
* @property boolean $is_frenchpoultry Is frenchpoultry or not.
* @property boolean $is_natureeleveurs Is natureeleveurs or not.
* @property \App\Enums\NutriscoreEnum $nutriscore Nutriscore.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*/
class ExpertiseProduct extends Model
{
use CastsEnums;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'page',
@ -29,28 +40,20 @@ class ExpertiseProduct extends Model
'points',
'is_frenchpoultry',
'is_natureeleveurs',
'nutriscore'
];
/**
* Map attribute names to enum classes.
*
* @var array
*/
protected $enumCasts = [
'page' => ExpertiseProductPageEnum::class,
'nutriscore' => NutriscoreEnum::class
'nutriscore',
];
/**
* The attributes that should be cast.
*
* @var array
* @var array<string,string>
*/
protected $casts = [
'points' => 'array',
'is_frenchpoultry' => 'bool',
'is_natureeleveurs' => 'bool'
'points' => 'array',
'is_frenchpoultry' => 'bool',
'is_natureeleveurs' => 'bool',
'page' => ExpertiseProductPageEnum::class,
'nutriscore' => NutriscoreEnum::class
];
/**
@ -58,7 +61,7 @@ class ExpertiseProduct extends Model
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
@ -79,28 +82,27 @@ class ExpertiseProduct extends Model
// * METHODS
/**
* Undocumented function
* Common methods function.
*
* @param Model $model
* @param self $model
* @return void
*/
private static function manageFields(Model $model)
private static function manageFields(self $model): void
{
static::assertFieldIsUnique('label', $model->label, $model->id);
static::assertFieldIsUnique('title', $model->title, $model->id);
self::assertFieldIsUnique('label', $model->label, $model->id);
self::assertFieldIsUnique('title', $model->title, $model->id);
self::assertNumberOfProductsVip($model->page->value);
}
/**
* Asserts using validation that the label is unique
* Asserts using validation that the label is unique.
*
* @param string $name
* @param mixed $field
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $name, $field, ?int $id = null)
private static function assertFieldIsUnique(string $name, mixed $field, ?int $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $name, $field, $id);
@ -113,7 +115,7 @@ class ExpertiseProduct extends Model
* @return void
* @throws \Illuminate\Validation\ValidationException If there are more than 3 products.
*/
private static function assertNumberOfProductsVip(int $specie)
private static function assertNumberOfProductsVip(int $specie): void
{
if (count(self::where('page', '=', $specie)->get()) >= 3) {
throw ValidationException::withMessages([

View file

@ -2,25 +2,67 @@
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 Spatie\Sitemap\Tags\Url;
use Spatie\CalendarLinks\Link;
use App\Lib\Helpers\ToolboxHelper;
use Uwil\Seo\Laravel\Objects\SeoItem;
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;
/**
* @property-read string $reading_time Time reading of a news' page
*/
class News extends Authenticatable
/**
* @property integer $id Id.
* @property string $title Title.
* @property string $subtitle Type of the advice.
* @property string $slug Slug of the title.
* @property string $seo_title SEO Title.
* @property string $seo_description SEO description.
* @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|null $picture2 Path of a second picture.
* @property string|null $picture2_alt Second picture's attribute alt.
* @property string|null $picture2_title Second picture's attribute title.
* @property string $content First WYSWYG content.
* @property string|null $content2 Second WYSWYG content.
* @property \Illuminate\Support\Carbon|null $event_start Start of the event.
* @property \Illuminate\Support\Carbon|null $event_end End of the event.
* @property string|null $event_text Text of the event.
* @property boolean $published Status.
* @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 $updated_at Updated date.
*
* @property-read \Spatie\CalendarLinks\Link|null $cal_link
*
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news
* News Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products
* Products Belongs-To-Many relationship.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tags> $categories
* Categories Morph-To-Many relationship.
*/
class News extends Authenticatable implements SeoInterface, Sitemapable, RedirectableInterface
{
use HasFactory;
use SeoTrait;
use IsRedirectable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
* @var array<int,string>
*/
protected $fillable = [
'title',
@ -39,18 +81,18 @@ class News extends Authenticatable
'event_start',
'event_end',
'event_text',
'status'
'published',
];
/**
* The attributes that should be cast.
*
* @var array
* @var array<string,string>
*/
protected $casts = [
'event_start' => 'datetime',
'event_end' => 'datetime',
'status' => 'bool'
'event_end' => 'datetime',
'published' => 'bool',
];
/**
@ -58,7 +100,7 @@ class News extends Authenticatable
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
@ -79,15 +121,36 @@ class News extends Authenticatable
});
}
// * 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
/**
* Manage fields on this model event.
* Common methods function.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param self $model
* @return void
*/
private static function manageFields(Model $model)
private static function manageFields(self $model): void
{
self::assertTitleIsUnique($model->title, $model->id);
$model->slug = Str::slug($model->title);
@ -104,11 +167,12 @@ class News extends Authenticatable
/**
* Limit products associated.
*
* @param integer $nbProductsInBdd
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitProducts(int $nbProductsInBdd)
private static function assertLimitProducts(int $nbProductsInBdd): void
{
if ($nbProductsInBdd > 3) {
throw ValidationException::withMessages([
@ -119,11 +183,12 @@ class News extends Authenticatable
/**
* Limit news associated.
*
* @param integer $nbNewsInBdd
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitNews(int $nbNewsInBdd)
private static function assertLimitNews(int $nbNewsInBdd): void
{
if ($nbNewsInBdd > 3) {
throw ValidationException::withMessages([
@ -134,11 +199,12 @@ class News extends Authenticatable
/**
* Limit news categories associated.
*
* @param integer $nbNewsCategoriesInBdd
* @return void
* @throws \Illuminate\Validation\ValidationException Error exception.
*/
private static function assertLimitNewsCategory(int $nbNewsCategoriesInBdd)
private static function assertLimitNewsCategory(int $nbNewsCategoriesInBdd): void
{
if ($nbNewsCategoriesInBdd < 1 or $nbNewsCategoriesInBdd > 2) {
throw ValidationException::withMessages([
@ -150,11 +216,11 @@ class News extends Authenticatable
/**
* Asserts using validation that the title is unique.
*
* @param mixed $title
* @param string $title
* @param integer|null $id
* @return void
*/
private static function assertTitleIsUnique($title, ?int $id = null)
private static function assertTitleIsUnique(string $title, ?int $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
@ -165,11 +231,9 @@ class News extends Authenticatable
/**
* Getter reading_time.
*
* * USED
*
* @return string
*/
public function getReadingTime()
public function getReadingTime(): string
{
$allWords = count(explode(
" ",
@ -185,35 +249,81 @@ class News extends Authenticatable
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
/**
* BelongsToMany news
* News Morph-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
* @phpcs:disable Generic.NamingConventions.ConstructorName.OldStyle
*/
public function news()
public function news(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
// phpcs:enable
return $this->belongsToMany(News::class, 'news_news', 'news_id', 'news_related_id');
}
/**
* BelongsToMany products
* Products Morph-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products()
public function products(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Product::class);
}
/**
* BelongsToMany categories
* Categories Morph-To-Many relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function categories()
public function categories(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
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)
);
}
}

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