Compare commits

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

177 commits

Author SHA1 Message Date
Vincent BRIDIER
fa2579bb58 Merge branch 'trunk' 2026-10-23 16:46:45 +02:00
Vincent BRIDIER
fa19add314 Merge branch 'develop' into trunk 2026-10-23 16:46:24 +02:00
Vincent BRIDIER
d9834acbe0 feat: Change Apache PID 2026-10-23 16:46:07 +02:00
K Muller
aca6082fc3 Merge branch 'trunk' 2026-10-22 16:34:02 +02:00
K Muller
6fc7ce4144 Merge branch 'develop' into trunk 2026-10-22 16:33:42 +02:00
K.
c5b0154a78 Merge branch 'issue-37' into 'develop'
chore: ⬆️ bump php to 8.5 #37

See merge request ldc-web/projets/com-app-bo!21
2026-10-22 14:31:06 +00:00
Alexis Gousseau
d9855a6aa7
chore: ⬆️ bump php to 8.5 #37 2026-10-22 16:18:08 +02:00
Alexis Gousseau
b5851d75b4
style: 💄 update height of outline bootstrap button 2026-10-22 16:18:08 +02:00
Alexis Gousseau
919550c1f2
fix: 🚨 resolve linters errors 2026-10-22 16:17:56 +02:00
Alexis Gousseau
ade271b88b
chore: ⬆️ bump dependencies 2026-10-22 16:00:28 +02:00
Noah Lancien
d67440770a Merge branch 'trunk' 2026-01-23 15:45:49 +01:00
Noah Lancien
39ff3e0fd8 Merge branch 'develop' into trunk 2026-01-23 15:45:24 +01:00
Noah Lancien
fd936efebb ci: limit jobs history to 1 2026-01-23 15:45:02 +01:00
Noah Lancien
202fcf73c9 Merge branch 'trunk' 2026-01-12 11:30:08 +01:00
Noah Lancien
706fb14dff Merge branch 'develop' into trunk 2026-01-12 11:29:54 +01:00
Noah Lancien
cef4f9764c feat: updated admin-secure 2026-01-12 11:29:21 +01:00
Noah Lancien
220cbe642a ci: added ci variables to define pod resources + added staging-resources.yaml 2026-01-12 10:46:39 +01:00
Vincent BRIDIER
d0b5805676 Merge branch 'trunk' 2025-09-24 11:29:21 +02:00
Vincent BRIDIER
34d919ee69 Merge branch 'develop' into trunk 2025-09-24 11:29:12 +02:00
Vincent BRIDIER
7b8000f9ba fix: Fix mail config - Close #63 2025-09-24 11:27:55 +02:00
Vincent BRIDIER
0e504be45f Merge branch 'trunk' 2025-09-05 10:22:15 +02:00
Vincent BRIDIER
42f3f08172 Merge branch 'develop' into trunk 2025-09-05 10:21:49 +02:00
Vincent BRIDIER
941c10b6f2 feat: remove useless 'Salon' feature 2025-09-05 10:21:40 +02:00
Vincent BRIDIER
3eed68a9c3 feat: Update download url signature for k8s env - Close #33 2025-09-05 10:03:55 +02:00
Vincent BRIDIER
a2e0a60d08 Merge branch 'develop' of lab.s-web.fr:ldc-web/projets/com-app-bo into develop 2025-09-04 20:39:49 +02:00
Vincent BRIDIER
3ead13a5a1 feat: Remove useless features 2025-09-04 20:39:38 +02:00
Vincent BRIDIER
597f5f91cf
Merge branch 'trunk' 2025-09-03 14:18:47 +02:00
Vincent BRIDIER
eeea8014d0
Merge branch 'develop' into trunk 2025-09-03 14:18:36 +02:00
Vincent BRIDIER
0f87cb1ee4 Merge branch 'doc-route' into 'develop'
fix: 🐛 update document download signed route

See merge request ldc-web/projets/com-app-bo!18
2025-09-03 12:18:23 +00:00
Alexis Gousseau
77a2b3f12d
fix: 🐛 update document download signed route 2025-09-03 14:06:14 +02:00
Vincent BRIDIER
051cb55324
Merge branch 'trunk' 2025-09-02 15:00:27 +02:00
Vincent BRIDIER
b49d34ee07
Merge branch 'develop' into trunk 2025-09-02 15:00:09 +02:00
Vincent BRIDIER
d80863c9a7 Merge branch 'issue-33' into 'develop'
issue #33

See merge request ldc-web/projets/com-app-bo!17
2025-09-02 12:59:29 +00:00
Alexis Gousseau
5bba7b87f4
fix: 🐛 update document accessibility 2025-09-02 12:44:53 +02:00
Alexis Gousseau
b999d29bb1
fix: 🍱 update logo project 2025-09-02 09:28:22 +02:00
Vincent BRIDIER
3ce421aff9 Merge branch 'develop' of lab.s-web.fr:ldc-web/projets/com-app-bo into develop 2025-09-01 18:23:33 +02:00
Vincent BRIDIER
615cc69892 fix: Fix queue mailable obj to sendflyer 2025-09-01 18:23:21 +02:00
Vincent BRIDIER
8be12b6e9e
Merge branch 'trunk' 2025-09-01 12:08:02 +02:00
Vincent BRIDIER
39631fb272
Merge branch 'develop' into trunk 2025-09-01 12:01:12 +02:00
Vincent BRIDIER
02bcda59e3 Merge branch 'issue-32' into 'develop'
fix: 🐛 update on obtaining data from the request to send a flyer #32

See merge request ldc-web/projets/com-app-bo!16
2025-09-01 10:00:33 +00:00
Alexis Gousseau
080e4f5959
fix: 🐛 update on obtaining data from the request to send a flyer #32 2025-09-01 10:57:57 +02:00
Vincent BRIDIER
cfe84bb9f8 Merge branch 'trunk' 2025-08-29 18:46:39 +02:00
Vincent BRIDIER
bc0c018995 Merge branch 'develop' into trunk 2025-08-29 16:25:55 +02:00
Vincent BRIDIER
74d89da1fa feat: Update manifest for mysql migration 2025-08-29 16:25:44 +02:00
Vincent BRIDIER
7be9395ae4 Merge branch 'develop' into trunk 2025-08-29 15:06:38 +02:00
Vincent BRIDIER
304f3b4e7f fix: tenant_user foreign key 2025-08-29 15:06:30 +02:00
Vincent BRIDIER
ded2a8da88 Merge branch 'develop' into trunk 2025-08-28 18:34:40 +02:00
Vincent BRIDIER
a9925ee8dc fix: Some fixes to be com-app front compliant 2025-08-28 18:34:10 +02:00
Vincent BRIDIER
b569e4ae43
Merge branch 'develop' into trunk 2025-07-18 16:49:48 +02:00
Vincent BRIDIER
24b0c449c5
feat: Update CICD 2025-07-18 16:49:36 +02:00
Vincent BRIDIER
b08b0d49cc
Merge branch 'develop' into trunk 2025-07-18 16:29:35 +02:00
Vincent BRIDIER
773924c250 Merge branch 'some-fixes' into 'develop'
fix: some fixes after local testing

See merge request ldc-web/projets/com-app-bo!15
2025-07-18 14:29:15 +00:00
Noah Lancien
7b8216fc33 fix: seeders were broken due to implementation of the whereTenantQuery() method 2025-06-18 11:52:45 +02:00
Noah Lancien
fb1f641bb2 chore: phpcs 2025-06-17 12:52:43 +02:00
Noah Lancien
73615cdf1f fix: some fixes after local testing 2025-06-17 12:29:15 +02:00
Vincent BRIDIER
adf5717110
Merge branch 'develop' into trunk 2025-06-12 16:51:07 +02:00
Vincent BRIDIER
3b93c4c49b
fix: Fix AppUser Bo Requests email validation 2025-06-12 16:50:56 +02:00
Vincent BRIDIER
0121ee8bdb
Merge branch 'develop' into trunk 2025-06-12 16:01:00 +02:00
Vincent BRIDIER
8696765b7e
fix: PHPCS & PHPStan 2025-06-12 16:00:53 +02:00
Vincent BRIDIER
728568d812
fix: Fix default Salon directory 2025-06-12 15:47:56 +02:00
Vincent BRIDIER
d4c72ed804
fix: Fix API Responses and logic 2025-06-12 15:47:26 +02:00
Vincent BRIDIER
122151da28
fix: Add missing Laravel K8S package 2025-06-12 15:46:18 +02:00
Vincent BRIDIER
5f372c71cd
Merge branch 'develop' into trunk 2025-06-11 11:15:22 +02:00
Vincent BRIDIER
7bc2b900c1 Merge branch 'csp' into 'develop'
fix: 🔒 resolve csp errors #31

See merge request ldc-web/projets/com-app-bo!14
2025-06-11 09:14:46 +00:00
Alexis Gousseau
f0f1458a0d fix: 🔒 resolve csp errors #31 2025-06-11 11:12:29 +02:00
Vincent BRIDIER
ffe16335e5
Merge branch 'develop' into trunk 2025-06-04 12:10:17 +02:00
Vincent BRIDIER
5035732542
feat: some env / CICD fixes 2025-06-04 12:10:04 +02:00
Vincent BRIDIER
14cc8dd5b3
Merge branch 'develop' into trunk 2025-06-04 09:31:55 +02:00
Vincent BRIDIER
6613606575 Merge branch 'issue-29' into 'develop'
Multi tenant + update

See merge request ldc-web/projets/com-app-bo!13
2025-06-04 07:30:16 +00:00
Alexis Gousseau
b59672f465 fix: 🐛 replace cache remember forever by 24 hours cache 2025-06-04 09:29:41 +02:00
Alexis Gousseau
ecadc53170 feat: ✨ add mail when tenant is deleted 2025-06-04 09:28:57 +02:00
Alexis Gousseau
741d4be1bf fix: 🐛 update validation of associated tenants of users 2025-05-28 17:25:44 +02:00
Alexis Gousseau
a978e6536c feat: ✨ add cache to get all tenants 2025-05-28 17:25:44 +02:00
Alexis Gousseau
d51c40b17e feat: 🚸 add double confirmation for the deletion of an existing tenant 2025-05-28 17:25:44 +02:00
Alexis Gousseau
56e9c03574 fix: 🐛 update user tenants management 2025-05-28 17:25:44 +02:00
Alexis Gousseau
8d1156382c fix: 🐛 update tenant in api routes 2025-05-28 17:25:44 +02:00
Alexis Gousseau
ea87502ba4 fix: 🐛 update api routes for multi-tenancy 2025-05-28 17:25:44 +02:00
Alexis Gousseau
2e8ca74962 fix: 🐛 save files in tenant directory 2025-05-28 17:25:44 +02:00
Alexis Gousseau
d393b39d98 fix: 🗃️ update database relations 2025-05-28 17:25:44 +02:00
Alexis Gousseau
b13a4ced30 feat: ✨ clean project (policies, translations, breadcrumbs, dead code etc...) 2025-05-28 17:25:39 +02:00
Alexis Gousseau
b5d46f54ee feat: ✨ add saml connection 2025-05-21 16:30:06 +02:00
Alexis Gousseau
49cef77cb8 feat: ✨ set multi tenancy 2025-05-21 16:29:18 +02:00
Alexis Gousseau
ec031ec3ec fix: 🐛 makes functional change order method on models who own order field 2025-05-20 14:34:57 +02:00
Alexis Gousseau
eb642c85eb fix: 🚨 resolve linters errors (phpcs/phpstan) 2025-05-20 11:45:42 +02:00
Alexis Gousseau
e173a6670b fix: 🚨 resolve linters errors (stylelint/eslint) 2025-05-20 10:28:50 +02:00
Alexis Gousseau
8860afc566 chore: ⬆️ bump laravel 12 with vite compilation 2025-05-20 10:28:42 +02:00
Karl Muller
ebc8e37b28 Merge branch 'develop' into trunk 2025-04-30 14:14:32 +00:00
Karl Muller
3021f60054 fix: add the ldb web api 2025-04-30 14:14:13 +00:00
Karl Muller
3f3ab0df5e fix: add the ldb web api 2025-04-30 14:13:18 +00:00
Karl Muller
1342a97f9c Merge branch 'develop' into trunk 2025-04-30 13:56:33 +00:00
Karl Muller
7d0541abdf fix: delete the resource limit in manifest 2025-04-30 13:55:55 +00:00
Karl Muller
a60e562ce8 Merge branch 'develop' into trunk 2025-04-30 11:14:49 +00:00
Karl Muller
5ae59be976 fix: 🐛 forgot mix webpack file on Dockerfile 2025-04-30 11:14:35 +00:00
Karl Muller
5ace87520e Merge branch 'develop' into trunk 2025-04-30 11:08:50 +00:00
Karl Muller
16cd1d09fd feat: add cicd + update composer in laravel 8 and php 7.3 2025-04-30 11:07:58 +00:00
Jérémy Munsch
ea9924faa1
Merge branch 'develop' into trunk 2023-10-04 16:37:24 +02:00
Jérémy Munsch
930457e5ee
Merge branch 'hotfix' into develop 2023-10-04 16:27:59 +02:00
Jérémy Munsch
ddc9b57c5f
Merge branch 'hotfix' 2023-10-04 16:27:23 +02:00
Jérémy Munsch
960d64eb12
Hotfix documents resource 2023-10-04 16:26:17 +02:00
Vincent BRIDIER
636918b9d5 Merge branch 'issue-25' into 'develop'
Closes #25 Remove status checker

See merge request ldc-web/projets/com-app-bo!12
2023-10-04 14:02:21 +00:00
Jérémy Munsch
618ac5e610
Merge branch 'trunk' 2023-10-04 16:00:47 +02:00
Jérémy Munsch
9fc599347d
Merge branch 'develop' into trunk 2023-10-04 16:00:30 +02:00
Jérémy Munsch
0824dc4cae
Merge branch 'fix-documents' into develop 2023-10-04 15:58:21 +02:00
Jérémy Munsch
5563dc1814
Closes #25 Remove status checker 2023-10-04 15:56:06 +02:00
Jérémy Munsch
6fd1439509
Fixes for document file add and validate 2023-10-04 15:51:07 +02:00
Vincent BRIDIER
20b600a190 Merge branch 'trunk' 2023-10-03 15:53:27 +02:00
Vincent BRIDIER
65429ebc33 Merge branch 'develop' into trunk 2023-10-03 15:53:18 +02:00
Jérémy Munsch
dd73555457
Fixes #26 2023-10-03 15:46:22 +02:00
Jérémy Munsch
c616686fd6
Put files download in public 2023-09-28 16:36:09 +02:00
Jérémy Munsch
52117734b7
SquaredLogo into mails 2023-09-28 16:34:30 +02:00
Jérémy Munsch
b920876e3a
Fixes #24 2023-09-28 15:14:32 +02:00
Jérémy Munsch
11bf59d437
Closes #22 2023-09-28 11:01:12 +02:00
Jérémy Munsch
9841d05aa9
Add trusted proxies 2023-09-28 10:12:30 +02:00
Jérémy Munsch
ef5fea3da0
Fix directory component compilation 2023-09-27 15:17:00 +02:00
Jérémy Munsch
62dfba632d
Closes #21 2023-09-13 16:04:12 +02:00
Jérémy Munsch
316b56b0c4
Fixes #20 Contact.show fix show locked field 2023-09-05 13:13:03 +02:00
Jérémy Munsch
dddd3ba356
Add filter duplicated documents migration and set unique on uuid 2023-09-05 12:59:09 +02:00
Jérémy Munsch
3a9d671373
Fixes https://lab.s-web.fr/ldc-web/projets/com-app/-/issues/47 StoreDocument set uuid on mode create 2023-09-05 12:50:01 +02:00
Jérémy Munsch
708405c7a2
Fix Document parent relation 2023-09-04 17:38:13 +02:00
Jérémy Munsch
262f742017
Add role if missing on create users 2023-09-04 17:21:06 +02:00
Jérémy Munsch
8622dfd2bd
remove disable FK check on migrate 2023-09-04 17:20:53 +02:00
Jérémy Munsch
5ea039ccfe
Add missing role for users migration 2023-09-04 17:13:56 +02:00
Jérémy Munsch
f2258fc738
Fiox add missing nonce on bo layout 2023-09-04 16:27:38 +02:00
Jérémy Munsch
1fba092fc3
Read missing force https 2023-09-04 15:52:23 +02:00
Jérémy Munsch
752f202c62
fix documents.store 2023-09-04 15:13:59 +02:00
Jérémy Munsch
76556e9f2e
Make Migrations compatible with old COM APPS 2023-09-04 13:33:20 +02:00
Jérémy Munsch
a6bc3f8f06
Fix contacts table migration should be json for visits and text for data 2023-09-04 13:30:42 +02:00
Jérémy Munsch
89c87f54b8
Fixes #19 Fixes https://lab.s-web.fr/ldc-web/projets/com-app/-/issues/43 Directories sort on api sync 2023-09-04 09:17:44 +02:00
Jérémy Munsch
25fd167cb4
Force send_to_app_user_id to null on api sync 2023-09-04 09:11:44 +02:00
Jérémy Munsch
133966cc2c
send contact feature 2023-09-01 18:09:27 +02:00
Jérémy Munsch
40b0e79e73
Add contact timestamp to allow keep most recent version of the contact 2023-09-01 16:50:18 +02:00
Jérémy Munsch
8bf14986d5
SyncController parse Flyer Json 2023-09-01 09:30:16 +02:00
Jérémy Munsch
0d06cce559
contact props hint and comment update 2023-09-01 09:30:01 +02:00
Jérémy Munsch
90a9770a17
Fix add missing uuid assignement on contact save 2023-08-31 16:38:02 +02:00
Jérémy Munsch
c2711e090a
add missing slug on Formulary resource 2023-08-31 16:17:32 +02:00
Jérémy Munsch
4fdd1bdc0e
Add no folder accessible on appUSer card 2023-08-31 13:01:08 +02:00
Jérémy Munsch
1656b4a887
phpcs fix for php v7.3.3 2023-08-31 13:01:08 +02:00
Jérémy Munsch
3f7de1d142
Upgrade Dynamic Form system to include Formulary Slug 2023-08-31 13:01:04 +02:00
Jérémy Munsch
1f31e1a0b7
FixMandatory Email Seed name 2023-08-31 11:12:33 +02:00
Alexis Gousseau
7cf0981f8c fix old formularies values 2023-08-31 09:31:55 +02:00
Jérémy Munsch
192de3cd76
Fix api sync Formulary orderby 2023-08-30 17:45:14 +02:00
Jérémy Munsch
733b6d64d2
Remove unwanted fields on mandatory formulary seeder 2023-08-30 17:44:38 +02:00
Jérémy Munsch
c0e87386a6
Fix contact upsert api method 2023-08-30 17:44:38 +02:00
Jérémy Munsch
513fa2acaf
Update contacts view 2023-08-30 17:44:38 +02:00
Jérémy Munsch
8dd4fc6d74
handle type issue on UpsertContact 2023-08-30 17:44:37 +02:00
Jérémy Munsch
5d4a1714d6
fix contact resource 2023-08-30 17:44:37 +02:00
Jérémy Munsch
f739685686
Fix destroy contact request 2023-08-30 17:44:36 +02:00
Alexis Gousseau
cfeb53a017 add old formularyModel values 2023-08-30 17:43:21 +02:00
Jérémy Munsch
fff06d743a
fix paginator avec Alexis 2023-08-29 13:14:53 +02:00
Jérémy Munsch
a443399a92
contact destroy param update 2023-08-29 11:39:46 +02:00
Jérémy Munsch
faa3e2e6e3
unset phone mandatory on contacts.view 2023-08-29 10:08:53 +02:00
Jérémy Munsch
b2e73f2ef2
Fix upsert contact request and contact model type hint in accordance with DB constrains 2023-08-29 10:07:36 +02:00
Jérémy Munsch
98a80bee98
typehint visits on contact 2023-08-29 10:00:27 +02:00
Jérémy Munsch
48533a0737
Fix form store 2023-08-29 09:54:58 +02:00
Jérémy Munsch
72177c522f
paginate fix from base project state 2023-08-29 09:39:34 +02:00
Jérémy Munsch
fc17fa6cc1
Redo contact Part with dynamic data and remove Export + update API call for contacts 2023-08-28 19:13:21 +02:00
Jérémy Munsch
15ee6ece22
- Rearange formularies in Settings Controller
- formulary api output
- Formulary seeder slug unique
- rename Formulary visible column because the prop is hidden by Model trait Concerns\HideAttributes
- mandatory input phone was not type of phone
- form factory to be status default
- formulary factory type
2023-08-28 19:12:36 +02:00
Jérémy Munsch
ed0e115ed0
add FIXME comment on api routes 2023-08-28 19:12:35 +02:00
Jérémy Munsch
88b6806acd
change label in InputStatusEnum for locked 2023-08-28 19:11:43 +02:00
Jérémy Munsch
ebc6028d70
add api route names 2023-08-23 09:37:53 +02:00
Jérémy Munsch
f82c434625
update git ignore 2023-08-23 09:29:33 +02:00
Jérémy Munsch
be40c13ddd
fix sync regression on php 7 Sync controller 2023-08-22 18:06:28 +02:00
Jérémy Munsch
b3ac4c899d
add missing remember token on users table 2023-08-22 17:13:22 +02:00
Jérémy Munsch
3632e9bf33
Fix regression on user Resource for API 2023-08-22 13:53:39 +02:00
Jérémy Munsch
47c09edd5b
Add Formulary seed + Fix mandatory Formulary seed 2023-08-22 10:01:47 +02:00
Jérémy Munsch
ccb6c2c38f
fix migrations drop unique on update_app_users and update_settings unique 2023-08-22 09:19:11 +02:00
Jérémy Munsch
c4ba52bbac
Merge remote-tracking branch 'origin/v2.1.0-api-calls' into v2.1.0 2023-08-21 17:12:26 +02:00
Jérémy Munsch
e7be34b4f2
composer for php7.3 as on servers 2023-08-21 17:10:06 +02:00
Vincent BRIDIER
11a071a0eb Merge branch 'issue-9' into 'v2.1.0'
update favicon - issue #9

See merge request ldc-web/projets/com-app-bo!10
2023-07-26 09:58:32 +00:00
Alexis Gousseau
3e8fb2a7f8 update favicon - issue #9 2023-07-26 11:57:31 +02:00
Vincent BRIDIER
ad2c060d56 Merge branch 'issue-13' into 'v2.1.0'
fix password validation - issue #13

See merge request ldc-web/projets/com-app-bo!9
2023-07-25 14:30:31 +00:00
Alexis Gousseau
189a114511 fix password validation - issue #13 2023-07-25 16:29:20 +02:00
Alexis Gousseau
5a1fcd6077 add exports postman api calls 2023-07-24 14:28:45 +02:00
Alexis Gousseau
1f21fe5afb fix flyer mail 2023-07-24 12:41:21 +02:00
Alexis Gousseau
b9e7927935 updating api calls according to the front 2023-07-24 12:06:21 +02:00
Alexis Gousseau
f2dc31bbee set relation user to directory 2023-07-21 14:51:02 +02:00
Alexis Gousseau
39b017d429 delete existings tags on document when remove it 2023-07-21 10:53:33 +02:00
403 changed files with 26975 additions and 21406 deletions

13
.browserslistrc Normal file
View file

@ -0,0 +1,13 @@
# Browsers that we support
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11

16
.dockerignore Normal file
View file

@ -0,0 +1,16 @@
.git
*.md
node_modules/
npm-debug.log
.npmrc
tests/
Dockerfile*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
vendor/
public/build/
auth.json

View file

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

View file

@ -5,8 +5,6 @@ APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
API_TOKEN=toto API_TOKEN=toto
BRAND=
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
@ -33,10 +31,20 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"
MAIL_ADMIN_ADDRESS=null
# SENTRY # SENTRY
SENTRY_LARAVEL_DSN= SENTRY_LARAVEL_DSN=
# LDC WEB STATUS CHECKER # SAML
LDC_STATUS_CHECKER_API_KEY="MY_KEY" SAML2_DEFAULT_TENANT_UUID=
LDC_STATUS_CHECKER_UUID="UUID_PROJECT" SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""

View file

@ -29,7 +29,14 @@
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["vue", "@typescript-eslint"], "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": { "rules": {
"indent": ["error", 4, { "SwitchCase": 1 }], "indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
@ -37,5 +44,19 @@
"semi": ["error", "always"], "semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }], "vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off" "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 *.blade.php diff=html
*.css diff=css *.css diff=css
@ -8,3 +8,4 @@
/.github export-ignore /.github export-ignore
CHANGELOG.md export-ignore CHANGELOG.md export-ignore
.styleci.yml export-ignore

27
.gitignore vendored
View file

@ -1,19 +1,30 @@
.env
.env.backup
.env.production
.phpunit.result.cache
.stylelintcache
/.fleet
/.idea
/.phpunit.cache
/.vscode/launch.json
/node_modules /node_modules
/public/hot
/public/storage
/public/build /public/build
/public/medias
/public/uploads
/public/cache /public/cache
/public/hot
/public/medias
/public/sitemap.xml /public/sitemap.xml
/public/storage
/public/uploads
/storage/*.key /storage/*.key
/vendor /vendor
.env auth.json
.phpunit.result.cache
docker-compose.override.yml docker-compose.override.yml
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
/.idea .vscode/settings.json
auth.json .npmrc
.zshrc
.composer/
.npm/

23
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,23 @@
include:
- project: "ldc-web/shared-ci"
ref: "main"
file: "lint/lint.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "security/security.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "build/docker-multistage.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "test/test.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "deploy/deploy.yml"
stages:
- lint
- security
- build
- test
- deploy

View file

@ -0,0 +1,11 @@
## Comportement constaté
## Comportement attendu
## Solution envisagée
## Étapes pour la reproduction du bug
*

View file

@ -0,0 +1,7 @@
## Présentation du besoin
## Implémentation
## Maquettes, documentations...

View file

@ -1,14 +0,0 @@
php:
preset: laravel
version: 8
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View file

@ -18,8 +18,7 @@
], ],
"customSyntax": "postcss-scss", "customSyntax": "postcss-scss",
"rules": { "rules": {
"indentation": 4, "declaration-property-value-no-unknown": true,
"string-quotes": "double",
"selector-max-id": null, "selector-max-id": null,
"color-no-invalid-hex": true, "color-no-invalid-hex": true,
"order/properties-alphabetical-order": null, "order/properties-alphabetical-order": null,

46
Dockerfile Normal file
View file

@ -0,0 +1,46 @@
FROM composer:2.8 AS vendor
ARG GITLAB_USER
ARG GITLAB_PASSWORD
ARG PHP_8_5_TAG=latest
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/
COPY --from=vendor /app/vendor/ /app/vendor/
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
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

@ -8,3 +8,6 @@
&nbsp; &nbsp;
- continue migrations : - continue migrations :
`php artisan migrate` `php artisan migrate`
TODO:
- sur les méthodes GET le token est visible dans les logs server..

View file

@ -0,0 +1,46 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class AsArrayOfInput implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array<\App\Lib\Inputs\Definition\Input>
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function get($model, string $key, $value, array $attributes): array
{
// phpcs:enable
return \collect(\json_decode($value, true))->map(function ($input) use ($model, $key, $attributes) {
$inputCast = new AsInput();
return $inputCast->get($model, $key, \json_encode($input), $attributes);
})->all();
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param \App\Lib\Inputs\Definition\Input $value
* @param array $attributes
* @return string Json encoded array<\App\Lib\Inputs\Definition\Input>
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function set($model, string $key, $value, array $attributes)
{
// phpcs:enable
return \json_encode(\collect($value)->map(function ($input) use ($model, $key, $attributes) {
$inputCast = new AsInput();
return \json_decode($inputCast->set($model, $key, $input, $attributes));
})->all());
}
}

68
app/Casts/AsInput.php Normal file
View file

@ -0,0 +1,68 @@
<?php
namespace App\Casts;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use App\Lib\Inputs\Definition\InputData;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsInput implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \App\Lib\Inputs\Definition\Input
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public function get(Model $model, string $key, mixed $value, array $attributes): Input
{
// phpcs:enable
/** @var array<string, integer|string|boolean> $inputData */
$inputData = \json_decode($value, true);
$inputClass = InputTypeEnum::make($inputData['type'])->getInputClass();
return $inputClass::from(InputData::make(...\array_values($inputData)), $inputData['required'] ?? false);
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return string Json encoded \App\Lib\Inputs\Definition\Input
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
// phpcs:enable
$this->assertIsInput($value);
/** @var \App\Lib\Inputs\Definition\Input $value */
return $value->toJson();
}
/**
* Assert is instance of \App\Lib\Inputs\Definition\Input
*
* @param mixed $value
* @return void
* @throws \RuntimeException If the value is not an input.
*/
private function assertIsInput(mixed $value): void
{
if (!\is_object($value) or !\in_array(Input::class, \class_parents($value))) {
$value = \json_encode($value);
throw new \RuntimeException(
__CLASS__ . ' needs value to be array of \'' . Input::class . "' given is {$value}"
);
}
}
}

View file

@ -28,37 +28,30 @@ class CreateAdmin extends Command
protected $description = 'Create a backend admin user'; protected $description = 'Create a backend admin user';
/** /**
* The firstname of the new user. * The first name of the new user.
* *
* @var string * @var string|null
*/ */
protected $firstname; protected $first_name;
/** /**
* The lastname of the new user. * The last name of the new user.
* *
* @var string * @var string|null
*/ */
protected $lastname; protected $last_name;
/** /**
* The email of the new user. * The email of the new user.
* *
* @var string * @var string|null
*/ */
protected $email; protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/** /**
* The role of the new user. * The role of the new user.
* *
* @var string * @var string|null
*/ */
protected $role; protected $role;
@ -67,30 +60,26 @@ class CreateAdmin extends Command
* *
* @return integer * @return integer
*/ */
public function handle() public function handle(): int
{ {
$this->firstname = null; $this->first_name = null;
$this->addFirstname(); $this->addFirstname();
$this->lastname = null; $this->last_name = null;
$this->addLastname(); $this->addLastname();
$this->email = null; $this->email = null;
$this->addEmail(); $this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null; $this->role = null;
$this->addRole(); $this->addRole();
$user = new User([ $user = new User([
'firstname' => $this->firstname, 'first_name' => $this->first_name,
'lastname' => $this->lastname, 'last_name' => $this->last_name,
'email' => $this->email, 'email' => $this->email,
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => $this->password, 'role' => $this->role,
'role' => $this->role
]); ]);
$user->saveOrFail(); $user->saveOrFail();
@ -106,14 +95,14 @@ class CreateAdmin extends Command
*/ */
private function addFirstname(): void private function addFirstname(): void
{ {
while (is_null($this->firstname)) { while (is_null($this->first_name)) {
try { try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin'); $tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [ $validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255' 'firstname' => 'required|min:3|max:255'
]); ]);
$validator->validate(); $validator->validate();
$this->firstname = $tmp; $this->first_name = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please provide a valid firstname %s', 'Please provide a valid firstname %s',
@ -131,14 +120,14 @@ class CreateAdmin extends Command
*/ */
private function addLastname(): void private function addLastname(): void
{ {
while (is_null($this->lastname)) { while (is_null($this->last_name)) {
try { try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama'); $tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [ $validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255' 'lastname' => 'required|min:3|max:255'
]); ]);
$validator->validate(); $validator->validate();
$this->lastname = $tmp; $this->last_name = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please provide a valid lastname %s', 'Please provide a valid lastname %s',
@ -174,22 +163,6 @@ class CreateAdmin extends Command
}; //end while }; //end while
} }
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
/** /**
* Add a role to the user. * Add a role to the user.
* *
@ -201,16 +174,16 @@ class CreateAdmin extends Command
try { try {
$tmp = $this->choice( $tmp = $this->choice(
'Select his role', 'Select his role',
[UserRolesEnum::admin()->label, UserRolesEnum::client()->label], [UserRolesEnum::admin->label(), UserRolesEnum::client->label()],
UserRolesEnum::client()->label, UserRolesEnum::client->label(),
$maxAttempts = null, $maxAttempts = null,
$allowMultipleSelections = false $allowMultipleSelections = false
); );
if ($tmp === UserRolesEnum::admin()->label) { if ($tmp === UserRolesEnum::admin->label()) {
$tmp = UserRolesEnum::admin(); $tmp = UserRolesEnum::admin;
} }
if ($tmp === UserRolesEnum::client()->label) { if ($tmp === UserRolesEnum::client->label()) {
$tmp = UserRolesEnum::client(); $tmp = UserRolesEnum::client;
} }
$validator = Validator::make(['role' => $tmp], [ $validator = Validator::make(['role' => $tmp], [
'role' => 'required' 'role' => 'required'
@ -219,7 +192,7 @@ class CreateAdmin extends Command
$this->role = $tmp; $this->role = $tmp;
} catch (ValidationException $e) { } catch (ValidationException $e) {
$this->error(sprintf( $this->error(sprintf(
'Please choose from the selection', 'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all()) \implode(',', collect($e->errors())->flatten()->all())
)); ));
continue; continue;

View file

@ -2,30 +2,17 @@
namespace App\Console; namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
}
/** /**
* Register the commands for the application. * Register the commands for the application.
* *
* @return void * @return void
*/ */
protected function commands() protected function commands(): void
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
} }
} }

View file

@ -2,37 +2,22 @@
namespace App\Enums; namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum AppUserRolesEnum: int
* This defines a data type.
*
* @method static self total() total role
* @method static self user() user role
*/
final class AppUserRolesEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case total = 1;
*/ case user = 2;
protected static function values(): array
{
return [
'total' => 1,
'user' => 2,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
{ self::total->name => 'Total',
return [ self::user->name => 'Utilisateur',
'total' => 'Total', ];
'user' => 'Utilisateur',
];
}
} }

View file

@ -2,43 +2,26 @@
namespace App\Enums\Documents; namespace App\Enums\Documents;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum LangEnum: int
* This defines a data type.
*
* @method static self fr()
* @method static self en()
* @method static self de()
* @method static self nl()
*/
final class LangEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case fr = 1;
*/ case en = 2;
protected static function values(): array case de = 3;
{ case nl = 4;
return [
'fr' => 1,
'en' => 2,
'de' => 3,
'nl' => 4,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
{ self::fr->name => 'Français (FR)',
return [ self::en->name => 'Anglais (EN)',
'fr' => 'Français (FR)', self::de->name => 'Allemand (DE)',
'en' => 'Anglais (EN)', self::nl->name => 'Néerlandais (NL)',
'de' => 'Allemand (DE)', ];
'nl' => 'Néerlandais (NL)',
];
}
} }

View file

@ -2,37 +2,36 @@
namespace App\Enums\Documents; namespace App\Enums\Documents;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum TypeEnum: int
* This defines a data type.
*
* @method static self pdf()
* @method static self mp4()
*/
final class TypeEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case pdf = 1;
*/ case mp4 = 2;
protected static function values(): array
{
return [
'pdf' => 1,
'mp4' => 2,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
self::pdf->name => 'PDF',
self::mp4->name => 'MP4',
];
/**
* Get translation key.
*
* @return string
*/
public function icon(): string
{ {
return [ return match ($this) {
'pdf' => 'PDF', self::pdf => 'file',
'mp4' => 'MP4', self::mp4 => 'video',
]; };
} }
} }

View file

@ -2,40 +2,24 @@
namespace App\Enums\Formularies; namespace App\Enums\Formularies;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum InputStatusEnum: int
* This defines a data type.
*
* @method static self system() input system
* @method static self locked() input locked
* @method static self default() input default
*/
final class InputStatusEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case system = 1;
*/ case locked = 2;
protected static function values(): array case default = 3;
{
return [
'system' => 1,
'locked' => 2,
'default' => 3,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
{ self::system->name => 'system',
return [ self::locked->name => 'verrouillé',
'system' => __('system'), self::default->name => 'default',
'locked' => __('e-mail'), ];
'default' => __('default'),
];
}
} }

View file

@ -2,52 +2,57 @@
namespace App\Enums\Formularies; namespace App\Enums\Formularies;
use Kwaadpepper\Enum\BaseEnumRoutable; use App\Lib\Inputs\InputCheckbox;
use App\Lib\Inputs\InputEmail;
use App\Lib\Inputs\InputPhone;
use App\Lib\Inputs\InputRadio;
use App\Lib\Inputs\InputSelect;
use App\Lib\Inputs\InputSelectMultiple;
use App\Lib\Inputs\InputText;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum InputTypeEnum: int
* This defines a data type.
*
* @method static self text() input text
* @method static self email() input email
* @method static self phone() input phone
* @method static self radio() input radio
* @method static self select() input select
* @method static self multipleselect() input multipleselect
* @method static self checkbox() input checkbox
*/
final class InputTypeEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case text = 1;
*/ case email = 2;
protected static function values(): array case phone = 3;
{ case radio = 4;
return [ case select = 5;
'text' => 1, case selectMultiple = 6;
'email' => 2, case checkbox = 7;
'phone' => 3,
'radio' => 4,
'select' => 5,
'multipleselect' => 6,
'checkbox' => 7,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
self::text->name => 'texte',
self::email->name => 'email',
self::phone->name => 'téléphone',
self::radio->name => 'radio',
self::select->name => 'liste déroulante',
self::selectMultiple->name => 'liste déroulante à choix multiple',
self::checkbox->name => 'case à cocher',
];
/**
* Get InputType class based on the enum value.
*
* @return string
*/
public function getInputClass(): string
{ {
return [ return match ($this) {
'text' => __('texte'), self::text => InputText::class,
'email' => __('e-mail'), self::email => InputEmail::class,
'phone' => __('téléphone'), self::phone => InputPhone::class,
'radio' => __('radio'), self::radio => InputRadio::class,
'select' => __('liste déroulante'), self::select => InputSelect::class,
'multipleselect' => __('liste déroulante à choix multiple'), self::selectMultiple => InputSelectMultiple::class,
'checkbox' => __('case à cocher'), self::checkbox => InputCheckbox::class,
]; };
} }
} }

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/**
* Defines the available pagination page size options.
*/
enum ItemsPerPaginationEnum: int
{
use BaseEnum;
case five = 5;
case twelve = 12;
case twentyfive = 25;
case fifty = 50;
case onehundred = 100;
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Enums\Pagination;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self five()
* @method static self twelve()
* @method static self twentytwo()
* @method static self fifty()
* @method static self onehundred()
*/
class ItemsPerPaginationEnum extends BaseEnumRoutable
{
/**
* @return array
*/
protected static function values(): array
{
return [
'five' => 5,
'twelve' => 12,
'twentytwo' => 25,
'fifty' => 50,
'onehundred' => 100,
];
}
}

View file

@ -2,52 +2,32 @@
namespace App\Enums; namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum SettingTypesEnum: int
* This defines a data type.
*
* @method static self string() string type
* @method static self numeric() numeric type
* @method static self picture() picture type
* @method static self json() string type
* @method static self boolean() boolean type
* @method static self color() color type
* @method static self directory() directory type
*/
final class SettingTypesEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case string = 1;
*/ case numeric = 2;
protected static function values(): array case picture = 3;
{ case json = 4;
return [ case boolean = 5;
'string' => 1, case color = 6;
'numeric' => 2, case directory = 7;
'picture' => 3,
'json' => 4,
'boolean' => 5,
'color' => 6,
'directory' => 7
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
{ self::string->name => 'String (Hexa)',
return [ self::numeric->name => 'Numeric',
'string' => 'String (Hexa)', self::picture->name => 'Picture',
'numeric' => 'Numeric', self::json->name => 'Json',
'picture' => 'Picture', self::boolean->name => 'Boolean',
'json' => 'Json', self::color->name => 'Color',
'boolean' => 'Boolean', self::directory->name => 'Directory',
'color' => 'Color', ];
'directory' => 'Directory',
];
}
} }

View file

@ -2,37 +2,22 @@
namespace App\Enums; namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable; use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
/** enum UserRolesEnum: int
* This defines a data type.
*
* @method static self admin() admin role
* @method static self client() client role
*/
final class UserRolesEnum extends BaseEnumRoutable
{ {
/** use BaseEnum;
* enum values
* @return array case admin = 1;
*/ case client = 2;
protected static function values(): array
{
return [
'admin' => 1,
'client' => 2,
];
}
/** /**
* enum labels * Optionnal labels definition.
* @return array *
* @phpstan-ignore-next-line
*/ */
protected static function labels(): array private const LABELS = [
{ self::admin->name => 'Administrateur',
return [ self::client->name => 'Client',
'admin' => 'Administrateur', ];
'client' => 'Client',
];
}
} }

View file

@ -17,7 +17,7 @@ class Handler extends ExceptionHandler
/** /**
* A list of the inputs that are never flashed for validation exceptions. * A list of the inputs that are never flashed for validation exceptions.
* *
* @var array<int, string> * @var list<string>
*/ */
protected $dontFlash = [ protected $dontFlash = [
'current_password', 'current_password',
@ -30,9 +30,12 @@ class Handler extends ExceptionHandler
* *
* @return void * @return void
*/ */
public function register() public function register(): void
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
}); });
} }
} }

View file

@ -11,15 +11,26 @@ class AppController extends Controller
{ {
/** /**
* Get App Config. * Get App Config.
* @return ApiResponseResource *
* @return \App\Http\Resources\ApiResponseResource
*/ */
public function get() public function get(): \App\Http\Resources\ApiResponseResource
{ {
/** @var string $tenantKey */
$tenantKey = request()->tenant;
$response = []; $response = [];
$settings = Setting::where('key', 'Button1')->orWhere('key', 'Button2')->orWhere('key', 'Button3')->get(); $settings = Setting::query()
->where('tenant_id', $tenantKey)
->orWhere('key', 'Button1')
->orWhere('key', 'Button2')
->orWhere('key', 'Button3')
->get();
foreach ($settings as $setting) { foreach ($settings as $setting) {
$response['folders'][$setting->key] = Directory::find($setting->value)->only('id', 'name'); $directory = Directory::find($setting->value)->only('id', 'name');
$directory['name'] = json_encode($directory['name']);
$response['folders'][$setting->key] = $directory;
} }
return new ApiResponseResource("OK", $response); return new ApiResponseResource("OK", $response);

View file

@ -6,38 +6,27 @@ use App\Enums\SettingTypesEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Setting; use App\Models\Setting;
use App\Http\Resources\ApiResponseResource; use App\Http\Resources\ApiResponseResource;
use App\Models\Formulary;
class SettingsController extends Controller class SettingsController extends Controller
{ {
/** /**
* Get Settings. * Get Settings.
* *
* @return \Illuminate\Http\JsonResponse * @return \App\Http\Resources\ApiResponseResource
*/ */
public function get() public function get(): \App\Http\Resources\ApiResponseResource
{ {
$response = []; $response = [];
$settings = Setting::all(); $settings = Setting::all();
foreach ($settings as $setting) { foreach ($settings as $setting) {
$response[$setting->key] = $response[$setting->key] =
SettingTypesEnum::numeric()->equals($setting->type) || SettingTypesEnum::numeric->equals($setting->type) ||
SettingTypesEnum::boolean()->equals($setting->type) || SettingTypesEnum::boolean->equals($setting->type) ||
SettingTypesEnum::directory()->equals($setting->type) SettingTypesEnum::directory->equals($setting->type)
? \intval($setting->value) ? \intval($setting->value)
: $setting->value; : $setting->value;
} }
return new ApiResponseResource("OK", $response); return new ApiResponseResource("OK", $response);
} }
/**
* Get Formulary settings.
*
* @return \Illuminate\Http\JsonResponse
*/
public function getFormulary()
{
return new ApiResponseResource("OK", Formulary::get()->makeHidden(['created_at', 'updated_at']));
}
} }

View file

@ -3,16 +3,27 @@
namespace App\Http\Controllers\API; namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Contacts\DestroyContactsRequest;
use App\Http\Requests\Api\Contacts\UpsertContactsRequest;
use App\Http\Resources\AppUserResource;
use App\Http\Resources\ContactResource;
use App\Http\Resources\DirectoryResource;
use App\Http\Resources\DocumentResource;
use App\Http\Resources\FormularyResource;
use App\Lib\Inputs\Definition\Input;
use App\Mail\SendFlyer; use App\Mail\SendFlyer;
use App\Mail\SendFlyerFailed; use App\Mail\SendFlyerFailed;
use App\Models\AppUser; use App\Models\AppUser;
use App\Models\Contact; use App\Models\Contact;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Document; use App\Models\Document;
use App\Models\Formulary;
use App\Models\Setting; use App\Models\Setting;
use Google\Cloud\Vision\V1\ImageAnnotatorClient;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
@ -20,87 +31,147 @@ use Illuminate\Support\Facades\URL;
class SyncController extends Controller class SyncController extends Controller
{ {
/** /**
* @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
* @return \App\Http\Resources\AppUserCollection
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function getUsers(Request $request) public function getUsers(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{ {
return new \App\Http\Resources\AppUserCollection(AppUser::where('status', 1)->get()); return AppUserResource::collection(
AppUser::query()->where('tenant_id', \tenant()->getKey())->where('status', 1)->get()
);
}
// *** --- START CONTACTS ---
/**
* Get Contacts
*
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function getContacts(AppUser $app_user): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
return ContactResource::collection(
Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->id);
})->get()
);
} }
/** /**
* @param \Illuminate\Http\Request $request * Upsert contacts
* @return \App\Http\Resources\ContactsCollection *
* @param \App\Http\Requests\Api\Contacts\UpsertContactsRequest $request
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\JsonResponse
*/ */
public function getContacts(Request $request) public function upsertContact(
{ UpsertContactsRequest $request,
$userId = $request->userId; AppUser $app_user
$this->syncContacts($request->contacts, $userId); ): \Illuminate\Http\JsonResponse {
return new \App\Http\Resources\ContactsCollection(Contact::where('app_user_id', $userId)->get()); /** @var \Illuminate\Support\Collection<string,integer> */
$formularies = Formulary::query()->pluck('slug')->flip();
$contactQuery = Contact::query()->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->getKey());
});
\collect($request->contacts)->each(
function (array $contactRequestData) use ($contactQuery, $formularies, $app_user) {
/** @var \App\Models\Contact $model */
$model = (clone $contactQuery)->where('id', Arr::get($contactRequestData, 'id'))->firstOrNew();
$contactRequestData['timestamp'] = Carbon::parse($contactRequestData['timestamp'], 'UTC')
->setTimezone(\now()->getTimezone());
// @phpstan-ignore logicalAnd.leftAlwaysTrue
if ($model->timestamp and $model->timestamp->gte($contactRequestData['timestamp'])) {
return;
}
// * Set parse values on data prop to fill in model.
$contactRequestData['data'] = \collect($contactRequestData['dataParsed'])
// * FILTER CONTACT DATA WITH ACTUAL FORMULARY
->filter(function (Input $input) use ($formularies) {
return $formularies->has($input->data->formSlug);
});
$model->fill($contactRequestData);
$model->app_user_id = $app_user->getKey();
// * Send contact to Feature
if (
!empty($contactRequestData['send_to_app_user_id']) and
($destUser = AppUser::query()->where('id', $contactRequestData['send_to_app_user_id'])->first())
) {
$model->appUser()->associate($destUser);
}
$model->saveOrFail();
}
);
// * HTTP 200 is ok.
return \response()->json();
} }
/** /**
* @param \Illuminate\Http\Request $request * Destroy contacts
* @return \App\Http\Resources\DirectoriesCollection *
* @param \App\Http\Requests\Api\Contacts\DestroyContactsRequest $request
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\JsonResponse
*/ */
public function getDirectories(Request $request) public function destroyContacts(DestroyContactsRequest $request, AppUser $app_user): \Illuminate\Http\JsonResponse
{ {
return new \App\Http\Resources\DirectoriesCollection(Directory::all()); Contact::query()->whereIn('id', $request->validated()['contactUuidList'])
->whereHas('appUser', function (Builder $q) use ($app_user) {
$q->where('tenant_id', \tenant()->getKey())->where('id', $app_user->id);
})->delete();
// * HTTP 200 is ok.
return \response()->json();
}
// *** --- END CONTACTS ---
/**
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function getDirectories(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
return DirectoryResource::collection(
Directory::query()->where('tenant_id', \tenant()->getKey())->orderBy('order')->get()
);
} }
/** /**
* @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
* @return \App\Http\Resources\DocumentsCollection
*/ */
public function getDocuments(Request $request) public function getDocuments(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{ {
return new \App\Http\Resources\DocumentsCollection(Document::where('status', 1)->get()); return DocumentResource::collection(
Document::query()->where('tenant_id', \tenant()->getKey())->where('status', 1)->get()
);
} }
/** /**
* @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
* @return array
*/ */
public function getVersion(Request $request) public function getFormularies(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{ {
$app_uuid = $request->app_uuid; return FormularyResource::collection(
$app_version = $request->app_version; Formulary::query()->where('tenant_id', \tenant()->getKey())->orderBy('order')->get()
$version = Setting::select('value AS version')->where('key', 'version')->first(); );
$ShowIsActive = Setting::select('value AS ShowIsActive')->where('key', 'ShowIsActive')->first();
try {
$client = new \GuzzleHttp\Client();
$response = $client->request(
'GET',
'https://store.ldc.fr/api/get-version?token=@P$oi6or!bL2-r%3C&app_uuid=' . $app_uuid
);
$body = $response->getBody();
$storeVersion = json_decode($body)->version;
} catch (ClientException $e) {
$storeVersion = $app_version;
}
if ($storeVersion > $app_version) {
$newApp = true;
} else {
$newApp = false;
}
return ['version' => $version->version, 'ShowIsActive' => $ShowIsActive->ShowIsActive, 'newApp' => $newApp];
} }
/** /**
* @param \Illuminate\Http\Request $request * @param \App\Http\Requests\Api\SendFlyerRequest $request
* @return string * @return string
*/ */
public function sendFlyer(Request $request) public function sendFlyer(Request $request): string
{ {
$email = $request->emails['email']; $requestData = $request->emails;
$subject = $request->emails['subject']; $email = $requestData['email'];
$message = $request->emails['message']; $subject = $requestData['subject'];
$app_user_id = $request->emails['app_user_id']; $message = $requestData['message'];
$docs = $request->emails['documents']; $app_user_id = $requestData['app_user_id'];
$docs = \json_decode($requestData['documents'], true);
$user = AppUser::find($app_user_id); $user = AppUser::find($app_user_id);
@ -108,12 +179,14 @@ class SyncController extends Controller
$filesName = []; $filesName = [];
$noExists = []; $noExists = [];
foreach ($docs as $doc) { foreach ($docs as $doc) {
$exists = Storage::disk('public')->exists('docs/' . $doc['uuid'] . '.' . $doc['type']); $exists = Storage::disk('public')
->exists(sprintf('docs/%s/%s.%s', \tenant()->getKey(), $doc['uuid'], $doc['type']));
if ($exists) { if ($exists) {
array_push($attach, URL::temporarySignedRoute( array_push($attach, config('app.url') . URL::temporarySignedRoute(
'bo.docs.download', 'docs.download',
now()->addDays(30), now()->addDays(30),
['uuid' => $doc['uuid']] ['tenant' => \tenant()->getKey(), 'uuid' => $doc['uuid']],
false
)); ));
array_push($filesName, $doc['title']); array_push($filesName, $doc['title']);
} else { } else {
@ -121,126 +194,26 @@ class SyncController extends Controller
} }
} }
if (count($noExists) === 0) { if (count($noExists) === 0) {
$message = new SendFlyer([ $mail = new SendFlyer([
'body' => $message, 'body' => $message,
'from' => $user->email, 'fromName' => $user->firstname . ' ' . $user->lastname,
'fromName' => $user->firstname . ' ' . $user->lastname, 'subject' => $subject,
'subject' => '[' . config('app.BRAND') . '] - ' . $subject, 'attach' => $attach,
'attach' => $attach,
'filesName' => $filesName, 'filesName' => $filesName,
]); ]);
Mail::to($email) Mail::to($email)->cc($user->email)->send($mail);
->cc($user->email)
->queue($message);
} else { } else {
$message = new SendFlyerFailed([ $mail = new SendFlyerFailed([
'email' => $email, 'email' => $email,
'message' => $message, 'message' => $message,
'subject' => $subject, 'subject' => $subject,
'filesName' => $filesName, 'filesName' => $filesName,
'noExists' => $noExists 'noExists' => $noExists
]); ]);
Mail::to($user->email) Mail::to($user->email)->send($mail);
->queue($message);
} //end if } //end if
return 'OK'; return 'OK';
} }
/**
* @param mixed $contacts
* @param mixed $userId
* @return void
*/
private function syncContacts(mixed $contacts, mixed $userId)
{
$contacts = json_decode(json_encode($contacts), false);
foreach ($contacts as $contact) {
if ($contact->to_delete === true) {
$tmp = Contact::where([
['id', '=', $contact->uniq_id],
['app_user_id', '=', $contact->app_user_id]
])->first();
if ($tmp) {
$tmp->delete();
}
} else {
if ($contact->app_user_id != $userId) {
$tmp = Contact::where([
['id', '=', $contact->uniq_id],
['app_user_id', '=', $userId]
])->first();
if ($tmp) {
$tmp->delete();
}
}
$tmp = Contact::where([
['id', '=', $contact->uniq_id],
['app_user_id', '=', $contact->app_user_id]
])->first();
if ($tmp) {
$tmp->email = $contact->email;
$tmp->type = $contact->type;
$tmp->other = $contact->other;
$tmp->select1 = $contact->select1;
$tmp->select2 = $contact->select2;
$tmp->lastname = $contact->lastname;
$tmp->firstname = $contact->firstname;
$tmp->company = $contact->company;
$tmp->address = $contact->address;
$tmp->phone = empty($contact->phone) ? "" : $contact->phone;
$tmp->products = $contact->products;
$tmp->notes = $contact->notes;
$tmp->visits = $contact->visits;
$tmp->app_user_id = $contact->app_user_id;
$tmp->save();
} else {
$tmp = new Contact();
$tmp->id = $contact->uniq_id;
$tmp->email = $contact->email;
$tmp->type = $contact->type;
$tmp->other = $contact->other;
$tmp->select1 = $contact->select1;
$tmp->select2 = $contact->select2;
$tmp->lastname = $contact->lastname;
$tmp->firstname = $contact->firstname;
$tmp->company = $contact->company;
$tmp->address = $contact->address;
$tmp->phone = empty($contact->phone) ? "" : $contact->phone;
$tmp->products = json_encode($contact->products);
$tmp->notes = $contact->notes;
$tmp->visits = $contact->visits;
$tmp->app_user_id = $contact->app_user_id;
$tmp->save();
} //end if
} //end if
} //end foreach
}
/**
* @param \Illuminate\Http\Request $request
* @return void
*/
public function processOcr(Request $request)
{
// Instantiates a client.
$imageAnnotator = new ImageAnnotatorClient([
'credentials' => public_path() . '/credentials.json'
]);
// The name of the image file to annotate.
$fileName = 'test.jpg';
// Prepare the image to be annotated.
$image = file_get_contents(public_path() . "/" . $fileName);
// Performs label detection on the image file.
$response = $imageAnnotator->textDetection($image);
$labels = $response->getTextAnnotations();
foreach ($labels as $label) {
echo $label->getDescription() . "<br>";
}
}
} }

View file

@ -11,30 +11,35 @@ use App\Models\AppUser;
use App\Models\Directory; use App\Models\Directory;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
class AppUserController extends Controller class AppUserController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(AppUser::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function index(Request $request) public function index(Request $request): \Illuminate\View\View
{ {
/** @var \Illuminate\Database\Eloquent\Builder $app_users */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$app_users = AppUser::query(); $query = AppUser::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$app_users, $query,
$request->search, $request->search,
null, null,
'lastname', 'lastname',
@ -42,40 +47,45 @@ class AppUserController extends Controller
'email' 'email'
); );
} }
$searchFields = trans('Nom, Prénom, Email'); $searchFields = \implode(', ', [
trans('validation.attributes.last_name'),
trans('validation.attributes.first_name'),
trans('validation.attributes.email'),
]);
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($app_users); $this->sortQuery($query);
/** @var integer $pagination Items per page */ $appUserModels = $this->paginate($query);
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$app_users = $app_users->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.app-users.index', compact('app_users', 'searchFields')); return view('bo.pages.app_users.index', compact('appUserModels', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return \Illuminate\Http\Response * @param \App\Models\AppUser $app_user
* @return \Illuminate\View\View
*/ */
public function create() public function create(AppUser $app_user): \Illuminate\View\View
{ {
$directories = Directory::renderDirectories(Directory::where('needAuthorization', 1) $directories = Directory::renderDirectories(
->orderBy('name', 'ASC')->get()); Directory::query()->where('needAuthorization', 1)->orderBy('name', 'asc')->get()
);
return view('bo.pages.app-users.create', compact('directories')); return view('bo.pages.app_users.create', [
'appUserModel' => $app_user,
'directories' => $directories
]);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\AppUser\StoreAppUserRequest $request * @param \App\Http\Requests\Bo\AppUser\StoreAppUserRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(StoreAppUserRequest $request) public function store(StoreAppUserRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$app_user = new AppUser(); $app_user = new AppUser();
@ -84,10 +94,10 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) { if ($app_user->saveOrFail()) {
$app_user->directories()->sync(collect($request->directories)->pluck('id')); $app_user->directories()->sync(collect($request->directories)->pluck('id'));
return redirect() return redirect()
->route('bo.app-users.edit', $app_user) ->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
} }
@ -95,7 +105,7 @@ class AppUserController extends Controller
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
}); });
} }
@ -104,17 +114,19 @@ class AppUserController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function edit(AppUser $app_user) public function edit(AppUser $app_user): \Illuminate\View\View
{ {
$directories = Directory::renderDirectories(Directory::where('needAuthorization', 1) $directories = Directory::renderDirectories(
->orderBy('name', 'ASC')->get()); Directory::query()->where('needAuthorization', 1)->orderBy('name', 'asc')->get()
);
// @phpstan-ignore assign.propertyReadOnly
$app_user->directories = Directory::renderDirectories($app_user->directories); $app_user->directories = Directory::renderDirectories($app_user->directories);
return view('bo.pages.app-users.edit', [ return view('bo.pages.app_users.edit', [
'app_userModel' => $app_user, 'appUserModel' => $app_user,
'directories' => $directories 'directories' => $directories
]); ]);
} }
@ -123,9 +135,9 @@ class AppUserController extends Controller
* *
* @param \App\Http\Requests\Bo\AppUser\UpdateAppUserRequest $request * @param \App\Http\Requests\Bo\AppUser\UpdateAppUserRequest $request
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(UpdateAppUserRequest $request, AppUser $app_user) public function update(UpdateAppUserRequest $request, AppUser $app_user): \Illuminate\Http\RedirectResponse
{ {
$app_user->directories()->sync(collect($request->directories)->pluck('id')); $app_user->directories()->sync(collect($request->directories)->pluck('id'));
$params = $request->validated(); $params = $request->validated();
@ -137,17 +149,17 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) { if ($app_user->saveOrFail()) {
return redirect() return redirect()
->route('bo.app-users.edit', $app_user) ->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
}); });
} }
@ -155,17 +167,17 @@ class AppUserController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\AppUser $app_user * @param \App\Models\AppUser $app_user
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(AppUser $app_user) public function destroy(AppUser $app_user): \Illuminate\Http\RedirectResponse
{ {
if ($app_user->deleteOrFail()) { if ($app_user->deleteOrFail()) {
return redirect() return redirect()
->route('bo.app-users.index') ->route('bo.app_users.index', ['tenant' => \tenant()])
->with('warning', trans( ->with('warning', trans(
':model a bien été supprimé', ':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
} }
@ -173,10 +185,21 @@ class AppUserController extends Controller
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été supprimé', ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.app_users'))] ['model' => trans('models.classes.app_user')]
)); ));
} }
/**
* Duplicate the specified resource.
*
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(AppUser $app_user): \Illuminate\Contracts\View\View
{
return $this->create($app_user->replicate());
}
/** /**
* Mass import by CSV. * Mass import by CSV.
* matricule;lastname;firstname;email * matricule;lastname;firstname;email
@ -185,10 +208,11 @@ class AppUserController extends Controller
* @param \App\Http\Requests\Bo\ImportRequest $request * @param \App\Http\Requests\Bo\ImportRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function import(ImportRequest $request) public function import(ImportRequest $request): \Illuminate\Http\RedirectResponse
{ {
Excel::import(new AppUserImport(), $request->import, null, \Maatwebsite\Excel\Excel::CSV); Excel::import(new AppUserImport(), $request->import, null, \Maatwebsite\Excel\Excel::CSV);
return redirect()->route('bo.app-users.index')->with('success', 'Import terminé.'); return redirect()->route('bo.app_users.index', ['tenant' => \tenant()])
->with('success', 'Import terminé.');
} }
} }

View file

@ -25,7 +25,7 @@ class LoginController extends Controller
* *
* @return \Illuminate\Contracts\Auth\StatefulGuard * @return \Illuminate\Contracts\Auth\StatefulGuard
*/ */
protected function guard() protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard
{ {
return Auth::guard('backend'); return Auth::guard('backend');
} }
@ -35,13 +35,14 @@ class LoginController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function showLoginForm() public function showLoginForm(): \Illuminate\View\View
{ {
return view('bo.auth.login'); return view('bo.auth.login');
} }
/** /**
* Where to redirect users after login. * Where to redirect users after login.
*
* @return string * @return string
*/ */
protected function redirectTo(): string protected function redirectTo(): string

View file

@ -15,13 +15,14 @@ class ResetPasswordController extends Controller
* *
* @return \Illuminate\Contracts\Auth\StatefulGuard * @return \Illuminate\Contracts\Auth\StatefulGuard
*/ */
protected function guard() protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard
{ {
return Auth::guard('backend'); return Auth::guard('backend');
} }
/** /**
* Where to redirect users after login. * Where to redirect users after login.
*
* @return string * @return string
*/ */
protected function redirectTo(): string protected function redirectTo(): string

View file

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\FilterCustomerRequest;
use App\Models\AppUser;
use App\Models\Contact;
use App\Models\Formulary;
use Illuminate\Database\Eloquent\Builder;
class ContactController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Contact::class);
}
/**
* Display a listing of the resource.
*
* @param \App\Http\Requests\Bo\FilterCustomerRequest $request
* @return \Illuminate\View\View
*/
public function index(FilterCustomerRequest $request): \Illuminate\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Contact::query()->with('appUser')->whereHas('appUser', function ($query) {
$query->where('tenant_id', tenant('id'));
});
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'firstname',
'lastname',
'email',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
trans('validation.attributes.phone'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
if ($request->user) {
$query->whereHas('appUser', function (Builder $q) use ($request) {
$q->where('id', $request->user);
});
}
$contactModels = $this->paginate($query);
$appUsers = AppUser::query()->select(['id', 'lastname', 'firstname'])->orderBy('lastname')->get();
return view('bo.pages.contacts.index', compact('contactModels', 'searchFields', 'appUsers'));
}
/**
* Display the specified resource.
*
* @param \App\Models\Contact $contact
* @return \Illuminate\View\View
*/
public function show(Contact $contact): \Illuminate\View\View
{
$contact->load('appUser.directories');
$fomularies = Formulary::all()->keyBy('name');
return view('bo.pages.contacts.show', [
'contactModel' => $contact,
'fomularies' => $fomularies
]);
}
}

View file

@ -3,10 +3,6 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Contact;
use Illuminate\Support\Facades\Storage;
use Parsedown;
use Illuminate\Support\Facades\File;
class DashboardController extends Controller class DashboardController extends Controller
{ {
@ -15,52 +11,8 @@ class DashboardController extends Controller
* *
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function index() public function index(): \Illuminate\Contracts\View\View
{ {
return view('bo.pages.dashboard'); return view('bo.pages.dashboard');
} }
/**
* Récupérer les contacts (fichier .csv) et les fichiers.
*
* @return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
*/
public function exportContact()
{
$csv_file = 'export-contacts.csv';
$csvExporter = new \Laracsv\Export();
$contacts = Contact::all();
$csvExporter->beforeEach(function ($contact) {
$contact->app_user_id = $contact->firstname . " " . $contact->lastname;
$contact->phone = "$contact->phone";
});
$csvExporter->getWriter()->setDelimiter(';');
$csvExporter->getWriter()->setEnclosure('"');
$csvExporter->getWriter()->setOutputBOM(\League\Csv\Reader::BOM_UTF8);
$csvExporter->build($contacts, [
'app_user_id' => 'Propriétaire',
'email' => 'Email',
'type' => 'Type du contact',
'other' => 'Type autre',
'select1' => 'Select n°1',
'select2' => 'Select n°2',
'name' => 'Nom',
'firstname' => 'Prénom',
'company' => 'Entreprise',
'address' => 'Adresse',
'phone' => 'Téléphone',
'notes' => 'Notes',
'visits' => 'Visites',
]);
Storage::disk('public')->put($csv_file, $csvExporter->getWriter()->getContent());
return response()->download(
storage_path('app/public/') . "$csv_file",
'contacts.csv',
['Content-Type' => 'text/csv; charset=utf-8']
)->deleteFileAfterSend();
}
} }

View file

@ -9,46 +9,52 @@ use App\Models\Directory;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DirectoriesController extends Controller class DirectoriesController extends Controller
{ {
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Directory::class);
}
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory * @return \Illuminate\Contracts\View\View
*/ */
public function index() public function index(): \Illuminate\Contracts\View\View
{ {
$directories = Directory::orderBy('order', 'ASC')->get(); $directoryModels = $this->getDirectoriesForComponent();
return \view('bo.pages.directories.index', \compact('directoryModels'));
return \view('bo.pages.directories.index', \compact('directories'));
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Directory\StoreDirectoryRequest $request * @param \App\Http\Requests\Bo\Directories\StoreDirectoryRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(StoreDirectoryRequest $request) public function store(StoreDirectoryRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$directory = new Directory(); $directory = new Directory();
$directory->fill($request->validated()); $directory->fill($request->validated());
if ($directory->saveOrFail()) { if ($directory->saveOrFail()) {
return redirect() return redirect()
->route('bo.directories.index') ->route('bo.directories.index', ['tenant' => \tenant()])
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => Str::singular(trans('models.classes.directories'))] ['model' => trans('models.classes.directory')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.directories'))] ['model' => trans('models.classes.directory')]
)); ));
}); });
} }
@ -57,9 +63,9 @@ class DirectoriesController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function edit(Directory $directory) public function edit(Directory $directory): \Illuminate\View\View
{ {
return view('bo.pages.directories.edit', ['directoryModel' => $directory]); return view('bo.pages.directories.edit', ['directoryModel' => $directory]);
} }
@ -69,26 +75,26 @@ class DirectoriesController extends Controller
* *
* @param \App\Http\Requests\Bo\Directories\UpdateDirectoryRequest $request * @param \App\Http\Requests\Bo\Directories\UpdateDirectoryRequest $request
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(UpdateDirectoryRequest $request, Directory $directory) public function update(UpdateDirectoryRequest $request, Directory $directory): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $directory) { return DB::transaction(function () use ($request, $directory) {
$directory->fill($request->validated()); $directory->fill($request->validated());
if ($directory->saveOrFail()) { if ($directory->saveOrFail()) {
return redirect() return redirect()
->route('bo.directories.edit', $directory) ->route('bo.directories.edit', ['tenant' => \tenant(), 'directory' => $directory])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.directories'))] ['model' => trans('models.classes.directory')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.directories'))] ['model' => trans('models.classes.directory')]
)); ));
}); });
} }
@ -97,32 +103,44 @@ class DirectoriesController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Directory $directory * @param \App\Models\Directory $directory
* @return \Illuminate\Http\Response * @return \Illuminate\Database\Eloquent\Collection
*/ */
public function destroy(Directory $directory) public function destroy(Directory $directory): \Illuminate\Database\Eloquent\Collection
{ {
foreach ($directory->children as $directoryChild) { return DB::transaction(function () use ($directory) {
$directoryChild->directory_id = null; /** @var \App\Models\Directory $directoryChild */
$directoryChild->saveOrFail(); foreach ($directory->children as $directoryChild) {
} $directoryChild->directory_id = null;
$buttons = Setting::where('key', 'Button1')->orWhere('key', 'Button2')->orWhere('key', 'Button3')->get(); $directoryChild->saveOrFail();
foreach ($buttons as $button) {
if (isset($button->value) && intval($button->value) === $directory->id) {
return [
$button->id => trans(
':model :name n\'a pas été supprimé,
un bouton est encore associé à ce dernier dans les paramètres :',
[
'model' => Str::singular(trans('models.classes.directories')),
'name' => $directory->name['fr']
]
)
];
} }
} /** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Document> */
if ($directory->deleteOrFail()) { $documents = $directory->documents()->get()->sortBy('lang', \SORT_REGULAR, true);
return Directory::orderBy('order', 'ASC')->get(); if ($documents->count()) {
} $documents->each(function (\Illuminate\Database\Eloquent\Model $document) {
/** @var \App\Models\Document $document */
$document->deleteOrFail();
});
}
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Setting> */
$buttons = Setting::query()->whereIn('key', ['Button1', 'Button2', 'Button3'])->get();
foreach ($buttons as $button) {
if (isset($button->value) && intval($button->value) === $directory->id) {
return [
$button->id => trans(
':model :name n\'a pas été supprimé,
un bouton est encore associé à ce dernier dans les paramètres :',
[
'model' => trans('models.classes.directory'),
'name' => $directory->name['fr']
]
)
];
}
}
if ($directory->deleteOrFail()) {
return $this->getDirectoriesForComponent();
}
});
} }
/** /**
@ -131,7 +149,7 @@ class DirectoriesController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return void * @return void
*/ */
public function saveOrder(Request $request) public function saveOrder(Request $request): void
{ {
$this->assignOrder($request->directories, null); $this->assignOrder($request->directories, null);
} }
@ -139,11 +157,11 @@ class DirectoriesController extends Controller
/** /**
* Assign the new order and directory id to each directory. * Assign the new order and directory id to each directory.
* *
* @param array $directories * @param array $directories
* @param integer $directoryId * @param integer|null $directoryId
* @return void * @return void
*/ */
public function assignOrder(array $directories, int $directoryId = null) public function assignOrder(array $directories, int|null $directoryId = null): void
{ {
foreach ($directories as $newDirectory) { foreach ($directories as $newDirectory) {
$directory = Directory::where('id', $newDirectory['id'])->first(); $directory = Directory::where('id', $newDirectory['id'])->first();
@ -160,15 +178,27 @@ class DirectoriesController extends Controller
* Save the new order/structure of directories. * Save the new order/structure of directories.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \App\Models\Directory
*/ */
public function needAuthorization(Request $request) public function needAuthorization(Request $request): \App\Models\Directory
{ {
$directory = Directory::where('id', $request->id)->firstOrFail(); $directory = Directory::query()->where('id', $request->id)->firstOrFail();
$directory->needAuthorization = $directory->needAuthorization ? false : true; $directory->needAuthorization = $directory->needAuthorization ? false : true;
$directory->saveOrFail();
return $directory;
}
if ($directory->saveOrFail()) { /**
return $directory; * Get directories for component.
} *
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getDirectoriesForComponent(): \Illuminate\Database\Eloquent\Collection
{
return Directory::query()
->where('tenant_id', \tenant()->getKey())
->withCount('documents')
->orderBy('order', 'asc')
->get();
} }
} }

View file

@ -2,9 +2,11 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\Documents\LangEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Documents\StoreDocumentRequest; use App\Http\Requests\Bo\Documents\StoreDocumentRequest;
use App\Http\Requests\Bo\Documents\UpdateDocumentRequest; use App\Http\Requests\Bo\Documents\UpdateDocumentRequest;
use App\Http\Requests\Bo\UploadRequest;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Document; use App\Models\Document;
use App\Models\Tag; use App\Models\Tag;
@ -12,7 +14,6 @@ use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -24,74 +25,83 @@ class DocumentController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Document::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function index(Request $request) public function index(Request $request): \Illuminate\View\View
{ {
/** @var \Illuminate\Database\Eloquent\Builder $documents */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$documents = Document::query(); $query = Document::query()->with(['directory', 'parentDocument']);
$tags = Tag::get(); $tags = Tag::get();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$documents, $query,
$request->search, $request->search,
null, null,
'title', 'title',
); );
} }
$searchFields = trans('Titre'); $searchFields = trans('validation.attributes.title');
/** Search by tags */ /** Search by tags */
$documents->when($request->tag, function ($query) use ($request) { $query->when($request->tag, function ($query) use ($request) {
$query->whereHas('tags', function (Builder $query) use ($request) { $query->whereHas('tags', function (Builder $query) use ($request) {
$query->where('id', $request->tag); $query->where('id', $request->tag);
}); });
}); });
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($documents); $this->sortQuery($query);
/** @var integer $pagination Items per page */ $documentModels = $this->paginate($query);
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$documents = $documents->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.documents.index', [ return view('bo.pages.documents.index', [
'documents' => $documents, 'documentModels' => $documentModels,
'searchFields' => $searchFields, 'searchFields' => $searchFields,
'searchTag' => $request->tag, 'searchTag' => $request->tag,
'tags' => $tags 'tags' => $tags
]); ]);
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return \Illuminate\Http\Response * @param \App\Models\Document $document
* @return \Illuminate\View\View
*/ */
public function create() public function create(Document $document): \Illuminate\View\View
{ {
$documents = Document::where('lang', \App\Enums\Documents\LangEnum::fr())->get(); $documentsList = Document::query()->where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories(); $directories = Directory::getChildrenDirectories();
$tags = Tag::get(); $tags = Tag::get();
return view('bo.pages.documents.create', compact('documents', 'directories', 'tags')); return view('bo.pages.documents.create', [
'documentModel' => $document,
'documentsList' => $documentsList,
'directories' => $directories,
'tags' => $tags
]);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Documents\StoreDocumentRequest $request * @param \App\Http\Requests\Bo\Documents\StoreDocumentRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(StoreDocumentRequest $request) public function store(StoreDocumentRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$document = new Document(); $document = new Document();
@ -99,17 +109,17 @@ class DocumentController extends Controller
if ($document->saveOrFail()) { if ($document->saveOrFail()) {
$document->tags()->sync(collect($request->tags)->pluck('id')); $document->tags()->sync(collect($request->tags)->pluck('id'));
return redirect() return redirect()
->route('bo.documents.edit', $document) ->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document])
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
}); });
} }
@ -118,47 +128,47 @@ class DocumentController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function edit(Document $document) public function edit(Document $document): \Illuminate\View\View
{ {
$documents = Document::where('lang', \App\Enums\Documents\LangEnum::fr())->get(); $documentsList = Document::query()->where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories(); $directories = Directory::getChildrenDirectories();
$tags = Tag::get(); $tags = Tag::get();
return view('bo.pages.documents.edit', [ return view('bo.pages.documents.edit', [
'documents' => $documents,
'documentModel' => $document, 'documentModel' => $document,
'directories' => $directories, 'documentsList' => $documentsList,
'tags' => $tags 'directories' => $directories,
'tags' => $tags
]); ]);
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Document\UpdateDocumentRequest $request * @param \App\Http\Requests\Bo\Documents\UpdateDocumentRequest $request
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(UpdateDocumentRequest $request, Document $document) public function update(UpdateDocumentRequest $request, Document $document): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $document) { return DB::transaction(function () use ($request, $document) {
$document->fill($request->validated()); $document->fill($request->validated());
$document->tags()->sync(collect($request->tags)->pluck('id')); $document->tags()->sync(collect($request->tags)->pluck('id'));
if ($document->saveOrFail()) { if ($document->saveOrFail()) {
return redirect() return redirect()
->route('bo.documents.edit', $document) ->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
}); });
} }
@ -166,56 +176,59 @@ class DocumentController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Document $document * @param \App\Models\Document $document
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Document $document) public function destroy(Document $document): \Illuminate\Http\RedirectResponse
{ {
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
$document->tags()->sync([]);
/** @var \App\Models\Document $documentChild */
foreach ($document->children as $documentChild) { foreach ($document->children as $documentChild) {
Document::removeDocument($documentChild); Document::removeDocument($documentChild);
$documentChild->deleteOrFail(); $documentChild->deleteOrFail();
} }
if ($document->deleteOrFail()) { if ($document->deleteOrFail()) {
return redirect() return redirect()
->route('bo.documents.index') ->route('bo.documents.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans( ->with('warning', trans(
':model a bien été supprimé', ':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été supprimé', ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.documents'))] ['model' => trans('models.classes.document')]
)); ));
} }
/** /**
* Download the document * Download the document
* *
* @param mixed $uuid * @param string $tenant
* @param string $uuid
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/ */
public function download(mixed $uuid) public function download(string $tenant, string $uuid): \Symfony\Component\HttpFoundation\BinaryFileResponse
{ {
$doc = Document::where('uuid', $uuid)->firstOrFail(); $doc = Document::where('uuid', $uuid)->firstOrFail();
$pathToFile = public_path() . Storage::url('docs/' . $doc->uuid . '.' . $doc->type); $pathToFile = public_path() . Storage::url(
sprintf('docs/%s/%s.%s', $tenant, $doc->uuid, $doc->type)
);
return response()->download($pathToFile, $doc->path); return response()->download($pathToFile, $doc->path);
} }
/** /**
* @param \Illuminate\Http\Request $request * @param \App\Http\Requests\Bo\UploadRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException Bla. * @throws \Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException Bla.
*/ */
public function upload(Request $request) public function upload(UploadRequest $request)
{ {
if (isset($request->uuid)) {
$uuid = $request->uuid;
} else {
$uuid = false;
}
// Create the file receiver. // Create the file receiver.
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request)); $receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
// Check if the upload is success, throw exception or return response you need. // Check if the upload is success, throw exception or return response you need.
@ -228,41 +241,31 @@ class DocumentController extends Controller
if ($save->isFinished()) { if ($save->isFinished()) {
// Save the file and return any response you need, current example uses `move` function. // Save the file and return any response you need, current example uses `move` function.
// If you are not using move, you need to manually delete the file: unlink($save->getFile()->getPathname()). // If you are not using move, you need to manually delete the file: unlink($save->getFile()->getPathname()).
return $this->saveFile($save->getFile(), $uuid); return $this->saveFile($save->getFile(), $request->uuid);
} }
// We are in chunk mode, lets send the current progress. // We are in chunk mode, lets send the current progress.
/** @var AbstractHandler $handler */
$handler = $save->handler(); $handler = $save->handler();
return response()->json([ return response()->json([
"done" => $handler->getPercentageDone(), "done" => $handler->getPercentageDone(),
'status' => true 'status' => true
]); ]);
} }
/** /**
* @param \Illuminate\Http\UploadedFile $file * @param \Illuminate\Http\UploadedFile $file
* @param mixed $uuid * @param string|null $uuid
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException Bla.
*/ */
protected function saveFile(UploadedFile $file, mixed $uuid) protected function saveFile(UploadedFile $file, ?string $uuid): \Illuminate\Http\JsonResponse
{ {
if ($uuid === false) { /** @var \Illuminate\Filesystem\FilesystemAdapter */
$uuid = sprintf( $disk = Storage::disk('docs');
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', $uuid = $uuid ?: Str::uuid();
mt_rand(0, 0xffff),
mt_rand(0, 0xffff), /** @var \Illuminate\Http\UploadedFile $file */
mt_rand(0, 0xffff), $filename = sprintf('%s/%s.%s', \tenant()->getKey(), $uuid, $file->guessExtension());
mt_rand(0, 0x0fff) | 0x4000, $disk->putFileAs('', $file, $filename);
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
$finalPath = storage_path('app/public/docs/');
// Move the file name.
$file->move($finalPath, $uuid . '.' . $file->getClientOriginalExtension());
return response()->json([ return response()->json([
'uid' => $uuid, 'uid' => $uuid,
]); ]);

View file

@ -2,72 +2,74 @@
namespace App\Http\Controllers\Bo; namespace App\Http\Controllers\Bo;
use App\Enums\Formularies\InputStatusEnum;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Formularies\StoreFormularyRequest; use App\Http\Requests\Bo\Formularies\StoreFormularyRequest;
use App\Http\Requests\Bo\Formularies\UpdateFormularyRequest; use App\Http\Requests\Bo\Formularies\UpdateFormularyRequest;
use App\Models\Contact;
use App\Models\Formulary; use App\Models\Formulary;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class FormularyController extends Controller class FormularyController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Formulary::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\View\View
*/ */
public function index(Request $request) public function index(Request $request): \Illuminate\View\View
{ {
/** @var \Illuminate\Database\Eloquent\Builder $formularies */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$formularies = Formulary::query(); $query = Formulary::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$formularies, $query,
$request->search, $request->search,
null, null,
'name', 'name',
); );
} }
$searchFields = trans('Nom'); $searchFields = trans('validation.attributes.name');
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($formularies); $this->sortQuery($query);
/** @var integer $pagination Items per page */ $formularyModels = $this->paginate($query);
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$formulariesModel = $formularies->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.formularies.index', compact('formulariesModel', 'searchFields')); return view('bo.pages.formularies.index', compact('formularyModels', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return \Illuminate\Http\Response * @param \App\Models\Formulary $formulary
* @return \Illuminate\View\View
*/ */
public function create() public function create(Formulary $formulary): \Illuminate\View\View
{ {
return view('bo.pages.formularies.create'); return view('bo.pages.formularies.create', ['formularyModel' => $formulary]);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Formularies\StoreFormularyRequest $request * @param \App\Http\Requests\Bo\Formularies\StoreFormularyRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(StoreFormularyRequest $request) public function store(StoreFormularyRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$formulary = new Formulary(); $formulary = new Formulary();
@ -75,17 +77,17 @@ class FormularyController extends Controller
if ($formulary->saveOrFail()) { if ($formulary->saveOrFail()) {
return redirect() return redirect()
->route('bo.formularies.edit', $formulary) ->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary])
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => Str::singular(trans('models.classes.formularies'))] ['model' => trans('models.classes.formulary')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.formularies'))] ['model' => trans('models.classes.formulary')]
)); ));
}); });
} }
@ -94,48 +96,40 @@ class FormularyController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
*/ */
public function edit(Formulary $formulary) public function edit(Formulary $formulary): \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
{ {
if ($formulary->status->value === InputStatusEnum::system()->value) { return view('bo.pages.formularies.edit', [
return redirect()->route('bo.formularies.index') 'formularyModel' => $formulary
->with('error', trans( ]);
'Administration du champs :name impossible car il est vérouillé !',
['name' => $formulary->name]
));
} else {
return view('bo.pages.formularies.edit', [
'formularyModel' => $formulary
]);
}
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Formulary\UpdateFormularyRequest $request * @param \App\Http\Requests\Bo\Formularies\UpdateFormularyRequest $request
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(UpdateFormularyRequest $request, Formulary $formulary) public function update(UpdateFormularyRequest $request, Formulary $formulary): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $formulary) { return DB::transaction(function () use ($request, $formulary) {
$formulary->fill($request->validated()); $formulary->fill($request->validated());
if ($formulary->saveOrFail()) { if ($formulary->saveOrFail()) {
return redirect() return redirect()
->route('bo.formularies.edit', $formulary) ->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))] ['model' => trans('models.classes.formulary')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))] ['model' => trans('models.classes.formulary')]
)); ));
}); });
} }
@ -143,25 +137,41 @@ class FormularyController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Formulary $formulary * @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Formulary $formulary) public function destroy(Formulary $formulary): \Illuminate\Http\RedirectResponse
{ {
if ($formulary->deleteOrFail()) { /** @var array<string,string> */
return redirect() $previousQueries = [];
->route('bo.formularies.index') \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
->with('warning', trans( return DB::transaction(function () use ($formulary, $previousQueries) {
':model a bien été supprimé', // * Remove the input on all contacts.
['model' => Str::singular(trans('models.classes.formularies'))] Contact::removeDataInputOnAllContacts($formulary);
)); if ($formulary->deleteOrFail()) {
} return redirect()
->route('bo.formularies.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.formulary')]
));
}
return redirect() return redirect()->back()->with('error', trans(
->back()
->with('error', trans(
':model n\'a pas été supprimé', ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))] ['model' => trans('models.classes.formulary')]
)); ));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Formulary $formulary): \Illuminate\Contracts\View\View
{
return $this->create($formulary->replicate());
} }
} }

View file

@ -6,38 +6,62 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Settings\UpdateSettingRequest; use App\Http\Requests\Bo\Settings\UpdateSettingRequest;
use App\Models\Directory; use App\Models\Directory;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SettingController extends Controller class SettingController extends Controller
{ {
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Setting::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @return \Illuminate\Http\Response * @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/ */
public function index() public function index(Request $request): \Illuminate\Contracts\View\View
{ {
$directories = Directory::renderDirectories(Directory::whereNull('directory_id')->get()); $directories = Directory::renderDirectories(
Directory::query()->whereNull('directory_id')->with('parent')->get(),
);
/** @var \Illuminate\Database\Eloquent\Builder $settings */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$settings = Setting::query(); $query = Setting::query();
$this->sortQuery($settings); if ($request->search) {
$settings = $settings->get(); $this->searchQuery(
$query,
$request->search,
null,
'key',
);
}
$searchFields = trans('validation.custom.key');
return view('bo.pages.settings.index', compact('settings', 'directories')); $this->sortQuery($query);
$settingModels = $query->get();
return view('bo.pages.settings.index', compact('settingModels', 'searchFields', 'directories'));
} }
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Http\Response * @return \Illuminate\Contracts\View\View
*/ */
public function edit(Setting $setting) public function edit(Setting $setting): \Illuminate\Contracts\View\View
{ {
$directories = Directory::renderDirectories(Directory::whereNull('directory_id')->get()); $directories = Directory::renderDirectories(
Directory::query()->whereNull('directory_id')->with('parent')->get(),
);
return view('bo.pages.settings.edit', ['settingModel' => $setting, 'directories' => $directories]); return view('bo.pages.settings.edit', ['settingModel' => $setting, 'directories' => $directories]);
} }
@ -45,27 +69,27 @@ class SettingController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Setting\UpdateSettingRequest $request * @param \App\Http\Requests\Bo\Settings\UpdateSettingRequest $request
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function update(UpdateSettingRequest $request, Setting $setting) public function update(UpdateSettingRequest $request, Setting $setting): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request, $setting) { return DB::transaction(function () use ($request, $setting) {
$setting->fill($request->validated()); $setting->fill($request->validated());
if ($setting->saveOrFail()) { if ($setting->saveOrFail()) {
return redirect() return redirect()
->route('bo.settings.edit', $setting) ->route('bo.settings.edit', ['tenant' => \tenant(), 'setting' => $setting])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.settings'))] ['model' => trans('models.classes.setting')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.settings'))] ['model' => trans('models.classes.setting')]
)); ));
}); });
} }
@ -73,24 +97,26 @@ class SettingController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Setting $setting * @param \App\Models\Setting $setting
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Setting $setting) public function destroy(Setting $setting): \Illuminate\Http\RedirectResponse
{ {
if ($setting->deleteOrFail()) { return DB::transaction(function () use ($setting) {
if ($setting->deleteOrFail()) {
return redirect()
->route('bo.settings.index', ['tenant' => \tenant()])
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.setting')]
));
}
return redirect() return redirect()
->route('bo.settings.index') ->back()
->with('warning', trans( ->with('error', trans(
':model a bien été supprimé', ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.settings'))] ['model' => trans('models.classes.setting')]
)); ));
} });
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.settings'))]
));
} }
} }

View file

@ -8,82 +8,84 @@ use App\Http\Requests\Bo\Tags\UpdateTagRequest;
use App\Models\Tag; use App\Models\Tag;
use App\Traits\Controllers\ChangesModelOrder; use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TagController extends Controller class TagController extends Controller
{ {
use ChangesModelOrder; use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tag::class);
}
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @param \Illuminate\Http\Request $request * @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 */ /** @var \Illuminate\Database\Eloquent\Builder $query */
$tags = Tag::query(); $query = Tag::query();
/** @var string $search Search field */
if ($request->search) { if ($request->search) {
$this->searchQuery( $this->searchQuery(
$tags, $query,
$request->search, $request->search,
null, null,
'name', 'name',
); );
} }
$searchFields = trans('Nom'); $searchFields = trans('validation.attributes.name');
/** Sort columns with a query */ /** Sort columns with a query */
$this->sortQuery($tags); $this->sortQuery($query);
/** @var integer $pagination Items per page */ $tagModels = $this->paginate($query);
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$tags = $tags->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.tags.index', compact('tags', 'searchFields')); return view('bo.pages.tags.index', compact('tagModels', 'searchFields'));
} }
/** /**
* Show the form for creating a new resource. * 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. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\Bo\Tag\StoreTagRequest $request * @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(StoreTagRequest $request) public function store(StoreTagRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$tag = new Tag(); $tag = new Tag();
$tag->fill($request->validated()); $tag->fill($request->validated());
if ($tag->saveOrFail()) { if ($tag->saveOrFail()) {
return redirect() return redirect()
->route('bo.tags.edit', $tag) ->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag])
->with('success', trans( ->with('success', trans(
':model a été créé', ':model a été créé',
['model' => Str::singular(trans('models.classes.tags'))] ['model' => trans('models.classes.tag')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été créé', ':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.tags'))] ['model' => trans('models.classes.tag')]
)); ));
}); });
} }
@ -92,9 +94,9 @@ class TagController extends Controller
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param \App\Models\Tag $tag * @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', ['tagModel' => $tag]); return view('bo.pages.tags.edit', ['tagModel' => $tag]);
} }
@ -102,28 +104,28 @@ class TagController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Http\Requests\Bo\Tag\UpdateTagRequest $request * @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request
* @param \App\Models\Tag $tag * @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response * @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) { return DB::transaction(function () use ($request, $tag) {
$tag->fill($request->validated()); $tag->fill($request->validated());
if ($tag->saveOrFail()) { if ($tag->saveOrFail()) {
return redirect() return redirect()
->route('bo.tags.edit', $tag) ->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag])
->with('success', trans( ->with('success', trans(
':model a été mis à jour', ':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))] ['model' => trans('models.classes.tag')]
)); ));
} }
return redirect() return redirect()
->back() ->back()
->with('error', trans( ->with('error', trans(
':model n\'a pas été mis à jour', ':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))] ['model' => trans('models.classes.tag')]
)); ));
}); });
} }
@ -131,24 +133,40 @@ class TagController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param \App\Models\Tag $tag * @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Tag $tag) public function destroy(Tag $tag): \Illuminate\Http\RedirectResponse
{ {
if ($tag->deleteOrFail()) { /** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($tag, $previousQueries) {
if ($tag->deleteOrFail()) {
return redirect()
->route('bo.tags.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.tag')]
));
}
return redirect() return redirect()
->route('bo.tags.index') ->back()
->with('warning', trans( ->with('error', trans(
':model a bien été supprimé', ':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.tags'))] ['model' => trans('models.classes.tag')]
)); ));
} });
return redirect() }
->back()
->with('error', trans( /**
':model n\'a pas été supprimé', * Duplicate the specified resource.
['model' => Str::singular(trans('models.classes.tags'))] *
)); * @param \App\Models\Tag $tag
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tag $tag): \Illuminate\Contracts\View\View
{
return $this->create($tag->replicate());
} }
} }

View file

@ -0,0 +1,174 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
use App\Http\Requests\Bo\Tenants\UpdateTenantRequest;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class TenantController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tenant::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 = Tenant::query();
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'name',
);
}
$searchFields = trans('validation.attributes.name');
/** Sort columns with a query */
$this->sortQuery($query);
$tenantModels = $this->paginate($query);
return view('bo.pages.tenants.index', compact('tenantModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function create(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.create', ['tenantModel' => $tenant]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTenantRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$tenant = new Tenant();
$tenant->fill($request->validated());
if ($tenant->saveOrFail()) {
return redirect()
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été créé',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function edit(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.edit', ['tenantModel' => $tenant]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\UpdateTenantRequest $request
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTenantRequest $request, Tenant $tenant): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $tenant) {
$tenant->fill($request->validated());
if ($tenant->saveOrFail()) {
return redirect()
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été mis à jour',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Tenant $tenant): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($tenant, $previousQueries) {
if ($tenant->deleteOrFail()) {
/** @var string $tenantKey */
$tenantKey = $tenant->getKey();
Storage::disk('public')->deleteDirectory("modelfiles/{$tenantKey}");
Storage::disk('public')->deleteDirectory("docs/{$tenantKey}");
return redirect()
->route('bo.tenants.index', $previousQueries)
->with('warning', trans(
':model a bien été supprimé',
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tenant $tenant): \Illuminate\Contracts\View\View
{
return $this->create($tenant->replicate());
}
}

View file

@ -0,0 +1,170 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class UserController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(User::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 $userModels */
$userModels = User::query()->with('tenants');
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$userModels,
$search,
null,
'first_name',
'last_name',
'email',
'role',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.first_name'),
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
trans('validation.attributes.role'),
]);
/** Sort columns with a query */
$this->sortQuery($userModels);
/** Custom pagination */
$userModels = $this->paginate($userModels);
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
$tenants = Tenant::query()->orderBy('name', 'asc')->get();
return view('bo.pages.users.create', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$user = new User();
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
$tenants = Tenant::query()->orderBy('name', 'asc')->get();
return view('bo.pages.users.edit', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
{
if ($user->deleteOrFail()) {
return redirect()->route('bo.users.index', ['tenant' => tenant()])
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(User $user): \Illuminate\Contracts\View\View
{
return $this->create($user->replicate());
}
}

View file

@ -2,13 +2,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Enums\ItemsPerPaginationEnum;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class Controller extends BaseController class Controller extends BaseController
{ {
@ -22,7 +26,7 @@ class Controller extends BaseController
* *
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder. * @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string. * @param string $search The query string.
* @param callable $searchQuery The query string. * @param callable|null $searchQuery The query string.
* @param string ...$fields Either a simple array or a string * @param string ...$fields Either a simple array or a string
* in case of a string the two fields * in case of a string the two fields
* would be concatenate to make the search. * would be concatenate to make the search.
@ -31,7 +35,7 @@ class Controller extends BaseController
protected function searchQuery( protected function searchQuery(
Builder $query, Builder $query,
string $search, string $search,
callable $searchQuery = null, callable|null $searchQuery = null,
string ...$fields string ...$fields
): Builder { ): Builder {
if (count($fields)) { if (count($fields)) {
@ -91,16 +95,16 @@ class Controller extends BaseController
* @param array $ignore * @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
protected function sortQuery(Builder $query, array $ignore = []): Builder protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder
{ {
$rName = request()->route()->getName(); $rName = request()->route()->getName();
$table = $query->getModel()->getTable(); $table = $query->getModel()->getTable();
if (request()->sort_col) { if (request()->input('sort_col')) {
Session::put("$rName.sort_col", request()->sort_col); Session::put("$rName.sort_col", request()->input('sort_col'));
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->sort_way) { if (request()->input('sort_way')) {
Session::put("$rName.sort_way", request()->sort_way); Session::put("$rName.sort_way", request()->input('sort_way'));
Session::put("$rName.sorted", true); Session::put("$rName.sorted", true);
} }
if (request()->rst) { if (request()->rst) {
@ -108,18 +112,56 @@ class Controller extends BaseController
Session::remove("$rName.sort_col"); Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way"); Session::remove("$rName.sort_way");
} }
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) { if (
if ( request()->input('sort_col') and
!isset($ignore) or request()->input('sort_way') and
(isset($ignore) and !in_array(request()->sort_col, $ignore)) Schema::hasColumn($table, request()->input('sort_col'))
) { ) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way); if (!in_array(request()->input('sort_col'), $ignore)) {
$query = $query->orderBy(
\sprintf("$table.%s", request()->input('sort_col')),
request()->input('sort_way')
);
} }
} elseif (Schema::hasColumn($table, 'order')) { } elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC'); $query = $query->orderBy('order', 'asc');
Session::put("$rName.sort_col", 'order'); Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc'); Session::put("$rName.sort_way", 'asc');
} elseif (Schema::hasColumn($table, 'updated_at')) {
$query = $query->orderBy('updated_at', 'desc');
Session::put("$rName.sort_col", 'updated_at');
Session::put("$rName.sort_way", 'desc');
} elseif (Schema::hasColumn($table, 'created_at')) {
$query = $query->orderBy('created_at', 'desc');
Session::put("$rName.sort_col", 'created_at');
Session::put("$rName.sort_way", 'desc');
} }
return $query; return $query;
} }
/**
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\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\ItemsPerPaginationEnum',
);
if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination);
}
$query = $query->paginate($pagination);
return $query;
}
} }

View file

@ -16,7 +16,6 @@ class Kernel extends HttpKernel
protected $middleware = [ protected $middleware = [
\App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,
@ -37,7 +36,13 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class \App\Http\Middleware\SecureHeaders::class,
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
], ],
'api' => [ 'api' => [
@ -54,16 +59,17 @@ class Kernel extends HttpKernel
* @var array<string, class-string|string> * @var array<string, class-string|string>
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, 'api.token' => \App\Http\Middleware\CheckApiToken::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'restrict_tenant_access' => \App\Http\Middleware\RestrictTenantAccess::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'api.token' => \App\Http\Middleware\CheckApiToken::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
]; ];
} }

View file

@ -13,7 +13,7 @@ class Authenticate extends Middleware
* @param mixed $request * @param mixed $request
* @return string|null * @return string|null
*/ */
protected function redirectTo($request) protected function redirectTo(mixed $request): string|null
{ {
$logins = [ $logins = [
'backend' => route('bo.login') 'backend' => route('bo.login')

View file

@ -14,7 +14,7 @@ class CheckApiToken
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next): mixed
{ {
if (isset($request->token) && $request->token == config('auth.API_TOKEN')) { if (isset($request->token) && $request->token == config('auth.API_TOKEN')) {
return $next($request); return $next($request);

View file

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

View file

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class HtmlSanitizer class HtmlSanitizer
{ {
@ -15,7 +15,7 @@ class HtmlSanitizer
* @param mixed ...$params * @param mixed ...$params
* @return mixed * @return mixed
*/ */
public function handle(Request $request, \Closure $next, ...$params) public function handle(Request $request, \Closure $next, mixed ...$params): mixed
{ {
$inputs = $request->all(); $inputs = $request->all();
/** @var callable|null */ /** @var callable|null */

View file

@ -12,6 +12,7 @@ class RedirectIfAuthenticated
* Handle an incoming request. * Handle an incoming request.
* *
* @phpcs:disable Generic.Files.LineLength.TooLong * @phpcs:disable Generic.Files.LineLength.TooLong
*
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards * @param string|null ...$guards
@ -19,8 +20,11 @@ class RedirectIfAuthenticated
* @throws \RuntimeException If guard is unhandled. * @throws \RuntimeException If guard is unhandled.
* @phpcs:enable * @phpcs:enable
*/ */
public function handle(Request $request, Closure $next, ...$guards) public function handle(
{ Request $request,
Closure $next,
string|null ...$guards
): \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse {
$guards = empty($guards) ? [null] : $guards; $guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) { foreach ($guards as $guard) {

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Middleware;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
class RestrictTenantAccess
{
/**
* Handle an incoming request.
*
* @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): \Symfony\Component\HttpFoundation\Response
{
/** @var \App\Models\User|null $user */
$user = \auth('backend')->user();
if ($user) {
if (
!$request->routeIs(['bo.dashboard', 'bo.users.*', 'bo.tenants.*']) &&
$request->tenant &&
!\in_array($request->tenant, Tenant::get()->pluck('id')->toArray())
) {
// @phpstan-ignore argument.type
abort('404');
}
if (Tenant::count() && $request->routeIs(['bo.tenants.*', 'bo.users.*']) && $user->can('isAdmin')) {
$tenant = Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
if (
Tenant::count() && ($request->routeIs('bo.dashboard')
|| $request->routeIs('bo.users.edit', $user->getKey()))
) {
$tenant = ($user->tenants->count()) ? $user->tenants->first() : Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
} //end if
return $next($request);
}
}

View file

@ -6,6 +6,7 @@ use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
class SecureHeaders class SecureHeaders
{ {
@ -26,14 +27,13 @@ class SecureHeaders
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{ {
View::share('nonce', $nonce = \uniqid()); View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute // * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'"; $jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ? $reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : ''; \sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList); $this->removeUnwantedHeaders($this->unwantedHeaderList);
@ -49,19 +49,18 @@ class SecureHeaders
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html // * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob:;", "connect-src 'self' blob:;",
"img-src * data: blob:;", "img-src * data: blob:;",
"object-src 'self' " . implode(' ', []) . ';',
'frame-src ' . implode(' ', []) . ';', 'frame-src ' . implode(' ', []) . ';',
"font-src 'self' data: ;", "font-src 'self' data: ;",
// ! Using CKEditor will must have unsafe-inline style // ! Using CKEditor will must have unsafe-inline style
// phpcs:ignore Generic.Files.LineLength.TooLong
"style-src 'self' 'unsafe-inline' " . \implode(' ', [ "style-src 'self' 'unsafe-inline' " . \implode(' ', [
// * File Manager // * File Manager
'https://cdn.jsdelivr.net', 'https://cdn.jsdelivr.net',
'https://use.fontawesome.com', 'https://use.fontawesome.com',
'https://fonts.googleapis.com' 'https://fonts.googleapis.com',
]) . ';', ]) . ';',
// ! If you dont use ckeditor use this // ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';", // "style-src 'self' 'nonce-$nonce';",
// phpcs:ignore Generic.Files.LineLength.TooLong
"script-src 'self' {$jsDev} " . \implode(' ', [ "script-src 'self' {$jsDev} " . \implode(' ', [
'https: blob:', 'https: blob:',
]) . ';' ]) . ';'
@ -76,12 +75,12 @@ class SecureHeaders
} }
/** /**
* Remove unwanted headers * Remove unwanted headers.
* *
* @param array $headerList * @param array $headerList
* @return void * @return void
*/ */
private function removeUnwantedHeaders(array $headerList) private function removeUnwantedHeaders(array $headerList): void
{ {
foreach ($headerList as $header) { foreach ($headerList as $header) {
header_remove($header); header_remove($header);

View file

@ -11,7 +11,7 @@ class TrustHosts extends Middleware
* *
* @return array<int, string|null> * @return array<int, string|null>
*/ */
public function hosts() public function hosts(): array
{ {
return [ return [
$this->allSubdomainsOfApplicationUrl(), $this->allSubdomainsOfApplicationUrl(),

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Api\Contacts;
use Illuminate\Foundation\Http\FormRequest;
class DestroyContactsRequest extends FormRequest
{
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'contactUuidList' => \collect($this->contactUuidList)->filter()->all(),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'contactUuidList' => 'present|array|min:0',
'contactUuidList.*' => 'uuid',
];
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace App\Http\Requests\Api\Contacts;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\InputData;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Rules\Enum;
class UpsertContactsRequest extends FormRequest
{
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'contacts' => \collect($this->contacts)->filter()
->map(function ($contact) {
$contact = \collect($contact);
if ($contact->has('data')) {
$contact->put('data', \collect($contact->get('data'))->all());
$contact->put('dataParsed', \collect($contact->get('data'))
->map(function ($value, $index) {
return \call_user_func([$this, 'filterDataLine'], \collect($value)->all(), $index);
})
->filter()->all());
}
if ($contact->has('visits')) {
$contact->put('visits', \collect($contact->get('visits'))->filter()
->map(function ($visit) {
if (!\is_array($visit)) {
return $visit;
}
$visit = \collect($visit);
$visit->put('note', $visit->get('note') ?? '');
return $visit->all();
})->all());
}
return $contact->all();
})->all()
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'contacts' => 'present|array',
'contacts.*' => 'array',
'contacts.*.id' => 'uuid',
'contacts.*.lastname' => 'nullable|string|between:1,255',
'contacts.*.firstname' => 'nullable|string|between:1,255',
// * Dot not validate email here (soft validate), if it does not pass, app will be blocked.
'contacts.*.email' => 'required|string|between:1,255',
'contacts.*.phone' => 'nullable|string|between:1,255',
'contacts.*.timestamp' => 'required|date',
// * DATA
'contacts.*.data' => 'present|array',
'contacts.*.dataParsed' => 'present',
'contacts.*.data.*' => 'array',
'contacts.*.data.*.title' => 'present|string|min:1|max:255',
'contacts.*.data.*.formSlug' => 'present|string|min:1|max:255',
'contacts.*.data.*.type' => ['present', new Enum(InputTypeEnum::class)],
'contacts.*.data.*.value' => 'present',
'contacts.*.data.*.required' => 'present|boolean',
'contacts.*.data.*.send_to_app_user_id' => 'nullable|numeric',
'contacts.*.notes' => 'nullable|string',
'contacts.*.visits' => 'present|array',
'contacts.*.visits.*' => 'required|array',
'contacts.*.visits.*.title' => 'present|string',
'contacts.*.visits.*.note' => 'present|string',
];
}
/**
* Handle a line of data (input)
*
* @param array $data
* @param mixed $index
* @return mixed
* @throws \Illuminate\Validation\ValidationException In case of parse failure.
*/
private function filterDataLine(array $data, mixed $index): mixed
{
$data = \collect($data);
try {
$title = $data->get('title') ? \strval($data->get('title')) : null;
if (\is_null($title)) {
throw ValidationException::withMessages([
'error' => "wrong title on data index '{$index}'"
]);
}
$formSlug = $data->get('formSlug') ? \strval($data->get('formSlug')) : null;
if (\is_null($formSlug)) {
throw ValidationException::withMessages([
'error' => "wrong formSlug on data index '{$index}'"
]);
}
$type = InputTypeEnum::make($data->get('type') ? $data->get('type') : null);
if (\is_null($type)) {
throw ValidationException::withMessages([
'error' => "wrong inputType on data index '{$index}'"
]);
}
$value = $data['value'] ?? null;
$required = $data->get('required') ? true : false;
$inputClass = $type->getInputClass();
return $inputClass::from(InputData::make(
$title,
$formSlug,
$type,
$value,
$required
), $required);
} catch (\TypeError | \BadMethodCallException $e) {
throw ValidationException::withMessages([
'error' => "wrong type on data index '{$index}'"
]);
} catch (\RuntimeException $e) {
$message = $e->getMessage();
throw ValidationException::withMessages([
'error' => "wrong data on index '{$index}' '{$message}'"
]);
} //end try
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/** FIXME: currently not used but will be in the future. */
class SendFlyerRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'emails.email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::exists('contacts', 'email')->where(function ($query) {
$query->whereIn('app_user_id', function ($subQuery) {
$subQuery->select('id')->from('app_users')->where('tenant_id', $this->tenant);
});
}),
],
'emails.subject' => 'required|string|between:3,255',
'emails.message' => 'required|string|min:3',
'emails.app_user_id' => [
'required',
'integer',
Rule::exists('app_users', 'id')->where('tenant_id', $this->tenant)
],
'emails.documents' => 'required|array|min:1',
'emails.documents.*.title' => 'required|string|min:3',
'emails.documents.*.uuid' => 'required|string|min:3',
'emails.documents.*.type' => 'required|string|min:1',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'emails.email' => trans('validation.attributes.email'),
'emails.subject' => trans('validation.attributes.subject'),
'emails.message' => trans('validation.attributes.message'),
'emails.app_user_id' => trans('models.classes.app_user'),
];
}
}

View file

@ -3,8 +3,14 @@
namespace App\Http\Requests\Bo\AppUser; namespace App\Http\Requests\Bo\AppUser;
use App\Enums\AppUserRolesEnum; use App\Enums\AppUserRolesEnum;
use App\Models\AppUser;
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule; use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
class StoreAppUserRequest extends FormRequest class StoreAppUserRequest extends FormRequest
{ {
@ -13,9 +19,9 @@ class StoreAppUserRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return Gate::check('create', AppUser::class);
} }
/** /**
@ -23,10 +29,10 @@ class StoreAppUserRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation() protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'status' => $this->status ? true : false, 'status' => $this->status ? true : false,
'authorizedFolders' => $this->authorizedFolders ? $this->authorizedFolders : [], 'authorizedFolders' => $this->authorizedFolders ? $this->authorizedFolders : [],
]); ]);
} }
@ -36,19 +42,34 @@ class StoreAppUserRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
return [ return [
'lastname' => 'required|min:3|max:255', 'lastname' => 'required|min:3|max:255',
'firstname' => 'required|min:3|max:255', 'firstname' => 'required|min:3|max:255',
'email' => 'required|min:3|max:255|email:rfc,dns|unique:app_users,email', 'email' => [
'password' => 'required|nullable|required_with:password_confirmation|confirmed|min:8|max:255',
'authorizedFolders' => 'present|array',
'role' => [
'required', 'required',
new EnumIsValidRule(AppUserRolesEnum::class) 'email:rfc,strict,dns,spoof,filter',
Rule::unique('app_users', 'email')
->where(fn(Builder $query) => $query->where('tenant_id', tenant('id')))
], ],
'status' => 'required|boolean' 'password' => [
'required',
'required_with:password_confirmation',
'confirmed',
'max:255',
Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
],
'authorizedFolders' => 'present|array',
'role' => [
'required',
new Enum(AppUserRolesEnum::class)
],
'status' => 'required|boolean'
]; ];
} }
@ -57,16 +78,16 @@ class StoreAppUserRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes() public function attributes(): array
{ {
return [ return [
'lastname' => \trans('nom'), 'lastname' => \trans('validation.attributes.last_name'),
'firstname' => \trans('prénom'), 'firstname' => \trans('validation.attributes.first_name'),
'email' => \trans('email'), 'email' => \trans('validation.attributes.email'),
'password' => \trans('mot de passe'), 'password' => \trans('validation.attributes.password'),
'authorizedFolders' => \trans('dossiers autorisés'), 'authorizedFolders' => Str::of(\trans('models.classes.directory'))->plural(),
'role' => \trans('rôle'), 'role' => \trans('validation.custom.role'),
'status' => \trans('statut') 'status' => \trans('validation.custom.status')
]; ];
} }
} }

View file

@ -2,35 +2,49 @@
namespace App\Http\Requests\Bo\AppUser; namespace App\Http\Requests\Bo\AppUser;
use App\Enums\AppUserRolesEnum; use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Kwaadpepper\Enum\Rules\EnumIsValidRule; use Illuminate\Validation\Rules\Password;
use Illuminate\Database\Query\Builder;
class UpdateAppUserRequest extends StoreAppUserRequest class UpdateAppUserRequest extends StoreAppUserRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('app_user'));
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
return [ $rules = [
'lastname' => 'required|min:3|max:255',
'firstname' => 'required|min:3|max:255',
'email' => [ 'email' => [
'required', 'required',
'min:3', 'min:3',
'max:255', 'max:255',
'email:rfc,dns', 'email:rfc,dns',
Rule::unique('app_users', 'email')->ignore($this->route('app_user')->id), Rule::unique('app_users', 'email')
->where(fn (Builder $query) => $query->where('tenant_id', tenant('id')))
->ignore($this->route('app_user')->getKey())
], ],
'password' => 'sometimes|nullable|required_with:password_confirmation|confirmed|min:8|max:255', 'password' => [
'role' => [ 'sometimes', 'nullable', 'required_with:password_confirmation', 'confirmed', 'max:255',
'required', Password::min(8)
new EnumIsValidRule(AppUserRolesEnum::class) ->letters()
->mixedCase()
->numbers()
->symbols()
], ],
'status' => 'sometimes'
]; ];
return array_merge(parent::rules(), $rules);
} }
} }

View file

@ -2,7 +2,10 @@
namespace App\Http\Requests\Bo\Directories; namespace App\Http\Requests\Bo\Directories;
use App\Enums\Documents\LangEnum;
use App\Models\Directory;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDirectoryRequest extends FormRequest class StoreDirectoryRequest extends FormRequest
{ {
@ -11,9 +14,9 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return Gate::check('create', Directory::class);
} }
/** /**
@ -21,7 +24,7 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation() protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'needAuthorization' => $this->needAuthorization ? true : false, 'needAuthorization' => $this->needAuthorization ? true : false,
@ -36,8 +39,8 @@ class StoreDirectoryRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'name' => 'required|array', 'name' => 'required|array',
'name.*' => 'required|string|min:3|max:255', 'name.*' => 'required|string|min:3|max:255',
'needAuthorization' => 'required|boolean' 'needAuthorization' => 'required|boolean'
]; ];
} }
@ -47,11 +50,16 @@ class StoreDirectoryRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes() public function attributes(): array
{ {
return [ $rules = [
'name' => \trans('nom du dossier'), 'name' => \trans('validation.attributes.name'),
'needAuthorization' => \trans('autorisation du dossier') 'needAuthorization' => \trans('validation.custom.authorization')
]; ];
/** @var Object $langEnum */
foreach (LangEnum::toArray() as $langEnum) {
$rules["name.{$langEnum->name}"] = sprintf('%s %s', trans('validation.attributes.name'), $langEnum->label);
}
return $rules;
} }
} }

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Directories; namespace App\Http\Requests\Bo\Directories;
use Illuminate\Support\Facades\Gate;
class UpdateDirectoryRequest extends StoreDirectoryRequest class UpdateDirectoryRequest extends StoreDirectoryRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('directory'));
}
} }

View file

@ -3,8 +3,14 @@
namespace App\Http\Requests\Bo\Documents; namespace App\Http\Requests\Bo\Documents;
use App\Enums\Documents\LangEnum; use App\Enums\Documents\LangEnum;
use App\Enums\Documents\TypeEnum;
use App\Models\Document;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule; use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Helpers\FileHelper;
class StoreDocumentRequest extends FormRequest class StoreDocumentRequest extends FormRequest
{ {
@ -13,9 +19,9 @@ class StoreDocumentRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return Gate::check('create', Document::class);
} }
/** /**
@ -23,11 +29,30 @@ class StoreDocumentRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation() protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'status' => $this->status ? true : false 'status' => $this->status ? true : false,
]); ]);
// * Sanitize Path
if (!empty($this->path)) {
$filename = $this->path;
$refMethod = new \ReflectionMethod(FileHelper::class, 'sanitizeFileName');
$refMethod->setAccessible(true);
$filename = \pathinfo(
$refMethod->invoke(null, htmlspecialchars($filename), false),
\PATHINFO_BASENAME
);
$this->merge([
'path' => $filename
]);
}
if (Document::query()->where('id', $this->document_id)->exists() && $this->type) {
$this->merge([
'directory_id' => Document::query()->where('id', $this->document_id)->value('directory_id'),
'type' => TypeEnum::make($this->type, 'name')->name(),
]);
}
} }
/** /**
@ -35,20 +60,35 @@ class StoreDocumentRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('docs');
return [ return [
'directory_id' => 'sometimes|nullable|integer', 'directory_id' => 'required|integer',
'document_id' => 'sometimes|nullable|integer', 'document_id' => 'sometimes:directory_id|nullable|integer',
'uuid' => 'required|min:3|max:255', 'uuid' => [
'title' => 'required|min:3|max:255',
'type' => 'required|min:3|max:255',
'path' => 'required|min:3|max:255',
'lang' => [
'required', 'required',
new EnumIsValidRule(LangEnum::class) 'uuid',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($disk) {
if ($this->type and \array_column(TypeEnum::toArray(), 'name')) {
$extension = TypeEnum::make($this->type, 'name')->name();
$value = "{$value}.{$extension}";
}
if (!$disk->has(sprintf('%s/%s', \tenant()->getKey(), $value))) {
$path = $this->path ?? $this->value ?? 'not found';
$fail(\trans('Le fichier :filename n\'est pas présent dans le système de fichier', [
'filename' => $path
]));
}
}
], ],
'status' => 'required|boolean' 'title' => 'required|min:3|max:255',
'type' => ['required', 'string', Rule::in(array_column(TypeEnum::toArray(), 'name'))],
'path' => 'required|min:3|max:255',
'lang' => ['required', new Enum(LangEnum::class)],
'status' => 'required|boolean'
]; ];
} }
@ -57,17 +97,17 @@ class StoreDocumentRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes() public function attributes(): array
{ {
return [ return [
'directory_id' => \trans('dossier à associer'), 'directory_id' => \trans('models.classes.directory'),
'document_id' => \trans('document (FR) à associer'), 'document_id' => \trans('models.classes.document'),
'title' => \trans('titre'), 'title' => \trans('validation.attributes.title'),
'uuid' => \trans('document'), 'uuid' => \trans('models.classes.document'),
'type' => \trans('type du document'), 'type' => \trans('validation.custom.type'),
'path' => \trans('chemin du document'), 'path' => \trans('validation.custom.path'),
'lang' => \trans('langue du document'), 'lang' => \trans('validation.custom.lang'),
'status' => \trans('état de publication du document'), 'status' => \trans('validation.custom.status'),
]; ];
} }
} }

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Documents; namespace App\Http\Requests\Bo\Documents;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentRequest extends StoreDocumentRequest class UpdateDocumentRequest extends StoreDocumentRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document'));
}
} }

View file

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class FilterCustomerRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return \auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'customer' => [
'sometimes',
'nullable',
'numeric',
Rule::unique('app_users', 'id')->where('tenant_id', tenant('id'))
],
];
}
}

View file

@ -4,19 +4,28 @@ namespace App\Http\Requests\Bo\Formularies;
use App\Enums\Formularies\InputStatusEnum; use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum; use App\Enums\Formularies\InputTypeEnum;
use App\Models\Formulary;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule; use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class StoreFormularyRequest extends FormRequest class StoreFormularyRequest extends FormRequest
{ {
/**
* * This should be set somewhere else.
*
* @var string[]
*/
private $mobileAppLocales = ['fr', 'en', 'de', 'nl'];
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return Gate::check('create', Formulary::class);
} }
/** /**
@ -24,11 +33,11 @@ class StoreFormularyRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation() protected function prepareForValidation(): void
{ {
$this->merge([ $this->merge([
'required' => $this->required ? true : false, 'required' => $this->required ? true : false,
'visible' => $this->visible ? true : false, 'is_visible' => $this->is_visible ? true : false,
]); ]);
} }
@ -37,22 +46,21 @@ class StoreFormularyRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
$rules = [ $rules = [
'type' => ['required', new EnumIsValidRule(InputTypeEnum::class)], 'type' => ['required', new Enum(InputTypeEnum::class)],
'placeholder' => 'present|array', 'placeholder' => 'present|array',
'placeholder.fr' => 'required|string|min:3|max:100', 'details' => 'nullable|array',
'placeholder.en' => 'required|string|min:3|max:100', 'details.*' => 'required|array',
'placeholder.de' => 'required|string|min:3|max:100', 'required' => 'required|boolean',
'placeholder.nl' => 'required|string|min:3|max:100', 'is_visible' => 'required|boolean',
'details' => 'nullable|array',
'details.*' => 'required|array',
'details.*.*' => 'sometimes|string|min:3|max:100',
'required' => 'required|boolean',
'visible' => 'required|boolean',
]; ];
if (intval($this->status) !== InputStatusEnum::locked()->value) { foreach ($this->mobileAppLocales as $locale) {
$rules["placeholder.{$locale}"] = 'required|string|min:3|max:100';
$rules["details.*.{$locale}"] = 'required|string|min:3|max:100';
}
if (intval($this->status) !== InputStatusEnum::locked->value()) {
$rules = array_merge($rules, ['name' => 'required|string|min:3|max:70']); $rules = array_merge($rules, ['name' => 'required|string|min:3|max:70']);
} }
return $rules; return $rules;
@ -65,19 +73,20 @@ class StoreFormularyRequest extends FormRequest
*/ */
public function attributes(): array public function attributes(): array
{ {
return [ $attributes = [
'type' => trans('type'), 'type' => trans('validation.custom.type'),
'name' => trans('nom'), 'name' => trans('validation.attributes.name'),
'placeholder' => trans('placeholder'), 'placeholder' => trans('validation.custom.placeholder'),
'placeholder.fr' => trans('placeholder (FR)'), 'details' => trans('validation.custom.detail'),
'placeholder.en' => trans('placeholder (EN)'), 'details.*' => trans('validation.custom.detail'),
'placeholder.de' => trans('placeholder (DE) '), 'details.*.value' => trans('validation.custom.valeur'),
'placeholder.nl' => trans('placeholder (NL)'), 'required' => trans('validation.custom.required'),
'details' => trans('détail'), 'is_visible' => trans('validation.custom.visibility'),
'details.*' => trans('détail 2'),
'details.*.value' => trans('valeur d\'un élément dans la liste des détails du contenu'),
'required' => trans('l\'état d\'obligation'),
'visible' => trans('l\'état de visibilité'),
]; ];
foreach ($this->mobileAppLocales as $locale) {
$attributes["placeholder.{$locale}"] = trans('placeholder (:lang)', ['lang' => $locale]);
$attributes["details.*.{$locale}"] = trans('details (:lang)', ['lang' => $locale]);
}
return $attributes;
} }
} }

View file

@ -2,14 +2,26 @@
namespace App\Http\Requests\Bo\Formularies; namespace App\Http\Requests\Bo\Formularies;
use Illuminate\Support\Facades\Gate;
class UpdateFormularyRequest extends StoreFormularyRequest class UpdateFormularyRequest extends StoreFormularyRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('formulary'));
}
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
$rules = [ $rules = [
'type' => 'nullable', 'type' => 'nullable',

View file

@ -11,7 +11,7 @@ class ImportRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return auth('backend')->check();
} }
@ -21,7 +21,7 @@ class ImportRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
return [ return [
'import' => [ 'import' => [
@ -30,7 +30,7 @@ class ImportRequest extends FormRequest
'mimes:csv,txt', 'mimes:csv,txt',
// phpcs:ignore Generic.Files.LineLength.TooLong // phpcs:ignore Generic.Files.LineLength.TooLong
'mimetypes:text/csv,text/plain,application/csv,text/comma-separated-values,text/anytext,application/octet-stream,application/txt', 'mimetypes:text/csv,text/plain,application/csv,text/comma-separated-values,text/anytext,application/octet-stream,application/txt',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
if ( if (
$this->import->getClientMimeType() !== 'text/csv' $this->import->getClientMimeType() !== 'text/csv'

View file

@ -2,11 +2,11 @@
namespace App\Http\Requests\Bo\Settings; namespace App\Http\Requests\Bo\Settings;
use App\Enums\UserRolesEnum;
use App\Models\Setting; use App\Models\Setting;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Uwil\LaravelCommon\Traits\Requests\HasPicture;
class UpdateSettingRequest extends FormRequest class UpdateSettingRequest extends FormRequest
{ {
@ -17,17 +17,18 @@ class UpdateSettingRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
if ( if (
$this->key === "Button1" || $this->key === "Button1" ||
$this->key === "Button2" || $this->key === "Button2" ||
$this->key === "Button3" || $this->key === "Button3"
$this->key === "ShowIsActive"
) { ) {
return auth('backend')->check(); return Gate::check('update', $this->route('setting'));
} else { } else {
return (auth('backend')->check() && Auth::user()->role === UserRolesEnum::admin()->value); /** @var \App\Models\User $user */
$user = Auth::guard('backend')->user();
return $user->can('isAdmin');
} }
} }
@ -36,11 +37,9 @@ class UpdateSettingRequest extends FormRequest
* *
* @return void * @return void
*/ */
protected function prepareForValidation() protected function prepareForValidation(): void
{ {
if ($this->key === "ShowIsActive") { if (
$this->merge(['value' => intval($this->value) ? "true" : "false"]);
} elseif (
$this->key === "Button1" || $this->key === "Button1" ||
$this->key === "Button2" || $this->key === "Button2" ||
$this->key === "Button3" $this->key === "Button3"
@ -61,7 +60,7 @@ class UpdateSettingRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
if ( if (
$this->key === "Button1" || $this->key === "Button1" ||
@ -69,10 +68,11 @@ class UpdateSettingRequest extends FormRequest
$this->key === "Button3" $this->key === "Button3"
) { ) {
$buttonsNullable = 0; $buttonsNullable = 0;
$buttons = Setting::where('key', 'Button1') $buttons = Setting::query()
->orWhere('key', 'Button2') ->where('key', 'Button1')
->orWhere('key', 'Button3') ->orWhere('key', 'Button2')
->get(); ->orWhere('key', 'Button3')
->get();
foreach ($buttons as $button) { foreach ($buttons as $button) {
if ($button->value === null) { if ($button->value === null) {
if ($button->key !== $this->key) { if ($button->key !== $this->key) {
@ -82,7 +82,7 @@ class UpdateSettingRequest extends FormRequest
} }
return [ return [
'value' => [ 'value' => [
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($buttonsNullable) { function ($attribute, $value, $fail) use ($buttonsNullable) {
if (!isset($value) && $buttonsNullable >= 2) { if (!isset($value) && $buttonsNullable >= 2) {
$fail(\trans('Au moins 1 bouton doît être défini.')); $fail(\trans('Au moins 1 bouton doît être défini.'));
@ -96,7 +96,7 @@ class UpdateSettingRequest extends FormRequest
$this->key === "BackgroundImage" || $this->key === "BackgroundImage" ||
$this->key === "HomeButtonImage" $this->key === "HomeButtonImage"
) { ) {
return $this->pictureRules('value', true, 100, 100, 1920, 1200); return $this->pictureRules('value', 100, 100, 1920, 1200, true);
} else { } else {
return ['value' => 'required|string|min:1']; return ['value' => 'required|string|min:1'];
} //end if } //end if
@ -107,12 +107,12 @@ class UpdateSettingRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function attributes() public function attributes(): array
{ {
return [ return [
'key' => \trans('clé'), 'key' => \trans('validation.custom.key'),
'value' => \trans('valeur'), 'value' => \trans('validation.custom.value'),
'type' => \trans('type'), 'type' => \trans('validation.custom.type'),
]; ];
} }
} }

View file

@ -2,8 +2,9 @@
namespace App\Http\Requests\Bo\Tags; namespace App\Http\Requests\Bo\Tags;
use App\Models\Tag;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Facades\Gate;
class StoreTagRequest extends FormRequest class StoreTagRequest extends FormRequest
{ {
@ -12,21 +13,9 @@ class StoreTagRequest extends FormRequest
* *
* @return boolean * @return boolean
*/ */
public function authorize() public function authorize(): bool
{ {
return auth('backend')->check(); return Gate::check('create', Tag::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug($this->name),
]);
} }
/** /**
@ -34,7 +23,7 @@ class StoreTagRequest extends FormRequest
* *
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
return [ return [
'name' => 'required|string|min:2|max:25', 'name' => 'required|string|min:2|max:25',
@ -49,8 +38,7 @@ class StoreTagRequest extends FormRequest
public function attributes(): array public function attributes(): array
{ {
return [ return [
'name' => trans('name of the tag'), 'name' => trans('models.classes.tag'),
'slug' => trans('slug of the tag'),
]; ];
} }
} }

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Tags; namespace App\Http\Requests\Bo\Tags;
use Illuminate\Support\Facades\Gate;
class UpdateTagRequest extends StoreTagRequest class UpdateTagRequest extends StoreTagRequest
{ {
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tag'));
}
} }

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use App\Models\Tenant;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreTenantRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::allows('create', Tenant::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|min:2|max:25',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
];
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use Illuminate\Support\Facades\Gate;
class UpdateTenantRequest extends StoreTenantRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tenant'));
}
}

View file

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

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class UploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return \auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'uuid' => 'nullable|uuid'
];
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', User::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
if ($this->input('role') == UserRolesEnum::admin->value()) {
$this->merge([
'tenants' => null
]);
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$isClient = $this->input('role') == UserRolesEnum::client->value();
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::unique('users', 'email')
],
'role' => ['required', new Enum(UserRolesEnum::class)],
'tenants' => $isClient ? 'required|array' : 'nullable|array',
'tenants.*' => $isClient ? 'required|array' : 'nullable|array',
'tenants.*.id' => $isClient ? 'required|exists:tenants,id' : 'nullable|exists:tenants,id',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'role' => trans('validation.attributes.role'),
'tenants' => Str::of(trans('models.classes.tenant'))->plural(),
];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class UpdateUserRequest extends StoreUserRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('user'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
$rules = [
'email' => [
'required',
'string',
'unique:users,email,' . $userModel->getKey(),
'email:rfc,strict,dns,spoof,filter',
'max:255'
],
'role' => ['sometimes', new Enum(UserRolesEnum::class)],
];
return \array_merge(parent::rules(), $rules);
}
}

View file

@ -8,12 +8,14 @@ class ApiResponseResource extends JsonResource
{ {
/** /**
* Status * Status
*
* @var string * @var string
*/ */
private $status; private $status;
/** /**
* Datas * Datas
*
* @var array * @var array
*/ */
private $datas; private $datas;
@ -35,12 +37,14 @@ class ApiResponseResource extends JsonResource
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array * @return array
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/ */
public function toArray($request) public function toArray($request): array
{ {
return [ return [
'status' => $this->status, 'status' => $this->status,
'datas' => $this->datas 'datas' => $this->datas
]; ];
} }
} }

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class AppUserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Http\Resources;
use App\Enums\AppUserRolesEnum;
use App\Models\Directory;
use Illuminate\Http\Resources\Json\JsonResource;
class AppUserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function toArray($request)
{
/**
* @var \App\Models\AppUser
* @phpstan-ignore varTag.nativeType
*/
$appUser = $this;
return [
'id' => $appUser->id,
'email' => $appUser->email,
'surname' => $appUser->lastname,
'firstname' => $appUser->firstname,
'password' => $appUser->password,
'activated' => $appUser->status,
'role' => ($appUser->role->equals(AppUserRolesEnum::total)) ? "TOTAL" : "USER",
'authorizedFolders' => json_encode(
$appUser->directories->each(
// @phpstan-ignore argument.type
function (Directory $directory) use ($request) {
return (new DirectoryResource($directory))->toArray($request);
}
)
),
'created_at' => $appUser->created_at,
'updated_at' => $appUser->updated_at,
];
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ContactResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request)
{
/**
* @var \App\Models\Contact
* @phpstan-ignore varTag.nativeType
*/
$contact = $this;
return [
'id' => $contact->id,
'lastname' => $contact->lastname,
'firstname' => $contact->firstname,
'email' => $contact->email,
'phone' => $contact->phone,
'data' => $contact->data,
'notes' => $contact->notes,
'visits' => $contact->visits,
'timestamp' => $contact->timestamp->toISOString(),
'app_user_id' => $contact->app_user_id,
'send_to_app_user_id' => null,
'created_at' => $contact->created_at,
'updated_at' => $contact->updated_at,
];
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ContactsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class DirectoriesCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class DirectoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request)
{
/**
* @var \App\Models\Directory
* @phpstan-ignore varTag.nativeType
*/
$directory = $this;
return [
'id' => $directory->id,
'name' => json_encode($directory->name),
'parent' => $directory->directory_id,
'order' => $directory->order,
'needAuthorization' => $directory->needAuthorization,
'created_at' => $directory->created_at,
'updated_at' => $directory->updated_at,
];
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class DocumentResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request)
{
/**
* @var \App\Models\Document
* @phpstan-ignore varTag.nativeType
*/
$document = $this;
return [
'id' => $document->id,
'uuid' => $document->uuid,
'title' => $document->title,
'type' => $document->type,
'directory' => $document->directory_id,
'path' => $document->path,
'lang' => $document->lang->name,
'published' => ($document->status) ? 1 : 0,
'parent' => $document->document_id,
'created_at' => $document->created_at,
'updated_at' => $document->updated_at,
];
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class DocumentsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class FormularyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request)
{
/**
* @var \App\Models\Formulary
* @phpstan-ignore varTag.nativeType
*/
$formulary = $this;
return [
'id' => $formulary->id,
'type' => $formulary->type->value,
'name' => $formulary->name,
'slug' => $formulary->slug,
'placeholder' => $formulary->placeholder,
'details' => \collect($formulary->details)->filter()->map(function (array $detail) {
// * Realm can't hold more deeper dans Map<mixed,primitive>
return \json_encode($detail);
})->all(),
'is_visible' => $formulary->is_visible,
'required' => $formulary->required,
'status' => $formulary->status->value,
'order' => $formulary->order,
];
}
}

View file

@ -17,7 +17,7 @@ class AppUserImport implements ToModel, WithUpserts, WithCustomCsvSettings
/** /**
* @return string|array * @return string|array
*/ */
public function uniqueBy() public function uniqueBy(): string|array
{ {
return 'email'; return 'email';
} }
@ -37,29 +37,27 @@ class AppUserImport implements ToModel, WithUpserts, WithCustomCsvSettings
* *
* @return \Illuminate\Database\Eloquent\Model|null * @return \Illuminate\Database\Eloquent\Model|null
*/ */
public function model(array $row) public function model(array $row): \Illuminate\Database\Eloquent\Model|null
{ {
if (!isset($row[0]) || !isset($row[1]) || !isset($row[2]) || !isset($row[3])) { if (!isset($row[0]) || !isset($row[1]) || !isset($row[2]) || !isset($row[3])) {
return null; return null;
} }
$user = new AppUser([ $password = Hash::make(Str::random(10));
'email' => $row[3], $user = new AppUser([
'lastname' => ucfirst(strtolower($row[1])), 'email' => $row[3],
'lastname' => ucfirst(strtolower($row[1])),
'firstname' => ucfirst(strtolower($row[2])), 'firstname' => ucfirst(strtolower($row[2])),
'password' => '', 'password' => $password,
'status' => 1, 'status' => 1,
'role' => AppUserRolesEnum::user(), 'role' => AppUserRolesEnum::user,
]); ]);
if ($user) {
$password = Str::random(10);
$user->password = Hash::make($password);
$user->save();
Mail::to($user->email)->send(new AccountCreated($user, $password));
}
$user->saveOrFail(); $user->saveOrFail();
Mail::to($user->email)->send(new AccountCreated([
'email' => $user->email,
'lastname' => $user->lastname,
'firstname' => $user->firstname,
'password' => $user->password,
]));
return $user; return $user;
} }

View file

@ -0,0 +1,46 @@
<?php
namespace App\Jobs\Mails;
use App\Mail\TenantDeleted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
class SendTenantDeletedMail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
/** @var string $author */
private $author;
/** @var string $name */
private $name;
/**
* Create a new job instance.
*
* @param string $author
* @param string $name
* @return void
*/
public function __construct(string $author, string $name)
{
$this->author = $author;
$this->name = $name;
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
foreach (config('mail.admin.address') as $address) {
Mail::to($address)->send(new TenantDeleted($this->author, $this->name));
}
}
}

View file

@ -1,190 +0,0 @@
<?php
namespace App\Lib\Helpers;
class ExceptionsHelper
{
/**
* Handle sentry event
*
* @param \Sentry\Event $event
* @return null|\Sentry\Event
*/
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
{
// Remove unwanted reports.
$event->setModules([]);
$event->setOsContext(null);
/** @var \Sentry\ExceptionDataBag[] */
$exceptionBag = [];
foreach ($event->getExceptions() as $exceptionDataBag) {
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
}
$event->setExceptions($exceptionBag);
return $event;
}
/**
* Anonimize Sentry Exception bag
*
* @param \Sentry\ExceptionDataBag $exceptionDataBag
* @return \Sentry\ExceptionDataBag
*/
private static function getAnonymizedSentryException(
\Sentry\ExceptionDataBag $exceptionDataBag
): \Sentry\ExceptionDataBag {
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
}
return $exceptionDataBag;
}
/**
* Anonymise sentry stacktrace
*
* @param \Sentry\Stacktrace $stackTrace
* @return \Sentry\Stacktrace
*/
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
{
/** @var \Sentry\Frame[] */
$frames = \array_reverse($stackTrace->getFrames(), true);
$i = \count($frames) - 1;
// * Remove all but keep first (index.php).
while ($i-- > 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
foreach ($frames as $index => $frame) {
$frame->setPostContext([]);
$frame->setPreContext([]);
$frame->setContextLine('');
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
$stackTrace->addFrame($frame);
if ($index === 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
}
return $stackTrace;
}
/**
* Get anonymized sentry params
*
* @param mixed[] $params
* @return string[]
*/
private static function getAnonymizedSentryParams(array $params): array
{
foreach ($params as &$param) {
$type = \strtolower(\gettype($param));
if (\is_string($param)) {
$type = \explode(' ', \trim($param))[0] ?? null;
}
switch ($type) {
case 'Lambda':
case 'Object':
continue 2;
case 'array':
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
if (\is_numeric($param)) {
$param = \ucfirst($type);
continue 2;
}
if (\is_string($param)) {
$param = sprintf('String(%d)', \strlen($param));
continue 2;
}
} //end switch
} //end foreach
return $params;
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
public static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
if ($sqlPos = \strpos($message, 'to use near')) {
$message = \mb_strcut($message, 0, $sqlPos);
}
return $message;
}
/**
* Anonymize stack trace
*
* @param \Throwable $e
* @return string
*/
public static function getAnonymizedStackTrace(\Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
$traceStr = '';
$maxStackStage = 0;
foreach ($traceStack as $stackStage => $stack) {
$maxStackStage = $stackStage;
$traceStr .= \sprintf(
'#%s %s(%d): ',
$stackStage,
str_replace($projectPath, '', $stack['file'] ?? ''),
$stack['line'] ?? ''
);
if (isset($stack['class'])) {
$traceStr .= "{$stack['class']}->{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
}
} //end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
/**
* Handle Args
*
* @param array $stack
* @return string
*/
public static function handleArgs(array $stack): string
{
$traceArgs = '';
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
return $traceArgs;
}
}

View file

@ -1,444 +0,0 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* This class is used to handle files in LDC Laravel stack
*
* * The storage strategy will be using date.
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
*
* posts/ - The model table name
* 10/ - Year
* 04/ - Month
* 19/ - Day
* 5f/ - First two letters of md5 file
* black-cat-under-red-car.png - Slugified file name
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
*
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
*
* ! No limits of file per folder will be handled.
*
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
*/
class FileStorageHelper
{
/**
* Destroy the file associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeFile(Model $model, string $attribute): void
{
$path = \ltrim(\urldecode($model->{$attribute}), '/');
if (\strpos($path, 'storage') !== 0) {
return;
}
$path = ToolboxHelper::mbReplace('storage/', '', $path);
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
/**
* Destroy the file associate with the attribute old value (before updated to DB).
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @param boolean $onlyIfUpdated
* @return void
*/
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)) {
return;
}
$originalPath = \ltrim(\urldecode($model->getOriginal($attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
return;
}
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
if (Storage::disk('public')->exists($originalPath)) {
Storage::disk('public')->delete($originalPath);
}
}
/**
* Store a file using optimized storage strategy
*
* You can use filename param to overload file name.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @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.
*/
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string $filename = null,
bool $private = false
): string {
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
if ($private) {
throw new \RuntimeException('Private storage for uploaded files is unhandled');
}
/** @var \Illuminate\Http\UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename);
$newPath = str_replace(storage_path('app'), '', $newPath);
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
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, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
'Failed to copy file %s to %s',
$file->getRealPath(),
$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);
}
/**
* Store all files from string attribute html
* 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
* files using storeModelFilesFromHtml.
* @return string Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$fileInfo = new \SplFileInfo($disk->path(
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplace(
$path,
'/' . self::storeFile($model, $fileInfo, true),
$htmlString
);
});
if ($oldHtmlString) {
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
}
return $htmlString;
}
/**
* Remove all old files from string attribute html that are stored in public that
* are nove present anymore on new html string
*
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
{
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
}
/**
* Remove all old files that are stored in public that
* are not present anymore on new array files
*
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
* @return void
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Find all occurence of files in html
* @param string $html
* @return array
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
return collect($m)->pluck('href')->filter()
->concat(collect($m)->pluck('url')->filter())
->concat(collect($m)->pluck('src')->filter())
->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param array $urls
* @return array
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('public')
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'uploads'
*
* @param array $urls
* @return array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$host = \parse_url($url, \PHP_URL_HOST);
$path = \parse_url($url, \PHP_URL_PATH);
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
\strpos(\ltrim($path, '/'), 'uploads') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('uploads')
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Prepare storage path and return
* the file full path
*
* @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, bool $private = false): string
{
// * 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(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
/**
* Create sub folders to store files using pattern
* 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 $storage_dir, string $table, string $filename): string
{
$filenameHash = \md5($filename);
$pathFolder = \storage_path(sprintf(
'%s/%s/%s/%s/%s/%s',
$storage_dir,
$table,
gmdate('y'),
gmdate('m'),
gmdate('d'),
substr($filenameHash, 0, 2)
));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
return $pathFolder;
}
/**
* Get unique filename if the path is already occupied
*
* @param string $folderPath
* @param string $filename
* @return string
* @throws \RuntimeException If a unique filename cannot be obtained.
*/
private static function getFileUniqueName(string $folderPath, string $filename): string
{
$attempts = 0;
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to has a unique filename for {$folderPath}/{$filename}");
}
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
pathinfo($filename, \PATHINFO_EXTENSION);
}
return $filename;
}
/**
* Slugify the filename
*
* @param string $filename
* @return string
*/
public static function slugifyFileName(string $filename): string
{
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
}
/**
* Sanitize File name for uploaded files
*
* @param string $filename The original uploaded client file name.
* @param boolean $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
{
// * Sanitize filename
// phpcs:disable Generic.Files.LineLength.TooLong
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// phpcs:enable
// * Avoids '.' '..' or '.hiddenFiles' .
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
/** @var string */
$filename = pathinfo($filename, \PATHINFO_FILENAME);
$encoding = mb_detect_encoding($filename);
$encoding = $encoding ? $encoding : null;
return \mb_strcut(
$filename,
0,
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (!empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*
* @param string $filename
* @return string
*/
private static function beautifyFilename(string $filename): string
{
// Reduce consecutive characters.
$filename = preg_replace([
// * "file name.zip" becomes 'file-name.zip'
'/ +/',
// * "file___name.zip" becomes 'file-name.zip'
'/_+/',
// * "file---name.zip" becomes 'file-name.zip'
'/-+/'
], '-', $filename);
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/'
], '.', $filename);
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
}

View file

@ -1,237 +0,0 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ToolboxHelper
{
/**
* Strip scheme (https) on oembed urls
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$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) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => ':attribute : Il est interdit de coller une url du back office dans ce champ'
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
self::assertNoInternalBoUrls($field, $text);
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$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)
) {
// 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')
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Mutltibyte string replace
* @param mixed $search
* @param mixed $replace
* @param mixed $subject
* @param integer $count
* @return mixed
*/
public static function mbReplace($search, $replace, $subject, int &$count = 0)
{
if (!is_array($search) && is_array($replace)) {
return false;
}
if (is_array($subject)) {
// Call mb_replace for each single string in $subject .
foreach ($subject as &$string) {
$string = &self::mbReplace($search, $replace, $string, $count);
}
} elseif (is_array($search)) {
if (!is_array($replace)) {
foreach ($search as &$string) {
$subject = self::mbReplace($string, $replace, $subject, $count);
}
} else {
$n = max(count($search), count($replace));
while ($n--) {
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
next($search);
next($replace);
}
}
} else {
$parts = mb_split(preg_quote($search), $subject);
$count = count($parts) - 1;
$subject = implode($replace, $parts);
} //end if
return $subject;
}
/**
* Asserts that the field is unique
*
* @param string $table
* @param string $field
* @param mixed $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,
?int $id = null,
?callable $query = null
) {
if (!$query) {
Validator::make([$field => $value], [
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('La valeur :value pour le champ :attribute est déjà utilisé', [
'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
])
])->validate();
}
/**
* Convert deeply an object to array
*
* @param array|object $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace App\Lib\Inputs\Definition;
use App\Models\Formulary;
/**
* To manipulate Input
*
* @property-read \App\Lib\Inputs\Definition\InputData $data The input type data.
* @package App\Lib\Inputs
*/
abstract class Input implements InputInterface
{
/**
* The Input Type.
*
* @var \App\Lib\Inputs\Definition\InputData
*/
private $data;
/**
* Constructor
*
* @param \App\Lib\Inputs\Definition\InputData $data
* @param boolean $required
* @return void
* @throws \RuntimeException If $data is wrong.
*/
public function __construct(InputData $data, bool $required = true)
{
InputData::assertIsInputTypeData($data);
static::validateData($data->value, $required);
$this->data = $data;
}
/**
* Get property
*
* @param string $name
* @return mixed
* @throws \TypeError If prop is undefined.
*/
public function __get(string $name)
{
if (in_array($name, ['data'])) {
return $this->{$name};
}
throw new \TypeError("undefined property {$name}");
}
/**
* Is this input required ?
*
* @return boolean
*/
public function isRequired(): bool
{
return $this->data->required;
}
/**
* Get the matching formulary for this object
*
* @return \App\Models\Formulary|null
*/
public function getFormulary(): \App\Models\Formulary|null
{
// @phpstan-ignore return.type
return $this->formularyQuery()->first();
}
/**
* Get this input has a matching Formulary ?
*
* @return boolean
*/
public function hasFormulary(): bool
{
return $this->formularyQuery()->exists();
}
/**
* Get Matching formulary query
*
* @return \Illuminate\Database\Eloquent\Builder
*/
private function formularyQuery(): \Illuminate\Database\Eloquent\Builder
{
return Formulary::query()
->where('slug', $this->data->formSlug)
->where('type', $this->data->type);
}
/**
* Parse DB line to Object
*
* ! Need to validate inputType
*
* @param \App\Lib\Inputs\Definition\InputData $data
* @param boolean $required
* @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/
public static function from(InputData $data, bool $required = true): static
{
$inputTypeDataClass = static::class;
$type = "";
try {
// @phpstan-ignore function.alreadyNarrowedType
$type = \is_numeric($data->type) ? intval($data->type) : strval($data->type);
return new static(
InputData::make($data->title, $data->formSlug, $type, $data->value, $required),
$required
);
} catch (\BadMethodCallException $e) {
throw new \RuntimeException("Could not instanciate {$type}");
} catch (\Illuminate\Validation\ValidationException $e) {
$encodedData = \json_encode($data);
throw new \RuntimeException("Data does not comply with {$inputTypeDataClass}, given {$encodedData}");
}
}
/**
* Parse DB line to Object
*
* ! Need to validate inputType
*
* @param string $title The line placeholder.
* @param string $formSlug The Form slug for this data.
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/
public static function make(string $title, string $formSlug, $data, bool $required = true): static
{
return static::from(
InputData::make(
$title,
$formSlug,
static::getType()->value,
$data,
$required
),
$required
);
}
/**
* Validate data using getValidator instance
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return void
* @throws \Illuminate\Validation\ValidationException If the data is invalid.
*/
public static function validateData($data, bool $required): void
{
static::getValidator($data, $required)->validate();
}
// * TRANSFORMERS
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray(): array
{
return (array) $this->data;
}
/**
* Convert the object to its JSON representation.
*
* @param integer $options
* @return string
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
*/
public function toJson($options = 0): string
{
// phpcs:enable
return \json_encode($this->jsonSerialize(), $options);
}
/**
* Convert the object into something JSON serializable.
*
* @return array
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace App\Lib\Inputs\Definition;
use App\Enums\Formularies\InputTypeEnum;
/**
* @property string $title The input type placeholder
* @property string $formSlug The original Formulary slug.
* @property integer $type The input type value
* @property string|array<integer,string> $value The input value, either a string or an array
* with the value index and label
* (for Multiselect and checkboxes)
* @property boolean $required The input is required ?
* @package App\Lib\Inputs
*/
class InputData extends \stdClass
{
/**
* Get property
*
* @param string $name
* @return mixed
* @throws \TypeError If prop is undefined.
*/
public function __get(string $name)
{
if (in_array($name, ['title', 'formSlug', 'type', 'value', 'required'])) {
return $this->{$name};
}
throw new \TypeError("undefined property {$name}");
}
/**
* Make and instance
*
* @param string $title The place holder (localized from in App).
* @param string $formSlug The original Formulary slug.
* @param \App\Enums\Formularies\InputTypeEnum|integer $type The original Formulary type.
* @param string|array<integer,string> $data The of the input depending on Formulary type.
* @param boolean $required Is the input required.
* @return \App\Lib\Inputs\Definition\InputData
* @throws \RuntimeException If $type or $data is wrong.
*/
public static function make(string $title, string $formSlug, $type, $data, bool $required): self
{
$ipTypeData = new self();
$ipTypeData->title = $title;
$ipTypeData->formSlug = $formSlug;
$ipTypeData->type = (\is_object($type) and \get_class($type) === InputTypeEnum::class) ?
$type->value : $type;
$ipTypeData->value = $data;
$ipTypeData->required = $required;
self::assertIsInputTypeData($ipTypeData);
return $ipTypeData;
}
/**
* Confirm $data is of type InputTypeData
*
* @param \App\Lib\Inputs\Definition\InputData $data
* @return void
* @throws \RuntimeException If $data is wrong.
*/
public static function assertIsInputTypeData(InputData $data): void
{
$title = $data->title;
$type = $data->type;
$formSlug = $data->formSlug;
$value = $data->value;
$required = $data->required;
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_string($title)) {
throw new \RuntimeException("InputTypeData 'title' should be a string");
}
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_string($formSlug)) {
throw new \RuntimeException("InputTypeData 'formSlug' should be a string");
}
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_integer($type)) {
throw new \RuntimeException("InputTypeData 'type' should be an integer");
}
// @phpstan-ignore function.alreadyNarrowedType
if (!\is_bool($required)) {
throw new \RuntimeException("InputTypeData 'required' should be a boolean");
}
try {
$type = InputTypeEnum::make($type);
} catch (\BadMethodCallException $e) {
throw new \RuntimeException($e->getMessage());
}
// @phpstan-ignore function.impossibleType, function.alreadyNarrowedType, booleanAnd.alwaysFalse
if (!\is_null($value) && !\is_string($value) && !\is_array($value)) {
$type = \gettype($value);
$value = \json_encode($value);
throw new \RuntimeException(
"InputTypeData 'value' should be string or array given '{$value}' of type '{$type}'"
);
}
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace App\Lib\Inputs\Definition;
use App\Enums\Formularies\InputTypeEnum;
use App\Models\Formulary;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Validation\Validator;
/**
* @package App\Lib\Inputs
*/
interface InputInterface extends Jsonable, Arrayable, \JsonSerializable
{
/**
* Constructor
*
* @param \App\Lib\Inputs\Definition\InputData $data
* @return void
*/
public function __construct(InputData $data);
/**
* Parse DB line to Object
*
* ! Need to validate inputType
*
* @param \App\Lib\Inputs\Definition\InputData $data
* @param boolean $required
* @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/
public static function from(InputData $data, bool $required = true): static;
/**
* Parse DB line to Object
*
* ! Need to validate inputType
*
* @param string $title The line placeholder.
* @param string $formSlug The Form slug for this data.
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return static
* @throws \RuntimeException If the input type is not recognized, or data is invalid.
*/
public static function make(string $title, string $formSlug, $data, bool $required = true): static;
// * --- CHILDS Should implement ---
/**
* Is this input required ?
*
* @return boolean
*/
public function isRequired(): bool;
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): InputTypeEnum;
/**
* Get the matching formulary for this object
*
* @return \App\Models\Formulary|null
*/
public function getFormulary(): ?Formulary;
/**
* Get this input has a matching Formulary ?
*
* @return boolean
*/
public function hasFormulary(): bool;
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): Validator;
/**
* Validate data using getValidator instance
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return void
* @throws \Illuminate\Validation\ValidationException If the data is invalid.
*/
public static function validateData($data, bool $required): void;
}

View file

@ -0,0 +1,43 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @inheritDoc Checkbox
* @package App\Lib\Inputs
*/
class InputCheckbox extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::checkbox;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
[
'value' => 'array' . ($required ? '|min:1' : ''),
'value.*' => 'required|string|max:255'
]
);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @inheritDoc Email
* @package App\Lib\Inputs
*/
class InputEmail extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::email;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
['value' => ($required ? 'required' : 'nullable') . '|string|email:rfc,spoof|max:255']
);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @inheritDoc Phone
* @package App\Lib\Inputs
*/
class InputPhone extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::phone;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
['value' => ($required ? 'required' : 'nullable') . '|string|max:255']
);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @inheritDoc Radio
* @package App\Lib\Inputs
*/
class InputRadio extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::radio;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
['value' => ($required ? 'required' : 'nullable') . '|string|max:255']
);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @inheritDoc Select
* @package App\Lib\Inputs
*/
class InputSelect extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::select;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
['value' => ($required ? 'required' : 'nullable') . '|string|max:255']
);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
/**
* @inheritDoc SelectMultiple
* @package App\Lib\Inputs
*/
class InputSelectMultiple extends InputCheckbox
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::selectMultiple;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Lib\Inputs;
use App\Enums\Formularies\InputTypeEnum;
use App\Lib\Inputs\Definition\Input;
use Illuminate\Support\Facades\Validator as FacadesValidator;
/**
* @package App\Lib\Inputs
*/
class InputText extends Input
{
/**
* Get the matching enum for this object
*
* @return \App\Enums\Formularies\InputTypeEnum
*/
public static function getType(): \App\Enums\Formularies\InputTypeEnum
{
return InputTypeEnum::text;
}
/**
* Get the validator this input
*
* @param string|array $data The line with data.
* @param boolean $required The line is required.
* @return \Illuminate\Contracts\Validation\Validator
*/
public static function getValidator($data, bool $required): \Illuminate\Contracts\Validation\Validator
{
return FacadesValidator::make(
['value' => $data],
['value' => ($required ? 'required' : 'nullable') . '|string|max:255']
);
}
}

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